mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-10-17 23:35:34 +00:00
Bug 1250896 - Move SourceActor into its own file;r=jryans
This commit is contained in:
parent
5315df62f9
commit
07777cfa9f
@ -8,6 +8,26 @@
|
||||
|
||||
const { ActorClass, method } = require("devtools/server/protocol");
|
||||
|
||||
/**
|
||||
* Set breakpoints on all the given entry points with the given
|
||||
* BreakpointActor as the handler.
|
||||
*
|
||||
* @param BreakpointActor actor
|
||||
* The actor handling the breakpoint hits.
|
||||
* @param Array entryPoints
|
||||
* An array of objects of the form `{ script, offsets }`.
|
||||
*/
|
||||
function setBreakpointAtEntryPoints(actor, entryPoints) {
|
||||
for (let { script, offsets } of entryPoints) {
|
||||
actor.addScript(script);
|
||||
for (let offset of offsets) {
|
||||
script.setBreakpoint(offset, actor);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
exports.setBreakpointAtEntryPoints = setBreakpointAtEntryPoints;
|
||||
|
||||
/**
|
||||
* BreakpointActors exist for the lifetime of their containing thread and are
|
||||
* responsible for deleting breakpoints, handling breakpoint hits and
|
||||
|
@ -49,6 +49,7 @@ DevToolsModules(
|
||||
'root.js',
|
||||
'script.js',
|
||||
'settings.js',
|
||||
'source.js',
|
||||
'storage.js',
|
||||
'string.js',
|
||||
'styleeditor.js',
|
||||
|
@ -9,14 +9,14 @@
|
||||
const Services = require("Services");
|
||||
const { Cc, Ci, Cu, Cr, components, ChromeWorker } = require("chrome");
|
||||
const { ActorPool, OriginalLocation, GeneratedLocation } = require("devtools/server/actors/common");
|
||||
const { BreakpointActor } = require("devtools/server/actors/breakpoint");
|
||||
const { BreakpointActor, setBreakpointAtEntryPoints } = require("devtools/server/actors/breakpoint");
|
||||
const { EnvironmentActor } = require("devtools/server/actors/environment");
|
||||
const { FrameActor } = require("devtools/server/actors/frame");
|
||||
const { ObjectActor, createValueGrip, longStringGrip } = require("devtools/server/actors/object");
|
||||
const { SourceActor, getSourceURL } = require("devtools/server/actors/source");
|
||||
const { DebuggerServer } = require("devtools/server/main");
|
||||
const DevToolsUtils = require("devtools/shared/DevToolsUtils");
|
||||
const { assert, dumpn, update, fetch } = DevToolsUtils;
|
||||
const { dirname, joinURI } = require("devtools/shared/path");
|
||||
const promise = require("promise");
|
||||
const PromiseDebugging = require("PromiseDebugging");
|
||||
const xpcInspector = require("xpcInspector");
|
||||
@ -30,8 +30,6 @@ loader.lazyGetter(this, "Debugger", () => {
|
||||
hackDebugger(Debugger);
|
||||
return Debugger;
|
||||
});
|
||||
loader.lazyRequireGetter(this, "SourceMapConsumer", "source-map", true);
|
||||
loader.lazyRequireGetter(this, "SourceMapGenerator", "source-map", true);
|
||||
loader.lazyRequireGetter(this, "CssLogic", "devtools/shared/inspector/css-logic", true);
|
||||
loader.lazyRequireGetter(this, "events", "sdk/event/core");
|
||||
loader.lazyRequireGetter(this, "mapURIToAddonID", "devtools/server/actors/utils/map-uri-to-addon-id");
|
||||
@ -2102,819 +2100,6 @@ PauseScopedActor.prototype = {
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Resolve a URI back to physical file.
|
||||
*
|
||||
* Of course, this works only for URIs pointing to local resources.
|
||||
*
|
||||
* @param aURI
|
||||
* URI to resolve
|
||||
* @return
|
||||
* resolved nsIURI
|
||||
*/
|
||||
function resolveURIToLocalPath(aURI) {
|
||||
let resolved;
|
||||
switch (aURI.scheme) {
|
||||
case "jar":
|
||||
case "file":
|
||||
return aURI;
|
||||
|
||||
case "chrome":
|
||||
resolved = Cc["@mozilla.org/chrome/chrome-registry;1"].
|
||||
getService(Ci.nsIChromeRegistry).convertChromeURL(aURI);
|
||||
return resolveURIToLocalPath(resolved);
|
||||
|
||||
case "resource":
|
||||
resolved = Cc["@mozilla.org/network/protocol;1?name=resource"].
|
||||
getService(Ci.nsIResProtocolHandler).resolveURI(aURI);
|
||||
aURI = Services.io.newURI(resolved, null, null);
|
||||
return resolveURIToLocalPath(aURI);
|
||||
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A SourceActor provides information about the source of a script. There
|
||||
* are two kinds of source actors: ones that represent real source objects,
|
||||
* and ones that represent non-existant "original" sources when the real
|
||||
* sources are sourcemapped. When a source is sourcemapped, actors are
|
||||
* created for both the "generated" and "original" sources, and the client will
|
||||
* only see the original sources. We separate these because there isn't
|
||||
* a 1:1 mapping of generated to original sources; one generated source
|
||||
* may represent N original sources, so we need to create N + 1 separate
|
||||
* actors.
|
||||
*
|
||||
* There are 4 different scenarios for sources that you should
|
||||
* understand:
|
||||
*
|
||||
* - A single non-sourcemapped source that is not inlined in HTML
|
||||
* (separate JS file, eval'ed code, etc)
|
||||
* - A single sourcemapped source which creates N original sources
|
||||
* - An HTML page with multiple inline scripts, which are distinct
|
||||
* sources, but should be represented as a single source
|
||||
* - A pretty-printed source (which may or may not be an original
|
||||
* sourcemapped source), which generates a sourcemap for itself
|
||||
*
|
||||
* The complexity of `SourceActor` and `ThreadSources` are to handle
|
||||
* all of thise cases and hopefully internalize the complexities.
|
||||
*
|
||||
* @param Debugger.Source source
|
||||
* The source object we are representing.
|
||||
* @param ThreadActor thread
|
||||
* The current thread actor.
|
||||
* @param String originalUrl
|
||||
* Optional. For sourcemapped urls, the original url this is representing.
|
||||
* @param Debugger.Source generatedSource
|
||||
* Optional, passed in when aSourceMap is also passed in. The generated
|
||||
* source object that introduced this source.
|
||||
* @param String contentType
|
||||
* Optional. The content type of this source, if immediately available.
|
||||
*/
|
||||
function SourceActor({ source, thread, originalUrl, generatedSource,
|
||||
isInlineSource, contentType }) {
|
||||
this._threadActor = thread;
|
||||
this._originalUrl = originalUrl;
|
||||
this._source = source;
|
||||
this._generatedSource = generatedSource;
|
||||
this._contentType = contentType;
|
||||
this._isInlineSource = isInlineSource;
|
||||
|
||||
this.onSource = this.onSource.bind(this);
|
||||
this._invertSourceMap = this._invertSourceMap.bind(this);
|
||||
this._encodeAndSetSourceMapURL = this._encodeAndSetSourceMapURL.bind(this);
|
||||
this._getSourceText = this._getSourceText.bind(this);
|
||||
|
||||
this._mapSourceToAddon();
|
||||
|
||||
if (this.threadActor.sources.isPrettyPrinted(this.url)) {
|
||||
this._init = this.onPrettyPrint({
|
||||
indent: this.threadActor.sources.prettyPrintIndent(this.url)
|
||||
}).then(null, error => {
|
||||
DevToolsUtils.reportException("SourceActor", error);
|
||||
});
|
||||
} else {
|
||||
this._init = null;
|
||||
}
|
||||
}
|
||||
|
||||
SourceActor.prototype = {
|
||||
constructor: SourceActor,
|
||||
actorPrefix: "source",
|
||||
|
||||
_oldSourceMap: null,
|
||||
_init: null,
|
||||
_addonID: null,
|
||||
_addonPath: null,
|
||||
|
||||
get isSourceMapped() {
|
||||
return !this.isInlineSource && (
|
||||
this._originalURL || this._generatedSource ||
|
||||
this.threadActor.sources.isPrettyPrinted(this.url)
|
||||
);
|
||||
},
|
||||
|
||||
get isInlineSource() {
|
||||
return this._isInlineSource;
|
||||
},
|
||||
|
||||
get threadActor() { return this._threadActor; },
|
||||
get sources() { return this._threadActor.sources; },
|
||||
get dbg() { return this.threadActor.dbg; },
|
||||
get scripts() { return this.threadActor.scripts; },
|
||||
get source() { return this._source; },
|
||||
get generatedSource() { return this._generatedSource; },
|
||||
get breakpointActorMap() { return this.threadActor.breakpointActorMap; },
|
||||
get url() {
|
||||
if (this.source) {
|
||||
return getSourceURL(this.source, this.threadActor._parent.window);
|
||||
}
|
||||
return this._originalUrl;
|
||||
},
|
||||
get addonID() { return this._addonID; },
|
||||
get addonPath() { return this._addonPath; },
|
||||
|
||||
get prettyPrintWorker() {
|
||||
return this.threadActor.prettyPrintWorker;
|
||||
},
|
||||
|
||||
form: function () {
|
||||
let source = this.source || this.generatedSource;
|
||||
// This might not have a source or a generatedSource because we
|
||||
// treat HTML pages with inline scripts as a special SourceActor
|
||||
// that doesn't have either
|
||||
let introductionUrl = null;
|
||||
if (source && source.introductionScript) {
|
||||
introductionUrl = source.introductionScript.source.url;
|
||||
}
|
||||
|
||||
return {
|
||||
actor: this.actorID,
|
||||
url: this.url ? this.url.split(" -> ").pop() : null,
|
||||
addonID: this._addonID,
|
||||
addonPath: this._addonPath,
|
||||
isBlackBoxed: this.threadActor.sources.isBlackBoxed(this.url),
|
||||
isPrettyPrinted: this.threadActor.sources.isPrettyPrinted(this.url),
|
||||
introductionUrl: introductionUrl ? introductionUrl.split(" -> ").pop() : null,
|
||||
introductionType: source ? source.introductionType : null
|
||||
};
|
||||
},
|
||||
|
||||
disconnect: function () {
|
||||
if (this.registeredPool && this.registeredPool.sourceActors) {
|
||||
delete this.registeredPool.sourceActors[this.actorID];
|
||||
}
|
||||
},
|
||||
|
||||
_mapSourceToAddon: function() {
|
||||
try {
|
||||
var nsuri = Services.io.newURI(this.url.split(" -> ").pop(), null, null);
|
||||
}
|
||||
catch (e) {
|
||||
// We can't do anything with an invalid URI
|
||||
return;
|
||||
}
|
||||
|
||||
let localURI = resolveURIToLocalPath(nsuri);
|
||||
|
||||
let id = {};
|
||||
if (localURI && mapURIToAddonID(localURI, id)) {
|
||||
this._addonID = id.value;
|
||||
|
||||
if (localURI instanceof Ci.nsIJARURI) {
|
||||
// The path in the add-on is easy for jar: uris
|
||||
this._addonPath = localURI.JAREntry;
|
||||
}
|
||||
else if (localURI instanceof Ci.nsIFileURL) {
|
||||
// For file: uris walk up to find the last directory that is part of the
|
||||
// add-on
|
||||
let target = localURI.file;
|
||||
let path = target.leafName;
|
||||
|
||||
// We can assume that the directory containing the source file is part
|
||||
// of the add-on
|
||||
let root = target.parent;
|
||||
let file = root.parent;
|
||||
while (file && mapURIToAddonID(Services.io.newFileURI(file), {})) {
|
||||
path = root.leafName + "/" + path;
|
||||
root = file;
|
||||
file = file.parent;
|
||||
}
|
||||
|
||||
if (!file) {
|
||||
const error = new Error("Could not find the root of the add-on for " + this.url);
|
||||
DevToolsUtils.reportException("SourceActor.prototype._mapSourceToAddon", error)
|
||||
return;
|
||||
}
|
||||
|
||||
this._addonPath = path;
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
_reportLoadSourceError: function (error, map=null) {
|
||||
try {
|
||||
DevToolsUtils.reportException("SourceActor", error);
|
||||
|
||||
JSON.stringify(this.form(), null, 4).split(/\n/g)
|
||||
.forEach(line => console.error("\t", line));
|
||||
|
||||
if (!map) {
|
||||
return;
|
||||
}
|
||||
|
||||
console.error("\t", "source map's sourceRoot =", map.sourceRoot);
|
||||
|
||||
console.error("\t", "source map's sources =");
|
||||
map.sources.forEach(s => {
|
||||
let hasSourceContent = map.sourceContentFor(s, true);
|
||||
console.error("\t\t", s, "\t",
|
||||
hasSourceContent ? "has source content" : "no source content");
|
||||
});
|
||||
|
||||
console.error("\t", "source map's sourcesContent =");
|
||||
map.sourcesContent.forEach(c => {
|
||||
if (c.length > 80) {
|
||||
c = c.slice(0, 77) + "...";
|
||||
}
|
||||
c = c.replace(/\n/g, "\\n");
|
||||
console.error("\t\t", c);
|
||||
});
|
||||
} catch (e) { }
|
||||
},
|
||||
|
||||
_getSourceText: function () {
|
||||
let toResolvedContent = t => ({
|
||||
content: t,
|
||||
contentType: this._contentType
|
||||
});
|
||||
|
||||
let genSource = this.generatedSource || this.source;
|
||||
return this.threadActor.sources.fetchSourceMap(genSource).then(map => {
|
||||
if (map) {
|
||||
try {
|
||||
let sourceContent = map.sourceContentFor(this.url);
|
||||
if (sourceContent) {
|
||||
return toResolvedContent(sourceContent);
|
||||
}
|
||||
} catch (error) {
|
||||
this._reportLoadSourceError(error, map);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
// Use `source.text` if it exists, is not the "no source"
|
||||
// string, and the content type of the source is JavaScript. It
|
||||
// will be "no source" if the Debugger API wasn't able to load
|
||||
// the source because sources were discarded
|
||||
// (javascript.options.discardSystemSource == true). Re-fetch
|
||||
// non-JS sources to get the contentType from the headers.
|
||||
if (this.source &&
|
||||
this.source.text !== "[no source]" &&
|
||||
this._contentType &&
|
||||
this._contentType.indexOf('javascript') !== -1) {
|
||||
return toResolvedContent(this.source.text);
|
||||
}
|
||||
else {
|
||||
// Only load the HTML page source from cache (which exists when
|
||||
// there are inline sources). Otherwise, we can't trust the
|
||||
// cache because we are most likely here because we are
|
||||
// fetching the original text for sourcemapped code, and the
|
||||
// page hasn't requested it before (if it has, it was a
|
||||
// previous debugging session).
|
||||
let sourceFetched = fetch(this.url, { loadFromCache: this.isInlineSource });
|
||||
|
||||
// Record the contentType we just learned during fetching
|
||||
return sourceFetched
|
||||
.then(result => {
|
||||
this._contentType = result.contentType;
|
||||
return result;
|
||||
}, error => {
|
||||
this._reportLoadSourceError(error, map);
|
||||
throw error;
|
||||
});
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Get all executable lines from the current source
|
||||
* @return Array - Executable lines of the current script
|
||||
**/
|
||||
getExecutableLines: function () {
|
||||
// Check if the original source is source mapped
|
||||
let packet = {
|
||||
from: this.actorID
|
||||
};
|
||||
|
||||
function sortLines(lines) {
|
||||
// Converting the Set into an array
|
||||
lines = [...lines];
|
||||
lines.sort((a, b) => {
|
||||
return a - b;
|
||||
});
|
||||
return lines;
|
||||
}
|
||||
|
||||
if (this.generatedSource) {
|
||||
return this.threadActor.sources.getSourceMap(this.generatedSource).then(sm => {
|
||||
let lines = new Set();
|
||||
|
||||
// Position of executable lines in the generated source
|
||||
let offsets = this.getExecutableOffsets(this.generatedSource, false);
|
||||
for (let offset of offsets) {
|
||||
let {line, source: sourceUrl} = sm.originalPositionFor({
|
||||
line: offset.lineNumber,
|
||||
column: offset.columnNumber
|
||||
});
|
||||
|
||||
if (sourceUrl === this.url) {
|
||||
lines.add(line);
|
||||
}
|
||||
}
|
||||
|
||||
packet.lines = sortLines(lines);
|
||||
return packet;
|
||||
});
|
||||
}
|
||||
|
||||
let lines = this.getExecutableOffsets(this.source, true);
|
||||
packet.lines = sortLines(lines);
|
||||
return packet;
|
||||
},
|
||||
|
||||
/**
|
||||
* Extract all executable offsets from the given script
|
||||
* @param String url - extract offsets of the script with this url
|
||||
* @param Boolean onlyLine - will return only the line number
|
||||
* @return Set - Executable offsets/lines of the script
|
||||
**/
|
||||
getExecutableOffsets: function (source, onlyLine) {
|
||||
let offsets = new Set();
|
||||
for (let s of this.threadActor.scripts.getScriptsBySource(source)) {
|
||||
for (let offset of s.getAllColumnOffsets()) {
|
||||
offsets.add(onlyLine ? offset.lineNumber : offset);
|
||||
}
|
||||
}
|
||||
|
||||
return offsets;
|
||||
},
|
||||
|
||||
/**
|
||||
* Handler for the "source" packet.
|
||||
*/
|
||||
onSource: function () {
|
||||
return resolve(this._init)
|
||||
.then(this._getSourceText)
|
||||
.then(({ content, contentType }) => {
|
||||
return {
|
||||
from: this.actorID,
|
||||
source: createValueGrip(content, this.threadActor.threadLifetimePool,
|
||||
this.threadActor.objectGrip),
|
||||
contentType: contentType
|
||||
};
|
||||
})
|
||||
.then(null, aError => {
|
||||
reportError(aError, "Got an exception during SA_onSource: ");
|
||||
return {
|
||||
"from": this.actorID,
|
||||
"error": this.url,
|
||||
"message": "Could not load the source for " + this.url + ".\n"
|
||||
+ DevToolsUtils.safeErrorString(aError)
|
||||
};
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Handler for the "prettyPrint" packet.
|
||||
*/
|
||||
onPrettyPrint: function ({ indent }) {
|
||||
this.threadActor.sources.prettyPrint(this.url, indent);
|
||||
return this._getSourceText()
|
||||
.then(this._sendToPrettyPrintWorker(indent))
|
||||
.then(this._invertSourceMap)
|
||||
.then(this._encodeAndSetSourceMapURL)
|
||||
.then(() => {
|
||||
// We need to reset `_init` now because we have already done the work of
|
||||
// pretty printing, and don't want onSource to wait forever for
|
||||
// initialization to complete.
|
||||
this._init = null;
|
||||
})
|
||||
.then(this.onSource)
|
||||
.then(null, error => {
|
||||
this.onDisablePrettyPrint();
|
||||
return {
|
||||
from: this.actorID,
|
||||
error: "prettyPrintError",
|
||||
message: DevToolsUtils.safeErrorString(error)
|
||||
};
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Return a function that sends a request to the pretty print worker, waits on
|
||||
* the worker's response, and then returns the pretty printed code.
|
||||
*
|
||||
* @param Number aIndent
|
||||
* The number of spaces to indent by the code by, when we send the
|
||||
* request to the pretty print worker.
|
||||
* @returns Function
|
||||
* Returns a function which takes an AST, and returns a promise that
|
||||
* is resolved with `{ code, mappings }` where `code` is the pretty
|
||||
* printed code, and `mappings` is an array of source mappings.
|
||||
*/
|
||||
_sendToPrettyPrintWorker: function (aIndent) {
|
||||
return ({ content }) => {
|
||||
return this.prettyPrintWorker.performTask("pretty-print", {
|
||||
url: this.url,
|
||||
indent: aIndent,
|
||||
source: content
|
||||
})
|
||||
};
|
||||
},
|
||||
|
||||
/**
|
||||
* Invert a source map. So if a source map maps from a to b, return a new
|
||||
* source map from b to a. We need to do this because the source map we get
|
||||
* from _generatePrettyCodeAndMap goes the opposite way we want it to for
|
||||
* debugging.
|
||||
*
|
||||
* Note that the source map is modified in place.
|
||||
*/
|
||||
_invertSourceMap: function ({ code, mappings }) {
|
||||
const generator = new SourceMapGenerator({ file: this.url });
|
||||
return DevToolsUtils.yieldingEach(mappings._array, m => {
|
||||
let mapping = {
|
||||
generated: {
|
||||
line: m.originalLine,
|
||||
column: m.originalColumn
|
||||
}
|
||||
};
|
||||
if (m.source) {
|
||||
mapping.source = m.source;
|
||||
mapping.original = {
|
||||
line: m.generatedLine,
|
||||
column: m.generatedColumn
|
||||
};
|
||||
mapping.name = m.name;
|
||||
}
|
||||
generator.addMapping(mapping);
|
||||
}).then(() => {
|
||||
generator.setSourceContent(this.url, code);
|
||||
let consumer = SourceMapConsumer.fromSourceMap(generator);
|
||||
|
||||
return {
|
||||
code: code,
|
||||
map: consumer
|
||||
};
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Save the source map back to our thread's ThreadSources object so that
|
||||
* stepping, breakpoints, debugger statements, etc can use it. If we are
|
||||
* pretty printing a source mapped source, we need to compose the existing
|
||||
* source map with our new one.
|
||||
*/
|
||||
_encodeAndSetSourceMapURL: function ({ map: sm }) {
|
||||
let source = this.generatedSource || this.source;
|
||||
let sources = this.threadActor.sources;
|
||||
|
||||
return sources.getSourceMap(source).then(prevMap => {
|
||||
if (prevMap) {
|
||||
// Compose the source maps
|
||||
this._oldSourceMapping = {
|
||||
url: source.sourceMapURL,
|
||||
map: prevMap
|
||||
};
|
||||
|
||||
prevMap = SourceMapGenerator.fromSourceMap(prevMap);
|
||||
prevMap.applySourceMap(sm, this.url);
|
||||
sm = SourceMapConsumer.fromSourceMap(prevMap);
|
||||
}
|
||||
|
||||
let sources = this.threadActor.sources;
|
||||
sources.clearSourceMapCache(source.sourceMapURL);
|
||||
sources.setSourceMapHard(source, null, sm);
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Handler for the "disablePrettyPrint" packet.
|
||||
*/
|
||||
onDisablePrettyPrint: function () {
|
||||
let source = this.generatedSource || this.source;
|
||||
let sources = this.threadActor.sources;
|
||||
let sm = sources.getSourceMap(source);
|
||||
|
||||
sources.clearSourceMapCache(source.sourceMapURL, { hard: true });
|
||||
|
||||
if (this._oldSourceMapping) {
|
||||
sources.setSourceMapHard(source,
|
||||
this._oldSourceMapping.url,
|
||||
this._oldSourceMapping.map);
|
||||
this._oldSourceMapping = null;
|
||||
}
|
||||
|
||||
this.threadActor.sources.disablePrettyPrint(this.url);
|
||||
return this.onSource();
|
||||
},
|
||||
|
||||
/**
|
||||
* Handler for the "blackbox" packet.
|
||||
*/
|
||||
onBlackBox: function (aRequest) {
|
||||
this.threadActor.sources.blackBox(this.url);
|
||||
let packet = {
|
||||
from: this.actorID
|
||||
};
|
||||
if (this.threadActor.state == "paused"
|
||||
&& this.threadActor.youngestFrame
|
||||
&& this.threadActor.youngestFrame.script.url == this.url) {
|
||||
packet.pausedInSource = true;
|
||||
}
|
||||
return packet;
|
||||
},
|
||||
|
||||
/**
|
||||
* Handler for the "unblackbox" packet.
|
||||
*/
|
||||
onUnblackBox: function (aRequest) {
|
||||
this.threadActor.sources.unblackBox(this.url);
|
||||
return {
|
||||
from: this.actorID
|
||||
};
|
||||
},
|
||||
|
||||
/**
|
||||
* Handle a request to set a breakpoint.
|
||||
*
|
||||
* @param JSON request
|
||||
* A JSON object representing the request.
|
||||
*
|
||||
* @returns Promise
|
||||
* A promise that resolves to a JSON object representing the
|
||||
* response.
|
||||
*/
|
||||
onSetBreakpoint: function (request) {
|
||||
if (this.threadActor.state !== "paused") {
|
||||
return {
|
||||
error: "wrongState",
|
||||
message: "Cannot set breakpoint while debuggee is running."
|
||||
};
|
||||
}
|
||||
|
||||
let { location: { line, column }, condition } = request;
|
||||
let location = new OriginalLocation(this, line, column);
|
||||
return this._getOrCreateBreakpointActor(
|
||||
location,
|
||||
condition
|
||||
).then((actor) => {
|
||||
let response = {
|
||||
actor: actor.actorID,
|
||||
isPending: actor.isPending
|
||||
};
|
||||
|
||||
let actualLocation = actor.originalLocation;
|
||||
if (!actualLocation.equals(location)) {
|
||||
response.actualLocation = actualLocation.toJSON();
|
||||
}
|
||||
|
||||
return response;
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Get or create a BreakpointActor for the given location in the original
|
||||
* source, and ensure it is set as a breakpoint handler on all scripts that
|
||||
* match the given location.
|
||||
*
|
||||
* @param OriginalLocation originalLocation
|
||||
* An OriginalLocation representing the location of the breakpoint in
|
||||
* the original source.
|
||||
* @param String condition
|
||||
* A string that is evaluated whenever the breakpoint is hit. If the
|
||||
* string evaluates to false, the breakpoint is ignored.
|
||||
*
|
||||
* @returns BreakpointActor
|
||||
* A BreakpointActor representing the breakpoint.
|
||||
*/
|
||||
_getOrCreateBreakpointActor: function (originalLocation, condition) {
|
||||
let actor = this.breakpointActorMap.getActor(originalLocation);
|
||||
if (!actor) {
|
||||
actor = new BreakpointActor(this.threadActor, originalLocation);
|
||||
this.threadActor.threadLifetimePool.addActor(actor);
|
||||
this.breakpointActorMap.setActor(originalLocation, actor);
|
||||
}
|
||||
|
||||
actor.condition = condition;
|
||||
|
||||
return this._setBreakpoint(actor);
|
||||
},
|
||||
|
||||
/*
|
||||
* Ensure the given BreakpointActor is set as a breakpoint handler on all
|
||||
* scripts that match its location in the original source.
|
||||
*
|
||||
* If there are no scripts that match the location of the BreakpointActor,
|
||||
* we slide its location to the next closest line (for line breakpoints) or
|
||||
* column (for column breakpoint) that does.
|
||||
*
|
||||
* If breakpoint sliding fails, then either there are no scripts that contain
|
||||
* any code for the given location, or they were all garbage collected before
|
||||
* the debugger started running. We cannot distinguish between these two
|
||||
* cases, so we insert the BreakpointActor in the BreakpointActorMap as
|
||||
* a pending breakpoint. Whenever a new script is introduced, this method is
|
||||
* called again for each pending breakpoint.
|
||||
*
|
||||
* @param BreakpointActor actor
|
||||
* The BreakpointActor to be set as a breakpoint handler.
|
||||
*
|
||||
* @returns A Promise that resolves to the given BreakpointActor.
|
||||
*/
|
||||
_setBreakpoint: function (actor) {
|
||||
const { originalLocation } = actor;
|
||||
const { originalLine, originalSourceActor } = originalLocation;
|
||||
|
||||
if (!this.isSourceMapped) {
|
||||
if (!this._setBreakpointAtGeneratedLocation(
|
||||
actor,
|
||||
GeneratedLocation.fromOriginalLocation(originalLocation)
|
||||
)) {
|
||||
const scripts = this.scripts.getScriptsBySourceActorAndLine(
|
||||
this,
|
||||
originalLine
|
||||
);
|
||||
|
||||
// Never do breakpoint sliding for column breakpoints.
|
||||
// Additionally, never do breakpoint sliding if no scripts
|
||||
// exist on this line.
|
||||
//
|
||||
// Sliding can go horribly wrong if we always try to find the
|
||||
// next line with valid entry points in the entire file.
|
||||
// Scripts may be completely GCed and we never knew they
|
||||
// existed, so we end up sliding through whole functions to
|
||||
// the user's bewilderment.
|
||||
//
|
||||
// We can slide reliably if any scripts exist, however, due
|
||||
// to how scripts are kept alive. A parent Debugger.Script
|
||||
// keeps all of its children alive, so as long as we have a
|
||||
// valid script, we can slide through it and know we won't
|
||||
// slide through any of its child scripts. Additionally, if a
|
||||
// script gets GCed, that means that all parents scripts are
|
||||
// GCed as well, and no scripts will exist on those lines
|
||||
// anymore. We will never slide through a GCed script.
|
||||
if (originalLocation.originalColumn || scripts.length === 0) {
|
||||
return promise.resolve(actor);
|
||||
}
|
||||
|
||||
// Find the script that spans the largest amount of code to
|
||||
// determine the bounds for sliding.
|
||||
const largestScript = scripts.reduce((largestScript, script) => {
|
||||
if (script.lineCount > largestScript.lineCount) {
|
||||
return script;
|
||||
}
|
||||
return largestScript;
|
||||
});
|
||||
const maxLine = largestScript.startLine + largestScript.lineCount - 1;
|
||||
|
||||
let actualLine = originalLine;
|
||||
for (; actualLine <= maxLine; actualLine++) {
|
||||
const loc = new GeneratedLocation(this, actualLine);
|
||||
if (this._setBreakpointAtGeneratedLocation(actor, loc)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// The above loop should never complete. We only did breakpoint sliding
|
||||
// because we found scripts on the line we started from,
|
||||
// which means there must be valid entry points somewhere
|
||||
// within those scripts.
|
||||
assert(
|
||||
actualLine <= maxLine,
|
||||
"Could not find any entry points to set a breakpoint on, " +
|
||||
"even though I was told a script existed on the line I started " +
|
||||
"the search with."
|
||||
);
|
||||
|
||||
// Update the actor to use the new location (reusing a
|
||||
// previous breakpoint if it already exists on that line).
|
||||
const actualLocation = new OriginalLocation(originalSourceActor, actualLine);
|
||||
const existingActor = this.breakpointActorMap.getActor(actualLocation);
|
||||
this.breakpointActorMap.deleteActor(originalLocation);
|
||||
if (existingActor) {
|
||||
actor.delete();
|
||||
actor = existingActor;
|
||||
} else {
|
||||
actor.originalLocation = actualLocation;
|
||||
this.breakpointActorMap.setActor(actualLocation, actor);
|
||||
}
|
||||
}
|
||||
|
||||
return promise.resolve(actor);
|
||||
} else {
|
||||
return this.sources.getAllGeneratedLocations(originalLocation).then((generatedLocations) => {
|
||||
this._setBreakpointAtAllGeneratedLocations(
|
||||
actor,
|
||||
generatedLocations
|
||||
);
|
||||
|
||||
return actor;
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
_setBreakpointAtAllGeneratedLocations: function (actor, generatedLocations) {
|
||||
let success = false;
|
||||
for (let generatedLocation of generatedLocations) {
|
||||
if (this._setBreakpointAtGeneratedLocation(
|
||||
actor,
|
||||
generatedLocation
|
||||
)) {
|
||||
success = true;
|
||||
}
|
||||
}
|
||||
return success;
|
||||
},
|
||||
|
||||
/*
|
||||
* Ensure the given BreakpointActor is set as breakpoint handler on all
|
||||
* scripts that match the given location in the generated source.
|
||||
*
|
||||
* @param BreakpointActor actor
|
||||
* The BreakpointActor to be set as a breakpoint handler.
|
||||
* @param GeneratedLocation generatedLocation
|
||||
* A GeneratedLocation representing the location in the generated
|
||||
* source for which the given BreakpointActor is to be set as a
|
||||
* breakpoint handler.
|
||||
*
|
||||
* @returns A Boolean that is true if the BreakpointActor was set as a
|
||||
* breakpoint handler on at least one script, and false otherwise.
|
||||
*/
|
||||
_setBreakpointAtGeneratedLocation: function (actor, generatedLocation) {
|
||||
let {
|
||||
generatedSourceActor,
|
||||
generatedLine,
|
||||
generatedColumn,
|
||||
generatedLastColumn
|
||||
} = generatedLocation;
|
||||
|
||||
// Find all scripts that match the given source actor and line number.
|
||||
let scripts = this.scripts.getScriptsBySourceActorAndLine(
|
||||
generatedSourceActor,
|
||||
generatedLine
|
||||
);
|
||||
|
||||
scripts = scripts.filter((script) => !actor.hasScript(script));
|
||||
|
||||
// Find all entry points that correspond to the given location.
|
||||
let entryPoints = [];
|
||||
if (generatedColumn === undefined) {
|
||||
// This is a line breakpoint, so we are interested in all offsets
|
||||
// that correspond to the given line number.
|
||||
for (let script of scripts) {
|
||||
let offsets = script.getLineOffsets(generatedLine);
|
||||
if (offsets.length > 0) {
|
||||
entryPoints.push({ script, offsets });
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// This is a column breakpoint, so we are interested in all column
|
||||
// offsets that correspond to the given line *and* column number.
|
||||
for (let script of scripts) {
|
||||
let columnToOffsetMap = script.getAllColumnOffsets()
|
||||
.filter(({ lineNumber }) => {
|
||||
return lineNumber === generatedLine;
|
||||
});
|
||||
for (let { columnNumber: column, offset } of columnToOffsetMap) {
|
||||
if (column >= generatedColumn && column <= generatedLastColumn) {
|
||||
entryPoints.push({ script, offsets: [offset] });
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (entryPoints.length === 0) {
|
||||
return false;
|
||||
}
|
||||
setBreakpointAtEntryPoints(actor, entryPoints);
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
SourceActor.prototype.requestTypes = {
|
||||
"source": SourceActor.prototype.onSource,
|
||||
"blackbox": SourceActor.prototype.onBlackBox,
|
||||
"unblackbox": SourceActor.prototype.onUnblackBox,
|
||||
"prettyPrint": SourceActor.prototype.onPrettyPrint,
|
||||
"disablePrettyPrint": SourceActor.prototype.onDisablePrettyPrint,
|
||||
"getExecutableLines": SourceActor.prototype.getExecutableLines,
|
||||
"setBreakpoint": SourceActor.prototype.onSetBreakpoint
|
||||
};
|
||||
|
||||
exports.SourceActor = SourceActor;
|
||||
|
||||
/**
|
||||
* Creates a pause-scoped actor for the specified object.
|
||||
* @see ObjectActor
|
||||
@ -3107,51 +2292,6 @@ reportError = function(aError, aPrefix="") {
|
||||
dumpn(msg);
|
||||
}
|
||||
|
||||
function isEvalSource(source) {
|
||||
let introType = source.introductionType;
|
||||
// These are all the sources that are essentially eval-ed (either
|
||||
// by calling eval or passing a string to one of these functions).
|
||||
return (introType === 'eval' ||
|
||||
introType === 'Function' ||
|
||||
introType === 'eventHandler' ||
|
||||
introType === 'setTimeout' ||
|
||||
introType === 'setInterval');
|
||||
}
|
||||
exports.isEvalSource = isEvalSource;
|
||||
|
||||
function getSourceURL(source, window) {
|
||||
if (isEvalSource(source)) {
|
||||
// Eval sources have no urls, but they might have a `displayURL`
|
||||
// created with the sourceURL pragma. If the introduction script
|
||||
// is a non-eval script, generate an full absolute URL relative to it.
|
||||
|
||||
if (source.displayURL && source.introductionScript &&
|
||||
!isEvalSource(source.introductionScript.source)) {
|
||||
|
||||
if (source.introductionScript.source.url === 'debugger eval code') {
|
||||
if (window) {
|
||||
// If this is a named eval script created from the console, make it
|
||||
// relative to the current page. window is only available
|
||||
// when we care about this.
|
||||
return joinURI(window.location.href, source.displayURL);
|
||||
}
|
||||
}
|
||||
else {
|
||||
return joinURI(dirname(source.introductionScript.source.url),
|
||||
source.displayURL);
|
||||
}
|
||||
}
|
||||
|
||||
return source.displayURL;
|
||||
}
|
||||
else if (source.url === 'debugger eval code') {
|
||||
// Treat code evaluated by the console as unnamed eval scripts
|
||||
return null;
|
||||
}
|
||||
return source.url;
|
||||
}
|
||||
exports.getSourceURL = getSourceURL;
|
||||
|
||||
/**
|
||||
* Find the scripts which contain offsets that are an entry point to the given
|
||||
* line.
|
||||
@ -3176,24 +2316,6 @@ function findEntryPointsForLine(scripts, line) {
|
||||
return entryPoints;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set breakpoints on all the given entry points with the given
|
||||
* BreakpointActor as the handler.
|
||||
*
|
||||
* @param BreakpointActor actor
|
||||
* The actor handling the breakpoint hits.
|
||||
* @param Array entryPoints
|
||||
* An array of objects of the form `{ script, offsets }`.
|
||||
*/
|
||||
function setBreakpointAtEntryPoints(actor, entryPoints) {
|
||||
for (let { script, offsets } of entryPoints) {
|
||||
actor.addScript(script);
|
||||
for (let offset of offsets) {
|
||||
script.setBreakpoint(offset, actor);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Unwrap a global that is wrapped in a |Debugger.Object|, or if the global has
|
||||
* become a dead object, return |undefined|.
|
||||
|
882
devtools/server/actors/source.js
Normal file
882
devtools/server/actors/source.js
Normal file
@ -0,0 +1,882 @@
|
||||
/* -*- 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 { Cc, Ci } = require("chrome");
|
||||
const { BreakpointActor, setBreakpointAtEntryPoints } = require("devtools/server/actors/breakpoint");
|
||||
const { OriginalLocation, GeneratedLocation } = require("devtools/server/actors/common");
|
||||
const { createValueGrip } = require("devtools/server/actors/object");
|
||||
const DevToolsUtils = require("devtools/shared/DevToolsUtils");
|
||||
const { assert, fetch } = DevToolsUtils;
|
||||
const { dirname, joinURI } = require("devtools/shared/path");
|
||||
const promise = require("promise");
|
||||
const { defer, resolve, reject, all } = promise;
|
||||
|
||||
loader.lazyRequireGetter(this, "SourceMapConsumer", "source-map", true);
|
||||
loader.lazyRequireGetter(this, "SourceMapGenerator", "source-map", true);
|
||||
loader.lazyRequireGetter(this, "mapURIToAddonID", "devtools/server/actors/utils/map-uri-to-addon-id");
|
||||
|
||||
function isEvalSource(source) {
|
||||
let introType = source.introductionType;
|
||||
// These are all the sources that are essentially eval-ed (either
|
||||
// by calling eval or passing a string to one of these functions).
|
||||
return (introType === 'eval' ||
|
||||
introType === 'Function' ||
|
||||
introType === 'eventHandler' ||
|
||||
introType === 'setTimeout' ||
|
||||
introType === 'setInterval');
|
||||
}
|
||||
|
||||
exports.isEvalSource = isEvalSource;
|
||||
|
||||
function getSourceURL(source, window) {
|
||||
if (isEvalSource(source)) {
|
||||
// Eval sources have no urls, but they might have a `displayURL`
|
||||
// created with the sourceURL pragma. If the introduction script
|
||||
// is a non-eval script, generate an full absolute URL relative to it.
|
||||
|
||||
if (source.displayURL && source.introductionScript &&
|
||||
!isEvalSource(source.introductionScript.source)) {
|
||||
|
||||
if (source.introductionScript.source.url === 'debugger eval code') {
|
||||
if (window) {
|
||||
// If this is a named eval script created from the console, make it
|
||||
// relative to the current page. window is only available
|
||||
// when we care about this.
|
||||
return joinURI(window.location.href, source.displayURL);
|
||||
}
|
||||
}
|
||||
else {
|
||||
return joinURI(dirname(source.introductionScript.source.url),
|
||||
source.displayURL);
|
||||
}
|
||||
}
|
||||
|
||||
return source.displayURL;
|
||||
}
|
||||
else if (source.url === 'debugger eval code') {
|
||||
// Treat code evaluated by the console as unnamed eval scripts
|
||||
return null;
|
||||
}
|
||||
return source.url;
|
||||
}
|
||||
|
||||
exports.getSourceURL = getSourceURL;
|
||||
|
||||
/**
|
||||
* Resolve a URI back to physical file.
|
||||
*
|
||||
* Of course, this works only for URIs pointing to local resources.
|
||||
*
|
||||
* @param aURI
|
||||
* URI to resolve
|
||||
* @return
|
||||
* resolved nsIURI
|
||||
*/
|
||||
function resolveURIToLocalPath(aURI) {
|
||||
let resolved;
|
||||
switch (aURI.scheme) {
|
||||
case "jar":
|
||||
case "file":
|
||||
return aURI;
|
||||
|
||||
case "chrome":
|
||||
resolved = Cc["@mozilla.org/chrome/chrome-registry;1"].
|
||||
getService(Ci.nsIChromeRegistry).convertChromeURL(aURI);
|
||||
return resolveURIToLocalPath(resolved);
|
||||
|
||||
case "resource":
|
||||
resolved = Cc["@mozilla.org/network/protocol;1?name=resource"].
|
||||
getService(Ci.nsIResProtocolHandler).resolveURI(aURI);
|
||||
aURI = Services.io.newURI(resolved, null, null);
|
||||
return resolveURIToLocalPath(aURI);
|
||||
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A SourceActor provides information about the source of a script. There
|
||||
* are two kinds of source actors: ones that represent real source objects,
|
||||
* and ones that represent non-existant "original" sources when the real
|
||||
* sources are sourcemapped. When a source is sourcemapped, actors are
|
||||
* created for both the "generated" and "original" sources, and the client will
|
||||
* only see the original sources. We separate these because there isn't
|
||||
* a 1:1 mapping of generated to original sources; one generated source
|
||||
* may represent N original sources, so we need to create N + 1 separate
|
||||
* actors.
|
||||
*
|
||||
* There are 4 different scenarios for sources that you should
|
||||
* understand:
|
||||
*
|
||||
* - A single non-sourcemapped source that is not inlined in HTML
|
||||
* (separate JS file, eval'ed code, etc)
|
||||
* - A single sourcemapped source which creates N original sources
|
||||
* - An HTML page with multiple inline scripts, which are distinct
|
||||
* sources, but should be represented as a single source
|
||||
* - A pretty-printed source (which may or may not be an original
|
||||
* sourcemapped source), which generates a sourcemap for itself
|
||||
*
|
||||
* The complexity of `SourceActor` and `ThreadSources` are to handle
|
||||
* all of thise cases and hopefully internalize the complexities.
|
||||
*
|
||||
* @param Debugger.Source source
|
||||
* The source object we are representing.
|
||||
* @param ThreadActor thread
|
||||
* The current thread actor.
|
||||
* @param String originalUrl
|
||||
* Optional. For sourcemapped urls, the original url this is representing.
|
||||
* @param Debugger.Source generatedSource
|
||||
* Optional, passed in when aSourceMap is also passed in. The generated
|
||||
* source object that introduced this source.
|
||||
* @param String contentType
|
||||
* Optional. The content type of this source, if immediately available.
|
||||
*/
|
||||
function SourceActor({ source, thread, originalUrl, generatedSource,
|
||||
isInlineSource, contentType }) {
|
||||
this._threadActor = thread;
|
||||
this._originalUrl = originalUrl;
|
||||
this._source = source;
|
||||
this._generatedSource = generatedSource;
|
||||
this._contentType = contentType;
|
||||
this._isInlineSource = isInlineSource;
|
||||
|
||||
this.onSource = this.onSource.bind(this);
|
||||
this._invertSourceMap = this._invertSourceMap.bind(this);
|
||||
this._encodeAndSetSourceMapURL = this._encodeAndSetSourceMapURL.bind(this);
|
||||
this._getSourceText = this._getSourceText.bind(this);
|
||||
|
||||
this._mapSourceToAddon();
|
||||
|
||||
if (this.threadActor.sources.isPrettyPrinted(this.url)) {
|
||||
this._init = this.onPrettyPrint({
|
||||
indent: this.threadActor.sources.prettyPrintIndent(this.url)
|
||||
}).then(null, error => {
|
||||
DevToolsUtils.reportException("SourceActor", error);
|
||||
});
|
||||
} else {
|
||||
this._init = null;
|
||||
}
|
||||
}
|
||||
|
||||
SourceActor.prototype = {
|
||||
constructor: SourceActor,
|
||||
actorPrefix: "source",
|
||||
|
||||
_oldSourceMap: null,
|
||||
_init: null,
|
||||
_addonID: null,
|
||||
_addonPath: null,
|
||||
|
||||
get isSourceMapped() {
|
||||
return !this.isInlineSource && (
|
||||
this._originalURL || this._generatedSource ||
|
||||
this.threadActor.sources.isPrettyPrinted(this.url)
|
||||
);
|
||||
},
|
||||
|
||||
get isInlineSource() {
|
||||
return this._isInlineSource;
|
||||
},
|
||||
|
||||
get threadActor() { return this._threadActor; },
|
||||
get sources() { return this._threadActor.sources; },
|
||||
get dbg() { return this.threadActor.dbg; },
|
||||
get scripts() { return this.threadActor.scripts; },
|
||||
get source() { return this._source; },
|
||||
get generatedSource() { return this._generatedSource; },
|
||||
get breakpointActorMap() { return this.threadActor.breakpointActorMap; },
|
||||
get url() {
|
||||
if (this.source) {
|
||||
return getSourceURL(this.source, this.threadActor._parent.window);
|
||||
}
|
||||
return this._originalUrl;
|
||||
},
|
||||
get addonID() { return this._addonID; },
|
||||
get addonPath() { return this._addonPath; },
|
||||
|
||||
get prettyPrintWorker() {
|
||||
return this.threadActor.prettyPrintWorker;
|
||||
},
|
||||
|
||||
form: function () {
|
||||
let source = this.source || this.generatedSource;
|
||||
// This might not have a source or a generatedSource because we
|
||||
// treat HTML pages with inline scripts as a special SourceActor
|
||||
// that doesn't have either
|
||||
let introductionUrl = null;
|
||||
if (source && source.introductionScript) {
|
||||
introductionUrl = source.introductionScript.source.url;
|
||||
}
|
||||
|
||||
return {
|
||||
actor: this.actorID,
|
||||
url: this.url ? this.url.split(" -> ").pop() : null,
|
||||
addonID: this._addonID,
|
||||
addonPath: this._addonPath,
|
||||
isBlackBoxed: this.threadActor.sources.isBlackBoxed(this.url),
|
||||
isPrettyPrinted: this.threadActor.sources.isPrettyPrinted(this.url),
|
||||
introductionUrl: introductionUrl ? introductionUrl.split(" -> ").pop() : null,
|
||||
introductionType: source ? source.introductionType : null
|
||||
};
|
||||
},
|
||||
|
||||
disconnect: function () {
|
||||
if (this.registeredPool && this.registeredPool.sourceActors) {
|
||||
delete this.registeredPool.sourceActors[this.actorID];
|
||||
}
|
||||
},
|
||||
|
||||
_mapSourceToAddon: function() {
|
||||
try {
|
||||
var nsuri = Services.io.newURI(this.url.split(" -> ").pop(), null, null);
|
||||
}
|
||||
catch (e) {
|
||||
// We can't do anything with an invalid URI
|
||||
return;
|
||||
}
|
||||
|
||||
let localURI = resolveURIToLocalPath(nsuri);
|
||||
|
||||
let id = {};
|
||||
if (localURI && mapURIToAddonID(localURI, id)) {
|
||||
this._addonID = id.value;
|
||||
|
||||
if (localURI instanceof Ci.nsIJARURI) {
|
||||
// The path in the add-on is easy for jar: uris
|
||||
this._addonPath = localURI.JAREntry;
|
||||
}
|
||||
else if (localURI instanceof Ci.nsIFileURL) {
|
||||
// For file: uris walk up to find the last directory that is part of the
|
||||
// add-on
|
||||
let target = localURI.file;
|
||||
let path = target.leafName;
|
||||
|
||||
// We can assume that the directory containing the source file is part
|
||||
// of the add-on
|
||||
let root = target.parent;
|
||||
let file = root.parent;
|
||||
while (file && mapURIToAddonID(Services.io.newFileURI(file), {})) {
|
||||
path = root.leafName + "/" + path;
|
||||
root = file;
|
||||
file = file.parent;
|
||||
}
|
||||
|
||||
if (!file) {
|
||||
const error = new Error("Could not find the root of the add-on for " + this.url);
|
||||
DevToolsUtils.reportException("SourceActor.prototype._mapSourceToAddon", error)
|
||||
return;
|
||||
}
|
||||
|
||||
this._addonPath = path;
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
_reportLoadSourceError: function (error, map=null) {
|
||||
try {
|
||||
DevToolsUtils.reportException("SourceActor", error);
|
||||
|
||||
JSON.stringify(this.form(), null, 4).split(/\n/g)
|
||||
.forEach(line => console.error("\t", line));
|
||||
|
||||
if (!map) {
|
||||
return;
|
||||
}
|
||||
|
||||
console.error("\t", "source map's sourceRoot =", map.sourceRoot);
|
||||
|
||||
console.error("\t", "source map's sources =");
|
||||
map.sources.forEach(s => {
|
||||
let hasSourceContent = map.sourceContentFor(s, true);
|
||||
console.error("\t\t", s, "\t",
|
||||
hasSourceContent ? "has source content" : "no source content");
|
||||
});
|
||||
|
||||
console.error("\t", "source map's sourcesContent =");
|
||||
map.sourcesContent.forEach(c => {
|
||||
if (c.length > 80) {
|
||||
c = c.slice(0, 77) + "...";
|
||||
}
|
||||
c = c.replace(/\n/g, "\\n");
|
||||
console.error("\t\t", c);
|
||||
});
|
||||
} catch (e) { }
|
||||
},
|
||||
|
||||
_getSourceText: function () {
|
||||
let toResolvedContent = t => ({
|
||||
content: t,
|
||||
contentType: this._contentType
|
||||
});
|
||||
|
||||
let genSource = this.generatedSource || this.source;
|
||||
return this.threadActor.sources.fetchSourceMap(genSource).then(map => {
|
||||
if (map) {
|
||||
try {
|
||||
let sourceContent = map.sourceContentFor(this.url);
|
||||
if (sourceContent) {
|
||||
return toResolvedContent(sourceContent);
|
||||
}
|
||||
} catch (error) {
|
||||
this._reportLoadSourceError(error, map);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
// Use `source.text` if it exists, is not the "no source"
|
||||
// string, and the content type of the source is JavaScript. It
|
||||
// will be "no source" if the Debugger API wasn't able to load
|
||||
// the source because sources were discarded
|
||||
// (javascript.options.discardSystemSource == true). Re-fetch
|
||||
// non-JS sources to get the contentType from the headers.
|
||||
if (this.source &&
|
||||
this.source.text !== "[no source]" &&
|
||||
this._contentType &&
|
||||
this._contentType.indexOf('javascript') !== -1) {
|
||||
return toResolvedContent(this.source.text);
|
||||
}
|
||||
else {
|
||||
// Only load the HTML page source from cache (which exists when
|
||||
// there are inline sources). Otherwise, we can't trust the
|
||||
// cache because we are most likely here because we are
|
||||
// fetching the original text for sourcemapped code, and the
|
||||
// page hasn't requested it before (if it has, it was a
|
||||
// previous debugging session).
|
||||
let sourceFetched = fetch(this.url, { loadFromCache: this.isInlineSource });
|
||||
|
||||
// Record the contentType we just learned during fetching
|
||||
return sourceFetched
|
||||
.then(result => {
|
||||
this._contentType = result.contentType;
|
||||
return result;
|
||||
}, error => {
|
||||
this._reportLoadSourceError(error, map);
|
||||
throw error;
|
||||
});
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Get all executable lines from the current source
|
||||
* @return Array - Executable lines of the current script
|
||||
**/
|
||||
getExecutableLines: function () {
|
||||
// Check if the original source is source mapped
|
||||
let packet = {
|
||||
from: this.actorID
|
||||
};
|
||||
|
||||
function sortLines(lines) {
|
||||
// Converting the Set into an array
|
||||
lines = [...lines];
|
||||
lines.sort((a, b) => {
|
||||
return a - b;
|
||||
});
|
||||
return lines;
|
||||
}
|
||||
|
||||
if (this.generatedSource) {
|
||||
return this.threadActor.sources.getSourceMap(this.generatedSource).then(sm => {
|
||||
let lines = new Set();
|
||||
|
||||
// Position of executable lines in the generated source
|
||||
let offsets = this.getExecutableOffsets(this.generatedSource, false);
|
||||
for (let offset of offsets) {
|
||||
let {line, source: sourceUrl} = sm.originalPositionFor({
|
||||
line: offset.lineNumber,
|
||||
column: offset.columnNumber
|
||||
});
|
||||
|
||||
if (sourceUrl === this.url) {
|
||||
lines.add(line);
|
||||
}
|
||||
}
|
||||
|
||||
packet.lines = sortLines(lines);
|
||||
return packet;
|
||||
});
|
||||
}
|
||||
|
||||
let lines = this.getExecutableOffsets(this.source, true);
|
||||
packet.lines = sortLines(lines);
|
||||
return packet;
|
||||
},
|
||||
|
||||
/**
|
||||
* Extract all executable offsets from the given script
|
||||
* @param String url - extract offsets of the script with this url
|
||||
* @param Boolean onlyLine - will return only the line number
|
||||
* @return Set - Executable offsets/lines of the script
|
||||
**/
|
||||
getExecutableOffsets: function (source, onlyLine) {
|
||||
let offsets = new Set();
|
||||
for (let s of this.threadActor.scripts.getScriptsBySource(source)) {
|
||||
for (let offset of s.getAllColumnOffsets()) {
|
||||
offsets.add(onlyLine ? offset.lineNumber : offset);
|
||||
}
|
||||
}
|
||||
|
||||
return offsets;
|
||||
},
|
||||
|
||||
/**
|
||||
* Handler for the "source" packet.
|
||||
*/
|
||||
onSource: function () {
|
||||
return resolve(this._init)
|
||||
.then(this._getSourceText)
|
||||
.then(({ content, contentType }) => {
|
||||
return {
|
||||
from: this.actorID,
|
||||
source: createValueGrip(content, this.threadActor.threadLifetimePool,
|
||||
this.threadActor.objectGrip),
|
||||
contentType: contentType
|
||||
};
|
||||
})
|
||||
.then(null, aError => {
|
||||
reportError(aError, "Got an exception during SA_onSource: ");
|
||||
return {
|
||||
"from": this.actorID,
|
||||
"error": this.url,
|
||||
"message": "Could not load the source for " + this.url + ".\n"
|
||||
+ DevToolsUtils.safeErrorString(aError)
|
||||
};
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Handler for the "prettyPrint" packet.
|
||||
*/
|
||||
onPrettyPrint: function ({ indent }) {
|
||||
this.threadActor.sources.prettyPrint(this.url, indent);
|
||||
return this._getSourceText()
|
||||
.then(this._sendToPrettyPrintWorker(indent))
|
||||
.then(this._invertSourceMap)
|
||||
.then(this._encodeAndSetSourceMapURL)
|
||||
.then(() => {
|
||||
// We need to reset `_init` now because we have already done the work of
|
||||
// pretty printing, and don't want onSource to wait forever for
|
||||
// initialization to complete.
|
||||
this._init = null;
|
||||
})
|
||||
.then(this.onSource)
|
||||
.then(null, error => {
|
||||
this.onDisablePrettyPrint();
|
||||
return {
|
||||
from: this.actorID,
|
||||
error: "prettyPrintError",
|
||||
message: DevToolsUtils.safeErrorString(error)
|
||||
};
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Return a function that sends a request to the pretty print worker, waits on
|
||||
* the worker's response, and then returns the pretty printed code.
|
||||
*
|
||||
* @param Number aIndent
|
||||
* The number of spaces to indent by the code by, when we send the
|
||||
* request to the pretty print worker.
|
||||
* @returns Function
|
||||
* Returns a function which takes an AST, and returns a promise that
|
||||
* is resolved with `{ code, mappings }` where `code` is the pretty
|
||||
* printed code, and `mappings` is an array of source mappings.
|
||||
*/
|
||||
_sendToPrettyPrintWorker: function (aIndent) {
|
||||
return ({ content }) => {
|
||||
return this.prettyPrintWorker.performTask("pretty-print", {
|
||||
url: this.url,
|
||||
indent: aIndent,
|
||||
source: content
|
||||
})
|
||||
};
|
||||
},
|
||||
|
||||
/**
|
||||
* Invert a source map. So if a source map maps from a to b, return a new
|
||||
* source map from b to a. We need to do this because the source map we get
|
||||
* from _generatePrettyCodeAndMap goes the opposite way we want it to for
|
||||
* debugging.
|
||||
*
|
||||
* Note that the source map is modified in place.
|
||||
*/
|
||||
_invertSourceMap: function ({ code, mappings }) {
|
||||
const generator = new SourceMapGenerator({ file: this.url });
|
||||
return DevToolsUtils.yieldingEach(mappings._array, m => {
|
||||
let mapping = {
|
||||
generated: {
|
||||
line: m.originalLine,
|
||||
column: m.originalColumn
|
||||
}
|
||||
};
|
||||
if (m.source) {
|
||||
mapping.source = m.source;
|
||||
mapping.original = {
|
||||
line: m.generatedLine,
|
||||
column: m.generatedColumn
|
||||
};
|
||||
mapping.name = m.name;
|
||||
}
|
||||
generator.addMapping(mapping);
|
||||
}).then(() => {
|
||||
generator.setSourceContent(this.url, code);
|
||||
let consumer = SourceMapConsumer.fromSourceMap(generator);
|
||||
|
||||
return {
|
||||
code: code,
|
||||
map: consumer
|
||||
};
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Save the source map back to our thread's ThreadSources object so that
|
||||
* stepping, breakpoints, debugger statements, etc can use it. If we are
|
||||
* pretty printing a source mapped source, we need to compose the existing
|
||||
* source map with our new one.
|
||||
*/
|
||||
_encodeAndSetSourceMapURL: function ({ map: sm }) {
|
||||
let source = this.generatedSource || this.source;
|
||||
let sources = this.threadActor.sources;
|
||||
|
||||
return sources.getSourceMap(source).then(prevMap => {
|
||||
if (prevMap) {
|
||||
// Compose the source maps
|
||||
this._oldSourceMapping = {
|
||||
url: source.sourceMapURL,
|
||||
map: prevMap
|
||||
};
|
||||
|
||||
prevMap = SourceMapGenerator.fromSourceMap(prevMap);
|
||||
prevMap.applySourceMap(sm, this.url);
|
||||
sm = SourceMapConsumer.fromSourceMap(prevMap);
|
||||
}
|
||||
|
||||
let sources = this.threadActor.sources;
|
||||
sources.clearSourceMapCache(source.sourceMapURL);
|
||||
sources.setSourceMapHard(source, null, sm);
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Handler for the "disablePrettyPrint" packet.
|
||||
*/
|
||||
onDisablePrettyPrint: function () {
|
||||
let source = this.generatedSource || this.source;
|
||||
let sources = this.threadActor.sources;
|
||||
let sm = sources.getSourceMap(source);
|
||||
|
||||
sources.clearSourceMapCache(source.sourceMapURL, { hard: true });
|
||||
|
||||
if (this._oldSourceMapping) {
|
||||
sources.setSourceMapHard(source,
|
||||
this._oldSourceMapping.url,
|
||||
this._oldSourceMapping.map);
|
||||
this._oldSourceMapping = null;
|
||||
}
|
||||
|
||||
this.threadActor.sources.disablePrettyPrint(this.url);
|
||||
return this.onSource();
|
||||
},
|
||||
|
||||
/**
|
||||
* Handler for the "blackbox" packet.
|
||||
*/
|
||||
onBlackBox: function (aRequest) {
|
||||
this.threadActor.sources.blackBox(this.url);
|
||||
let packet = {
|
||||
from: this.actorID
|
||||
};
|
||||
if (this.threadActor.state == "paused"
|
||||
&& this.threadActor.youngestFrame
|
||||
&& this.threadActor.youngestFrame.script.url == this.url) {
|
||||
packet.pausedInSource = true;
|
||||
}
|
||||
return packet;
|
||||
},
|
||||
|
||||
/**
|
||||
* Handler for the "unblackbox" packet.
|
||||
*/
|
||||
onUnblackBox: function (aRequest) {
|
||||
this.threadActor.sources.unblackBox(this.url);
|
||||
return {
|
||||
from: this.actorID
|
||||
};
|
||||
},
|
||||
|
||||
/**
|
||||
* Handle a request to set a breakpoint.
|
||||
*
|
||||
* @param JSON request
|
||||
* A JSON object representing the request.
|
||||
*
|
||||
* @returns Promise
|
||||
* A promise that resolves to a JSON object representing the
|
||||
* response.
|
||||
*/
|
||||
onSetBreakpoint: function (request) {
|
||||
if (this.threadActor.state !== "paused") {
|
||||
return {
|
||||
error: "wrongState",
|
||||
message: "Cannot set breakpoint while debuggee is running."
|
||||
};
|
||||
}
|
||||
|
||||
let { location: { line, column }, condition } = request;
|
||||
let location = new OriginalLocation(this, line, column);
|
||||
return this._getOrCreateBreakpointActor(
|
||||
location,
|
||||
condition
|
||||
).then((actor) => {
|
||||
let response = {
|
||||
actor: actor.actorID,
|
||||
isPending: actor.isPending
|
||||
};
|
||||
|
||||
let actualLocation = actor.originalLocation;
|
||||
if (!actualLocation.equals(location)) {
|
||||
response.actualLocation = actualLocation.toJSON();
|
||||
}
|
||||
|
||||
return response;
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Get or create a BreakpointActor for the given location in the original
|
||||
* source, and ensure it is set as a breakpoint handler on all scripts that
|
||||
* match the given location.
|
||||
*
|
||||
* @param OriginalLocation originalLocation
|
||||
* An OriginalLocation representing the location of the breakpoint in
|
||||
* the original source.
|
||||
* @param String condition
|
||||
* A string that is evaluated whenever the breakpoint is hit. If the
|
||||
* string evaluates to false, the breakpoint is ignored.
|
||||
*
|
||||
* @returns BreakpointActor
|
||||
* A BreakpointActor representing the breakpoint.
|
||||
*/
|
||||
_getOrCreateBreakpointActor: function (originalLocation, condition) {
|
||||
let actor = this.breakpointActorMap.getActor(originalLocation);
|
||||
if (!actor) {
|
||||
actor = new BreakpointActor(this.threadActor, originalLocation);
|
||||
this.threadActor.threadLifetimePool.addActor(actor);
|
||||
this.breakpointActorMap.setActor(originalLocation, actor);
|
||||
}
|
||||
|
||||
actor.condition = condition;
|
||||
|
||||
return this._setBreakpoint(actor);
|
||||
},
|
||||
|
||||
/*
|
||||
* Ensure the given BreakpointActor is set as a breakpoint handler on all
|
||||
* scripts that match its location in the original source.
|
||||
*
|
||||
* If there are no scripts that match the location of the BreakpointActor,
|
||||
* we slide its location to the next closest line (for line breakpoints) or
|
||||
* column (for column breakpoint) that does.
|
||||
*
|
||||
* If breakpoint sliding fails, then either there are no scripts that contain
|
||||
* any code for the given location, or they were all garbage collected before
|
||||
* the debugger started running. We cannot distinguish between these two
|
||||
* cases, so we insert the BreakpointActor in the BreakpointActorMap as
|
||||
* a pending breakpoint. Whenever a new script is introduced, this method is
|
||||
* called again for each pending breakpoint.
|
||||
*
|
||||
* @param BreakpointActor actor
|
||||
* The BreakpointActor to be set as a breakpoint handler.
|
||||
*
|
||||
* @returns A Promise that resolves to the given BreakpointActor.
|
||||
*/
|
||||
_setBreakpoint: function (actor) {
|
||||
const { originalLocation } = actor;
|
||||
const { originalLine, originalSourceActor } = originalLocation;
|
||||
|
||||
if (!this.isSourceMapped) {
|
||||
if (!this._setBreakpointAtGeneratedLocation(
|
||||
actor,
|
||||
GeneratedLocation.fromOriginalLocation(originalLocation)
|
||||
)) {
|
||||
const scripts = this.scripts.getScriptsBySourceActorAndLine(
|
||||
this,
|
||||
originalLine
|
||||
);
|
||||
|
||||
// Never do breakpoint sliding for column breakpoints.
|
||||
// Additionally, never do breakpoint sliding if no scripts
|
||||
// exist on this line.
|
||||
//
|
||||
// Sliding can go horribly wrong if we always try to find the
|
||||
// next line with valid entry points in the entire file.
|
||||
// Scripts may be completely GCed and we never knew they
|
||||
// existed, so we end up sliding through whole functions to
|
||||
// the user's bewilderment.
|
||||
//
|
||||
// We can slide reliably if any scripts exist, however, due
|
||||
// to how scripts are kept alive. A parent Debugger.Script
|
||||
// keeps all of its children alive, so as long as we have a
|
||||
// valid script, we can slide through it and know we won't
|
||||
// slide through any of its child scripts. Additionally, if a
|
||||
// script gets GCed, that means that all parents scripts are
|
||||
// GCed as well, and no scripts will exist on those lines
|
||||
// anymore. We will never slide through a GCed script.
|
||||
if (originalLocation.originalColumn || scripts.length === 0) {
|
||||
return promise.resolve(actor);
|
||||
}
|
||||
|
||||
// Find the script that spans the largest amount of code to
|
||||
// determine the bounds for sliding.
|
||||
const largestScript = scripts.reduce((largestScript, script) => {
|
||||
if (script.lineCount > largestScript.lineCount) {
|
||||
return script;
|
||||
}
|
||||
return largestScript;
|
||||
});
|
||||
const maxLine = largestScript.startLine + largestScript.lineCount - 1;
|
||||
|
||||
let actualLine = originalLine;
|
||||
for (; actualLine <= maxLine; actualLine++) {
|
||||
const loc = new GeneratedLocation(this, actualLine);
|
||||
if (this._setBreakpointAtGeneratedLocation(actor, loc)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// The above loop should never complete. We only did breakpoint sliding
|
||||
// because we found scripts on the line we started from,
|
||||
// which means there must be valid entry points somewhere
|
||||
// within those scripts.
|
||||
assert(
|
||||
actualLine <= maxLine,
|
||||
"Could not find any entry points to set a breakpoint on, " +
|
||||
"even though I was told a script existed on the line I started " +
|
||||
"the search with."
|
||||
);
|
||||
|
||||
// Update the actor to use the new location (reusing a
|
||||
// previous breakpoint if it already exists on that line).
|
||||
const actualLocation = new OriginalLocation(originalSourceActor, actualLine);
|
||||
const existingActor = this.breakpointActorMap.getActor(actualLocation);
|
||||
this.breakpointActorMap.deleteActor(originalLocation);
|
||||
if (existingActor) {
|
||||
actor.delete();
|
||||
actor = existingActor;
|
||||
} else {
|
||||
actor.originalLocation = actualLocation;
|
||||
this.breakpointActorMap.setActor(actualLocation, actor);
|
||||
}
|
||||
}
|
||||
|
||||
return promise.resolve(actor);
|
||||
} else {
|
||||
return this.sources.getAllGeneratedLocations(originalLocation).then((generatedLocations) => {
|
||||
this._setBreakpointAtAllGeneratedLocations(
|
||||
actor,
|
||||
generatedLocations
|
||||
);
|
||||
|
||||
return actor;
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
_setBreakpointAtAllGeneratedLocations: function (actor, generatedLocations) {
|
||||
let success = false;
|
||||
for (let generatedLocation of generatedLocations) {
|
||||
if (this._setBreakpointAtGeneratedLocation(
|
||||
actor,
|
||||
generatedLocation
|
||||
)) {
|
||||
success = true;
|
||||
}
|
||||
}
|
||||
return success;
|
||||
},
|
||||
|
||||
/*
|
||||
* Ensure the given BreakpointActor is set as breakpoint handler on all
|
||||
* scripts that match the given location in the generated source.
|
||||
*
|
||||
* @param BreakpointActor actor
|
||||
* The BreakpointActor to be set as a breakpoint handler.
|
||||
* @param GeneratedLocation generatedLocation
|
||||
* A GeneratedLocation representing the location in the generated
|
||||
* source for which the given BreakpointActor is to be set as a
|
||||
* breakpoint handler.
|
||||
*
|
||||
* @returns A Boolean that is true if the BreakpointActor was set as a
|
||||
* breakpoint handler on at least one script, and false otherwise.
|
||||
*/
|
||||
_setBreakpointAtGeneratedLocation: function (actor, generatedLocation) {
|
||||
let {
|
||||
generatedSourceActor,
|
||||
generatedLine,
|
||||
generatedColumn,
|
||||
generatedLastColumn
|
||||
} = generatedLocation;
|
||||
|
||||
// Find all scripts that match the given source actor and line number.
|
||||
let scripts = this.scripts.getScriptsBySourceActorAndLine(
|
||||
generatedSourceActor,
|
||||
generatedLine
|
||||
);
|
||||
|
||||
scripts = scripts.filter((script) => !actor.hasScript(script));
|
||||
|
||||
// Find all entry points that correspond to the given location.
|
||||
let entryPoints = [];
|
||||
if (generatedColumn === undefined) {
|
||||
// This is a line breakpoint, so we are interested in all offsets
|
||||
// that correspond to the given line number.
|
||||
for (let script of scripts) {
|
||||
let offsets = script.getLineOffsets(generatedLine);
|
||||
if (offsets.length > 0) {
|
||||
entryPoints.push({ script, offsets });
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// This is a column breakpoint, so we are interested in all column
|
||||
// offsets that correspond to the given line *and* column number.
|
||||
for (let script of scripts) {
|
||||
let columnToOffsetMap = script.getAllColumnOffsets()
|
||||
.filter(({ lineNumber }) => {
|
||||
return lineNumber === generatedLine;
|
||||
});
|
||||
for (let { columnNumber: column, offset } of columnToOffsetMap) {
|
||||
if (column >= generatedColumn && column <= generatedLastColumn) {
|
||||
entryPoints.push({ script, offsets: [offset] });
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (entryPoints.length === 0) {
|
||||
return false;
|
||||
}
|
||||
setBreakpointAtEntryPoints(actor, entryPoints);
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
SourceActor.prototype.requestTypes = {
|
||||
"source": SourceActor.prototype.onSource,
|
||||
"blackbox": SourceActor.prototype.onBlackBox,
|
||||
"unblackbox": SourceActor.prototype.onUnblackBox,
|
||||
"prettyPrint": SourceActor.prototype.onPrettyPrint,
|
||||
"disablePrettyPrint": SourceActor.prototype.onDisablePrettyPrint,
|
||||
"getExecutableLines": SourceActor.prototype.getExecutableLines,
|
||||
"setBreakpoint": SourceActor.prototype.onSetBreakpoint
|
||||
};
|
||||
|
||||
exports.SourceActor = SourceActor;
|
||||
|
@ -12,8 +12,8 @@ const { OriginalLocation, GeneratedLocation } = require("devtools/server/actors/
|
||||
const { resolve } = require("promise");
|
||||
const URL = require("URL");
|
||||
|
||||
loader.lazyRequireGetter(this, "SourceActor", "devtools/server/actors/script", true);
|
||||
loader.lazyRequireGetter(this, "isEvalSource", "devtools/server/actors/script", true);
|
||||
loader.lazyRequireGetter(this, "SourceActor", "devtools/server/actors/source", true);
|
||||
loader.lazyRequireGetter(this, "isEvalSource", "devtools/server/actors/source", true);
|
||||
loader.lazyRequireGetter(this, "SourceMapConsumer", "source-map", true);
|
||||
loader.lazyRequireGetter(this, "SourceMapGenerator", "source-map", true);
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user