Bug 1452200 - 1c. Inject logging functions into GeckoView JS modules; r?snorp draft
authorJim Chen <nchen@mozilla.com>
Sun, 15 Apr 2018 14:53:29 -0400
changeset 782398 1c04b6c7f87c3450253a1805f87a9b1b2917d826
parent 782397 cbdbdc5f5607401fe402f7e51103130050a8b5e7
child 782399 32ad3d7b0eaecca97e6b0b68d451fa6d656476c6
push id106522
push userbmo:nchen@mozilla.com
push dateSun, 15 Apr 2018 18:53:59 +0000
reviewerssnorp
bugs1452200
milestone61.0a1
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);