--- a/mobile/android/modules/geckoview/GeckoViewUtils.jsm
+++ b/mobile/android/modules/geckoview/GeckoViewUtils.jsm
@@ -27,18 +27,18 @@ var GeckoViewUtils = {
* component must include the line
* "this.wrappedJSObject = this;" in its constructor.
* @param module If specified, load the object from a JS module.
* @param init For non-scripts, optional post-load initialization function.
* @param observers If specified, listen to specified observer notifications.
* @param ppmm If specified, listen to specified process messages.
* @param mm If specified, listen to specified frame messages.
* @param ged If specified, listen to specified global EventDispatcher events.
- * @param once If specified, only listen to the specified
- * notifications/messages once.
+ * @param once if true, only listen to the specified
+ * events/messages/notifications once.
*/
addLazyGetter: function(scope, name, {script, service, module, handler,
observers, ppmm, mm, ged, init, once}) {
if (script) {
XPCOMUtils.defineLazyScriptGetter(scope, name, script);
} else {
XPCOMUtils.defineLazyGetter(scope, name, _ => {
let ret = undefined;
@@ -98,70 +98,118 @@ var GeckoViewUtils = {
EventDispatcher.instance.registerListener(scope[name], event);
}
scope[name].onEvent(event, data, callback);
};
EventDispatcher.instance.registerListener(listener, ged);
}
},
+ _addLazyListeners: function(events, handler, scope, name, addFn, handleFn) {
+ if (!handler) {
+ handler = (_ => Array.isArray(name) ? name.map(n => scope[n]) : scope[name]);
+ }
+ let listener = (...args) => {
+ let handlers = handler(...args);
+ if (!handlers) {
+ return;
+ }
+ if (!Array.isArray(handlers)) {
+ handlers = [handlers];
+ }
+ handleFn(handlers, listener, args);
+ };
+ if (Array.isArray(events)) {
+ addFn(events, listener);
+ } else {
+ addFn([events], listener);
+ }
+ },
+
/**
* Add lazy event listeners that only load the actual handler when an event
* is being handled.
*
* @param target Event target for the event listeners.
* @param events Event name as a string or array.
* @param handler If specified, function that, for a given event, returns the
* actual event handler as an object or an array of objects.
* If handler is not specified, the actual event handler is
* specified using the scope and name pair.
* @param scope See handler.
* @param name See handler.
* @param options Options for addEventListener.
*/
addLazyEventListener: function(target, events, {handler, scope, name, options}) {
- if (!handler) {
- handler = (_ => Array.isArray(name) ? name.map(n => scope[n]) : scope[name]);
- }
- let listener = event => {
- let handlers = handler(event);
- if (!handlers) {
- return;
- }
- if (!Array.isArray(handlers)) {
- handlers = [handlers];
- }
- if (!options || !options.once) {
- target.removeEventListener(event.type, listener, options);
- handlers.forEach(handler => target.addEventListener(event.type, handler, options));
- }
- handlers.forEach(handler => handler.handleEvent(event));
- };
- if (Array.isArray(events)) {
- events.forEach(event => target.addEventListener(event, listener, options));
- } else {
- target.addEventListener(events, listener, options);
- }
+ this._addLazyListeners(events, handler, scope, name,
+ (events, listener) => {
+ events.forEach(event => target.addEventListener(event, listener, options));
+ },
+ (handlers, listener, args) => {
+ if (!options || !options.once) {
+ target.removeEventListener(args[0].type, listener, options);
+ handlers.forEach(handler =>
+ target.addEventListener(args[0].type, handler, options));
+ }
+ handlers.forEach(handler => handler.handleEvent(args[0]));
+ });
+ },
+
+ /**
+ * Add lazy event listeners on the per-window EventDispatcher, and only load
+ * the actual handler when an event is being handled.
+ *
+ * @param window Window with the target EventDispatcher.
+ * @param events Event name as a string or array.
+ * @param handler If specified, function that, for a given event, returns the
+ * actual event handler as an object or an array of objects.
+ * If handler is not specified, the actual event handler is
+ * specified using the scope and name pair.
+ * @param scope See handler.
+ * @param name See handler.
+ * @param once If true, only listen to the specified events once.
+ */
+ registerLazyWindowEventListener: function(window, events,
+ {handler, scope, name, once}) {
+ let dispatcher = this.getDispatcherForWindow(window);
+
+ this._addLazyListeners(events, handler, scope, name,
+ (events, listener) => {
+ dispatcher.registerListener(listener, events);
+ },
+ (handlers, listener, args) => {
+ if (!once) {
+ dispatcher.unregisterListener(listener, args[0]);
+ handlers.forEach(handler =>
+ dispatcher.registerListener(handler, args[0]));
+ }
+ handlers.forEach(handler => handler.onEvent(...args));
+ });
},
/**
* Return the outermost chrome DOM window (the XUL window) for a given DOM
* window.
*
* @param aWin a DOM window.
*/
getChromeWindow: function(aWin) {
- return aWin.QueryInterface(Ci.nsIInterfaceRequestor)
+ return aWin &&
+ aWin.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIDocShell).QueryInterface(Ci.nsIDocShellTreeItem)
.rootTreeItem.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIDOMWindow);
},
/**
* Return the per-nsWindow EventDispatcher for a given DOM window.
*
* @param aWin a DOM window.
*/
getDispatcherForWindow: function(aWin) {
- let win = this.getChromeWindow(aWin.top);
- return win.WindowEventDispatcher || EventDispatcher.for(win);
+ try {
+ let win = this.getChromeWindow(aWin.top);
+ return win.WindowEventDispatcher || EventDispatcher.for(win);
+ } catch (e) {
+ return null;
+ }
},
};
--- a/mobile/android/modules/geckoview/Messaging.jsm
+++ b/mobile/android/modules/geckoview/Messaging.jsm
@@ -211,32 +211,48 @@ DispatcherDelegate.prototype = {
});
},
},
};
var EventDispatcher = {
instance: new DispatcherDelegate(IS_PARENT_PROCESS ? Services.androidBridge : undefined),
+ /**
+ * Return an EventDispatcher instance for a chrome DOM window. In a content
+ * process, return a proxy through the message manager that automatically
+ * forwards events to the main process.
+ *
+ * To force using a message manager proxy (for example in a frame script
+ * environment), call forMessageManager.
+ *
+ * @param aWindow a chrome DOM window.
+ */
for: function(aWindow) {
let view = aWindow && aWindow.arguments && aWindow.arguments[0] &&
aWindow.arguments[0].QueryInterface(Ci.nsIAndroidView);
if (!view) {
- let mm = aWindow && aWindow.messageManager;
+ let mm = !IS_PARENT_PROCESS && aWindow && aWindow.messageManager;
if (!mm) {
throw new Error("window is not a GeckoView-connected window and does" +
" not have a message manager");
}
return this.forMessageManager(mm);
}
return new DispatcherDelegate(view);
},
+ /**
+ * Return an EventDispatcher instance for a message manager associated with a
+ * window.
+ *
+ * @param aWindow a message manager.
+ */
forMessageManager: function(aMessageManager) {
return new DispatcherDelegate(null, aMessageManager);
},
receiveMessage: function(aMsg) {
// aMsg.data includes keys: global, event, data, uuid
let callback;
if (aMsg.data.uuid) {