diff --git a/devtools/client/debugger/packages/devtools-source-map/src/source-map.js b/devtools/client/debugger/packages/devtools-source-map/src/source-map.js index 116a5cc6103d..b39b168bef07 100644 --- a/devtools/client/debugger/packages/devtools-source-map/src/source-map.js +++ b/devtools/client/debugger/packages/devtools-source-map/src/source-map.js @@ -48,7 +48,11 @@ type Range = { }; export type SourceMapInput = {| id: SourceId, + // This URL isn't actually used in the source-map module, but we have it + // passed in so that the Toolbox can throw a more useful error message + // if the sourcemap for a given generated source file fails to load. url: string, + sourceMapBaseURL: string, sourceMapURL: string, isWasm: boolean, |}; diff --git a/devtools/client/debugger/packages/devtools-source-map/src/tests/helpers.js b/devtools/client/debugger/packages/devtools-source-map/src/tests/helpers.js index e42d9dfdcf38..cc279a5f620c 100644 --- a/devtools/client/debugger/packages/devtools-source-map/src/tests/helpers.js +++ b/devtools/client/debugger/packages/devtools-source-map/src/tests/helpers.js @@ -27,7 +27,7 @@ async function setupBundleFixtureAndData(name) { const source = { id: `${name}.js`, sourceMapURL: `${name}.js.map`, - url: `http://example.com/${name}.js`, + sourceMapBaseURL: `http://example.com/${name}.js`, }; require("devtools-utils/src/network-request").mockImplementationOnce(() => { diff --git a/devtools/client/debugger/packages/devtools-source-map/src/tests/source-map.js b/devtools/client/debugger/packages/devtools-source-map/src/tests/source-map.js index a71630e18ac8..3c164bbce207 100644 --- a/devtools/client/debugger/packages/devtools-source-map/src/tests/source-map.js +++ b/devtools/client/debugger/packages/devtools-source-map/src/tests/source-map.js @@ -136,7 +136,7 @@ describe("source maps", () => { const source = { id: "missingmap.js", sourceMapURL: "missingmap.js.map", - url: "http:://example.com/missingmap.js", + sourceMapBaseURL: "http:://example.com/missingmap.js", }; networkRequest.mockImplementationOnce(() => { diff --git a/devtools/client/debugger/packages/devtools-source-map/src/tests/wasm-source-map.js b/devtools/client/debugger/packages/devtools-source-map/src/tests/wasm-source-map.js index bbdd4b4772d4..b5f97b01280b 100644 --- a/devtools/client/debugger/packages/devtools-source-map/src/tests/wasm-source-map.js +++ b/devtools/client/debugger/packages/devtools-source-map/src/tests/wasm-source-map.js @@ -91,7 +91,7 @@ describe("wasm source maps", () => { test("read and transpose wasm map", async () => { const source = { id: "min.js", - url: "wasm:http://example.com/whatever/:min.js", + sourceMapBaseURL: "wasm:http://example.com/whatever/:min.js", sourceMapURL: "http://example.com/whatever/min.js.map", isWasm: true, }; diff --git a/devtools/client/debugger/packages/devtools-source-map/src/utils/fetchSourceMap.js b/devtools/client/debugger/packages/devtools-source-map/src/utils/fetchSourceMap.js index bd4d0cc92421..3a1287ed51d5 100644 --- a/devtools/client/debugger/packages/devtools-source-map/src/utils/fetchSourceMap.js +++ b/devtools/client/debugger/packages/devtools-source-map/src/utils/fetchSourceMap.js @@ -25,13 +25,14 @@ function hasOriginalURL(url: string): boolean { } function _resolveSourceMapURL(source: SourceMapInput) { - let { url = "", sourceMapURL } = source; + let { sourceMapBaseURL, sourceMapURL } = source; + sourceMapBaseURL = sourceMapBaseURL || ""; + sourceMapURL = sourceMapURL || ""; - if (!url) { + if (!sourceMapBaseURL) { // If the source doesn't have a URL, don't resolve anything. return { sourceMapURL, baseURL: sourceMapURL }; } - sourceMapURL = sourceMapURL || ""; let resolvedString; let baseURL; @@ -41,14 +42,14 @@ function _resolveSourceMapURL(source: SourceMapInput) { // for large inlined source-maps, and we don't actually need to parse them. if (sourceMapURL.startsWith("data:")) { resolvedString = sourceMapURL; - baseURL = url; + baseURL = sourceMapBaseURL; } else { resolvedString = new URL( sourceMapURL, // If the URL is a data: URL, the sourceMapURL needs to be absolute, so // we might as well pass `undefined` to avoid parsing a potentially // very large data: URL for no reason. - url.startsWith("data:") ? undefined : url + sourceMapBaseURL.startsWith("data:") ? undefined : sourceMapBaseURL ).toString(); baseURL = resolvedString; } diff --git a/devtools/client/debugger/src/actions/sources/newSources.js b/devtools/client/debugger/src/actions/sources/newSources.js index 3646eb3ce893..62a86283ecc1 100644 --- a/devtools/client/debugger/src/actions/sources/newSources.js +++ b/devtools/client/debugger/src/actions/sources/newSources.js @@ -106,18 +106,6 @@ function loadSourceMap(cx: Context, sourceActor: SourceActor) { let data = null; try { - // Unable to correctly type the result of a spread on a union type. - // See https://github.com/facebook/flow/pull/7298 - let url = sourceActor.url || ""; - if (!sourceActor.url && typeof sourceActor.introductionUrl === "string") { - // If the source was dynamically generated (via eval, dynamically - // created script elements, and so forth), it won't have a URL, so that - // it is not collapsed into other sources from the same place. The - // introduction URL will include the point it was constructed at, - // however, so use that for resolving any source maps in the source. - url = sourceActor.introductionUrl; - } - // Ignore sourceMapURL on scripts that are part of HTML files, since // we currently treat sourcemaps as Source-wide, not SourceActor-specific. const source = getSourceByActorId(getState(), sourceActor.id); @@ -126,7 +114,8 @@ function loadSourceMap(cx: Context, sourceActor: SourceActor) { // Using source ID here is historical and eventually we'll want to // switch to all of this being per-source-actor. id: source.id, - url, + url: sourceActor.url || "", + sourceMapBaseURL: sourceActor.sourceMapBaseURL || "", sourceMapURL: sourceActor.sourceMapURL || "", isWasm: sourceActor.introductionType === "wasm", }); @@ -345,9 +334,9 @@ export function newGeneratedSources(sourceInfo: Array) { thread, source: newId, isBlackBoxed: source.isBlackBoxed, + sourceMapBaseURL: source.sourceMapBaseURL, sourceMapURL: source.sourceMapURL, url: source.url, - introductionUrl: source.introductionUrl, introductionType: source.introductionType, }); } diff --git a/devtools/client/debugger/src/client/firefox/create.js b/devtools/client/debugger/src/client/firefox/create.js index 874619d2d878..fe55ddaf03cc 100644 --- a/devtools/client/debugger/src/client/firefox/create.js +++ b/devtools/client/debugger/src/client/firefox/create.js @@ -31,6 +31,19 @@ export function prepareSourcePayload( makeSourceId(source, isServiceWorker) ); + source = { ...source }; + + // Maintain backward-compat with servers that only return introductionUrl and + // not sourceMapBaseURL. + if ( + typeof source.sourceMapBaseURL === "undefined" && + typeof (source: any).introductionUrl !== "undefined" + ) { + source.sourceMapBaseURL = + source.url || (source: any).introductionUrl || null; + delete (source: any).introductionUrl; + } + return { thread: threadFront.actor, isServiceWorker, source }; } diff --git a/devtools/client/debugger/src/client/firefox/types.js b/devtools/client/debugger/src/client/firefox/types.js index 5b470c2f41a6..df9dd2c65f6a 100644 --- a/devtools/client/debugger/src/client/firefox/types.js +++ b/devtools/client/debugger/src/client/firefox/types.js @@ -101,8 +101,8 @@ export type SourcePayload = { actor: ActorId, url: URL | null, isBlackBoxed: boolean, + sourceMapBaseURL: URL | null, sourceMapURL: URL | null, - introductionUrl: URL | null, introductionType: string | null, extensionName: string | null, }; diff --git a/devtools/client/debugger/src/reducers/source-actors.js b/devtools/client/debugger/src/reducers/source-actors.js index 93899638f570..9c9859051335 100644 --- a/devtools/client/debugger/src/reducers/source-actors.js +++ b/devtools/client/debugger/src/reducers/source-actors.js @@ -44,6 +44,9 @@ export type SourceActor = {| +isBlackBoxed: boolean, + // The URL that the sourcemap should be loaded relative to. + +sourceMapBaseURL: URL | null, + // The URL of the sourcemap for this source if there is one. +sourceMapURL: URL | null, @@ -51,10 +54,6 @@ export type SourceActor = {| // string-based source, this will not be known. +url: URL | null, - // If this script was introduced by an eval, this will be the URL of the - // script that triggered the evaluation. - +introductionUrl: URL | null, - // The debugger's Debugger.Source API provides type information for the // cause of this source's creation. +introductionType: string | null, diff --git a/devtools/client/debugger/src/utils/test-head.js b/devtools/client/debugger/src/utils/test-head.js index 0756a45b0759..1533ed1a2a4b 100644 --- a/devtools/client/debugger/src/utils/test-head.js +++ b/devtools/client/debugger/src/utils/test-head.js @@ -133,9 +133,9 @@ function makeSourceURL(filename: string) { } type MakeSourceProps = { + sourceMapBaseURL?: string, sourceMapURL?: string, introductionType?: string, - introductionUrl?: string, isBlackBoxed?: boolean, }; function createMakeSource(): ( @@ -155,9 +155,9 @@ function createMakeSource(): ( source: { actor: `${name}-${index}-actor`, url: `http://localhost:8000/examples/${name}`, + sourceMapBaseURL: props.sourceMapBaseURL || null, sourceMapURL: props.sourceMapURL || null, introductionType: props.introductionType || null, - introductionUrl: props.introductionUrl || null, isBlackBoxed: !!props.isBlackBoxed, extensionName: null, }, diff --git a/devtools/client/framework/source-map-url-service.js b/devtools/client/framework/source-map-url-service.js index 21ee5814832c..6d24e0f3c514 100644 --- a/devtools/client/framework/source-map-url-service.js +++ b/devtools/client/framework/source-map-url-service.js @@ -157,12 +157,18 @@ SourceMapURLService.prototype._registerNewSource = function(source) { return; } - const { generatedUrl, url, actor: id, sourceMapURL } = source; + const { + generatedUrl, + url, + actor: id, + sourceMapBaseURL, + sourceMapURL, + } = source; // |generatedUrl| comes from the actor and is extracted from the // source code by SpiderMonkey. const seenUrl = generatedUrl || url; - this._urls.set(seenUrl, { id, url: seenUrl, sourceMapURL }); + this._urls.set(seenUrl, { id, url: seenUrl, sourceMapBaseURL, sourceMapURL }); this._idMap.set(id, seenUrl); return seenUrl; @@ -197,9 +203,9 @@ SourceMapURLService.prototype._registerNewStyleSheet = function(sheet) { return; } - const { href, nodeHref, sourceMapURL, actorID: id } = sheet; + const { href, nodeHref, sourceMapBaseURL, sourceMapURL, actorID: id } = sheet; const url = href || nodeHref; - this._urls.set(url, { id, url, sourceMapURL }); + this._urls.set(url, { id, url, sourceMapBaseURL, sourceMapURL }); this._idMap.set(id, url); return url; @@ -293,7 +299,12 @@ SourceMapURLService.prototype.originalPositionFor = async function( } // Call getOriginalURLs to make sure the source map has been // fetched. We don't actually need the result of this though. - await this._sourceMapService.getOriginalURLs(urlInfo); + await this._sourceMapService.getOriginalURLs({ + id: urlInfo.id, + url: urlInfo.url, + sourceMapBaseURL: urlInfo.sourceMapBaseURL, + sourceMapURL: urlInfo.sourceMapURL, + }); const location = { sourceId: urlInfo.id, line, column, sourceUrl: url }; const resolvedLocation = await this._sourceMapService.getOriginalLocation( location diff --git a/devtools/client/fronts/stylesheets.js b/devtools/client/fronts/stylesheets.js index c244e1e3538c..0246331e4632 100644 --- a/devtools/client/fronts/stylesheets.js +++ b/devtools/client/fronts/stylesheets.js @@ -117,6 +117,14 @@ class StyleSheetFront extends FrontClassWithSpec(styleSheetSpec) { get ruleCount() { return this._form.ruleCount; } + get sourceMapBaseURL() { + // Handle backward-compat for servers that don't return sourceMapBaseURL. + if (this._form.sourceMapBaseURL === undefined) { + return this.href || this.nodeHref; + } + + return this._form.sourceMapBaseURL; + } get sourceMapURL() { return this._form.sourceMapURL; } diff --git a/devtools/client/shared/source-map/worker.js b/devtools/client/shared/source-map/worker.js index ce8e4dc0e1a0..dbe979c80271 100644 --- a/devtools/client/shared/source-map/worker.js +++ b/devtools/client/shared/source-map/worker.js @@ -3869,11 +3869,13 @@ function hasOriginalURL(url) { function _resolveSourceMapURL(source) { let { - url = "", + sourceMapBaseURL, sourceMapURL } = source; + sourceMapBaseURL = sourceMapBaseURL || ""; + sourceMapURL = sourceMapURL || ""; - if (!url) { + if (!sourceMapBaseURL) { // If the source doesn't have a URL, don't resolve anything. return { sourceMapURL, @@ -3881,7 +3883,6 @@ function _resolveSourceMapURL(source) { }; } - sourceMapURL = sourceMapURL || ""; let resolvedString; let baseURL; // When the sourceMap is a data: URL, fall back to using the source's URL, // if possible. We don't use `new URL` here because it will be _very_ slow @@ -3889,12 +3890,12 @@ function _resolveSourceMapURL(source) { if (sourceMapURL.startsWith("data:")) { resolvedString = sourceMapURL; - baseURL = url; + baseURL = sourceMapBaseURL; } else { resolvedString = new URL(sourceMapURL, // If the URL is a data: URL, the sourceMapURL needs to be absolute, so // we might as well pass `undefined` to avoid parsing a potentially // very large data: URL for no reason. - url.startsWith("data:") ? undefined : url).toString(); + sourceMapBaseURL.startsWith("data:") ? undefined : sourceMapBaseURL).toString(); baseURL = resolvedString; } diff --git a/devtools/client/styleeditor/StyleEditorUI.jsm b/devtools/client/styleeditor/StyleEditorUI.jsm index 21f5986bc1c8..f3b05fb0a73c 100644 --- a/devtools/client/styleeditor/StyleEditorUI.jsm +++ b/devtools/client/styleeditor/StyleEditorUI.jsm @@ -344,11 +344,17 @@ StyleEditorUI.prototype = { return editor; } - const { href, nodeHref, actorID: id, sourceMapURL } = styleSheet; - const url = href || nodeHref; + const { + href, + nodeHref, + actorID: id, + sourceMapURL, + sourceMapBaseURL, + } = styleSheet; const sources = await sourceMapService.getOriginalURLs({ id, - url, + url: href || nodeHref, + sourceMapBaseURL, sourceMapURL, }); // A single generated sheet might map to multiple original diff --git a/devtools/server/actors/source.js b/devtools/server/actors/source.js index 32dd9ad9da2a..20dfc04e6b4a 100644 --- a/devtools/server/actors/source.js +++ b/devtools/server/actors/source.js @@ -181,8 +181,10 @@ const SourceActor = ActorClassWithSpec(sourceSpec, { const source = this._source; let introductionUrl = null; - if (source.introductionScript) { - introductionUrl = source.introductionScript.source.url; + if (source.introductionScript && source.introductionScript.source.url) { + introductionUrl = source.introductionScript.source.url + .split(" -> ") + .pop(); } return { @@ -190,10 +192,13 @@ const SourceActor = ActorClassWithSpec(sourceSpec, { extensionName: this.extensionName, url: this.url, isBlackBoxed: this.threadActor.sources.isBlackBoxed(this.url), + // If the source was dynamically generated (via eval, dynamically + // created script elements, and so forth), it won't have a URL, so that + // it is not collapsed into other sources from the same place. The + // introduction URL will include the point it was constructed at, + // however, so use that for resolving any source maps in the source. + sourceMapBaseURL: this.url || introductionUrl || null, sourceMapURL: source.sourceMapURL, - introductionUrl: introductionUrl - ? introductionUrl.split(" -> ").pop() - : null, introductionType: source.introductionType, }; }, diff --git a/devtools/server/actors/stylesheets.js b/devtools/server/actors/stylesheets.js index 39bc14d013a1..885247ac3181 100644 --- a/devtools/server/actors/stylesheets.js +++ b/devtools/server/actors/stylesheets.js @@ -431,6 +431,7 @@ var StyleSheetActor = protocol.ActorClassWithSpec(styleSheetSpec, { title: this.rawSheet.title, system: !CssLogic.isAuthorStylesheet(this.rawSheet), styleSheetIndex: this.styleSheetIndex, + sourceMapBaseURL: this.href || docHref || null, sourceMapURL: this.rawSheet.sourceMapURL, };