mirror of
https://repo.dactyloidae.xyz/Dactyloidae/UXP.git
synced 2026-10-16 04:07:31 +09:00
import FIREFOX_52_6_0esr_RELEASE from mozilla-esr52 hg repo
This commit is contained in:
commit
dcd9973243
150858 changed files with 23884658 additions and 0 deletions
333
devtools/server/actors/webextension.js
Normal file
333
devtools/server/actors/webextension.js
Normal file
|
|
@ -0,0 +1,333 @@
|
|||
/* 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 { Ci, Cu } = require("chrome");
|
||||
const Services = require("Services");
|
||||
const { ChromeActor } = require("./chrome");
|
||||
const makeDebugger = require("./utils/make-debugger");
|
||||
|
||||
var DevToolsUtils = require("devtools/shared/DevToolsUtils");
|
||||
var { assert } = DevToolsUtils;
|
||||
|
||||
loader.lazyRequireGetter(this, "mapURIToAddonID", "devtools/server/actors/utils/map-uri-to-addon-id");
|
||||
loader.lazyRequireGetter(this, "unwrapDebuggerObjectGlobal", "devtools/server/actors/script", true);
|
||||
|
||||
loader.lazyImporter(this, "AddonManager", "resource://gre/modules/AddonManager.jsm");
|
||||
loader.lazyImporter(this, "XPIProvider", "resource://gre/modules/addons/XPIProvider.jsm");
|
||||
|
||||
const FALLBACK_DOC_MESSAGE = "Your addon does not have any document opened yet.";
|
||||
|
||||
/**
|
||||
* Creates a TabActor for debugging all the contexts associated to a target WebExtensions
|
||||
* add-on.
|
||||
* Most of the implementation is inherited from ChromeActor (which inherits most of its
|
||||
* implementation from TabActor).
|
||||
* WebExtensionActor is a child of RootActor, it can be retrieved via
|
||||
* RootActor.listAddons request.
|
||||
* WebExtensionActor exposes all tab actors via its form() request, like TabActor.
|
||||
*
|
||||
* History lecture:
|
||||
* The add-on actors used to not inherit TabActor because of the different way the
|
||||
* add-on APIs where exposed to the add-on itself, and for this reason the Addon Debugger
|
||||
* has only a sub-set of the feature available in the Tab or in the Browser Toolbox.
|
||||
* In a WebExtensions add-on all the provided contexts (background and popup pages etc.),
|
||||
* besides the Content Scripts which run in the content process, hooked to an existent
|
||||
* tab, by creating a new WebExtensionActor which inherits from ChromeActor, we can
|
||||
* provide a full features Addon Toolbox (which is basically like a BrowserToolbox which
|
||||
* filters the visible sources and frames to the one that are related to the target
|
||||
* add-on).
|
||||
*
|
||||
* @param conn DebuggerServerConnection
|
||||
* The connection to the client.
|
||||
* @param addon AddonWrapper
|
||||
* The target addon.
|
||||
*/
|
||||
function WebExtensionActor(conn, addon) {
|
||||
ChromeActor.call(this, conn);
|
||||
|
||||
this.id = addon.id;
|
||||
this.addon = addon;
|
||||
|
||||
// Bind the _allowSource helper to this, it is used in the
|
||||
// TabActor to lazily create the TabSources instance.
|
||||
this._allowSource = this._allowSource.bind(this);
|
||||
|
||||
// Set the consoleAPIListener filtering options
|
||||
// (retrieved and used in the related webconsole child actor).
|
||||
this.consoleAPIListenerOptions = {
|
||||
addonId: addon.id,
|
||||
};
|
||||
|
||||
// This creates a Debugger instance for debugging all the add-on globals.
|
||||
this.makeDebugger = makeDebugger.bind(null, {
|
||||
findDebuggees: dbg => {
|
||||
return dbg.findAllGlobals().filter(this._shouldAddNewGlobalAsDebuggee);
|
||||
},
|
||||
shouldAddNewGlobalAsDebuggee: this._shouldAddNewGlobalAsDebuggee.bind(this),
|
||||
});
|
||||
|
||||
// Discover the preferred debug global for the target addon
|
||||
this.preferredTargetWindow = null;
|
||||
this._findAddonPreferredTargetWindow();
|
||||
|
||||
AddonManager.addAddonListener(this);
|
||||
}
|
||||
exports.WebExtensionActor = WebExtensionActor;
|
||||
|
||||
WebExtensionActor.prototype = Object.create(ChromeActor.prototype);
|
||||
|
||||
WebExtensionActor.prototype.actorPrefix = "webExtension";
|
||||
WebExtensionActor.prototype.constructor = WebExtensionActor;
|
||||
|
||||
// NOTE: This is needed to catch in the webextension webconsole all the
|
||||
// errors raised by the WebExtension internals that are not currently
|
||||
// associated with any window.
|
||||
WebExtensionActor.prototype.isRootActor = true;
|
||||
|
||||
WebExtensionActor.prototype.form = function () {
|
||||
assert(this.actorID, "addon should have an actorID.");
|
||||
|
||||
let baseForm = ChromeActor.prototype.form.call(this);
|
||||
|
||||
return Object.assign(baseForm, {
|
||||
actor: this.actorID,
|
||||
id: this.id,
|
||||
name: this.addon.name,
|
||||
url: this.addon.sourceURI ? this.addon.sourceURI.spec : undefined,
|
||||
iconURL: this.addon.iconURL,
|
||||
debuggable: this.addon.isDebuggable,
|
||||
temporarilyInstalled: this.addon.temporarilyInstalled,
|
||||
isWebExtension: this.addon.isWebExtension,
|
||||
});
|
||||
};
|
||||
|
||||
WebExtensionActor.prototype._attach = function () {
|
||||
// NOTE: we need to be sure that `this.window` can return a
|
||||
// window before calling the ChromeActor.onAttach, or the TabActor
|
||||
// will not be subscribed to the child doc shell updates.
|
||||
|
||||
// If a preferredTargetWindow exists, set it as the target for this actor
|
||||
// when the client request to attach this actor.
|
||||
if (this.preferredTargetWindow) {
|
||||
this._setWindow(this.preferredTargetWindow);
|
||||
} else {
|
||||
this._createFallbackWindow();
|
||||
}
|
||||
|
||||
// Call ChromeActor's _attach to listen for any new/destroyed chrome docshell
|
||||
ChromeActor.prototype._attach.apply(this);
|
||||
};
|
||||
|
||||
WebExtensionActor.prototype._detach = function () {
|
||||
this._destroyFallbackWindow();
|
||||
|
||||
// Call ChromeActor's _detach to unsubscribe new/destroyed chrome docshell listeners.
|
||||
ChromeActor.prototype._detach.apply(this);
|
||||
};
|
||||
|
||||
/**
|
||||
* Called when the actor is removed from the connection.
|
||||
*/
|
||||
WebExtensionActor.prototype.exit = function () {
|
||||
AddonManager.removeAddonListener(this);
|
||||
|
||||
this.preferredTargetWindow = null;
|
||||
this.addon = null;
|
||||
this.id = null;
|
||||
|
||||
return ChromeActor.prototype.exit.apply(this);
|
||||
};
|
||||
|
||||
// Addon Specific Remote Debugging requestTypes and methods.
|
||||
|
||||
/**
|
||||
* Reloads the addon.
|
||||
*/
|
||||
WebExtensionActor.prototype.onReload = function () {
|
||||
return this.addon.reload()
|
||||
.then(() => {
|
||||
// send an empty response
|
||||
return {};
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Set the preferred global for the add-on (called from the AddonManager).
|
||||
*/
|
||||
WebExtensionActor.prototype.setOptions = function (addonOptions) {
|
||||
if ("global" in addonOptions) {
|
||||
// Set the proposed debug global as the preferred target window
|
||||
// (the actor will eventually set it as the target once it is attached)
|
||||
this.preferredTargetWindow = addonOptions.global;
|
||||
}
|
||||
};
|
||||
|
||||
// AddonManagerListener callbacks.
|
||||
|
||||
WebExtensionActor.prototype.onInstalled = function (addon) {
|
||||
if (addon.id != this.id) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Update the AddonManager's addon object on reload/update.
|
||||
this.addon = addon;
|
||||
};
|
||||
|
||||
WebExtensionActor.prototype.onUninstalled = function (addon) {
|
||||
if (addon != this.addon) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.exit();
|
||||
};
|
||||
|
||||
WebExtensionActor.prototype.onPropertyChanged = function (addon, changedPropNames) {
|
||||
if (addon != this.addon) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Refresh the preferred debug global on disabled/reloaded/upgraded addon.
|
||||
if (changedPropNames.includes("debugGlobal")) {
|
||||
this._findAddonPreferredTargetWindow();
|
||||
}
|
||||
};
|
||||
|
||||
// Private helpers
|
||||
|
||||
WebExtensionActor.prototype._createFallbackWindow = function () {
|
||||
if (this.fallbackWindow) {
|
||||
// Skip if there is already an existent fallback window.
|
||||
return;
|
||||
}
|
||||
|
||||
// Create an empty hidden window as a fallback (e.g. the background page could be
|
||||
// not defined for the target add-on or not yet when the actor instance has been
|
||||
// created).
|
||||
this.fallbackWebNav = Services.appShell.createWindowlessBrowser(true);
|
||||
this.fallbackWebNav.loadURI(
|
||||
`data:text/html;charset=utf-8,${FALLBACK_DOC_MESSAGE}`,
|
||||
0, null, null, null
|
||||
);
|
||||
|
||||
this.fallbackDocShell = this.fallbackWebNav
|
||||
.QueryInterface(Ci.nsIInterfaceRequestor)
|
||||
.getInterface(Ci.nsIDocShell);
|
||||
|
||||
Object.defineProperty(this, "docShell", {
|
||||
value: this.fallbackDocShell,
|
||||
configurable: true
|
||||
});
|
||||
|
||||
// Save the reference to the fallback DOMWindow
|
||||
this.fallbackWindow = this.fallbackDocShell.QueryInterface(Ci.nsIInterfaceRequestor)
|
||||
.getInterface(Ci.nsIDOMWindow);
|
||||
};
|
||||
|
||||
WebExtensionActor.prototype._destroyFallbackWindow = function () {
|
||||
if (this.fallbackWebNav) {
|
||||
// Explicitly close the fallback windowless browser to prevent it to leak
|
||||
// (and to prevent it to freeze devtools xpcshell tests).
|
||||
this.fallbackWebNav.loadURI("about:blank", 0, null, null, null);
|
||||
this.fallbackWebNav.close();
|
||||
|
||||
this.fallbackWebNav = null;
|
||||
this.fallbackWindow = null;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Discover the preferred debug global and switch to it if the addon has been attached.
|
||||
*/
|
||||
WebExtensionActor.prototype._findAddonPreferredTargetWindow = function () {
|
||||
return new Promise(resolve => {
|
||||
let activeAddon = XPIProvider.activeAddons.get(this.id);
|
||||
|
||||
if (!activeAddon) {
|
||||
// The addon is not active, the background page is going to be destroyed,
|
||||
// navigate to the fallback window (if it already exists).
|
||||
resolve(null);
|
||||
} else {
|
||||
AddonManager.getAddonByInstanceID(activeAddon.instanceID)
|
||||
.then(privateWrapper => {
|
||||
let targetWindow = privateWrapper.getDebugGlobal();
|
||||
|
||||
// Do not use the preferred global if it is not a DOMWindow as expected.
|
||||
if (!(targetWindow instanceof Ci.nsIDOMWindow)) {
|
||||
targetWindow = null;
|
||||
}
|
||||
|
||||
resolve(targetWindow);
|
||||
});
|
||||
}
|
||||
}).then(preferredTargetWindow => {
|
||||
this.preferredTargetWindow = preferredTargetWindow;
|
||||
|
||||
if (!preferredTargetWindow) {
|
||||
// Create a fallback window if no preferred target window has been found.
|
||||
this._createFallbackWindow();
|
||||
} else if (this.attached) {
|
||||
// Change the top level document if the actor is already attached.
|
||||
this._changeTopLevelDocument(preferredTargetWindow);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Return an array of the json details related to an array/iterator of docShells.
|
||||
*/
|
||||
WebExtensionActor.prototype._docShellsToWindows = function (docshells) {
|
||||
return ChromeActor.prototype._docShellsToWindows.call(this, docshells)
|
||||
.filter(windowDetails => {
|
||||
// filter the docShells based on the addon id
|
||||
return windowDetails.addonID == this.id;
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Return true if the given source is associated with this addon and should be
|
||||
* added to the visible sources (retrieved and used by the webbrowser actor module).
|
||||
*/
|
||||
WebExtensionActor.prototype._allowSource = function (source) {
|
||||
try {
|
||||
let uri = Services.io.newURI(source.url, null, null);
|
||||
let addonID = mapURIToAddonID(uri);
|
||||
|
||||
return addonID == this.id;
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Return true if the given global is associated with this addon and should be
|
||||
* added as a debuggee, false otherwise.
|
||||
*/
|
||||
WebExtensionActor.prototype._shouldAddNewGlobalAsDebuggee = function (newGlobal) {
|
||||
const global = unwrapDebuggerObjectGlobal(newGlobal);
|
||||
|
||||
if (global instanceof Ci.nsIDOMWindow) {
|
||||
return global.document.nodePrincipal.originAttributes.addonId == this.id;
|
||||
}
|
||||
|
||||
try {
|
||||
// This will fail for non-Sandbox objects, hence the try-catch block.
|
||||
let metadata = Cu.getSandboxMetadata(global);
|
||||
if (metadata) {
|
||||
return metadata.addonID === this.id;
|
||||
}
|
||||
} catch (e) {
|
||||
// Unable to retrieve the sandbox metadata.
|
||||
}
|
||||
|
||||
return false;
|
||||
};
|
||||
|
||||
/**
|
||||
* Override WebExtensionActor requestTypes:
|
||||
* - redefined `reload`, which should reload the target addon
|
||||
* (instead of the entire browser as the regular ChromeActor does).
|
||||
*/
|
||||
WebExtensionActor.prototype.requestTypes.reload = WebExtensionActor.prototype.onReload;
|
||||
Loading…
Add table
Add a link
Reference in a new issue