зеркало из https://github.com/mozilla/gecko-dev.git
Bug 723904 - Implement a EventEmiter mechanism that can be used by any tool. r=jwalker
This commit is contained in:
Родитель
98c7561041
Коммит
6e8b065c31
|
@ -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;
|
||||
|
||||
|
|
Загрузка…
Ссылка в новой задаче