Commit 70aad3b8 authored by Jim Chen's avatar Jim Chen
Browse files

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

--HG--
extra : rebase_source : 0c3d92ee2426026d9ec2ad78d77b2c03aa247811
parent bcd45085
Loading
Loading
Loading
Loading
+14 −41
Original line number Diff line number Diff line
@@ -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,9 +63,9 @@ BrowserCLH.prototype = {
            ],
          });
        }
        this.addObserverScripts(observerScripts);
        break;
      }
    }
  },

  // QI
+11 −74
Original line number Diff line number Diff line
@@ -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,
+1 −1
Original line number Diff line number Diff line
@@ -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();
    }
  },
+162 −0
Original line number Diff line number Diff line
/* 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));
    }

    if (ppmm || mm) {
      let target = ppmm ? Services.ppmm : Services.mm;
      let listener = msg => {
        target.removeMessageListener(msg.name, listener);
        if (!once) {
          target.addMessageListener(msg.name, scope[name]);
        }
        scope[name].receiveMessage(msg);
      };
      (ppmm || mm).forEach(msg => target.addMessageListener(msg, listener));
    }

    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);
  },
};
+1 −0
Original line number Diff line number Diff line
@@ -13,5 +13,6 @@ EXTRA_JS_MODULES += [
    'GeckoViewProgress.jsm',
    'GeckoViewScroll.jsm',
    'GeckoViewSettings.jsm',
    'GeckoViewUtils.jsm',
    'Messaging.jsm',
]