Bug 1367077 - 1. Move startup utility functions into GeckoViewUtils; r=snorp

Move `addLazyGetter` and `addLazyEventListener` utility functions from
GeckoViewStartup.js into GeckoViewUtils.jsm, so they can be used for
both Fennec and standalone GeckoView.

Also switch to "chrome-document-loaded" for loading
DownloadNotifications because that's later in the startup sequence.

MozReview-Commit-ID: 1caMtufkHGR
This commit is contained in:
Jim Chen 2017-09-14 17:50:54 -04:00
parent 1151db8b05
commit 3dfb8bc0dc
6 changed files with 195 additions and 117 deletions

View File

@ -315,7 +315,7 @@ var WebrtcUI = {
let uri = aContentWindow.document.documentURIObject;
let host = uri.host;
let requestor = (chromeWin.BrowserApp && chromeWin.BrowserApp.manifest) ?
"'" + BrowserApp.manifest.name + "'" : host;
"'" + chromeWin.BrowserApp.manifest.name + "'" : host;
let message = Strings.browser.formatStringFromName("getUserMedia.share" + requestType + ".message", [ requestor ], 1);
let options = { inputs: [] };

View File

@ -2,15 +2,15 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
const Cc = Components.classes;
const Ci = Components.interfaces;
const Cu = Components.utils;
const {classes: Cc, interfaces: Ci, utils: Cu} = Components;
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/Services.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "AppConstants",
"resource://gre/modules/AppConstants.jsm");
XPCOMUtils.defineLazyModuleGetters(this, {
AppConstants: "resource://gre/modules/AppConstants.jsm",
GeckoViewUtils: "resource://gre/modules/GeckoViewUtils.jsm",
Services: "resource://gre/modules/Services.jsm",
});
var Strings = {};
@ -40,47 +40,20 @@ BrowserCLH.prototype = {
protocolHandler.setSubstitution("android", Services.io.newURI(url));
},
addObserverScripts: function(aScripts) {
aScripts.forEach(item => {
let {name, topics, script} = item;
XPCOMUtils.defineLazyGetter(this, name, _ => {
let sandbox = {};
if (script.endsWith(".jsm")) {
Cu.import(script, sandbox);
} else {
Services.scriptloader.loadSubScript(script, sandbox);
}
return sandbox[name];
});
let observer = (subject, topic, data) => {
Services.obs.removeObserver(observer, topic);
if (!item.once) {
Services.obs.addObserver(this[name], topic);
}
this[name].observe(subject, topic, data); // Explicitly notify new observer
};
topics.forEach(topic => {
Services.obs.addObserver(observer, topic);
});
});
},
observe: function(subject, topic, data) {
switch (topic) {
case "app-startup":
case "app-startup": {
this.setResourceSubstitutions();
let observerScripts = [{
name: "DownloadNotifications",
script: "resource://gre/modules/DownloadNotifications.jsm",
topics: ["chrome-document-interactive"],
GeckoViewUtils.addLazyGetter(this, "DownloadNotifications", {
module: "resource://gre/modules/DownloadNotifications.jsm",
observers: ["chrome-document-loaded"],
once: true,
}];
});
if (AppConstants.MOZ_WEBRTC) {
observerScripts.push({
name: "WebrtcUI",
GeckoViewUtils.addLazyGetter(this, "WebrtcUI", {
script: "chrome://browser/content/WebrtcUI.js",
topics: [
observers: [
"getUserMedia:ask-device-permission",
"getUserMedia:request",
"PeerConnection:request",
@ -90,8 +63,8 @@ BrowserCLH.prototype = {
],
});
}
this.addObserverScripts(observerScripts);
break;
}
}
},

View File

@ -6,8 +6,10 @@ const {classes: Cc, interfaces: Ci, utils: Cu} = Components;
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "Services",
"resource://gre/modules/Services.jsm");
XPCOMUtils.defineLazyModuleGetters(this, {
GeckoViewUtils: "resource://gre/modules/GeckoViewUtils.jsm",
Services: "resource://gre/modules/Services.jsm",
});
function GeckoViewStartup() {
}
@ -17,59 +19,6 @@ GeckoViewStartup.prototype = {
QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver]),
addLazyGetter: function({name, script, service, module,
observers, ppmm, mm, init, once}) {
if (script) {
XPCOMUtils.defineLazyScriptGetter(this, name, script);
} else if (module) {
XPCOMUtils.defineLazyGetter(this, name, _ => {
let sandbox = {};
Cu.import(module, sandbox);
if (init) {
init.call(this, sandbox[name]);
}
return sandbox[name];
});
} else if (service) {
XPCOMUtils.defineLazyGetter(this, name, _ =>
Cc[service].getService(Ci.nsISupports).wrappedJSObject);
}
if (observers) {
let observer = (subject, topic, data) => {
Services.obs.removeObserver(observer, topic);
if (!once) {
Services.obs.addObserver(this[name], topic);
}
this[name].observe(subject, topic, data); // Explicitly notify new observer
};
observers.forEach(topic => Services.obs.addObserver(observer, topic));
}
if (ppmm || mm) {
let target = ppmm ? Services.ppmm : Services.mm;
let listener = msg => {
target.removeMessageListener(msg.name, listener);
if (!once) {
target.addMessageListener(msg.name, this[name]);
}
this[name].receiveMessage(msg);
};
(ppmm || mm).forEach(msg => target.addMessageListener(msg, listener));
}
},
addLazyEventListener: function({name, target, events, options}) {
let listener = event => {
if (!options || !options.once) {
target.removeEventListener(event.type, listener, options);
target.addEventListener(event.type, this[name], options);
}
this[name].handleEvent(event);
};
events.forEach(event => target.addEventListener(event, listener, options));
},
/* ---------- nsIObserver ---------- */
observe: function(aSubject, aTopic, aData) {
switch (aTopic) {
@ -78,8 +27,7 @@ GeckoViewStartup.prototype = {
Services.obs.addObserver(this, "chrome-document-global-created");
Services.obs.addObserver(this, "content-document-global-created");
this.addLazyGetter({
name: "GeckoViewPermission",
GeckoViewUtils.addLazyGetter(this, "GeckoViewPermission", {
service: "@mozilla.org/content-permission/prompt;1",
observers: [
"getUserMedia:ask-device-permission",
@ -90,8 +38,7 @@ GeckoViewStartup.prototype = {
if (Services.appinfo.processType != Services.appinfo.PROCESS_TYPE_DEFAULT) {
// Content process only.
this.addLazyGetter({
name: "GeckoViewPrompt",
GeckoViewUtils.addLazyGetter(this, "GeckoViewPrompt", {
service: "@mozilla.org/prompter;1",
});
}
@ -101,8 +48,7 @@ GeckoViewStartup.prototype = {
case "profile-after-change": {
// Parent process only.
// ContentPrefServiceParent is needed for e10s file picker.
this.addLazyGetter({
name: "ContentPrefServiceParent",
GeckoViewUtils.addLazyGetter(this, "ContentPrefServiceParent", {
module: "resource://gre/modules/ContentPrefServiceParent.jsm",
init: cpsp => cpsp.alwaysInit(),
ppmm: [
@ -112,8 +58,7 @@ GeckoViewStartup.prototype = {
],
});
this.addLazyGetter({
name: "GeckoViewPrompt",
GeckoViewUtils.addLazyGetter(this, "GeckoViewPrompt", {
service: "@mozilla.org/prompter;1",
mm: [
"GeckoView:Prompt",
@ -124,22 +69,14 @@ GeckoViewStartup.prototype = {
case "chrome-document-global-created":
case "content-document-global-created": {
let win = aSubject.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIDocShell).QueryInterface(Ci.nsIDocShellTreeItem)
.rootTreeItem.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIDOMWindow);
let win = GeckoViewUtils.getChromeWindow(aSubject);
if (win !== aSubject) {
// Only attach to top-level windows.
return;
}
this.addLazyEventListener({
name: "GeckoViewPrompt",
target: win,
events: [
"click",
"contextmenu",
],
GeckoViewUtils.addLazyEventListener(win, ["click", "contextmenu"], {
handler: _ => this.GeckoViewPrompt,
options: {
capture: false,
mozSystemGroup: true,

View File

@ -47,7 +47,7 @@ var DownloadNotifications = {
_notificationKey: "downloads",
observe: function(subject, topic, data) {
if (topic === "chrome-document-interactive") {
if (topic === "chrome-document-loaded") {
this.init();
}
},

View File

@ -0,0 +1,167 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict"
const { classes: Cc, interfaces: Ci, utils: Cu } = Components;
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
XPCOMUtils.defineLazyModuleGetters(this, {
Services: "resource://gre/modules/Services.jsm",
EventDispatcher: "resource://gre/modules/Messaging.jsm",
});
this.EXPORTED_SYMBOLS = ["GeckoViewUtils"];
var GeckoViewUtils = {
/**
* Define a lazy getter that loads an object from external code, and
* optionally handles observer and/or message manager notifications for the
* object, so the object only loads when a notification is received.
*
* @param scope Scope for holding the loaded object.
* @param name Name of the object to load.
* @param script If specified, load the object from a JS subscript.
* @param service If specified, load the object from a JS component; the
* 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.
*/
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;
if (module) {
ret = Cu.import(module, {})[name];
} else if (service) {
ret = Cc[service].getService(Ci.nsISupports).wrappedJSObject;
} else if (typeof handler === "function") {
ret = {
handleEvent: handler,
observe: handler,
onEvent: handler,
receiveMessage: handler,
};
} else if (handler) {
ret = handler;
}
if (ret && init) {
init.call(scope, ret);
}
return ret;
});
}
if (observers) {
let observer = (subject, topic, data) => {
Services.obs.removeObserver(observer, topic);
if (!once) {
Services.obs.addObserver(scope[name], topic);
}
scope[name].observe(subject, topic, data); // Explicitly notify new observer
};
observers.forEach(topic => Services.obs.addObserver(observer, topic));
}
let addMMListener = (target, names) => {
let listener = msg => {
target.removeMessageListener(msg.name, listener);
if (!once) {
target.addMessageListener(msg.name, scope[name]);
}
scope[name].receiveMessage(msg);
};
names.forEach(msg => target.addMessageListener(msg, listener));
};
if (ppmm) {
addMMListener(Services.ppmm, ppmm);
}
if (mm) {
addMMListener(Services.mm, mm);
}
if (ged) {
let listener = (event, data, callback) => {
EventDispatcher.instance.unregisterListener(listener, event);
if (!once) {
EventDispatcher.instance.registerListener(scope[name], event);
}
scope[name].onEvent(event, data, callback);
};
EventDispatcher.instance.registerListener(listener, ged);
}
},
/**
* 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);
}
},
/**
* 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)
.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);
},
};

View File

@ -13,6 +13,7 @@ EXTRA_JS_MODULES += [
'GeckoViewProgress.jsm',
'GeckoViewScroll.jsm',
'GeckoViewSettings.jsm',
'GeckoViewUtils.jsm',
'GeckoViewTab.jsm',
'Messaging.jsm',
]