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
--- 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);