Bug 723904 - Implement a EventEmiter mechanism that can be used by any tool. r=jwalker

This commit is contained in:
Paul Rouget 2012-09-11 13:25:52 +02:00
Родитель 98c7561041
Коммит 6e8b065c31
7 изменённых файлов: 176 добавлений и 53 удалений

Просмотреть файл

@ -18,6 +18,7 @@ Cu.import("resource:///modules/devtools/MarkupView.jsm");
Cu.import("resource:///modules/highlighter.jsm");
Cu.import("resource:///modules/devtools/LayoutView.jsm");
Cu.import("resource:///modules/devtools/LayoutHelpers.jsm");
Cu.import("resource:///modules/devtools/EventEmitter.jsm");
// Inspector notifications dispatched through the nsIObserverService.
const INSPECTOR_NOTIFICATIONS = {
@ -67,7 +68,7 @@ function Inspector(aIUI)
this._IUI = aIUI;
this._winID = aIUI.winID;
this._browser = aIUI.browser;
this._listeners = {};
this._eventEmitter = new EventEmitter();
this._browser.addEventListener("resize", this, true);
@ -147,7 +148,7 @@ Inspector.prototype = {
this._destroyMarkup();
this._browser.removeEventListener("resize", this, true);
delete this._IUI;
delete this._listeners;
delete this._eventEmitter;
},
/**
@ -284,7 +285,7 @@ Inspector.prototype = {
this._markupBox.removeAttribute("hidden");
this.markup = new MarkupView(this, this._markupFrame);
this._emit("markuploaded");
this.emit("markuploaded");
},
_destroyMarkup: function Inspector__destroyMarkup()
@ -348,8 +349,7 @@ Inspector.prototype = {
delete this._frozen;
},
/// Event stuff. Would like to refactor this eventually.
/// Emulates the jetpack event source, which has a nice API.
/// Forward the events related calls to the event emitter.
/**
* Connect a listener to this object.
@ -361,10 +361,7 @@ Inspector.prototype = {
*/
on: function Inspector_on(aEvent, aListener)
{
if (!(aEvent in this._listeners)) {
this._listeners[aEvent] = [];
}
this._listeners[aEvent].push(aListener);
this._eventEmitter.on(aEvent, aListener);
},
/**
@ -377,11 +374,7 @@ Inspector.prototype = {
*/
once: function Inspector_once(aEvent, aListener)
{
let handler = function() {
this.removeListener(aEvent, handler);
aListener();
}.bind(this);
this.on(aEvent, handler);
this._eventEmitter.once(aEvent, aListener);
},
/**
@ -393,35 +386,18 @@ Inspector.prototype = {
* @param function aListener
* The listener to remove.
*/
removeListener: function Inspector_removeListener(aEvent, aListener)
off: function Inspector_removeListener(aEvent, aListener)
{
this._listeners[aEvent] = this._listeners[aEvent].filter(function(l) aListener != l);
this._eventEmitter.off(aEvent, aListener);
},
/**
* Emit an event on the inspector. All arguments to this method will
* be sent to listner functions.
*/
_emit: function Inspector__emit(aEvent)
emit: function Inspector_emit()
{
if (!(aEvent in this._listeners))
return;
let originalListeners = this._listeners[aEvent];
for (let listener of this._listeners[aEvent]) {
// If the inspector was destroyed during event emission, stop
// emitting.
if (!this._listeners) {
break;
}
// If listeners were removed during emission, make sure the
// event handler we're going to fire wasn't removed.
if (originalListeners === this._listeners[aEvent] ||
this._listeners[aEvent].some(function(l) l === listener)) {
listener.apply(null, arguments);
}
}
this._eventEmitter.emit.apply(this._eventEmitter, arguments);
}
}
@ -872,13 +848,13 @@ InspectorUI.prototype = {
this.inspecting = true;
this.highlighter.unlock();
this._notifySelected();
this._currentInspector._emit("unlocked");
this._currentInspector.emit("unlocked");
},
_notifySelected: function IUI__notifySelected(aFrom)
{
this._currentInspector._cancelLayoutChange();
this._currentInspector._emit("select", aFrom);
this._currentInspector.emit("select", aFrom);
},
/**
@ -908,7 +884,7 @@ InspectorUI.prototype = {
this.highlighter.lock();
this._notifySelected();
this._currentInspector._emit("locked");
this._currentInspector.emit("locked");
},
/**
@ -993,7 +969,7 @@ InspectorUI.prototype = {
this.highlighter.updateInfobar();
this.highlighter.invalidateSize();
this.breadcrumbs.updateSelectors();
this._currentInspector._emit("change", aUpdater);
this._currentInspector.emit("change", aUpdater);
},
/////////////////////////////////////////////////////////////////////////
@ -1821,8 +1797,8 @@ InspectorStyleSidebar.prototype = {
// If the current tool is already loaded, notify that we're
// showing this sidebar.
if (aTool.loaded) {
this._inspector._emit("sidebaractivated", aTool.id);
this._inspector._emit("sidebaractivated-" + aTool.id);
this._inspector.emit("sidebaractivated", aTool.id);
this._inspector.emit("sidebaractivated-" + aTool.id);
return;
}
@ -1841,14 +1817,14 @@ InspectorStyleSidebar.prototype = {
aTool.loaded = true;
aTool.context = aTool.registration.load(this._inspector, aTool.frame);
this._inspector._emit("sidebaractivated", aTool.id);
this._inspector.emit("sidebaractivated", aTool.id);
// Send an event specific to the activation of this panel. For
// this initial event, include a "createpanel" argument
// to let panels watch sidebaractivated to refresh themselves
// but ignore the one immediately after their load.
// I don't really like this, we should find a better solution.
this._inspector._emit("sidebaractivated-" + aTool.id, "createpanel");
this._inspector.emit("sidebaractivated-" + aTool.id, "createpanel");
}.bind(this);
aTool.frame.addEventListener("load", aTool.onLoad, true);
aTool.frame.setAttribute("src", aTool.registration.contentURL);

Просмотреть файл

@ -123,8 +123,8 @@ LayoutView.prototype = {
* Destroy the nodes. Remove listeners.
*/
destroy: function LV_destroy() {
this.inspector.removeListener("select", this.onSelect);
this.inspector.removeListener("unlocked", this.onUnlock);
this.inspector.off("select", this.onSelect);
this.inspector.off("unlocked", this.onUnlock);
this.browser.removeEventListener("MozAfterPaint", this.update, true);
this.iframe.removeEventListener("keypress", this.bound_handleKeypress, true);
this.inspector.chromeWindow.removeEventListener("message", this.onMessage, true);

Просмотреть файл

@ -331,7 +331,7 @@ MarkupView.prototype = {
this._updateChildren(container);
}
}
this._inspector._emit("markupmutation");
this._inspector.emit("markupmutation");
},
/**
@ -479,7 +479,7 @@ MarkupView.prototype = {
this._frame.removeEventListener("keydown", this._boundKeyDown, true);
delete this._boundKeyDown;
this._inspector.removeListener("select", this._boundSelect);
this._inspector.off("select", this._boundSelect);
delete this._boundSelect;
delete this._elt;

Просмотреть файл

@ -0,0 +1,80 @@
var EXPORTED_SYMBOLS = ["EventEmitter"];
function EventEmitter() {
}
EventEmitter.prototype = {
/**
* Connect a listener.
*
* @param string aEvent
* The event name to which we're connecting.
* @param function aListener
* Called when the event is fired.
*/
on: function EventEmitter_on(aEvent, aListener) {
if (!this._eventEmitterListeners)
this._eventEmitterListeners = new Map();
if (!this._eventEmitterListeners.has(aEvent)) {
this._eventEmitterListeners.set(aEvent, []);
}
this._eventEmitterListeners.get(aEvent).push(aListener);
},
/**
* Listen for the next time an event is fired.
*
* @param string aEvent
* The event name to which we're connecting.
* @param function aListener
* Called when the event is fired. Will be called at most one time.
*/
once: function EventEmitter_once(aEvent, aListener) {
let handler = function() {
this.off(aEvent, handler);
aListener();
}.bind(this);
this.on(aEvent, handler);
},
/**
* Remove a previously-registered event listener. Works for events
* registered with either on or once.
*
* @param string aEvent
* The event name whose listener we're disconnecting.
* @param function aListener
* The listener to remove.
*/
off: function EventEmitter_off(aEvent, aListener) {
if (!this._eventEmitterListeners)
return;
let listeners = this._eventEmitterListeners.get(aEvent);
this._eventEmitterListeners.set(aEvent, listeners.filter(function(l) aListener != l));
},
/**
* Emit an event. All arguments to this method will
* be sent to listner functions.
*/
emit: function EventEmitter_emit(aEvent) {
if (!this._eventEmitterListeners || !this._eventEmitterListeners.has(aEvent))
return;
let originalListeners = this._eventEmitterListeners.get(aEvent);
for (let listener of this._eventEmitterListeners.get(aEvent)) {
// If the object was destroyed during event emission, stop
// emitting.
if (!this._eventEmitterListeners) {
break;
}
// If listeners were removed during emission, make sure the
// event handler we're going to fire wasn't removed.
if (originalListeners === this._eventEmitterListeners.get(aEvent) ||
this._eventEmitterListeners.get(aEvent).some(function(l) l === listener)) {
listener.apply(null, arguments);
}
}
},
}

Просмотреть файл

@ -22,6 +22,7 @@ MOCHITEST_BROWSER_FILES = \
browser_toolbar_tooltip.js \
browser_toolbar_webconsole_errors_count.js \
browser_layoutHelpers.js \
browser_eventemitter_basic.js \
head.js \
helpers.js \
leakhunt.js \

Просмотреть файл

@ -0,0 +1,66 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
function test() {
Cu.import("resource:///modules/devtools/EventEmitter.jsm", this);
let emitter = new EventEmitter();
ok(emitter, "We have an event emitter");
emitter.on("next", next);
emitter.emit("next", "abc", "def");
let beenHere1 = false;
function next(eventName, str1, str2) {
is(eventName, "next", "Got event");
is(str1, "abc", "Argument 1 is correct");
is(str2, "def", "Argument 2 is correct");
ok(!beenHere1, "first time in next callback");
beenHere1 = true;
emitter.off("next", next);
emitter.emit("next");
emitter.once("onlyonce", onlyOnce);
emitter.emit("onlyonce");
emitter.emit("onlyonce");
}
let beenHere2 = false;
function onlyOnce() {
ok(!beenHere2, "\"once\" listner has been called once");
beenHere2 = true;
emitter.emit("onlyonce");
killItWhileEmitting();
}
function killItWhileEmitting() {
function c1() {
ok(true, "c1 called");
}
function c2() {
ok(true, "c2 called");
emitter.off("tick", c3);
}
function c3() {
ok(false, "c3 should not be called");
}
function c4() {
ok(true, "c4 called");
}
emitter.on("tick", c1);
emitter.on("tick", c2);
emitter.on("tick", c3);
emitter.on("tick", c4);
emitter.emit("tick");
delete emitter;
finish();
}
}

Просмотреть файл

@ -146,9 +146,9 @@ RuleViewTool.prototype = {
},
destroy: function RVT_destroy() {
this.inspector.removeListener("select", this._onSelect);
this.inspector.removeListener("change", this._onChange);
this.inspector.removeListener("sidebaractivated-ruleview", this._onChange);
this.inspector.off("select", this._onSelect);
this.inspector.off("change", this._onChange);
this.inspector.off("sidebaractivated-ruleview", this._onChange);
this.view.element.removeEventListener("CssRuleViewChanged",
this._changeHandler);
this.view.element.removeEventListener("CssRuleViewCSSLinkClicked",
@ -214,9 +214,9 @@ ComputedViewTool.prototype = {
destroy: function CVT_destroy(aContext)
{
this.inspector.removeListener("select", this._onSelect);
this.inspector.removeListener("change", this._onChange);
this.inspector.removeListener("sidebaractivated-computedview", this._onChange);
this.inspector.off("select", this._onSelect);
this.inspector.off("change", this._onChange);
this.inspector.off("sidebaractivated-computedview", this._onChange);
this.view.destroy();
delete this.view;