Bug 1435187 - Refactor the script actor. r=jdescottes

- Extract paused scoped objects.
 - Extract event loop stack. r=jdescottes
 - Extract actor stores. r=jdescottes
 - Move script.js to actor.js. r=jdescottes

--HG--
rename : devtools/server/actors/script.js => devtools/server/actors/thread.js
This commit is contained in:
Jason Laster 2018-02-02 10:57:00 +02:00
parent 1f140ec503
commit f805944739
18 changed files with 539 additions and 493 deletions

View File

@ -13,8 +13,8 @@ var { ConsoleAPIListener } = require("devtools/server/actors/webconsole/listener
var DevToolsUtils = require("devtools/shared/DevToolsUtils");
var { assert, update } = DevToolsUtils;
loader.lazyRequireGetter(this, "AddonThreadActor", "devtools/server/actors/script", true);
loader.lazyRequireGetter(this, "unwrapDebuggerObjectGlobal", "devtools/server/actors/script", true);
loader.lazyRequireGetter(this, "AddonThreadActor", "devtools/server/actors/thread", true);
loader.lazyRequireGetter(this, "unwrapDebuggerObjectGlobal", "devtools/server/actors/thread", true);
loader.lazyRequireGetter(this, "mapURIToAddonID", "devtools/server/actors/utils/map-uri-to-addon-id");
loader.lazyRequireGetter(this, "WebConsoleActor", "devtools/server/actors/webconsole", true);

View File

@ -7,7 +7,7 @@
const { Cc, Ci, Cu } = require("chrome");
const Services = require("Services");
const { ChromeDebuggerActor } = require("devtools/server/actors/script");
const { ChromeDebuggerActor } = require("devtools/server/actors/thread");
const { WebConsoleActor } = require("devtools/server/actors/webconsole");
const makeDebugger = require("devtools/server/actors/utils/make-debugger");
const { ActorPool } = require("devtools/server/main");

View File

@ -42,6 +42,7 @@ DevToolsModules(
'memory.js',
'monitor.js',
'object.js',
'pause-scoped.js',
'perf.js',
'performance-recording.js',
'performance.js',
@ -51,13 +52,13 @@ DevToolsModules(
'promises.js',
'reflow.js',
'root.js',
'script.js',
'source.js',
'storage.js',
'string.js',
'styles.js',
'stylesheets.js',
'tab.js',
'thread.js',
'timeline.js',
'webaudio.js',
'webbrowser.js',

View File

@ -0,0 +1,128 @@
/* -*- 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 { ObjectActor } = require("devtools/server/actors/object");
/**
* A base actor for any actors that should only respond receive messages in the
* paused state. Subclasses may expose a `threadActor` which is used to help
* determine when we are in a paused state. Subclasses should set their own
* "constructor" property if they want better error messages. You should never
* instantiate a PauseScopedActor directly, only through subclasses.
*/
function PauseScopedActor() {
}
/**
* A function decorator for creating methods to handle protocol messages that
* should only be received while in the paused state.
*
* @param method Function
* The function we are decorating.
*/
PauseScopedActor.withPaused = function (method) {
return function () {
if (this.isPaused()) {
return method.apply(this, arguments);
}
return this._wrongState();
};
};
PauseScopedActor.prototype = {
/**
* Returns true if we are in the paused state.
*/
isPaused: function () {
// When there is not a ThreadActor available (like in the webconsole) we
// have to be optimistic and assume that we are paused so that we can
// respond to requests.
return this.threadActor ? this.threadActor.state === "paused" : true;
},
/**
* Returns the wrongState response packet for this actor.
*/
_wrongState: function () {
return {
error: "wrongState",
message: this.constructor.name +
" actors can only be accessed while the thread is paused."
};
}
};
/**
* Creates a pause-scoped actor for the specified object.
* @see ObjectActor
*/
function PauseScopedObjectActor(obj, hooks) {
ObjectActor.call(this, obj, hooks);
this.hooks.promote = hooks.promote;
this.hooks.isThreadLifetimePool = hooks.isThreadLifetimePool;
}
PauseScopedObjectActor.prototype = Object.create(PauseScopedActor.prototype);
Object.assign(PauseScopedObjectActor.prototype, ObjectActor.prototype);
Object.assign(PauseScopedObjectActor.prototype, {
constructor: PauseScopedObjectActor,
actorPrefix: "pausedobj",
onOwnPropertyNames:
PauseScopedActor.withPaused(ObjectActor.prototype.onOwnPropertyNames),
onPrototypeAndProperties:
PauseScopedActor.withPaused(ObjectActor.prototype.onPrototypeAndProperties),
onPrototype: PauseScopedActor.withPaused(ObjectActor.prototype.onPrototype),
onProperty: PauseScopedActor.withPaused(ObjectActor.prototype.onProperty),
onDecompile: PauseScopedActor.withPaused(ObjectActor.prototype.onDecompile),
onDisplayString:
PauseScopedActor.withPaused(ObjectActor.prototype.onDisplayString),
onParameterNames:
PauseScopedActor.withPaused(ObjectActor.prototype.onParameterNames),
/**
* Handle a protocol request to promote a pause-lifetime grip to a
* thread-lifetime grip.
*
* @param request object
* The protocol request object.
*/
onThreadGrip: PauseScopedActor.withPaused(function (request) {
this.hooks.promote();
return {};
}),
/**
* Handle a protocol request to release a thread-lifetime grip.
*
* @param request object
* The protocol request object.
*/
onRelease: PauseScopedActor.withPaused(function (request) {
if (this.hooks.isThreadLifetimePool()) {
return { error: "notReleasable",
message: "Only thread-lifetime actors can be released." };
}
this.release();
return {};
}),
});
Object.assign(PauseScopedObjectActor.prototype.requestTypes, {
"threadGrip": PauseScopedObjectActor.prototype.onThreadGrip,
});
exports.PauseScopedObjectActor = PauseScopedObjectActor;

View File

@ -27,8 +27,8 @@ const EventEmitter = require("devtools/shared/event-emitter");
const EXTENSION_CONTENT_JSM = "resource://gre/modules/ExtensionContent.jsm";
loader.lazyRequireGetter(this, "ThreadActor", "devtools/server/actors/script", true);
loader.lazyRequireGetter(this, "unwrapDebuggerObjectGlobal", "devtools/server/actors/script", true);
loader.lazyRequireGetter(this, "ThreadActor", "devtools/server/actors/thread", true);
loader.lazyRequireGetter(this, "unwrapDebuggerObjectGlobal", "devtools/server/actors/thread", true);
loader.lazyRequireGetter(this, "WorkerActorList", "devtools/server/actors/worker-list", true);
loader.lazyImporter(this, "ExtensionContent", EXTENSION_CONTENT_JSM);

View File

@ -8,14 +8,13 @@
const Services = require("Services");
const { Cc, Ci, Cr } = require("chrome");
const { ActorPool, OriginalLocation, GeneratedLocation } = require("devtools/server/actors/common");
const { ObjectActor, createValueGrip, longStringGrip } = require("devtools/server/actors/object");
const { ActorPool, GeneratedLocation } = require("devtools/server/actors/common");
const { createValueGrip, longStringGrip } = require("devtools/server/actors/object");
const { ActorClassWithSpec } = require("devtools/shared/protocol");
const DevToolsUtils = require("devtools/shared/DevToolsUtils");
const flags = require("devtools/shared/flags");
const { assert, dumpn } = DevToolsUtils;
const promise = require("promise");
const xpcInspector = require("xpcInspector");
const { DevToolsWorker } = require("devtools/shared/worker/worker");
const { threadSpec } = require("devtools/shared/specs/script");
@ -26,372 +25,17 @@ loader.lazyGetter(this, "Debugger", () => {
hackDebugger(Debugger);
return Debugger;
});
loader.lazyRequireGetter(this, "CssLogic", "devtools/server/css-logic", true);
loader.lazyRequireGetter(this, "findCssSelector", "devtools/shared/inspector/css-logic", true);
loader.lazyRequireGetter(this, "mapURIToAddonID", "devtools/server/actors/utils/map-uri-to-addon-id");
loader.lazyRequireGetter(this, "BreakpointActor", "devtools/server/actors/breakpoint", true);
loader.lazyRequireGetter(this, "setBreakpointAtEntryPoints", "devtools/server/actors/breakpoint", true);
loader.lazyRequireGetter(this, "getSourceURL", "devtools/server/actors/source", true);
loader.lazyRequireGetter(this, "EnvironmentActor", "devtools/server/actors/environment", true);
loader.lazyRequireGetter(this, "SourceActorStore", "devtools/server/actors/utils/source-actor-store", true);
loader.lazyRequireGetter(this, "BreakpointActorMap", "devtools/server/actors/utils/breakpoint-actor-map", true);
loader.lazyRequireGetter(this, "PauseScopedObjectActor", "devtools/server/actors/pause-scoped", true);
loader.lazyRequireGetter(this, "EventLoopStack", "devtools/server/actors/utils/event-loop", true);
loader.lazyRequireGetter(this, "FrameActor", "devtools/server/actors/frame", true);
loader.lazyRequireGetter(this, "EventEmitter", "devtools/shared/event-emitter");
/**
* A BreakpointActorMap is a map from locations to instances of BreakpointActor.
*/
function BreakpointActorMap() {
this._size = 0;
this._actors = {};
}
BreakpointActorMap.prototype = {
/**
* Return the number of BreakpointActors in this BreakpointActorMap.
*
* @returns Number
* The number of BreakpointActor in this BreakpointActorMap.
*/
get size() {
return this._size;
},
/**
* Generate all BreakpointActors that match the given location in
* this BreakpointActorMap.
*
* @param OriginalLocation location
* The location for which matching BreakpointActors should be generated.
*/
findActors: function* (location = new OriginalLocation()) {
// Fast shortcut for when we know we won't find any actors. Surprisingly
// enough, this speeds up refreshing when there are no breakpoints set by
// about 2x!
if (this.size === 0) {
return;
}
function* findKeys(obj, key) {
if (key !== undefined) {
if (key in obj) {
yield key;
}
} else {
for (key of Object.keys(obj)) {
yield key;
}
}
}
let query = {
sourceActorID: location.originalSourceActor
? location.originalSourceActor.actorID
: undefined,
line: location.originalLine,
};
// If location contains a line, assume we are searching for a whole line
// breakpoint, and set begin/endColumn accordingly. Otherwise, we are
// searching for all breakpoints, so begin/endColumn should be left unset.
if (location.originalLine) {
query.beginColumn = location.originalColumn ? location.originalColumn : 0;
query.endColumn = location.originalColumn ? location.originalColumn + 1 : Infinity;
} else {
query.beginColumn = location.originalColumn ? query.originalColumn : undefined;
query.endColumn = location.originalColumn ? query.originalColumn + 1 : undefined;
}
for (let sourceActorID of findKeys(this._actors, query.sourceActorID)) {
let actor = this._actors[sourceActorID];
for (let line of findKeys(actor, query.line)) {
for (let beginColumn of findKeys(actor[line], query.beginColumn)) {
for (let endColumn of findKeys(actor[line][beginColumn],
query.endColumn)) {
yield actor[line][beginColumn][endColumn];
}
}
}
}
},
/**
* Return the BreakpointActor at the given location in this
* BreakpointActorMap.
*
* @param OriginalLocation location
* The location for which the BreakpointActor should be returned.
*
* @returns BreakpointActor actor
* The BreakpointActor at the given location.
*/
getActor: function (originalLocation) {
for (let actor of this.findActors(originalLocation)) {
return actor;
}
return null;
},
/**
* Set the given BreakpointActor to the given location in this
* BreakpointActorMap.
*
* @param OriginalLocation location
* The location to which the given BreakpointActor should be set.
*
* @param BreakpointActor actor
* The BreakpointActor to be set to the given location.
*/
setActor: function (location, actor) {
let { originalSourceActor, originalLine, originalColumn } = location;
let sourceActorID = originalSourceActor.actorID;
let line = originalLine;
let beginColumn = originalColumn ? originalColumn : 0;
let endColumn = originalColumn ? originalColumn + 1 : Infinity;
if (!this._actors[sourceActorID]) {
this._actors[sourceActorID] = [];
}
if (!this._actors[sourceActorID][line]) {
this._actors[sourceActorID][line] = [];
}
if (!this._actors[sourceActorID][line][beginColumn]) {
this._actors[sourceActorID][line][beginColumn] = [];
}
if (!this._actors[sourceActorID][line][beginColumn][endColumn]) {
++this._size;
}
this._actors[sourceActorID][line][beginColumn][endColumn] = actor;
},
/**
* Delete the BreakpointActor from the given location in this
* BreakpointActorMap.
*
* @param OriginalLocation location
* The location from which the BreakpointActor should be deleted.
*/
deleteActor: function (location) {
let { originalSourceActor, originalLine, originalColumn } = location;
let sourceActorID = originalSourceActor.actorID;
let line = originalLine;
let beginColumn = originalColumn ? originalColumn : 0;
let endColumn = originalColumn ? originalColumn + 1 : Infinity;
if (this._actors[sourceActorID]) {
if (this._actors[sourceActorID][line]) {
if (this._actors[sourceActorID][line][beginColumn]) {
if (this._actors[sourceActorID][line][beginColumn][endColumn]) {
--this._size;
}
delete this._actors[sourceActorID][line][beginColumn][endColumn];
if (Object.keys(this._actors[sourceActorID][line][beginColumn]).length === 0) {
delete this._actors[sourceActorID][line][beginColumn];
}
}
if (Object.keys(this._actors[sourceActorID][line]).length === 0) {
delete this._actors[sourceActorID][line];
}
}
}
}
};
exports.BreakpointActorMap = BreakpointActorMap;
/**
* Keeps track of persistent sources across reloads and ties different
* source instances to the same actor id so that things like
* breakpoints survive reloads. ThreadSources uses this to force the
* same actorID on a SourceActor.
*/
function SourceActorStore() {
// source identifier --> actor id
this._sourceActorIds = Object.create(null);
}
SourceActorStore.prototype = {
/**
* Lookup an existing actor id that represents this source, if available.
*/
getReusableActorId: function (source, originalUrl) {
let url = this.getUniqueKey(source, originalUrl);
if (url && url in this._sourceActorIds) {
return this._sourceActorIds[url];
}
return null;
},
/**
* Update a source with an actorID.
*/
setReusableActorId: function (source, originalUrl, actorID) {
let url = this.getUniqueKey(source, originalUrl);
if (url) {
this._sourceActorIds[url] = actorID;
}
},
/**
* Make a unique URL from a source that identifies it across reloads.
*/
getUniqueKey: function (source, originalUrl) {
if (originalUrl) {
// Original source from a sourcemap.
return originalUrl;
}
return getSourceURL(source);
}
};
exports.SourceActorStore = SourceActorStore;
/**
* Manages pushing event loops and automatically pops and exits them in the
* correct order as they are resolved.
*
* @param ThreadActor thread
* The thread actor instance that owns this EventLoopStack.
* @param DebuggerServerConnection connection
* The remote protocol connection associated with this event loop stack.
* @param Object hooks
* An object with the following properties:
* - url: The URL string of the debuggee we are spinning an event loop
* for.
* - preNest: function called before entering a nested event loop
* - postNest: function called after exiting a nested event loop
*/
function EventLoopStack({ thread, connection, hooks }) {
this._hooks = hooks;
this._thread = thread;
this._connection = connection;
}
EventLoopStack.prototype = {
/**
* The number of nested event loops on the stack.
*/
get size() {
return xpcInspector.eventLoopNestLevel;
},
/**
* The URL of the debuggee who pushed the event loop on top of the stack.
*/
get lastPausedUrl() {
let url = null;
if (this.size > 0) {
try {
url = xpcInspector.lastNestRequestor.url;
} catch (e) {
// The tab's URL getter may throw if the tab is destroyed by the time
// this code runs, but we don't really care at this point.
dumpn(e);
}
}
return url;
},
/**
* The DebuggerServerConnection of the debugger who pushed the event loop on
* top of the stack
*/
get lastConnection() {
return xpcInspector.lastNestRequestor._connection;
},
/**
* Push a new nested event loop onto the stack.
*
* @returns EventLoop
*/
push: function () {
return new EventLoop({
thread: this._thread,
connection: this._connection,
hooks: this._hooks
});
}
};
/**
* An object that represents a nested event loop. It is used as the nest
* requestor with nsIJSInspector instances.
*
* @param ThreadActor thread
* The thread actor that is creating this nested event loop.
* @param DebuggerServerConnection connection
* The remote protocol connection associated with this event loop.
* @param Object hooks
* The same hooks object passed into EventLoopStack during its
* initialization.
*/
function EventLoop({ thread, connection, hooks }) {
this._thread = thread;
this._hooks = hooks;
this._connection = connection;
this.enter = this.enter.bind(this);
this.resolve = this.resolve.bind(this);
}
EventLoop.prototype = {
entered: false,
resolved: false,
get url() {
return this._hooks.url;
},
/**
* Enter this nested event loop.
*/
enter: function () {
let nestData = this._hooks.preNest
? this._hooks.preNest()
: null;
this.entered = true;
xpcInspector.enterNestedEventLoop(this);
// Keep exiting nested event loops while the last requestor is resolved.
if (xpcInspector.eventLoopNestLevel > 0) {
const { resolved } = xpcInspector.lastNestRequestor;
if (resolved) {
xpcInspector.exitNestedEventLoop();
}
}
if (this._hooks.postNest) {
this._hooks.postNest(nestData);
}
},
/**
* Resolve this nested event loop.
*
* @returns boolean
* True if we exited this nested event loop because it was on top of
* the stack, false if there is another nested event loop above this
* one that hasn't resolved yet.
*/
resolve: function () {
if (!this.entered) {
throw new Error("Can't resolve an event loop before it has been entered!");
}
if (this.resolved) {
throw new Error("Already resolved this nested event loop!");
}
this.resolved = true;
if (this === xpcInspector.lastNestRequestor) {
xpcInspector.exitNestedEventLoop();
return true;
}
return false;
},
};
/**
* JSD2 actors.
*/
@ -2092,123 +1736,6 @@ PauseActor.prototype = {
actorPrefix: "pause"
};
/**
* A base actor for any actors that should only respond receive messages in the
* paused state. Subclasses may expose a `threadActor` which is used to help
* determine when we are in a paused state. Subclasses should set their own
* "constructor" property if they want better error messages. You should never
* instantiate a PauseScopedActor directly, only through subclasses.
*/
function PauseScopedActor() {
}
/**
* A function decorator for creating methods to handle protocol messages that
* should only be received while in the paused state.
*
* @param method Function
* The function we are decorating.
*/
PauseScopedActor.withPaused = function (method) {
return function () {
if (this.isPaused()) {
return method.apply(this, arguments);
}
return this._wrongState();
};
};
PauseScopedActor.prototype = {
/**
* Returns true if we are in the paused state.
*/
isPaused: function () {
// When there is not a ThreadActor available (like in the webconsole) we
// have to be optimistic and assume that we are paused so that we can
// respond to requests.
return this.threadActor ? this.threadActor.state === "paused" : true;
},
/**
* Returns the wrongState response packet for this actor.
*/
_wrongState: function () {
return {
error: "wrongState",
message: this.constructor.name +
" actors can only be accessed while the thread is paused."
};
}
};
/**
* Creates a pause-scoped actor for the specified object.
* @see ObjectActor
*/
function PauseScopedObjectActor(obj, hooks) {
ObjectActor.call(this, obj, hooks);
this.hooks.promote = hooks.promote;
this.hooks.isThreadLifetimePool = hooks.isThreadLifetimePool;
}
PauseScopedObjectActor.prototype = Object.create(PauseScopedActor.prototype);
Object.assign(PauseScopedObjectActor.prototype, ObjectActor.prototype);
Object.assign(PauseScopedObjectActor.prototype, {
constructor: PauseScopedObjectActor,
actorPrefix: "pausedobj",
onOwnPropertyNames:
PauseScopedActor.withPaused(ObjectActor.prototype.onOwnPropertyNames),
onPrototypeAndProperties:
PauseScopedActor.withPaused(ObjectActor.prototype.onPrototypeAndProperties),
onPrototype: PauseScopedActor.withPaused(ObjectActor.prototype.onPrototype),
onProperty: PauseScopedActor.withPaused(ObjectActor.prototype.onProperty),
onDecompile: PauseScopedActor.withPaused(ObjectActor.prototype.onDecompile),
onDisplayString:
PauseScopedActor.withPaused(ObjectActor.prototype.onDisplayString),
onParameterNames:
PauseScopedActor.withPaused(ObjectActor.prototype.onParameterNames),
/**
* Handle a protocol request to promote a pause-lifetime grip to a
* thread-lifetime grip.
*
* @param request object
* The protocol request object.
*/
onThreadGrip: PauseScopedActor.withPaused(function (request) {
this.hooks.promote();
return {};
}),
/**
* Handle a protocol request to release a thread-lifetime grip.
*
* @param request object
* The protocol request object.
*/
onRelease: PauseScopedActor.withPaused(function (request) {
if (this.hooks.isThreadLifetimePool()) {
return { error: "notReleasable",
message: "Only thread-lifetime actors can be released." };
}
this.release();
return {};
}),
});
Object.assign(PauseScopedObjectActor.prototype.requestTypes, {
"threadGrip": PauseScopedObjectActor.prototype.onThreadGrip,
});
function hackDebugger(Debugger) {
// TODO: Improve native code instead of hacking on top of it

View File

@ -0,0 +1,173 @@
/* -*- 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 { OriginalLocation } = require("devtools/server/actors/common");
/**
* A BreakpointActorMap is a map from locations to instances of BreakpointActor.
*/
function BreakpointActorMap() {
this._size = 0;
this._actors = {};
}
BreakpointActorMap.prototype = {
/**
* Return the number of BreakpointActors in this BreakpointActorMap.
*
* @returns Number
* The number of BreakpointActor in this BreakpointActorMap.
*/
get size() {
return this._size;
},
/**
* Generate all BreakpointActors that match the given location in
* this BreakpointActorMap.
*
* @param OriginalLocation location
* The location for which matching BreakpointActors should be generated.
*/
findActors: function* (location = new OriginalLocation()) {
// Fast shortcut for when we know we won't find any actors. Surprisingly
// enough, this speeds up refreshing when there are no breakpoints set by
// about 2x!
if (this.size === 0) {
return;
}
function* findKeys(obj, key) {
if (key !== undefined) {
if (key in obj) {
yield key;
}
} else {
for (key of Object.keys(obj)) {
yield key;
}
}
}
let query = {
sourceActorID: location.originalSourceActor
? location.originalSourceActor.actorID
: undefined,
line: location.originalLine,
};
// If location contains a line, assume we are searching for a whole line
// breakpoint, and set begin/endColumn accordingly. Otherwise, we are
// searching for all breakpoints, so begin/endColumn should be left unset.
if (location.originalLine) {
query.beginColumn = location.originalColumn ? location.originalColumn : 0;
query.endColumn = location.originalColumn ? location.originalColumn + 1 : Infinity;
} else {
query.beginColumn = location.originalColumn ? query.originalColumn : undefined;
query.endColumn = location.originalColumn ? query.originalColumn + 1 : undefined;
}
for (let sourceActorID of findKeys(this._actors, query.sourceActorID)) {
let actor = this._actors[sourceActorID];
for (let line of findKeys(actor, query.line)) {
for (let beginColumn of findKeys(actor[line], query.beginColumn)) {
for (let endColumn of findKeys(actor[line][beginColumn],
query.endColumn)) {
yield actor[line][beginColumn][endColumn];
}
}
}
}
},
/**
* Return the BreakpointActor at the given location in this
* BreakpointActorMap.
*
* @param OriginalLocation location
* The location for which the BreakpointActor should be returned.
*
* @returns BreakpointActor actor
* The BreakpointActor at the given location.
*/
getActor: function (originalLocation) {
for (let actor of this.findActors(originalLocation)) {
return actor;
}
return null;
},
/**
* Set the given BreakpointActor to the given location in this
* BreakpointActorMap.
*
* @param OriginalLocation location
* The location to which the given BreakpointActor should be set.
*
* @param BreakpointActor actor
* The BreakpointActor to be set to the given location.
*/
setActor: function (location, actor) {
let { originalSourceActor, originalLine, originalColumn } = location;
let sourceActorID = originalSourceActor.actorID;
let line = originalLine;
let beginColumn = originalColumn ? originalColumn : 0;
let endColumn = originalColumn ? originalColumn + 1 : Infinity;
if (!this._actors[sourceActorID]) {
this._actors[sourceActorID] = [];
}
if (!this._actors[sourceActorID][line]) {
this._actors[sourceActorID][line] = [];
}
if (!this._actors[sourceActorID][line][beginColumn]) {
this._actors[sourceActorID][line][beginColumn] = [];
}
if (!this._actors[sourceActorID][line][beginColumn][endColumn]) {
++this._size;
}
this._actors[sourceActorID][line][beginColumn][endColumn] = actor;
},
/**
* Delete the BreakpointActor from the given location in this
* BreakpointActorMap.
*
* @param OriginalLocation location
* The location from which the BreakpointActor should be deleted.
*/
deleteActor: function (location) {
let { originalSourceActor, originalLine, originalColumn } = location;
let sourceActorID = originalSourceActor.actorID;
let line = originalLine;
let beginColumn = originalColumn ? originalColumn : 0;
let endColumn = originalColumn ? originalColumn + 1 : Infinity;
if (this._actors[sourceActorID]) {
if (this._actors[sourceActorID][line]) {
if (this._actors[sourceActorID][line][beginColumn]) {
if (this._actors[sourceActorID][line][beginColumn][endColumn]) {
--this._size;
}
delete this._actors[sourceActorID][line][beginColumn][endColumn];
if (Object.keys(this._actors[sourceActorID][line][beginColumn]).length === 0) {
delete this._actors[sourceActorID][line][beginColumn];
}
}
if (Object.keys(this._actors[sourceActorID][line]).length === 0) {
delete this._actors[sourceActorID][line];
}
}
}
}
};
exports.BreakpointActorMap = BreakpointActorMap;

View File

@ -0,0 +1,157 @@
/* -*- 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 xpcInspector = require("xpcInspector");
const DevToolsUtils = require("devtools/shared/DevToolsUtils");
const { dumpn } = DevToolsUtils;
/**
* Manages pushing event loops and automatically pops and exits them in the
* correct order as they are resolved.
*
* @param ThreadActor thread
* The thread actor instance that owns this EventLoopStack.
* @param DebuggerServerConnection connection
* The remote protocol connection associated with this event loop stack.
* @param Object hooks
* An object with the following properties:
* - url: The URL string of the debuggee we are spinning an event loop
* for.
* - preNest: function called before entering a nested event loop
* - postNest: function called after exiting a nested event loop
*/
function EventLoopStack({ thread, connection, hooks }) {
this._hooks = hooks;
this._thread = thread;
this._connection = connection;
}
EventLoopStack.prototype = {
/**
* The number of nested event loops on the stack.
*/
get size() {
return xpcInspector.eventLoopNestLevel;
},
/**
* The URL of the debuggee who pushed the event loop on top of the stack.
*/
get lastPausedUrl() {
let url = null;
if (this.size > 0) {
try {
url = xpcInspector.lastNestRequestor.url;
} catch (e) {
// The tab's URL getter may throw if the tab is destroyed by the time
// this code runs, but we don't really care at this point.
dumpn(e);
}
}
return url;
},
/**
* The DebuggerServerConnection of the debugger who pushed the event loop on
* top of the stack
*/
get lastConnection() {
return xpcInspector.lastNestRequestor._connection;
},
/**
* Push a new nested event loop onto the stack.
*
* @returns EventLoop
*/
push: function () {
return new EventLoop({
thread: this._thread,
connection: this._connection,
hooks: this._hooks
});
}
};
/**
* An object that represents a nested event loop. It is used as the nest
* requestor with nsIJSInspector instances.
*
* @param ThreadActor thread
* The thread actor that is creating this nested event loop.
* @param DebuggerServerConnection connection
* The remote protocol connection associated with this event loop.
* @param Object hooks
* The same hooks object passed into EventLoopStack during its
* initialization.
*/
function EventLoop({ thread, connection, hooks }) {
this._thread = thread;
this._hooks = hooks;
this._connection = connection;
this.enter = this.enter.bind(this);
this.resolve = this.resolve.bind(this);
}
EventLoop.prototype = {
entered: false,
resolved: false,
get url() {
return this._hooks.url;
},
/**
* Enter this nested event loop.
*/
enter: function () {
let nestData = this._hooks.preNest
? this._hooks.preNest()
: null;
this.entered = true;
xpcInspector.enterNestedEventLoop(this);
// Keep exiting nested event loops while the last requestor is resolved.
if (xpcInspector.eventLoopNestLevel > 0) {
const { resolved } = xpcInspector.lastNestRequestor;
if (resolved) {
xpcInspector.exitNestedEventLoop();
}
}
if (this._hooks.postNest) {
this._hooks.postNest(nestData);
}
},
/**
* Resolve this nested event loop.
*
* @returns boolean
* True if we exited this nested event loop because it was on top of
* the stack, false if there is another nested event loop above this
* one that hasn't resolved yet.
*/
resolve: function () {
if (!this.entered) {
throw new Error("Can't resolve an event loop before it has been entered!");
}
if (this.resolved) {
throw new Error("Already resolved this nested event loop!");
}
this.resolved = true;
if (this === xpcInspector.lastNestRequestor) {
xpcInspector.exitNestedEventLoop();
return true;
}
return false;
},
};
exports.EventLoopStack = EventLoopStack;

View File

@ -8,10 +8,13 @@ DevToolsModules(
'actor-registry-utils.js',
'audionodes.json',
'automation-timeline.js',
'breakpoint-actor-map.js',
'css-grid-utils.js',
'event-loop.js',
'make-debugger.js',
'map-uri-to-addon-id.js',
'shapes-utils.js',
'source-actor-store.js',
'stack.js',
'TabSources.js',
'walker-search.js',

View File

@ -0,0 +1,57 @@
/* -*- 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";
loader.lazyRequireGetter(this, "getSourceURL", "devtools/server/actors/source", true);
/**
* Keeps track of persistent sources across reloads and ties different
* source instances to the same actor id so that things like
* breakpoints survive reloads. ThreadSources uses this to force the
* same actorID on a SourceActor.
*/
function SourceActorStore() {
// source identifier --> actor id
this._sourceActorIds = Object.create(null);
}
SourceActorStore.prototype = {
/**
* Lookup an existing actor id that represents this source, if available.
*/
getReusableActorId: function (source, originalUrl) {
let url = this.getUniqueKey(source, originalUrl);
if (url && url in this._sourceActorIds) {
return this._sourceActorIds[url];
}
return null;
},
/**
* Update a source with an actorID.
*/
setReusableActorId: function (source, originalUrl, actorID) {
let url = this.getUniqueKey(source, originalUrl);
if (url) {
this._sourceActorIds[url] = actorID;
}
},
/**
* Make a unique URL from a source that identifies it across reloads.
*/
getUniqueKey: function (source, originalUrl) {
if (originalUrl) {
// Original source from a sourcemap.
return originalUrl;
}
return getSourceURL(source);
}
};
exports.SourceActorStore = SourceActorStore;

View File

@ -11,7 +11,7 @@
const Services = require("Services");
const { Cc, Ci, Cu } = require("chrome");
const { DebuggerServer, ActorPool } = require("devtools/server/main");
const { ThreadActor } = require("devtools/server/actors/script");
const { ThreadActor } = require("devtools/server/actors/thread");
const { ObjectActor, LongStringActor, createValueGrip, stringIsLong } = require("devtools/server/actors/object");
const DevToolsUtils = require("devtools/shared/DevToolsUtils");
const ErrorDocs = require("devtools/server/actors/errordocs");

View File

@ -11,7 +11,7 @@ const { ChromeActor } = require("./chrome");
const makeDebugger = require("./utils/make-debugger");
loader.lazyRequireGetter(this, "mapURIToAddonID", "devtools/server/actors/utils/map-uri-to-addon-id");
loader.lazyRequireGetter(this, "unwrapDebuggerObjectGlobal", "devtools/server/actors/script", true);
loader.lazyRequireGetter(this, "unwrapDebuggerObjectGlobal", "devtools/server/actors/thread", true);
loader.lazyRequireGetter(this, "ChromeUtils");
const FALLBACK_DOC_MESSAGE = "Your addon does not have any document opened yet.";

View File

@ -6,7 +6,7 @@
// Test the functionality of the BreakpointActorMap object.
const { BreakpointActorMap } = require("devtools/server/actors/script");
const { BreakpointActorMap } = require("devtools/server/actors/utils/breakpoint-actor-map");
function run_test() {
test_get_actor();

View File

@ -5,7 +5,7 @@
const { ActorPool, appendExtraActors, createExtraActors } = require("devtools/server/actors/common");
const { RootActor } = require("devtools/server/actors/root");
const { ThreadActor } = require("devtools/server/actors/script");
const { ThreadActor } = require("devtools/server/actors/thread");
const { DebuggerServer } = require("devtools/server/main");
const { TabSources } = require("devtools/server/actors/utils/TabSources");
const makeDebugger = require("devtools/server/actors/utils/make-debugger");

View File

@ -30,7 +30,7 @@ loadSubScript("resource://devtools/shared/worker/loader.js");
var defer = worker.require("devtools/shared/defer");
var { ActorPool } = worker.require("devtools/server/actors/common");
var { ThreadActor } = worker.require("devtools/server/actors/script");
var { ThreadActor } = worker.require("devtools/server/actors/thread");
var { WebConsoleActor } = worker.require("devtools/server/actors/webconsole");
var { TabSources } = worker.require("devtools/server/actors/utils/TabSources");
var makeDebugger = worker.require("devtools/server/actors/utils/make-debugger");

View File

@ -6,7 +6,7 @@
const { ActorPool, appendExtraActors, createExtraActors } =
require("devtools/server/actors/common");
const { RootActor } = require("devtools/server/actors/root");
const { ThreadActor } = require("devtools/server/actors/script");
const { ThreadActor } = require("devtools/server/actors/thread");
const { DebuggerServer } = require("devtools/server/main");
const promise = require("promise");

View File

@ -98,7 +98,7 @@ consoleService.registerListener(listener);
* Initialize the testing debugger server.
*/
function initTestDebuggerServer() {
DebuggerServer.registerModule("devtools/server/actors/script", {
DebuggerServer.registerModule("devtools/server/actors/thread", {
prefix: "script",
constructor: "ScriptActor",
type: { global: true, tab: true }

View File

@ -5,7 +5,7 @@
const { ActorPool, appendExtraActors, createExtraActors } =
require("devtools/server/actors/common");
const { RootActor } = require("devtools/server/actors/root");
const { ThreadActor } = require("devtools/server/actors/script");
const { ThreadActor } = require("devtools/server/actors/thread");
const { DebuggerServer } = require("devtools/server/main");
const promise = require("promise");