/* 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/. */
function resolveSourceURL(sourceURL, targetActor) { if (sourceURL) { try {
let baseURL; if (targetActor.window) {
baseURL = targetActor.window.location?.href;
} // For worker, we don't have easy access to location, // so pull extra information directly from the target actor. if (targetActor.workerUrl) {
baseURL = targetActor.workerUrl;
} returnnew URL(sourceURL, baseURL || undefined).href;
} catch (err) {}
}
returnnull;
} function getSourceURL(source, targetActor) { // Some eval sources have URLs, but we want to explicitly ignore those because // they are generally useless strings like "eval" or "debugger eval code".
let resourceURL = getDebuggerSourceURL(source) || "";
// Strip out eventual stack trace stored in Source's url. // (not clear if that still happens)
resourceURL = resourceURL.split(" -> ").pop();
// A "//# sourceURL=" pragma should basically be treated as a source file's // full URL, so that is what we want to use as the base if it is present. // If this is not an absolute URL, this will mean the maps in the file // will not have a valid base URL, but that is up to tooling that
let result = resolveSourceURL(source.displayURL, targetActor); if (!result) {
result = resolveSourceURL(resourceURL, targetActor) || resourceURL;
// In XPCShell tests, the source URL isn't actually a URL, it's a file path. // That causes issues because "C:/folder/file.js" is parsed as a URL with // "c:" as the URL scheme, which causes the drive letter to be unexpectedly // lower-cased when the parsed URL is re-serialized. To avoid that, we // detect that case and re-uppercase it again. This is a bit gross and // ideally it seems like XPCShell tests should use file:// URLs for files, // but alas they do not. if (
resourceURL &&
resourceURL.match(windowsDrive) &&
result.slice(0, 2) == resourceURL.slice(0, 2).toLowerCase()
) {
result = resourceURL.slice(0, 2) + result.slice(2);
}
}
// Avoid returning empty string and return null if no URL is found return result || null;
}
/** * A SourceActor provides information about the source of a script. Source * actors are 1:1 with Debugger.Source objects. * * @param Debugger.Source source * The source object we are representing. * @param ThreadActor thread * The current thread actor.
*/ class SourceActor extends Actor {
constructor({ source, thread }) { super(thread.conn, sourceSpec);
get _isInlineSource() { const source = this._source; if (this.__isInlineSource === undefined) { // If the source has a usable displayURL, the source is treated as not // inlined because it has its own URL. // Also consider sources loaded from <iframe srcdoc> as independant sources, // because we can't easily fetch the full html content of the srcdoc attribute. this.__isInlineSource =
source.introductionType === "inlineScript" &&
!resolveSourceURL(source.displayURL, this.threadActor.targetActor) &&
!this.url.startsWith("about:srcdoc");
} returnthis.__isInlineSource;
}
get threadActor() { returnthis._threadActor;
}
get sourcesManager() { returnthis._threadActor.sourcesManager;
}
get dbg() { returnthis.threadActor.dbg;
}
get breakpointActorMap() { returnthis.threadActor.breakpointActorMap;
}
get url() { if (this._url === undefined) { this._url = getSourceURL(this._source, this.threadActor.targetActor);
} returnthis._url;
}
get extensionName() { if (this._extensionName === undefined) { this._extensionName = null;
// Cu is not available for workers and so we are not able to get a // WebExtensionPolicy object if (!isWorker && this.url?.startsWith("moz-extension:")) { try { const extURI = Services.io.newURI(this.url); const policy = WebExtensionPolicy.getByURI(extURI); if (policy) { this._extensionName = policy.name;
}
} catch (e) {
console.warn(`Failed to find extension name for ${this.url} : ${e}`);
}
}
}
returnthis._extensionName;
}
get internalSourceId() { returnthis._source.id;
}
form() { const source = this._source;
let introductionType = source.introductionType; if (
introductionType === "srcScript" ||
introductionType === "inlineScript" ||
introductionType === "injectedScript"
) { // These three used to be one single type, so here we combine them all // so that clients don't see any change in behavior.
introductionType = "scriptElement";
}
// NOTE: Debugger.Source.prototype.startColumn is 1-based. // Convert to 0-based, while keeping the wasm's column (1) as is. // (bug 1863878) const columnBase = source.introductionType === "wasm" ? 0 : 1;
// Use `source.text` if it exists, is not the "no source" string, and // the source isn't one that is inlined into some larger file. // It will be "no source" if the Debugger API wasn't able to load // the source because sources were discarded // (javascript.options.discardSystemSource == true). // // For inline source, we do something special and ignore individual source content. // Instead, each inline source will return the full HTML page content where // the inline source is (i.e. `<script> js source </script>`). // // When using srcdoc attribute on iframes: // <iframe srcdoc="<script> js source </script>"></iframe> // The whole iframe source is going to be considered as an inline source because displayURL is null // and introductionType is inlineScript. But Debugger.Source.text is the only way // to retrieve the source content. if (this._source.text !== "[no source]" && !this._isInlineSource) { return {
content: this.actualText(),
contentType: "text/javascript",
};
}
// Get the actual text of this source, padded so that line numbers will match // up with the source itself.
actualText() { // If the source doesn't start at line 1, line numbers in the client will // not match up with those in the source. Pad the text with blank lines to // fix this. This can show up for sources associated with inline scripts // in HTML created via document.write() calls: the script's source line // number is relative to the start of the written HTML, but we show the // source's content by itself. const padding = this._source.startLine
? "\n".repeat(this._source.startLine - 1)
: ""; return padding + this._source.text;
}
// Return whether the specified fetched contents includes the actual text of // this source in the expected position.
contentMatches(fileContents) { const lineBreak = /\r\n?|\n|\u2028|\u2029/; const contentLines = fileContents.content.split(lineBreak); const sourceLines = this._source.text.split(lineBreak);
let line = this._source.startLine - 1; for (const sourceLine of sourceLines) { const contentLine = contentLines[line++] || ""; if (!contentLine.includes(sourceLine)) { returnfalse;
}
} returntrue;
}
getBreakableLines() { const positions = this._getBreakpointPositions(); const lines = new Set(); for (const position of positions) { if (!lines.has(position.line)) {
lines.add(position.line);
}
}
return Array.from(lines);
}
// Get all toplevel scripts in the source. Transitive child scripts must be // found by traversing the child script tree.
_getTopLevelDebuggeeScripts() { if (this._scripts) { returnthis._scripts;
}
let scripts = this.dbg.findScripts({ source: this._source });
if (!this._isWasm) { // There is no easier way to get the top-level scripts right now, so // we have to build that up the list manually. // Note: It is not valid to simply look for scripts where // `.isFunction == false` because a source may have executed multiple // where some have been GCed and some have not (bug 1627712). const allScripts = new Set(scripts); for (const script of allScripts) { for (const child of script.getChildScripts()) {
allScripts.delete(child);
}
}
scripts = [...allScripts];
}
this._scripts = scripts; return scripts;
}
resetDebuggeeScripts() { this._scripts = null;
}
// Get toplevel scripts which contain all breakpoint positions for the source. // This is different from _scripts if we detected that some scripts have been // GC'ed and reparsed the source contents.
_getTopLevelBreakpointPositionScripts() { if (this._breakpointPositionScripts) { returnthis._breakpointPositionScripts;
}
let scripts = this._getTopLevelDebuggeeScripts();
// We need to find all breakpoint positions, even if scripts associated with // this source have been GC'ed. We detect this by looking for a script which // does not have a function: a source will typically have a top level // non-function script. If this top level script still exists, then it keeps // all its child scripts alive and we will find all breakpoint positions by // scanning the existing scripts. If the top level script has been GC'ed // then we won't find its breakpoint positions, and inner functions may have // been GC'ed as well. In this case we reparse the source and generate a new // and complete set of scripts to look for the breakpoint positions. // Note that in some cases like "new Function(stuff)" there might not be a // top level non-function script, but if there is a non-function script then // it must be at the top level and will keep all other scripts in the source // alive. if (!this._isWasm && !scripts.some(script => !script.isFunction)) {
let newScript; try {
newScript = this._source.reparse();
} catch (e) { // reparse() will throw if the source is not valid JS. This can happen // if this source is the resurrection of a GC'ed source and there are // parse errors in the refetched contents.
} if (newScript) {
scripts = [newScript];
}
}
// Get all scripts in this source that might include content in the range // specified by the given query.
_findDebuggeeScripts(query, forBreakpointPositions) { const scripts = forBreakpointPositions
? this._getTopLevelBreakpointPositionScripts()
: this._getTopLevelDebuggeeScripts();
function scriptMatches(script) { // These tests are approximate, as we can't easily get the script's end // column.
let lineCount; try {
lineCount = script.lineCount;
} catch (err) { // Accessing scripts which were optimized out during parsing can throw // an exception. Tolerate these so that we can still get positions for // other scripts in the source. returnfalse;
}
// NOTE: Debugger.Script.prototype.startColumn is 1-based. // Convert to 0-based, while keeping the wasm's column (1) as is. // (bug 1863878) const columnBase = script.format === "wasm" ? 0 : 1; if (
script.startLine > endLine ||
script.startLine + lineCount <= startLine ||
(script.startLine == endLine &&
script.startColumn - columnBase > endColumn)
) { returnfalse;
}
function addMatchingScripts(childScripts) { for (const script of childScripts) { if (scriptMatches(script)) {
rv.push(script); if (script.format === "js") {
addMatchingScripts(script.getChildScripts());
}
}
}
}
}
// NOTE: Debugger.Script.prototype.startColumn is 1-based. // Convert to 0-based, while keeping the wasm's column (1) as is. // (bug 1863878) const columnBase = script.format === "wasm" ? 0 : 1;
/** * Handler for the "onSource" packet. * @return Object * The return of this function contains a field `contentType`, and * a field `source`. `source` can either be an ArrayBuffer or * a LongString.
*/
async source() { try { const { content, contentType } = await this._getSourceText(); if ( typeof content === "object" &&
content &&
content.constructor &&
content.constructor.name === "ArrayBuffer"
) { return {
source: new ArrayBufferActor(this.threadActor.conn, content),
contentType,
};
}
return {
source: new LongStringActor(this.threadActor.conn, content),
contentType,
};
} catch (error) { thrownew Error( "Could not load the source for " + this.url + ".\n" +
DevToolsUtils.safeErrorString(error)
);
}
}
/** * Handler for the "blackbox" packet.
*/
blackbox(range) { this.sourcesManager.blackBox(this.url, range); if ( this.threadActor.state == "paused" && this.threadActor.youngestFrame && this.threadActor.youngestFrame.script.url == this.url
) { returntrue;
} returnfalse;
}
/** * Handler for the "unblackbox" packet.
*/
unblackbox(range) { this.sourcesManager.unblackBox(this.url, range);
}
for (const line in pausePoints) {
uncompressed[line] = {}; for (const col in pausePoints[line]) {
uncompressed[line][col] = points[pausePoints[line][col]];
}
}
this.pausePoints = uncompressed;
}
/* * Ensure the given BreakpointActor is set as a breakpoint handler on all * scripts that match its location in the generated source. * * @param BreakpointActor actor * The BreakpointActor to be set as a breakpoint handler. * * @returns A Promise that resolves to the given BreakpointActor.
*/
async applyBreakpoint(actor) { const { line, column } = actor.location;
// Find all entry points that correspond to the given location. const entryPoints = []; if (column === undefined) { // Find all scripts that match the given source actor and line // number. const query = { start: { line }, end: { line } }; const scripts = this._findDebuggeeScripts(query).filter(
script => !actor.hasScript(script)
);
// NOTE: Debugger.Script.prototype.getPossibleBreakpoints returns // columnNumber in 1-based. // The following code uses columnNumber only for comparing against // other columnNumber, and we don't need to convert to 0-based.
// This is a line breakpoint, so we add a breakpoint on the first // breakpoint on the line. const lineMatches = []; for (const script of scripts) { const possibleBreakpoints = script.getPossibleBreakpoints({ line }); for (const possibleBreakpoint of possibleBreakpoints) {
lineMatches.push({ ...possibleBreakpoint, script });
}
}
lineMatches.sort((a, b) => a.columnNumber - b.columnNumber);
if (lineMatches.length) { // A single Debugger.Source may have _multiple_ Debugger.Scripts // at the same position from multiple evaluations of the source, // so we explicitly want to take all of the matches for the matched // column number. const firstColumn = lineMatches[0].columnNumber; const firstColumnMatches = lineMatches.filter(
m => m.columnNumber === firstColumn
);
for (const { script, offset } of firstColumnMatches) {
entryPoints.push({ script, offsets: [offset] });
}
}
} else { // Find all scripts that match the given source actor, line, // and column number. const query = { start: { line, column }, end: { line, column } }; const scripts = this._findDebuggeeScripts(query).filter(
script => !actor.hasScript(script)
);
for (const script of scripts) { // NOTE: getPossibleBreakpoints's minColumn/maxColumn parameters are // 1-based. // Convert to 1-based, while keeping the wasm's column (1) as is. // (bug 1863878) const columnBase = script.format === "wasm" ? 0 : 1;
// Check to see if the script contains a breakpoint position at // this line and column. const possibleBreakpoint = script
.getPossibleBreakpoints({
line,
minColumn: column + columnBase,
maxColumn: column + columnBase + 1,
})
.pop();
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung ist noch experimentell.