/* 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 . */ "use strict"; const { networkRequest, } = require("resource://devtools/client/shared/source-map-loader/utils/network-request"); const { SourceMapConsumer, } = require("resource://devtools/client/shared/vendor/source-map/source-map.js"); const { getSourceMap, setSourceMap, } = require("resource://devtools/client/shared/source-map-loader/utils/sourceMapRequests"); const { WasmRemap, } = require("resource://devtools/client/shared/source-map-loader/utils/wasmRemap"); const { convertToJSON, } = require("resource://devtools/client/shared/source-map-loader/wasm-dwarf/convertToJSON"); // URLs which have been seen in a completed source map request. const originalURLs = new Set(); function clearOriginalURLs() { originalURLs.clear(); } function hasOriginalURL(url) { return originalURLs.has(url); } function _resolveSourceMapURL(source) { let { sourceMapBaseURL, sourceMapURL } = source; sourceMapBaseURL = sourceMapBaseURL || ""; sourceMapURL = sourceMapURL || ""; if (!sourceMapBaseURL) { // If the source doesn't have a URL, don't resolve anything. return { sourceMapURL, baseURL: 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 // for large inlined source-maps, and we don't actually need to parse them. if (sourceMapURL.startsWith("data:")) { resolvedString = sourceMapURL; 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. sourceMapBaseURL.startsWith("data:") ? undefined : sourceMapBaseURL ).toString(); baseURL = resolvedString; } return { sourceMapURL: resolvedString, baseURL }; } async function _resolveAndFetch(generatedSource) { // Fetch the sourcemap over the network and create it. const { sourceMapURL, baseURL } = _resolveSourceMapURL(generatedSource); let fetched = await networkRequest(sourceMapURL, { loadFromCache: false, // Blocking redirects on the sourceMappingUrl as its not easy to verify if the // redirect protocol matches the supported ones. allowRedirects: false, sourceMapBaseURL: generatedSource.sourceMapBaseURL, }); if (fetched.isDwarf) { fetched = { content: await convertToJSON(fetched.content) }; } // Create the source map and fix it up. let map = await new SourceMapConsumer(fetched.content, baseURL); if (generatedSource.isWasm) { map = new WasmRemap(map); // Check if experimental scope info exists. if (fetched.content.includes("x-scopes")) { const parsedJSON = JSON.parse(fetched.content); map.xScopes = parsedJSON["x-scopes"]; } } // Extend the default map object with sourceMapBaseURL, used to check further // network requests made for this sourcemap. map.sourceMapBaseURL = generatedSource.sourceMapBaseURL; if (map && map.sources) { map.sources.forEach(url => originalURLs.add(url)); } return map; } function fetchSourceMap(generatedSource) { const existingRequest = getSourceMap(generatedSource.id); // If it has already been requested, return the request. Make sure // to do this even if sourcemapping is turned off, because // pretty-printing uses sourcemaps. // // An important behavior here is that if it's in the middle of // requesting it, all subsequent calls will block on the initial // request. if (existingRequest) { return existingRequest; } if (!generatedSource.sourceMapURL) { return null; } // Fire off the request, set it in the cache, and return it. const req = _resolveAndFetch(generatedSource); // Make sure the cached promise does not reject, because we only // want to report the error once. setSourceMap( generatedSource.id, req.catch(() => null) ); return req; } module.exports = { fetchSourceMap, hasOriginalURL, clearOriginalURLs };