Bug 1382173 - devtools shim support initialized and installed states;r=ochameau
MozReview-Commit-ID: 4CmfzyLVrM4
--- a/devtools/shim/DevToolsShim.jsm
+++ b/devtools/shim/DevToolsShim.jsm
@@ -1,14 +1,18 @@
/* 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 Cu = Components.utils;
+const Ci = Components.interfaces;
+const { Services } = Cu.import("resource://gre/modules/Services.jsm", {});
+
this.EXPORTED_SYMBOLS = [
"DevToolsShim",
];
function removeItem(array, callback) {
let index = array.findIndex(callback);
if (index >= 0) {
array.splice(index, 1);
@@ -32,21 +36,32 @@ function removeItem(array, callback) {
*/
this.DevToolsShim = {
gDevTools: null,
listeners: [],
tools: [],
themes: [],
/**
- * Check if DevTools are currently installed and available.
+ * Check if DevTools are currently installed (but not necessarily initialized).
*
* @return {Boolean} true if DevTools are installed.
*/
isInstalled: function () {
+ return Services.io.getProtocolHandler("resource")
+ .QueryInterface(Ci.nsIResProtocolHandler)
+ .hasSubstitution("devtools");
+ },
+
+ /**
+ * Check if DevTools have already been initialized.
+ *
+ * @return {Boolean} true if DevTools are initialized.
+ */
+ isInitialized: function () {
return !!this.gDevTools;
},
/**
* Register an instance of gDevTools. Should be called by DevTools during startup.
*
* @param {DevTools} a devtools instance (from client/framework/devtools)
*/
@@ -56,129 +71,139 @@ this.DevToolsShim = {
this.gDevTools.emit("devtools-registered");
},
/**
* Unregister the current instance of gDevTools. Should be called by DevTools during
* shutdown.
*/
unregister: function () {
- if (this.isInstalled()) {
+ if (this.isInitialized()) {
this.gDevTools.emit("devtools-unregistered");
this.gDevTools = null;
}
},
/**
- * The following methods can be called before DevTools are installed:
+ * The following methods can be called before DevTools are initialized:
* - on
* - off
* - registerTool
* - unregisterTool
* - registerTheme
* - unregisterTheme
*
- * If DevTools are not installed when calling the method, DevToolsShim will call the
+ * If DevTools are not initialized when calling the method, DevToolsShim will call the
* appropriate method as soon as a gDevTools instance is registered.
*/
/**
* This method is used by browser/components/extensions/ext-devtools.js for the events:
* - toolbox-created
* - toolbox-destroyed
*/
on: function (event, listener) {
- if (this.isInstalled()) {
+ if (this.isInitialized()) {
this.gDevTools.on(event, listener);
} else {
this.listeners.push([event, listener]);
}
},
/**
* This method is currently only used by devtools code, but is kept here for consistency
* with on().
*/
off: function (event, listener) {
- if (this.isInstalled()) {
+ if (this.isInitialized()) {
this.gDevTools.off(event, listener);
} else {
removeItem(this.listeners, ([e, l]) => e === event && l === listener);
}
},
/**
* This method is only used by the addon-sdk and should be removed when Firefox 56 is
* no longer supported.
*/
registerTool: function (tool) {
- if (this.isInstalled()) {
+ if (this.isInitialized()) {
this.gDevTools.registerTool(tool);
} else {
this.tools.push(tool);
}
},
/**
* This method is only used by the addon-sdk and should be removed when Firefox 56 is
* no longer supported.
*/
unregisterTool: function (tool) {
- if (this.isInstalled()) {
+ if (this.isInitialized()) {
this.gDevTools.unregisterTool(tool);
} else {
removeItem(this.tools, t => t === tool);
}
},
/**
* This method is only used by the addon-sdk and should be removed when Firefox 56 is
* no longer supported.
*/
registerTheme: function (theme) {
- if (this.isInstalled()) {
+ if (this.isInitialized()) {
this.gDevTools.registerTheme(theme);
} else {
this.themes.push(theme);
}
},
/**
* This method is only used by the addon-sdk and should be removed when Firefox 56 is
* no longer supported.
*/
unregisterTheme: function (theme) {
- if (this.isInstalled()) {
+ if (this.isInitialized()) {
this.gDevTools.unregisterTheme(theme);
} else {
removeItem(this.themes, t => t === theme);
}
},
/**
* Called from SessionStore.jsm in mozilla-central when saving the current state.
*
* @return {Array} array of currently opened scratchpad windows. Empty array if devtools
* are not installed
*/
getOpenedScratchpads: function () {
if (!this.isInstalled()) {
return [];
}
+
+ if (!this.isInitialized()) {
+ this._initDevTools();
+ }
+
return this.gDevTools.getOpenedScratchpads();
},
/**
* Called from SessionStore.jsm in mozilla-central when restoring a state that contained
* opened scratchpad windows.
*/
restoreScratchpadSession: function (scratchpads) {
if (!this.isInstalled()) {
return;
}
+
+ if (!this.isInitialized()) {
+ this._initDevTools();
+ }
+
this.gDevTools.restoreScratchpadSession(scratchpads);
},
/**
* Called from nsContextMenu.js in mozilla-central when using the Inspect Element
* context menu item.
*
* @param {XULTab} tab
@@ -189,19 +214,29 @@ this.DevToolsShim = {
* document.
* @return {Promise} a promise that resolves when the node is selected in the inspector
* markup view or that resolves immediately if DevTools are not installed.
*/
inspectNode: function (tab, selectors) {
if (!this.isInstalled()) {
return Promise.resolve();
}
+
+ if (!this.isInitialized()) {
+ this._initDevTools();
+ }
+
return this.gDevTools.inspectNode(tab, selectors);
},
+ _initDevTools: function () {
+ let { loader } = Cu.import("resource://devtools/shared/Loader.jsm", {});
+ loader.require("devtools/client/framework/devtools-browser");
+ },
+
_onDevToolsRegistered: function () {
// Register all pending event listeners on the real gDevTools object.
for (let [event, listener] of this.listeners) {
this.gDevTools.on(event, listener);
}
for (let tool of this.tools) {
this.gDevTools.registerTool(tool);
@@ -245,11 +280,15 @@ let webExtensionsMethods = [
];
for (let method of [...addonSdkMethods, ...webExtensionsMethods]) {
this.DevToolsShim[method] = function () {
if (!this.isInstalled()) {
throw new Error(`Method ${method} unavailable if DevTools are not installed`);
}
+ if (!this.isInitialized()) {
+ this._initDevTools();
+ }
+
return this.gDevTools[method].apply(this.gDevTools, arguments);
};
}
--- a/devtools/shim/tests/unit/test_devtools_shim.js
+++ b/devtools/shim/tests/unit/test_devtools_shim.js
@@ -1,17 +1,20 @@
/* -*- js-indent-level: 2; indent-tabs-mode: nil -*- */
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
-const { DevToolsShim } =
+const { DevToolsShim: realDevToolsShim } =
Components.utils.import("chrome://devtools-shim/content/DevToolsShim.jsm", {});
+// Create a copy of the DevToolsShim for the test.
+const DevToolsShim = Object.assign({}, realDevToolsShim);
+
// Test the DevToolsShim
/**
* Create a mocked version of DevTools that records all calls made to methods expected
* to be called by DevToolsShim.
*/
function createMockDevTools() {
let methods = [
@@ -36,16 +39,24 @@ function createMockDevTools() {
mock.callLog[method].push(args);
};
mock.callLog[method] = [];
}
return mock;
}
+function mockDevToolsInstalled() {
+ DevToolsShim.isInstalled = () => true;
+}
+
+function mockDevToolsUninstalled() {
+ DevToolsShim.isInstalled = () => false;
+}
+
/**
* Check if a given method was called an expected number of times, and finally check the
* arguments provided to the last call, if appropriate.
*/
function checkCalls(mock, method, length, lastArgs) {
ok(mock.callLog[method].length === length,
"Devtools.on was called the expected number of times");
@@ -57,72 +68,72 @@ function checkCalls(mock, method, length
for (let i = 0; i < lastArgs.length; i++) {
let expectedArg = lastArgs[i];
ok(mock.callLog[method][length - 1][i] === expectedArg,
`Devtools.${method} was called with the expected argument (index ${i})`);
}
}
function test_register_unregister() {
- ok(!DevToolsShim.isInstalled(), "DevTools are not installed");
+ ok(!DevToolsShim.isInitialized(), "DevTools are not initialized");
DevToolsShim.register(createMockDevTools());
- ok(DevToolsShim.isInstalled(), "DevTools are installed");
+ ok(DevToolsShim.isInitialized(), "DevTools are installed");
DevToolsShim.unregister();
- ok(!DevToolsShim.isInstalled(), "DevTools are not installed");
+ ok(!DevToolsShim.isInitialized(), "DevTools are not initialized");
}
function test_on_is_forwarded_to_devtools() {
- ok(!DevToolsShim.isInstalled(), "DevTools are not installed");
+ ok(!DevToolsShim.isInitialized(), "DevTools are not initialized");
function cb1() {}
function cb2() {}
let mock = createMockDevTools();
DevToolsShim.on("test_event", cb1);
DevToolsShim.register(mock);
checkCalls(mock, "on", 1, ["test_event", cb1]);
DevToolsShim.on("other_event", cb2);
checkCalls(mock, "on", 2, ["other_event", cb2]);
}
function test_off_called_before_registering_devtools() {
- ok(!DevToolsShim.isInstalled(), "DevTools are not installed");
+ ok(!DevToolsShim.isInitialized(), "DevTools are not initialized");
function cb1() {}
let mock = createMockDevTools();
DevToolsShim.on("test_event", cb1);
DevToolsShim.off("test_event", cb1);
DevToolsShim.register(mock);
checkCalls(mock, "on", 0);
}
function test_off_called_before_with_bad_callback() {
- ok(!DevToolsShim.isInstalled(), "DevTools are not installed");
+ ok(!DevToolsShim.isInitialized(), "DevTools are not initialized");
function cb1() {}
function cb2() {}
let mock = createMockDevTools();
DevToolsShim.on("test_event", cb1);
DevToolsShim.off("test_event", cb2);
DevToolsShim.register(mock);
// on should still be called
checkCalls(mock, "on", 1, ["test_event", cb1]);
// Calls to off should not be held and forwarded.
checkCalls(mock, "off", 0);
}
function test_registering_tool() {
- ok(!DevToolsShim.isInstalled(), "DevTools are not installed");
+ ok(!DevToolsShim.isInitialized(), "DevTools are not initialized");
let tool1 = {};
let tool2 = {};
let tool3 = {};
let mock = createMockDevTools();
// Pre-register tool1
DevToolsShim.registerTool(tool1);
@@ -141,17 +152,17 @@ function test_registering_tool() {
// Create a new mock and check the tools are not added once again.
mock = createMockDevTools();
DevToolsShim.register(mock);
checkCalls(mock, "registerTool", 0);
}
function test_registering_theme() {
- ok(!DevToolsShim.isInstalled(), "DevTools are not installed");
+ ok(!DevToolsShim.isInitialized(), "DevTools are not initialized");
let theme1 = {};
let theme2 = {};
let theme3 = {};
let mock = createMockDevTools();
// Pre-register theme1
DevToolsShim.registerTheme(theme1);
@@ -170,46 +181,51 @@ function test_registering_theme() {
// Create a new mock and check the themes are not added once again.
mock = createMockDevTools();
DevToolsShim.register(mock);
checkCalls(mock, "registerTheme", 0);
}
function test_events() {
- ok(!DevToolsShim.isInstalled(), "DevTools are not installed");
+ ok(!DevToolsShim.isInitialized(), "DevTools are not initialized");
let mock = createMockDevTools();
// Check emit was not called.
checkCalls(mock, "emit", 0);
// Check emit is called once with the devtools-registered event.
DevToolsShim.register(mock);
checkCalls(mock, "emit", 1, ["devtools-registered"]);
// Check emit is called once with the devtools-unregistered event.
DevToolsShim.unregister();
checkCalls(mock, "emit", 2, ["devtools-unregistered"]);
}
function test_scratchpad_apis() {
+ mockDevToolsUninstalled();
+
ok(!DevToolsShim.isInstalled(), "DevTools are not installed");
// Check that restoreScratchpadSession doesn't crash.
DevToolsShim.restoreScratchpadSession([{}]);
let scratchpads = DevToolsShim.getOpenedScratchpads();
equal(scratchpads.length, 0,
"getOpenedScratchpads returns [] when DevTools are not installed");
let mock = createMockDevTools();
- DevToolsShim.register(mock);
- // Check that calls to restoreScratchpadSession are not held.
- checkCalls(mock, "restoreScratchpadSession", 0);
+ mockDevToolsInstalled();
+ DevToolsShim._initDevTools = () => {
+ // Next call to getOpenedScratchpags is expected to initialize DevTools, which we
+ // simulate here by registering our mock.
+ DevToolsShim.register(mock);
+ };
DevToolsShim.getOpenedScratchpads();
checkCalls(mock, "getOpenedScratchpads", 1, []);
let scratchpadSessions = [{}];
DevToolsShim.restoreScratchpadSession(scratchpadSessions);
checkCalls(mock, "restoreScratchpadSession", 1, [scratchpadSessions]);
}