Bug 1452200 - 1c. Inject logging functions into GeckoView JS modules; r=snorp
☠☠ backed out by e96685584bf7 ☠ ☠
authorJim Chen <nchen@mozilla.com>
Sun, 15 Apr 2018 00:15:27 -0400
changeset 413394 e4cdad2cd3d2e21fd0994ba523f743a7b7f43896
parent 413393 8ac249bdc7726ab42cb4e381aae09a573e454e81
child 413395 b03e9dc6ecd7751d56be0d68ac3b338dcee2cc2c
push id33847
push userncsoregi@mozilla.com
push dateSun, 15 Apr 2018 09:17:31 +0000
treeherdermozilla-central@e96685584bf7 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerssnorp
bugs1452200
milestone61.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1452200 - 1c. Inject logging functions into GeckoView JS modules; r=snorp Inject new logging functions, "debug" and "warn", into each GeckoView JS module that geckoview.js loads. Also do the same thing for frame script classes that extend from GeckoViewContentModule. The new logging functions are used with template literals (debug `hello ${foo} world`;), which are lazily evaluated, so disabled logs don't use as many CPU cycles. They can also be easily enabled/disabled. MozReview-Commit-ID: 7ZfYAMrcCyU
mobile/android/chrome/geckoview/ErrorPageEventHandler.js
mobile/android/chrome/geckoview/GeckoViewContent.js
mobile/android/chrome/geckoview/GeckoViewContentSettings.js
mobile/android/chrome/geckoview/GeckoViewNavigationContent.js
mobile/android/chrome/geckoview/GeckoViewScrollContent.js
mobile/android/chrome/geckoview/GeckoViewSelectionActionContent.js
mobile/android/chrome/geckoview/geckoview.js
mobile/android/components/geckoview/GeckoViewExternalAppService.js
mobile/android/modules/geckoview/.eslintrc.js
mobile/android/modules/geckoview/GeckoViewContentModule.jsm
mobile/android/modules/geckoview/GeckoViewModule.jsm
mobile/android/modules/geckoview/GeckoViewUtils.jsm
--- a/mobile/android/chrome/geckoview/ErrorPageEventHandler.js
+++ b/mobile/android/chrome/geckoview/ErrorPageEventHandler.js
@@ -1,14 +1,18 @@
 /* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
 /* 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/. */
 
 ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
+ChromeUtils.import("resource://gre/modules/GeckoViewUtils.jsm");
+
+/* global debug:false, warn:false */
+GeckoViewUtils.initLogging("GeckoView.ErrorPageEventHandler", this);
 
 ChromeUtils.defineModuleGetter(this, "SSLExceptions",
                                "resource://gre/modules/SSLExceptions.jsm");
 
 XPCOMUtils.defineLazyModuleGetters(this, {
   Services: "resource://gre/modules/Services.jsm",
 });
 
--- a/mobile/android/chrome/geckoview/GeckoViewContent.js
+++ b/mobile/android/chrome/geckoview/GeckoViewContent.js
@@ -192,9 +192,10 @@ class GeckoViewContent extends GeckoView
         this.eventDispatcher.sendRequest({
           type: "GeckoView:DOMWindowClose"
         });
         break;
     }
   }
 }
 
-var contentListener = new GeckoViewContent("GeckoViewContent", this);
+let {debug, warn} = GeckoViewContent.initLogging("GeckoViewContent");
+let module = GeckoViewContent.create(this);
--- a/mobile/android/chrome/geckoview/GeckoViewContentSettings.js
+++ b/mobile/android/chrome/geckoview/GeckoViewContentSettings.js
@@ -66,9 +66,10 @@ class GeckoViewContentSettings extends G
   set displayMode(aMode) {
     const docShell = content && GeckoViewUtils.getRootDocShell(content);
     if (docShell) {
       docShell.displayMode = aMode;
     }
   }
 }
 
-var settings = new GeckoViewContentSettings("GeckoViewSettings", this);
+let {debug, warn} = GeckoViewContentSettings.initLogging("GeckoViewSettings");
+let module = GeckoViewContentSettings.create(this);
--- a/mobile/android/chrome/geckoview/GeckoViewNavigationContent.js
+++ b/mobile/android/chrome/geckoview/GeckoViewNavigationContent.js
@@ -42,9 +42,10 @@ class GeckoViewNavigationContent extends
       addEventListener("click", ErrorPageEventHandler, true);
     }
 
     return LoadURIDelegate.load(this.eventDispatcher, aUri, aWhere, aFlags,
                                 aTriggeringPrincipal);
   }
 }
 
-var navigationListener = new GeckoViewNavigationContent("GeckoViewNavigation", this);
+let {debug, warn} = GeckoViewNavigationContent.initLogging("GeckoViewNavigation");
+let module = GeckoViewNavigationContent.create(this);
--- a/mobile/android/chrome/geckoview/GeckoViewScrollContent.js
+++ b/mobile/android/chrome/geckoview/GeckoViewScrollContent.js
@@ -38,9 +38,11 @@ class GeckoViewScrollContent extends Gec
           type: "GeckoView:ScrollChanged",
           scrollX: Math.round(content.scrollX),
           scrollY: Math.round(content.scrollY)
         });
         break;
     }
   }
 }
-var scrollListener = new GeckoViewScrollContent("GeckoViewScroll", this);
+
+let {debug, warn} = GeckoViewScrollContent.initLogging("GeckoViewScroll");
+let module = GeckoViewScrollContent.create(this);
--- a/mobile/android/chrome/geckoview/GeckoViewSelectionActionContent.js
+++ b/mobile/android/chrome/geckoview/GeckoViewSelectionActionContent.js
@@ -253,10 +253,11 @@ class GeckoViewSelectionActionContent ex
       });
 
     } else {
       dump("Unknown reason: " + reason);
     }
   }
 }
 
-var selectionActionListener =
-    new GeckoViewSelectionActionContent("GeckoViewSelectionAction", this);
+let {debug, warn} =
+    GeckoViewSelectionActionContent.initLogging("GeckoViewSelectionAction");
+let module = GeckoViewSelectionActionContent.create(this);
--- a/mobile/android/chrome/geckoview/geckoview.js
+++ b/mobile/android/chrome/geckoview/geckoview.js
@@ -1,21 +1,22 @@
 /* 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";
 
-ChromeUtils.import("resource://gre/modules/AppConstants.jsm");
 ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
 
-ChromeUtils.defineModuleGetter(this, "EventDispatcher",
-  "resource://gre/modules/Messaging.jsm");
-ChromeUtils.defineModuleGetter(this, "Services",
-  "resource://gre/modules/Services.jsm");
+XPCOMUtils.defineLazyModuleGetters(this, {
+  EventDispatcher: "resource://gre/modules/Messaging.jsm",
+  GeckoViewUtils: "resource://gre/modules/GeckoViewUtils.jsm",
+  Services: "resource://gre/modules/Services.jsm",
+});
+
 XPCOMUtils.defineLazyGetter(this, "WindowEventDispatcher",
   () => EventDispatcher.for(window));
 
 XPCOMUtils.defineLazyGetter(this, "dump", () =>
     ChromeUtils.import("resource://gre/modules/AndroidLog.jsm",
                        {}).AndroidLog.d.bind(null, "View"));
 
 // Creates and manages GeckoView modules.
@@ -27,18 +28,21 @@ XPCOMUtils.defineLazyGetter(this, "dump"
 var ModuleManager = {
   init: function(aBrowser) {
     this.browser = aBrowser;
     this.modules = new Map();
   },
 
   add: function(aResource, aType, ...aArgs) {
     this.remove(aType);
-    let scope = {};
-    ChromeUtils.import(aResource, scope);
+
+    const scope = {};
+    const global = ChromeUtils.import(aResource, scope);
+    const tag = aType.replace("GeckoView", "GeckoView.");
+    GeckoViewUtils.initLogging(tag, global);
 
     this.modules.set(aType, new scope[aType](
       aType, window, this.browser, WindowEventDispatcher, ...aArgs
     ));
   },
 
   remove: function(aType) {
     this.modules.delete(aType);
@@ -53,16 +57,18 @@ function createBrowser() {
   const browser = window.browser = document.createElement("browser");
   browser.setAttribute("type", "content");
   browser.setAttribute("primary", "true");
   browser.setAttribute("flex", "1");
   return browser;
 }
 
 function startup() {
+  GeckoViewUtils.initLogging("GeckoView.XUL", window);
+
   const browser = createBrowser();
   ModuleManager.init(browser);
 
   ModuleManager.add("resource://gre/modules/GeckoViewNavigation.jsm",
                     "GeckoViewNavigation");
   ModuleManager.add("resource://gre/modules/GeckoViewSettings.jsm",
                     "GeckoViewSettings");
   ModuleManager.add("resource://gre/modules/GeckoViewContent.jsm",
--- a/mobile/android/components/geckoview/GeckoViewExternalAppService.js
+++ b/mobile/android/components/geckoview/GeckoViewExternalAppService.js
@@ -1,15 +1,19 @@
 /* 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";
 
 ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
+ChromeUtils.import("resource://gre/modules/GeckoViewUtils.jsm");
+
+/* global debug:false, warn:false */
+GeckoViewUtils.initLogging("GeckoView.ExternalAppService", this);
 
 ChromeUtils.defineModuleGetter(this, "EventDispatcher",
   "resource://gre/modules/Messaging.jsm");
 
 XPCOMUtils.defineLazyGetter(this, "dump", () =>
     ChromeUtils.import("resource://gre/modules/AndroidLog.jsm",
                        {}).AndroidLog.d.bind(null, "ViewContent"));
 
new file mode 100644
--- /dev/null
+++ b/mobile/android/modules/geckoview/.eslintrc.js
@@ -0,0 +1,8 @@
+"use strict";
+
+module.exports = {
+  "globals": {
+    "debug": false,
+    "warn": false,
+  },
+};
--- a/mobile/android/modules/geckoview/GeckoViewContentModule.jsm
+++ b/mobile/android/modules/geckoview/GeckoViewContentModule.jsm
@@ -2,29 +2,42 @@
  * 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";
 
 var EXPORTED_SYMBOLS = ["GeckoViewContentModule"];
 
 ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
+ChromeUtils.import("resource://gre/modules/GeckoViewUtils.jsm");
+
+GeckoViewUtils.initLogging("GeckoView.Module.[C]", this);
 
 ChromeUtils.defineModuleGetter(this, "EventDispatcher",
   "resource://gre/modules/Messaging.jsm");
 
 XPCOMUtils.defineLazyGetter(this, "dump", () =>
     ChromeUtils.import("resource://gre/modules/AndroidLog.jsm",
                        {}).AndroidLog.d.bind(null, "ViewContentModule"));
 
 // function debug(aMsg) {
 //   dump(aMsg);
 // }
 
 class GeckoViewContentModule {
+  static initLogging(aModuleName) {
+    this._moduleName = aModuleName;
+    const tag = aModuleName.replace("GeckoView", "GeckoView.") + ".[C]";
+    return GeckoViewUtils.initLogging(tag, {});
+  }
+
+  static create(aGlobal, aModuleName) {
+    return new this(aModuleName || this._moduleName, aGlobal);
+  }
+
   constructor(aModuleName, aMessageManager) {
     this.moduleName = aModuleName;
     this.messageManager = aMessageManager;
     this.eventDispatcher = EventDispatcher.forMessageManager(aMessageManager);
 
     this.messageManager.addMessageListener(
       "GeckoView:UpdateSettings",
       aMsg => {
--- a/mobile/android/modules/geckoview/GeckoViewModule.jsm
+++ b/mobile/android/modules/geckoview/GeckoViewModule.jsm
@@ -2,16 +2,19 @@
  * 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";
 
 var EXPORTED_SYMBOLS = ["GeckoViewModule"];
 
 ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
+ChromeUtils.import("resource://gre/modules/GeckoViewUtils.jsm");
+
+GeckoViewUtils.initLogging("GeckoView.Module", this);
 
 XPCOMUtils.defineLazyGetter(this, "dump", () =>
     ChromeUtils.import("resource://gre/modules/AndroidLog.jsm",
                        {}).AndroidLog.d.bind(null, "ViewModule"));
 
 // function debug(aMsg) {
 //   dump(aMsg);
 // }
--- a/mobile/android/modules/geckoview/GeckoViewUtils.jsm
+++ b/mobile/android/modules/geckoview/GeckoViewUtils.jsm
@@ -1,18 +1,19 @@
 /* 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";
 
 ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
 
 XPCOMUtils.defineLazyModuleGetters(this, {
+  EventDispatcher: "resource://gre/modules/Messaging.jsm",
+  Log: "resource://gre/modules/Log.jsm",
   Services: "resource://gre/modules/Services.jsm",
-  EventDispatcher: "resource://gre/modules/Messaging.jsm",
 });
 
 var 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
@@ -247,12 +248,96 @@ var GeckoViewUtils = {
       dispatcher = this.getDispatcherForWindow(
           iter.getNext().QueryInterface(Ci.nsIDOMWindow));
       if (dispatcher) {
         return dispatcher;
       }
     }
     return null;
   },
+
+  /**
+   * Add logging functions to the specified scope that forward to the given
+   * Log.jsm logger. Currently "debug" and "warn" functions are supported. To
+   * log something, call the function through a template literal:
+   *
+   *   function foo(bar, baz) {
+   *     debug `hello world`;
+   *     debug `foo called with ${bar} as bar`;
+   *     warn `this is a warning for ${baz}`;
+   *   }
+   *
+   * An inline format can also be used for logging:
+   *
+   *   let bar = 42;
+   *   do_something(bar); // No log.
+   *   do_something(debug.foo = bar); // Output "foo = 42" to the log.
+   *
+   * @param tag Name of the Log.jsm logger to forward logs to.
+   * @param scope Scope to add the logging functions to.
+   */
+  initLogging: function(tag, scope) {
+    // Only provide two levels for simplicity.
+    // For "info", use "debug" instead.
+    // For "error", throw an actual JS error instead.
+    for (const level of ["debug", "warn"]) {
+      const log = (strings, ...exprs) =>
+          this._log(log.logger, level, strings, exprs);
+
+      XPCOMUtils.defineLazyGetter(log, "logger", _ => {
+        const logger = Log.repository.getLogger(tag);
+        logger.parent = this.rootLogger;
+        return logger;
+      });
+
+      scope[level] = new Proxy(log, {
+        set: (obj, prop, value) => obj([prop + " = ", ""], value) || true,
+      });
+    }
+    return scope;
+  },
+
+  get rootLogger() {
+    if (!this._rootLogger) {
+      this._rootLogger = Log.repository.getLogger("GeckoView");
+      this._rootLogger.addAppender(new Log.AndroidAppender());
+    }
+    return this._rootLogger;
+  },
+
+  _log: function(logger, level, strings, exprs) {
+    if (!Array.isArray(strings)) {
+      const [, file, line] =
+          (new Error()).stack.match(/.*\n.*\n.*@(.*):(\d+):/);
+      throw Error(`Expecting template literal: ${level} \`foo \${bar}\``,
+                  file, +line);
+    }
+
+    // Do some GeckoView-specific formatting:
+    // 1) Heuristically format flags as hex.
+    // 2) Heuristically format nsresult as string name or hex.
+    for (let i = 0; i < exprs.length; i++) {
+      const expr = exprs[i];
+      switch (typeof expr) {
+        case "number":
+          if (expr > 0 && /\ba?[fF]lags?[\s=:]+$/.test(strings[i])) {
+            // Likely a flag; display in hex.
+            exprs[i] = `0x${expr.toString(0x10)}`;
+          } else if (expr >= 0 && /\b(a?[sS]tatus|rv)[\s=:]+$/.test(strings[i])) {
+            // Likely an nsresult; display in name or hex.
+            exprs[i] = `0x${expr.toString(0x10)}`;
+            for (const name in Cr) {
+              if (expr === Cr[name]) {
+                exprs[i] = name;
+                break;
+              }
+            }
+          }
+          break;
+      }
+    }
+
+    return logger[level](strings, ...exprs);
+  },
 };
 
 XPCOMUtils.defineLazyGetter(GeckoViewUtils, "IS_PARENT_PROCESS", _ =>
     Services.appinfo.processType == Services.appinfo.PROCESS_TYPE_DEFAULT);