mirror of
https://gitlab.winehq.org/wine/wine-gecko.git
synced 2024-09-13 09:24:08 -07:00
4b3635c0bb
Move major DevTools files to new directories using the following steps: hg mv browser/devtools devtools/client hg mv toolkit/devtools/server devtools/server hg mv toolkit/devtools devtools/shared No other changes are made.
783 lines
21 KiB
JavaScript
783 lines
21 KiB
JavaScript
/* -*- indent-tabs-mode: nil; js-indent-level: 2; js-indent-level: 2 -*- */
|
|
/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
|
|
/* 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/server/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");
|
|
|
|
/**
|
|
* 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";
|
|
|
|
/**
|
|
* Type describing a messageport event
|
|
*/
|
|
types.addDictType("messageportevent", {
|
|
isTrusted: "boolean",
|
|
data: "nullable:primitive",
|
|
origin: "nullable:string",
|
|
lastEventId: "nullable:string",
|
|
source: "messageport",
|
|
ports: "nullable:array:messageport"
|
|
});
|
|
|
|
/**
|
|
* A MessagePort Actor allowing communication through messageport events
|
|
* over the remote debugging protocol.
|
|
*/
|
|
var MessagePortActor = exports.MessagePortActor = protocol.ActorClass({
|
|
typeName: "messageport",
|
|
|
|
/**
|
|
* 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: method(function (msg) {
|
|
if (!this.port) {
|
|
console.error(ERR_MESSAGEPORT_FINALIZED);
|
|
return;
|
|
}
|
|
|
|
this.port.postMessage(msg);
|
|
}, {
|
|
oneway: true,
|
|
request: {
|
|
msg: Arg(0, "nullable:json")
|
|
}
|
|
}),
|
|
|
|
/**
|
|
* Starts to receive and send queued messages on this message port.
|
|
*/
|
|
start: method(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
|
|
});
|
|
};
|
|
}, {
|
|
oneway: true,
|
|
request: {}
|
|
}),
|
|
|
|
/**
|
|
* Starts to receive and send queued messages on this message port, or
|
|
* raise an exception if the port is null
|
|
*/
|
|
close: method(function () {
|
|
if (!this.port) {
|
|
console.error(ERR_MESSAGEPORT_FINALIZED);
|
|
return;
|
|
}
|
|
|
|
this.port.onmessage = null;
|
|
this.port.close();
|
|
}, {
|
|
oneway: true,
|
|
request: {}
|
|
}),
|
|
|
|
finalize: method(function () {
|
|
this.close();
|
|
this.port = null;
|
|
}, {
|
|
oneway: true
|
|
}),
|
|
|
|
/**
|
|
* Events emitted by this actor.
|
|
*/
|
|
events: {
|
|
"message": {
|
|
type: "message",
|
|
msg: Arg(0, "nullable:messageportevent")
|
|
}
|
|
}
|
|
});
|
|
|
|
/**
|
|
* The corresponding Front object for the MessagePortActor.
|
|
*/
|
|
var MessagePortFront = exports.MessagePortFront = protocol.FrontClass(MessagePortActor, {
|
|
initialize: function (client, form) {
|
|
protocol.Front.prototype.initialize.call(this, client, form);
|
|
}
|
|
});
|
|
|
|
|
|
/**
|
|
* Type describing a director-script error
|
|
*/
|
|
types.addDictType("director-script-error", {
|
|
directorScriptId: "string",
|
|
message: "nullable:string",
|
|
stack: "nullable:string",
|
|
fileName: "nullable:string",
|
|
lineNumber: "nullable:number",
|
|
columnNumber: "nullable:number"
|
|
});
|
|
|
|
/**
|
|
* Type describing a director-script attach event
|
|
*/
|
|
types.addDictType("director-script-attach", {
|
|
directorScriptId: "string",
|
|
url: "string",
|
|
innerId: "number",
|
|
port: "nullable:messageport"
|
|
});
|
|
|
|
/**
|
|
* Type describing a director-script detach event
|
|
*/
|
|
types.addDictType("director-script-detach", {
|
|
directorScriptId: "string",
|
|
innerId: "number"
|
|
});
|
|
|
|
/**
|
|
* 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.ActorClass({
|
|
typeName: "director-script",
|
|
|
|
/**
|
|
* Events emitted by this actor.
|
|
*/
|
|
events: {
|
|
"error": {
|
|
type: "error",
|
|
data: Arg(0, "director-script-error")
|
|
},
|
|
"attach": {
|
|
type: "attach",
|
|
data: Arg(0, "director-script-attach")
|
|
},
|
|
"detach": {
|
|
type: "detach",
|
|
data: Arg(0, "director-script-detach")
|
|
}
|
|
},
|
|
|
|
/**
|
|
* 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: method(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 });
|
|
}
|
|
}, {
|
|
request: {
|
|
reload: Option(0, "boolean"),
|
|
skipAttach: Option(0, "boolean")
|
|
},
|
|
oneway: true
|
|
}),
|
|
|
|
/**
|
|
* Get the attached MessagePort actor if any
|
|
*/
|
|
getMessagePort: method(function () {
|
|
return this._messagePortActor;
|
|
}, {
|
|
request: { },
|
|
response: {
|
|
port: RetVal("nullable:messageport")
|
|
}
|
|
}),
|
|
|
|
/**
|
|
* Stop listening for document global changes, destroy the content worker and puts
|
|
* this actor to hibernation.
|
|
*/
|
|
finalize: method(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;
|
|
}, {
|
|
oneway: true
|
|
}),
|
|
|
|
// 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 corresponding Front object for the DirectorScriptActor.
|
|
*/
|
|
var DirectorScriptFront = exports.DirectorScriptFront = protocol.FrontClass(DirectorScriptActor, {
|
|
initialize: function (client, form) {
|
|
protocol.Front.prototype.initialize.call(this, client, form);
|
|
}
|
|
});
|
|
|
|
/**
|
|
* The DirectorManager Actor is a tab actor which manages enabling/disabling director scripts.
|
|
*/
|
|
const DirectorManagerActor = exports.DirectorManagerActor = protocol.ActorClass({
|
|
typeName: "director-manager",
|
|
|
|
/**
|
|
* Events emitted by this actor.
|
|
*/
|
|
events: {
|
|
"director-script-error": {
|
|
type: "error",
|
|
data: Arg(0, "director-script-error")
|
|
},
|
|
"director-script-attach": {
|
|
type: "attach",
|
|
data: Arg(0, "director-script-attach")
|
|
},
|
|
"director-script-detach": {
|
|
type: "detach",
|
|
data: Arg(0, "director-script-detach")
|
|
}
|
|
},
|
|
|
|
/* 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: method(function () {
|
|
let enabledScriptIds = [...this._directorScriptActorsMap.keys()];
|
|
|
|
return {
|
|
installed: DirectorRegistry.list(),
|
|
enabled: enabledScriptIds
|
|
};
|
|
}, {
|
|
response: {
|
|
directorScripts: RetVal("json")
|
|
}
|
|
}),
|
|
|
|
/**
|
|
* 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: method(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();
|
|
}
|
|
}, {
|
|
oneway: true,
|
|
request: {
|
|
selectedIds: Arg(0, "array:string"),
|
|
reload: Option(1, "boolean")
|
|
}
|
|
}),
|
|
|
|
/**
|
|
* 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: method(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();
|
|
}
|
|
}, {
|
|
oneway: true,
|
|
request: {
|
|
selectedIds: Arg(0, "array:string"),
|
|
reload: Option(1, "boolean")
|
|
}
|
|
}),
|
|
|
|
/**
|
|
* Retrieves the actor instance of an installed director-script
|
|
* (and create the actor instance if it doesn't exists yet).
|
|
*/
|
|
getByScriptId: method(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;
|
|
}, {
|
|
request: {
|
|
scriptId: Arg(0, "string")
|
|
},
|
|
response: {
|
|
directorScript: RetVal("director-script")
|
|
}
|
|
}),
|
|
|
|
finalize: method(function() {
|
|
this.disableByScriptIds(["*"], false);
|
|
}, {
|
|
oneway: true
|
|
})
|
|
});
|
|
|
|
/**
|
|
* The corresponding Front object for the DirectorManagerActor.
|
|
*/
|
|
exports.DirectorManagerFront = protocol.FrontClass(DirectorManagerActor, {
|
|
initialize: function(client, { directorManagerActor }) {
|
|
protocol.Front.prototype.initialize.call(this, client, {
|
|
actor: directorManagerActor
|
|
});
|
|
this.manage(this);
|
|
}
|
|
});
|
|
|
|
/* 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;
|
|
}
|