Bug 1031609 - Event target shims (r=mconley,smaug)

This commit is contained in:
Bill McCloskey 2014-07-14 22:10:06 -07:00
Родитель a0b866406e
Коммит cb5a9b296c
2 изменённых файлов: 196 добавлений и 1 удалений

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

@ -178,7 +178,36 @@ let ObserverChild = {
};
ObserverChild.init();
// There is one of these objects per browser tab in the child. When an
// add-on in the parent listens for an event, this child object
// listens for that event in the child.
function EventTargetChild(childGlobal)
{
this._childGlobal = childGlobal;
NotificationTracker.watch("event", (path, count) => this.track(path, count));
}
EventTargetChild.prototype = {
track: function(path, count) {
let eventType = path[1];
let useCapture = path[2];
if (count) {
this._childGlobal.addEventListener(eventType, this, useCapture, true);
} else {
this._childGlobal.removeEventListener(eventType, this, useCapture);
}
},
handleEvent: function(event) {
this._childGlobal.sendRpcMessage("Addons:Event:Run",
{type: event.type, isTrusted: event.isTrusted},
{event: event});
}
};
let RemoteAddonsChild = {
init: function(global) {
// Return this so it gets rooted in the content script.
return [new EventTargetChild(global)];
},
};

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

@ -246,6 +246,164 @@ ObserverInterposition.methods.removeObserver =
target.removeObserver(observer, topic);
};
// This object is responsible for forwarding events from the child to
// the parent.
let EventTargetParent = {
init: function() {
// The _listeners map goes from targets (either <browser> elements
// or windows) to a dictionary from event types to listeners.
this._listeners = new WeakMap();
let mm = Cc["@mozilla.org/globalmessagemanager;1"].
getService(Ci.nsIMessageListenerManager);
mm.addMessageListener("Addons:Event:Run", this);
},
// If target is not on the path from a <browser> element to the
// window root, then we return null here to ignore the
// target. Otherwise, if the target is a browser-specific element
// (the <browser> or <tab> elements), then we return the
// <browser>. If it's some generic element, then we return the
// window itself.
redirectEventTarget: function(target) {
if (Cu.isCrossProcessWrapper(target)) {
return null;
}
if (target instanceof Ci.nsIDOMChromeWindow) {
return target;
}
const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
if (target instanceof Ci.nsIDOMXULElement) {
if (target.localName == "browser") {
return target;
} else if (target.localName == "tab") {
return target.linkedBrowser;
}
// Check if |target| is somewhere on the patch from the
// <tabbrowser> up to the root element.
let window = target.ownerDocument.defaultView;
if (target.contains(window.gBrowser)) {
return window;
}
}
return null;
},
// When a given event fires in the child, we fire it on the
// <browser> element and the window since those are the two possible
// results of redirectEventTarget.
getTargets: function(browser) {
let window = browser.ownerDocument.defaultView;
return [browser, window];
},
addEventListener: function(target, type, listener, useCapture, wantsUntrusted) {
let newTarget = this.redirectEventTarget(target);
if (!newTarget) {
return;
}
useCapture = useCapture || false;
wantsUntrusted = wantsUntrusted || false;
NotificationTracker.add(["event", type, useCapture]);
let listeners = this._listeners.get(newTarget);
if (!listeners) {
listeners = {};
this._listeners.set(newTarget, listeners);
}
let forType = setDefault(listeners, type, []);
// If there's already an identical listener, don't do anything.
for (let i = 0; i < forType.length; i++) {
if (forType[i].listener === listener &&
forType[i].useCapture === useCapture &&
forType[i].wantsUntrusted === wantsUntrusted) {
return;
}
}
forType.push({listener: listener, wantsUntrusted: wantsUntrusted, useCapture: useCapture});
},
removeEventListener: function(target, type, listener, useCapture) {
let newTarget = this.redirectEventTarget(target);
if (!newTarget) {
return;
}
useCapture = useCapture || false;
let listeners = this._listeners.get(newTarget);
if (!listeners) {
return;
}
let forType = setDefault(listeners, type, []);
for (let i = 0; i < forType.length; i++) {
if (forType[i].listener === listener && forType[i].useCapture === useCapture) {
forType.splice(i, 1);
NotificationTracker.remove(["event", type, useCapture]);
break;
}
}
},
receiveMessage: function(msg) {
switch (msg.name) {
case "Addons:Event:Run":
this.dispatch(msg.target, msg.data.type, msg.data.isTrusted, msg.objects.event);
break;
}
},
dispatch: function(browser, type, isTrusted, event) {
let targets = this.getTargets(browser);
for (target of targets) {
let listeners = this._listeners.get(target);
if (!listeners) {
continue;
}
let forType = setDefault(listeners, type, []);
for (let {listener, wantsUntrusted} of forType) {
if (wantsUntrusted || isTrusted) {
try {
if ("handleEvent" in listener) {
listener.handleEvent(event);
} else {
listener.call(event.target, event);
}
} catch (e) {
Cu.reportError(e);
}
}
}
}
}
};
EventTargetParent.init();
// This interposition redirects addEventListener and
// removeEventListener to EventTargetParent.
let EventTargetInterposition = new Interposition();
EventTargetInterposition.methods.addEventListener =
function(addon, target, type, listener, useCapture, wantsUntrusted) {
EventTargetParent.addEventListener(target, type, listener, useCapture, wantsUntrusted);
target.addEventListener(type, listener, useCapture, wantsUntrusted);
};
EventTargetInterposition.methods.removeEventListener =
function(addon, target, type, listener, useCapture) {
EventTargetParent.removeEventListener(target, type, listener, useCapture);
target.removeEventListener(type, listener, useCapture);
};
let RemoteAddonsParent = {
init: function() {
},
@ -264,6 +422,14 @@ let RemoteAddonsParent = {
},
getTaggedInterpositions: function() {
return {};
let result = {};
function register(tag, interp) {
result[tag] = interp;
}
register("EventTarget", EventTargetInterposition);
return result;
},
};