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
--- a/mobile/android/chrome/content/WebrtcUI.js
+++ b/mobile/android/chrome/content/WebrtcUI.js
@@ -310,17 +310,17 @@ var WebrtcUI = {
requestType = "Camera";
else
return;
let chromeWin = this.getChromeWindow(aContentWindow);
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: [] };
if (videoDevices.length > 1 || audioDevices.length > 0) {
// videoSource is both the string used for l10n lookup and the object that will be returned
this._addDevicesToOptions(videoDevices, "videoSource", options);
}
--- a/mobile/android/components/BrowserCLH.js
+++ b/mobile/android/components/BrowserCLH.js
@@ -1,21 +1,21 @@
/* 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/. */
-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 = {};
XPCOMUtils.defineLazyGetter(Strings, "brand", _ =>
Services.strings.createBundle("chrome://branding/locale/brand.properties"));
XPCOMUtils.defineLazyGetter(Strings, "browser", _ =>
Services.strings.createBundle("chrome://browser/locale/browser.properties"));
XPCOMUtils.defineLazyGetter(Strings, "reader", _ =>
@@ -35,68 +35,41 @@ BrowserCLH.prototype = {
let url = registry.convertChromeURL(Services.io.newURI("chrome://browser/content/aboutHome.xhtml")).spec;
// Like jar:file:///data/app/org.mozilla.fennec-2.apk!/
url = url.substring(4, url.indexOf("!/") + 2);
let protocolHandler = Services.io.getProtocolHandler("resource").QueryInterface(Ci.nsIResProtocolHandler);
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",
"recording-device-events",
"VideoCapture:Paused",
"VideoCapture:Resumed",
],
});
}
- this.addObserverScripts(observerScripts);
break;
+ }
}
},
// QI
QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver]),
// XPCOMUtils factory
classID: Components.ID("{be623d20-d305-11de-8a39-0800200c9a66}")
--- a/mobile/android/components/geckoview/GeckoViewStartup.js
+++ b/mobile/android/components/geckoview/GeckoViewStartup.js
@@ -1,150 +1,87 @@
/* 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/. */
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() {
}
GeckoViewStartup.prototype = {
classID: Components.ID("{8e993c34-fdd6-432c-967e-f995d888777f}"),
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) {
case "app-startup": {
// Parent and content process.
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",
"getUserMedia:request",
"PeerConnection:request",
],
});
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",
});
}
break;
}
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: [
"ContentPrefs:FunctionCall",
"ContentPrefs:AddObserverForName",
"ContentPrefs:RemoveObserverForName",
],
});
- this.addLazyGetter({
- name: "GeckoViewPrompt",
+ GeckoViewUtils.addLazyGetter(this, "GeckoViewPrompt", {
service: "@mozilla.org/prompter;1",
mm: [
"GeckoView:Prompt",
],
});
break;
}
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,
},
});
break;
}
}
--- a/mobile/android/modules/DownloadNotifications.jsm
+++ b/mobile/android/modules/DownloadNotifications.jsm
@@ -42,17 +42,17 @@ const kButtons = {
};
var notifications = new Map();
var DownloadNotifications = {
_notificationKey: "downloads",
observe: function(subject, topic, data) {
- if (topic === "chrome-document-interactive") {
+ if (topic === "chrome-document-loaded") {
this.init();
}
},
init: function() {
Downloads.getList(Downloads.ALL)
.then(list => list.addView(this))
.then(() => this._viewAdded = true, Cu.reportError);
new file mode 100644
--- /dev/null
+++ b/mobile/android/modules/geckoview/GeckoViewUtils.jsm
@@ -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);
+ },
+};
--- a/mobile/android/modules/geckoview/moz.build
+++ b/mobile/android/modules/geckoview/moz.build
@@ -9,10 +9,11 @@ EXTRA_JS_MODULES += [
'GeckoViewContent.jsm',
'GeckoViewContentModule.jsm',
'GeckoViewModule.jsm',
'GeckoViewNavigation.jsm',
'GeckoViewProgress.jsm',
'GeckoViewScroll.jsm',
'GeckoViewSettings.jsm',
'GeckoViewTab.jsm',
+ 'GeckoViewUtils.jsm',
'Messaging.jsm',
]