mirror of
https://repo.dactyloidae.xyz/Dactyloidae/UXP.git
synced 2026-10-15 19:57:29 +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
615
devtools/server/actors/director-manager.js
Normal file
615
devtools/server/actors/director-manager.js
Normal file
|
|
@ -0,0 +1,615 @@
|
|||
/* 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 events = require("sdk/event/core");
|
||||
const protocol = require("devtools/shared/protocol");
|
||||
|
||||
const { Cu, Ci } = require("chrome");
|
||||
|
||||
const { on, once, off, emit } = events;
|
||||
const { method, Arg, Option, RetVal, types } = protocol;
|
||||
|
||||
const { sandbox, evaluate } = require("sdk/loader/sandbox");
|
||||
const { Class } = require("sdk/core/heritage");
|
||||
|
||||
const { PlainTextConsole } = require("sdk/console/plain-text");
|
||||
|
||||
const { DirectorRegistry } = require("./director-registry");
|
||||
|
||||
const {
|
||||
messagePortSpec,
|
||||
directorManagerSpec,
|
||||
directorScriptSpec,
|
||||
} = require("devtools/shared/specs/director-manager");
|
||||
|
||||
/**
|
||||
* Error Messages
|
||||
*/
|
||||
|
||||
const ERR_MESSAGEPORT_FINALIZED = "message port finalized";
|
||||
|
||||
const ERR_DIRECTOR_UNKNOWN_SCRIPTID = "unkown director-script id";
|
||||
const ERR_DIRECTOR_UNINSTALLED_SCRIPTID = "uninstalled director-script id";
|
||||
|
||||
/**
|
||||
* A MessagePort Actor allowing communication through messageport events
|
||||
* over the remote debugging protocol.
|
||||
*/
|
||||
var MessagePortActor = exports.MessagePortActor = protocol.ActorClassWithSpec(messagePortSpec, {
|
||||
/**
|
||||
* Create a MessagePort actor.
|
||||
*
|
||||
* @param DebuggerServerConnection conn
|
||||
* The server connection.
|
||||
* @param MessagePort port
|
||||
* The wrapped MessagePort.
|
||||
*/
|
||||
initialize: function (conn, port) {
|
||||
protocol.Actor.prototype.initialize.call(this, conn);
|
||||
|
||||
// NOTE: can't get a weak reference because we need to subscribe events
|
||||
// using port.onmessage or addEventListener
|
||||
this.port = port;
|
||||
},
|
||||
|
||||
destroy: function (conn) {
|
||||
protocol.Actor.prototype.destroy.call(this, conn);
|
||||
this.finalize();
|
||||
},
|
||||
|
||||
/**
|
||||
* Sends a message on the wrapped message port.
|
||||
*
|
||||
* @param Object msg
|
||||
* The JSON serializable message event payload
|
||||
*/
|
||||
postMessage: function (msg) {
|
||||
if (!this.port) {
|
||||
console.error(ERR_MESSAGEPORT_FINALIZED);
|
||||
return;
|
||||
}
|
||||
|
||||
this.port.postMessage(msg);
|
||||
},
|
||||
|
||||
/**
|
||||
* Starts to receive and send queued messages on this message port.
|
||||
*/
|
||||
start: function () {
|
||||
if (!this.port) {
|
||||
console.error(ERR_MESSAGEPORT_FINALIZED);
|
||||
return;
|
||||
}
|
||||
|
||||
// NOTE: set port.onmessage to a function is an implicit start
|
||||
// and starts to send queued messages.
|
||||
// On the client side we should set MessagePortClient.onmessage
|
||||
// to a setter which register an handler to the message event
|
||||
// and call the actor start method to start receiving messages
|
||||
// from the MessagePort's queue.
|
||||
this.port.onmessage = (evt) => {
|
||||
var ports;
|
||||
|
||||
// TODO: test these wrapped ports
|
||||
if (Array.isArray(evt.ports)) {
|
||||
ports = evt.ports.map((port) => {
|
||||
let actor = new MessagePortActor(this.conn, port);
|
||||
this.manage(actor);
|
||||
return actor;
|
||||
});
|
||||
}
|
||||
|
||||
emit(this, "message", {
|
||||
isTrusted: evt.isTrusted,
|
||||
data: evt.data,
|
||||
origin: evt.origin,
|
||||
lastEventId: evt.lastEventId,
|
||||
source: this,
|
||||
ports: ports
|
||||
});
|
||||
};
|
||||
},
|
||||
|
||||
/**
|
||||
* Starts to receive and send queued messages on this message port, or
|
||||
* raise an exception if the port is null
|
||||
*/
|
||||
close: function () {
|
||||
if (!this.port) {
|
||||
console.error(ERR_MESSAGEPORT_FINALIZED);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
this.port.onmessage = null;
|
||||
this.port.close();
|
||||
} catch (e) {
|
||||
// The port might be a dead object
|
||||
console.error(e);
|
||||
}
|
||||
},
|
||||
|
||||
finalize: function () {
|
||||
this.close();
|
||||
this.port = null;
|
||||
},
|
||||
});
|
||||
|
||||
/**
|
||||
* The Director Script Actor manage javascript code running in a non-privileged sandbox with the same
|
||||
* privileges of the target global (browser tab or a firefox os app).
|
||||
*
|
||||
* After retrieving an instance of this actor (from the tab director actor), you'll need to set it up
|
||||
* by calling setup().
|
||||
*
|
||||
* After the setup, this actor will automatically attach/detach the content script (and optionally a
|
||||
* directly connect the debugger client and the content script using a MessageChannel) on tab
|
||||
* navigation.
|
||||
*/
|
||||
var DirectorScriptActor = exports.DirectorScriptActor = protocol.ActorClassWithSpec(directorScriptSpec, {
|
||||
/**
|
||||
* Creates the director script actor
|
||||
*
|
||||
* @param DebuggerServerConnection conn
|
||||
* The server connection.
|
||||
* @param Actor tabActor
|
||||
* The tab (or root) actor.
|
||||
* @param String scriptId
|
||||
* The director-script id.
|
||||
* @param String scriptCode
|
||||
* The director-script javascript source.
|
||||
* @param Object scriptOptions
|
||||
* The director-script options object.
|
||||
*/
|
||||
initialize: function (conn, tabActor, { scriptId, scriptCode, scriptOptions }) {
|
||||
protocol.Actor.prototype.initialize.call(this, conn, tabActor);
|
||||
|
||||
this.tabActor = tabActor;
|
||||
|
||||
this._scriptId = scriptId;
|
||||
this._scriptCode = scriptCode;
|
||||
this._scriptOptions = scriptOptions;
|
||||
this._setupCalled = false;
|
||||
|
||||
this._onGlobalCreated = this._onGlobalCreated.bind(this);
|
||||
this._onGlobalDestroyed = this._onGlobalDestroyed.bind(this);
|
||||
},
|
||||
destroy: function (conn) {
|
||||
protocol.Actor.prototype.destroy.call(this, conn);
|
||||
|
||||
this.finalize();
|
||||
},
|
||||
|
||||
/**
|
||||
* Starts listening to the tab global created, in order to create the director-script sandbox
|
||||
* using the configured scriptCode, attached/detached automatically to the tab
|
||||
* window on tab navigation.
|
||||
*
|
||||
* @param Boolean reload
|
||||
* attach the page immediately or reload it first.
|
||||
* @param Boolean skipAttach
|
||||
* skip the attach
|
||||
*/
|
||||
setup: function ({ reload, skipAttach }) {
|
||||
if (this._setupCalled) {
|
||||
// do nothing
|
||||
return;
|
||||
}
|
||||
|
||||
this._setupCalled = true;
|
||||
|
||||
on(this.tabActor, "window-ready", this._onGlobalCreated);
|
||||
on(this.tabActor, "window-destroyed", this._onGlobalDestroyed);
|
||||
|
||||
// optional skip attach (needed by director-manager for director scripts bulk activation)
|
||||
if (skipAttach) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (reload) {
|
||||
this.window.location.reload();
|
||||
} else {
|
||||
// fake a global created event to attach without reload
|
||||
this._onGlobalCreated({ id: getWindowID(this.window), window: this.window, isTopLevel: true });
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Get the attached MessagePort actor if any
|
||||
*/
|
||||
getMessagePort: function () {
|
||||
return this._messagePortActor;
|
||||
},
|
||||
|
||||
/**
|
||||
* Stop listening for document global changes, destroy the content worker and puts
|
||||
* this actor to hibernation.
|
||||
*/
|
||||
finalize: function () {
|
||||
if (!this._setupCalled) {
|
||||
return;
|
||||
}
|
||||
|
||||
off(this.tabActor, "window-ready", this._onGlobalCreated);
|
||||
off(this.tabActor, "window-destroyed", this._onGlobalDestroyed);
|
||||
|
||||
this._onGlobalDestroyed({ id: this._lastAttachedWinId });
|
||||
|
||||
this._setupCalled = false;
|
||||
},
|
||||
|
||||
// local helpers
|
||||
get window() {
|
||||
return this.tabActor.window;
|
||||
},
|
||||
|
||||
/* event handlers */
|
||||
_onGlobalCreated: function ({ id, window, isTopLevel }) {
|
||||
if (!isTopLevel) {
|
||||
// filter iframes
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
if (this._lastAttachedWinId) {
|
||||
// if we have received a global created without a previous global destroyed,
|
||||
// it's time to cleanup the previous state
|
||||
this._onGlobalDestroyed(this._lastAttachedWinId);
|
||||
}
|
||||
|
||||
// TODO: check if we want to share a single sandbox per global
|
||||
// for multiple debugger clients
|
||||
|
||||
// create & attach the new sandbox
|
||||
this._scriptSandbox = new DirectorScriptSandbox({
|
||||
scriptId: this._scriptId,
|
||||
scriptCode: this._scriptCode,
|
||||
scriptOptions: this._scriptOptions
|
||||
});
|
||||
|
||||
// attach the global window
|
||||
this._lastAttachedWinId = id;
|
||||
var port = this._scriptSandbox.attach(window, id);
|
||||
this._onDirectorScriptAttach(window, port);
|
||||
} catch (e) {
|
||||
this._onDirectorScriptError(e);
|
||||
}
|
||||
},
|
||||
_onGlobalDestroyed: function ({ id }) {
|
||||
if (id !== this._lastAttachedWinId) {
|
||||
// filter destroyed globals
|
||||
return;
|
||||
}
|
||||
|
||||
// unmanage and cleanup the messageport actor
|
||||
if (this._messagePortActor) {
|
||||
this.unmanage(this._messagePortActor);
|
||||
this._messagePortActor = null;
|
||||
}
|
||||
|
||||
// NOTE: destroy here the old worker
|
||||
if (this._scriptSandbox) {
|
||||
this._scriptSandbox.destroy(this._onDirectorScriptError.bind(this));
|
||||
|
||||
// send a detach event to the debugger client
|
||||
emit(this, "detach", {
|
||||
directorScriptId: this._scriptId,
|
||||
innerId: this._lastAttachedWinId
|
||||
});
|
||||
|
||||
this._lastAttachedWinId = null;
|
||||
this._scriptSandbox = null;
|
||||
}
|
||||
},
|
||||
_onDirectorScriptError: function (error) {
|
||||
// route the content script error to the debugger client
|
||||
if (error) {
|
||||
// prevents silent director-script-errors
|
||||
console.error("director-script-error", error);
|
||||
// route errors to debugger server clients
|
||||
emit(this, "error", {
|
||||
directorScriptId: this._scriptId,
|
||||
message: error.toString(),
|
||||
stack: error.stack,
|
||||
fileName: error.fileName,
|
||||
lineNumber: error.lineNumber,
|
||||
columnNumber: error.columnNumber
|
||||
});
|
||||
}
|
||||
},
|
||||
_onDirectorScriptAttach: function (window, port) {
|
||||
let portActor = new MessagePortActor(this.conn, port);
|
||||
this.manage(portActor);
|
||||
this._messagePortActor = portActor;
|
||||
|
||||
emit(this, "attach", {
|
||||
directorScriptId: this._scriptId,
|
||||
url: (window && window.location) ? window.location.toString() : "",
|
||||
innerId: this._lastAttachedWinId,
|
||||
port: this._messagePortActor
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* The DirectorManager Actor is a tab actor which manages enabling/disabling director scripts.
|
||||
*/
|
||||
const DirectorManagerActor = exports.DirectorManagerActor = protocol.ActorClassWithSpec(directorManagerSpec, {
|
||||
/* init & destroy methods */
|
||||
initialize: function (conn, tabActor) {
|
||||
protocol.Actor.prototype.initialize.call(this, conn);
|
||||
this.tabActor = tabActor;
|
||||
this._directorScriptActorsMap = new Map();
|
||||
},
|
||||
destroy: function (conn) {
|
||||
protocol.Actor.prototype.destroy.call(this, conn);
|
||||
this.finalize();
|
||||
},
|
||||
|
||||
/**
|
||||
* Retrieves the list of installed director-scripts.
|
||||
*/
|
||||
list: function () {
|
||||
let enabledScriptIds = [...this._directorScriptActorsMap.keys()];
|
||||
|
||||
return {
|
||||
installed: DirectorRegistry.list(),
|
||||
enabled: enabledScriptIds
|
||||
};
|
||||
},
|
||||
|
||||
/**
|
||||
* Bulk enabling director-scripts.
|
||||
*
|
||||
* @param Array[String] selectedIds
|
||||
* The list of director-script ids to be enabled,
|
||||
* ["*"] will activate all the installed director-scripts
|
||||
* @param Boolean reload
|
||||
* optionally reload the target window
|
||||
*/
|
||||
enableByScriptIds: function (selectedIds, { reload }) {
|
||||
if (selectedIds && selectedIds.length === 0) {
|
||||
// filtered all director scripts ids
|
||||
return;
|
||||
}
|
||||
|
||||
for (let scriptId of DirectorRegistry.list()) {
|
||||
// filter director script ids
|
||||
if (selectedIds.indexOf("*") < 0 &&
|
||||
selectedIds.indexOf(scriptId) < 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
let actor = this.getByScriptId(scriptId);
|
||||
|
||||
// skip attach if reload is true (activated director scripts
|
||||
// will be automatically attached on the final reload)
|
||||
actor.setup({ reload: false, skipAttach: reload });
|
||||
}
|
||||
|
||||
if (reload) {
|
||||
this.tabActor.window.location.reload();
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Bulk disabling director-scripts.
|
||||
*
|
||||
* @param Array[String] selectedIds
|
||||
* The list of director-script ids to be disable,
|
||||
* ["*"] will de-activate all the enable director-scripts
|
||||
* @param Boolean reload
|
||||
* optionally reload the target window
|
||||
*/
|
||||
disableByScriptIds: function (selectedIds, { reload }) {
|
||||
if (selectedIds && selectedIds.length === 0) {
|
||||
// filtered all director scripts ids
|
||||
return;
|
||||
}
|
||||
|
||||
for (let scriptId of this._directorScriptActorsMap.keys()) {
|
||||
// filter director script ids
|
||||
if (selectedIds.indexOf("*") < 0 &&
|
||||
selectedIds.indexOf(scriptId) < 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
let actor = this._directorScriptActorsMap.get(scriptId);
|
||||
this._directorScriptActorsMap.delete(scriptId);
|
||||
|
||||
// finalize the actor (which will produce director-script-detach event)
|
||||
actor.finalize();
|
||||
// unsubscribe event handlers on the disabled actor
|
||||
off(actor);
|
||||
|
||||
this.unmanage(actor);
|
||||
}
|
||||
|
||||
if (reload) {
|
||||
this.tabActor.window.location.reload();
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Retrieves the actor instance of an installed director-script
|
||||
* (and create the actor instance if it doesn't exists yet).
|
||||
*/
|
||||
getByScriptId: function (scriptId) {
|
||||
var id = scriptId;
|
||||
// raise an unknown director-script id exception
|
||||
if (!DirectorRegistry.checkInstalled(id)) {
|
||||
console.error(ERR_DIRECTOR_UNKNOWN_SCRIPTID, id);
|
||||
throw Error(ERR_DIRECTOR_UNKNOWN_SCRIPTID);
|
||||
}
|
||||
|
||||
// get a previous created actor instance
|
||||
let actor = this._directorScriptActorsMap.get(id);
|
||||
|
||||
// create a new actor instance
|
||||
if (!actor) {
|
||||
let directorScriptDefinition = DirectorRegistry.get(id);
|
||||
|
||||
// test lazy director-script (e.g. uninstalled in the parent process)
|
||||
if (!directorScriptDefinition) {
|
||||
|
||||
console.error(ERR_DIRECTOR_UNINSTALLED_SCRIPTID, id);
|
||||
throw Error(ERR_DIRECTOR_UNINSTALLED_SCRIPTID);
|
||||
}
|
||||
|
||||
actor = new DirectorScriptActor(this.conn, this.tabActor, directorScriptDefinition);
|
||||
this._directorScriptActorsMap.set(id, actor);
|
||||
|
||||
on(actor, "error", emit.bind(null, this, "director-script-error"));
|
||||
on(actor, "attach", emit.bind(null, this, "director-script-attach"));
|
||||
on(actor, "detach", emit.bind(null, this, "director-script-detach"));
|
||||
|
||||
this.manage(actor);
|
||||
}
|
||||
|
||||
return actor;
|
||||
},
|
||||
|
||||
finalize: function () {
|
||||
this.disableByScriptIds(["*"], false);
|
||||
}
|
||||
});
|
||||
|
||||
/* private helpers */
|
||||
|
||||
/**
|
||||
* DirectorScriptSandbox is a private utility class, which attach a non-priviliged sandbox
|
||||
* to a target window.
|
||||
*/
|
||||
const DirectorScriptSandbox = Class({
|
||||
initialize: function ({scriptId, scriptCode, scriptOptions}) {
|
||||
this._scriptId = scriptId;
|
||||
this._scriptCode = scriptCode;
|
||||
this._scriptOptions = scriptOptions;
|
||||
},
|
||||
|
||||
attach: function (window, innerId) {
|
||||
this._innerId = innerId,
|
||||
this._window = window;
|
||||
this._proto = Cu.createObjectIn(this._window);
|
||||
|
||||
var id = this._scriptId;
|
||||
var uri = this._scriptCode;
|
||||
|
||||
this._sandbox = sandbox(window, {
|
||||
sandboxName: uri,
|
||||
sandboxPrototype: this._proto,
|
||||
sameZoneAs: window,
|
||||
wantXrays: true,
|
||||
wantComponents: false,
|
||||
wantExportHelpers: false,
|
||||
metadata: {
|
||||
URI: uri,
|
||||
addonID: id,
|
||||
SDKDirectorScript: true,
|
||||
"inner-window-id": innerId
|
||||
}
|
||||
});
|
||||
|
||||
// create a CommonJS module object which match the interface from addon-sdk
|
||||
// (addon-sdk/sources/lib/toolkit/loader.js#L678-L686)
|
||||
var module = Cu.cloneInto(Object.create(null, {
|
||||
id: { enumerable: true, value: id },
|
||||
uri: { enumerable: true, value: uri },
|
||||
exports: { enumerable: true, value: Cu.createObjectIn(this._sandbox) }
|
||||
}), this._sandbox);
|
||||
|
||||
// create a console API object
|
||||
let directorScriptConsole = new PlainTextConsole(null, this._innerId);
|
||||
|
||||
// inject CommonJS module globals into the sandbox prototype
|
||||
Object.defineProperties(this._proto, {
|
||||
module: { enumerable: true, value: module },
|
||||
exports: { enumerable: true, value: module.exports },
|
||||
console: {
|
||||
enumerable: true,
|
||||
value: Cu.cloneInto(directorScriptConsole, this._sandbox, { cloneFunctions: true })
|
||||
}
|
||||
});
|
||||
|
||||
Object.defineProperties(this._sandbox, {
|
||||
require: {
|
||||
enumerable: true,
|
||||
value: Cu.cloneInto(function () {
|
||||
throw Error("NOT IMPLEMENTED");
|
||||
}, this._sandbox, { cloneFunctions: true })
|
||||
}
|
||||
});
|
||||
|
||||
// TODO: if the debugger target is local, the debugger client could pass
|
||||
// to the director actor the resource url instead of the entire javascript source code.
|
||||
|
||||
// evaluate the director script source in the sandbox
|
||||
evaluate(this._sandbox, this._scriptCode, "javascript:" + this._scriptCode);
|
||||
|
||||
// prepare the messageport connected to the debugger client
|
||||
let { port1, port2 } = new this._window.MessageChannel();
|
||||
|
||||
// prepare the unload callbacks queue
|
||||
var sandboxOnUnloadQueue = this._sandboxOnUnloadQueue = [];
|
||||
|
||||
// create the attach options
|
||||
var attachOptions = this._attachOptions = Cu.createObjectIn(this._sandbox);
|
||||
Object.defineProperties(attachOptions, {
|
||||
port: { enumerable: true, value: port1 },
|
||||
window: { enumerable: true, value: window },
|
||||
scriptOptions: { enumerable: true, value: Cu.cloneInto(this._scriptOptions, this._sandbox) },
|
||||
onUnload: {
|
||||
enumerable: true,
|
||||
value: Cu.cloneInto(function (cb) {
|
||||
// collect unload callbacks
|
||||
if (typeof cb == "function") {
|
||||
sandboxOnUnloadQueue.push(cb);
|
||||
}
|
||||
}, this._sandbox, { cloneFunctions: true })
|
||||
}
|
||||
});
|
||||
|
||||
// select the attach method
|
||||
var exports = this._proto.module.exports;
|
||||
if (this._scriptOptions && "attachMethod" in this._scriptOptions) {
|
||||
this._sandboxOnAttach = exports[this._scriptOptions.attachMethod];
|
||||
} else {
|
||||
this._sandboxOnAttach = exports;
|
||||
}
|
||||
|
||||
if (typeof this._sandboxOnAttach !== "function") {
|
||||
throw Error("the configured attachMethod '" +
|
||||
(this._scriptOptions.attachMethod || "module.exports") +
|
||||
"' is not exported by the directorScript");
|
||||
}
|
||||
|
||||
// call the attach method
|
||||
this._sandboxOnAttach.call(this._sandbox, attachOptions);
|
||||
|
||||
return port2;
|
||||
},
|
||||
destroy: function (onError) {
|
||||
// evaluate queue unload methods if any
|
||||
while (this._sandboxOnUnloadQueue && this._sandboxOnUnloadQueue.length > 0) {
|
||||
let cb = this._sandboxOnUnloadQueue.pop();
|
||||
|
||||
try {
|
||||
cb();
|
||||
} catch (e) {
|
||||
console.error("Exception on DirectorScript Sandbox destroy", e);
|
||||
onError(e);
|
||||
}
|
||||
}
|
||||
|
||||
Cu.nukeSandbox(this._sandbox);
|
||||
}
|
||||
});
|
||||
|
||||
function getWindowID(window) {
|
||||
return window.QueryInterface(Ci.nsIInterfaceRequestor)
|
||||
.getInterface(Ci.nsIDOMWindowUtils)
|
||||
.currentInnerWindowID;
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue