gecko-dev/devtools/shim/DevToolsShim.jsm

313 строки
8.7 KiB
JavaScript

/* 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 {utils: Cu, classes: Cc, interfaces: Ci} = Components;
const { Services } = Cu.import("resource://gre/modules/Services.jsm", {});
const { XPCOMUtils } = Cu.import("resource://gre/modules/XPCOMUtils.jsm", {});
XPCOMUtils.defineLazyGetter(this, "DevtoolsStartup", () => {
return Cc["@mozilla.org/devtools/startup-clh;1"]
.getService(Ci.nsICommandLineHandler)
.wrappedJSObject;
});
this.EXPORTED_SYMBOLS = [
"DevToolsShim",
];
function removeItem(array, callback) {
let index = array.findIndex(callback);
if (index >= 0) {
array.splice(index, 1);
}
}
/**
* The DevToolsShim is a part of the DevTools go faster project, which moves the Firefox
* DevTools outside of mozilla-central to an add-on. It aims to bridge the gap for
* existing mozilla-central code that still needs to interact with DevTools (such as
* web-extensions).
*
* DevToolsShim is a singleton that provides a set of helpers to interact with DevTools,
* that work whether the DevTools addon is installed or not. It can be used to start
* listening to events, register tools, themes. As soon as a DevTools addon is installed
* the DevToolsShim will forward all the requests received until then to the real DevTools
* instance.
*
* DevToolsShim.isInstalled() can also be used to know if DevTools are currently
* installed.
*/
this.DevToolsShim = {
_gDevTools: null,
listeners: [],
tools: [],
themes: [],
/**
* Lazy getter for the `gDevTools` instance. Should only be called when users interacts
* with DevTools as it will force loading them.
*
* @return {DevTools} a devtools instance (from client/framework/devtools)
*/
get gDevTools() {
if (!this.isInstalled()) {
throw new Error(`Trying to interact with DevTools, but they are not installed`);
}
if (!this.isInitialized()) {
this._initDevTools();
}
return this._gDevTools;
},
/**
* 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)
*/
register: function (gDevTools) {
this._gDevTools = gDevTools;
this._onDevToolsRegistered();
this._gDevTools.emit("devtools-registered");
},
/**
* Unregister the current instance of gDevTools. Should be called by DevTools during
* shutdown.
*/
unregister: function () {
if (this.isInitialized()) {
this._gDevTools.emit("devtools-unregistered");
this._gDevTools = null;
}
},
/**
* The following methods can be called before DevTools are initialized:
* - on
* - off
* - registerTool
* - unregisterTool
* - registerTheme
* - unregisterTheme
*
* 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.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.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.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.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.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.isInitialized()) {
this._gDevTools.unregisterTheme(theme);
} else {
removeItem(this.themes, t => t === theme);
}
},
/**
* Called from SessionStore.jsm in mozilla-central when saving the current state.
*
* @param {Object} state
* A SessionStore state object that gets modified by reference
*/
saveDevToolsSession: function (state) {
if (!this.isInitialized()) {
return;
}
this._gDevTools.saveDevToolsSession(state);
},
/**
* Called from SessionStore.jsm in mozilla-central when restoring a state that contained
* opened scratchpad windows and browser console.
*/
restoreDevToolsSession: function (session) {
if (!this.isInstalled()) {
return;
}
this.gDevTools.restoreDevToolsSession(session);
},
/**
* Called from nsContextMenu.js in mozilla-central when using the Inspect Element
* context menu item.
*
* @param {XULTab} tab
* The browser tab on which inspect node was used.
* @param {Array} selectors
* An array of CSS selectors to find the target node. Several selectors can be
* needed if the element is nested in frames and not directly in the root
* 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();
}
// Initialize DevTools explicitly to pass the "ContextMenu" reason to telemetry.
if (!this.isInitialized()) {
this._initDevTools("ContextMenu");
}
return this.gDevTools.inspectNode(tab, selectors);
},
/**
* Initialize DevTools via the devtools-startup command line handler component.
* Overridden in tests.
*
* @param {String} reason
* optional, if provided should be a valid entry point for DEVTOOLS_ENTRY_POINT
* in toolkit/components/telemetry/Histograms.json
*/
_initDevTools: function (reason) {
DevtoolsStartup.initDevTools(reason);
},
_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);
}
for (let theme of this.themes) {
this._gDevTools.registerTheme(theme);
}
this.listeners = [];
this.tools = [];
this.themes = [];
},
};
/**
* Compatibility layer for addon-sdk. Remove when Firefox 57 hits release.
*
* The methods below are used by classes and tests from addon-sdk/
* If DevTools are not installed when calling one of them, the call will throw.
*/
let addonSdkMethods = [
"closeToolbox",
"connectDebuggerServer",
"createDebuggerClient",
"getToolbox",
"initBrowserToolboxProcessForAddon",
"showToolbox",
];
/**
* Compatibility layer for webextensions.
*
* Those methods are called only after a DevTools webextension was loaded in DevTools,
* therefore DevTools should always be available when they are called.
*/
let webExtensionsMethods = [
"createTargetForTab",
"createWebExtensionInspectedWindowFront",
"getTargetForTab",
"getTheme",
"openBrowserConsole",
];
for (let method of [...addonSdkMethods, ...webExtensionsMethods]) {
this.DevToolsShim[method] = function () {
return this.gDevTools[method].apply(this.gDevTools, arguments);
};
}