fetchSourceMap.js (4347B)
1 /* This Source Code Form is subject to the terms of the Mozilla Public 2 * License, v. 2.0. If a copy of the MPL was not distributed with this 3 * file, You can obtain one at <http://mozilla.org/MPL/2.0/>. */ 4 5 "use strict"; 6 7 const { 8 networkRequest, 9 } = require("resource://devtools/client/shared/source-map-loader/utils/network-request"); 10 11 const { 12 SourceMapConsumer, 13 } = require("resource://devtools/client/shared/vendor/source-map/source-map.js"); 14 const { 15 getSourceMap, 16 setSourceMap, 17 } = require("resource://devtools/client/shared/source-map-loader/utils/sourceMapRequests"); 18 const { 19 WasmRemap, 20 } = require("resource://devtools/client/shared/source-map-loader/utils/wasmRemap"); 21 const { 22 convertToJSON, 23 } = require("resource://devtools/client/shared/source-map-loader/wasm-dwarf/convertToJSON"); 24 25 // URLs which have been seen in a completed source map request. 26 const originalURLs = new Set(); 27 28 function clearOriginalURLs() { 29 originalURLs.clear(); 30 } 31 32 function hasOriginalURL(url) { 33 return originalURLs.has(url); 34 } 35 36 function resolveSourceMapURL(source) { 37 let { sourceMapBaseURL, sourceMapURL } = source; 38 sourceMapBaseURL = sourceMapBaseURL || ""; 39 sourceMapURL = sourceMapURL || ""; 40 41 if (!sourceMapBaseURL) { 42 // If the source doesn't have a URL, don't resolve anything. 43 return { resolvedSourceMapURL: sourceMapURL, baseURL: sourceMapURL }; 44 } 45 46 let resolvedString; 47 let baseURL; 48 49 // When the sourceMap is a data: URL, fall back to using the source's URL, 50 // if possible. We don't use `new URL` here because it will be _very_ slow 51 // for large inlined source-maps, and we don't actually need to parse them. 52 if (sourceMapURL.startsWith("data:")) { 53 resolvedString = sourceMapURL; 54 baseURL = sourceMapBaseURL; 55 } else { 56 resolvedString = new URL( 57 sourceMapURL, 58 // If the URL is a data: URL, the sourceMapURL needs to be absolute, so 59 // we might as well pass `undefined` to avoid parsing a potentially 60 // very large data: URL for no reason. 61 sourceMapBaseURL.startsWith("data:") ? undefined : sourceMapBaseURL 62 ).toString(); 63 baseURL = resolvedString; 64 } 65 66 return { resolvedSourceMapURL: resolvedString, baseURL }; 67 } 68 69 async function _fetch(generatedSource, resolvedSourceMapURL, baseURL) { 70 let fetched = await networkRequest(resolvedSourceMapURL, { 71 loadFromCache: false, 72 // Blocking redirects on the sourceMappingUrl as its not easy to verify if the 73 // redirect protocol matches the supported ones. 74 allowRedirects: false, 75 sourceMapBaseURL: generatedSource.sourceMapBaseURL, 76 }); 77 78 if (fetched.isDwarf) { 79 fetched = { content: await convertToJSON(fetched.content) }; 80 } 81 82 // Create the source map and fix it up. 83 let map = await new SourceMapConsumer(fetched.content, baseURL); 84 85 if (generatedSource.isWasm) { 86 map = new WasmRemap(map); 87 // Check if experimental scope info exists. 88 if (fetched.content.includes("x-scopes")) { 89 const parsedJSON = JSON.parse(fetched.content); 90 map.xScopes = parsedJSON["x-scopes"]; 91 } 92 } 93 94 // Extend the default map object with sourceMapBaseURL, used to check further 95 // network requests made for this sourcemap. 96 map.sourceMapBaseURL = generatedSource.sourceMapBaseURL; 97 98 if (map && map.sources) { 99 map.sources.forEach(url => originalURLs.add(url)); 100 } 101 102 return map; 103 } 104 105 function fetchSourceMap(generatedSource, resolvedSourceMapURL, baseURL) { 106 const existingRequest = getSourceMap(generatedSource.id); 107 108 // If it has already been requested, return the request. Make sure 109 // to do this even if sourcemapping is turned off, because 110 // pretty-printing uses sourcemaps. 111 // 112 // An important behavior here is that if it's in the middle of 113 // requesting it, all subsequent calls will block on the initial 114 // request. 115 if (existingRequest) { 116 return existingRequest; 117 } 118 119 if (!generatedSource.sourceMapURL) { 120 return null; 121 } 122 123 // Fire off the request, set it in the cache, and return it. 124 const req = _fetch(generatedSource, resolvedSourceMapURL, baseURL); 125 // Make sure the cached promise does not reject, because we only 126 // want to report the error once. 127 setSourceMap( 128 generatedSource.id, 129 req.catch(() => null) 130 ); 131 return req; 132 } 133 134 module.exports = { 135 fetchSourceMap, 136 hasOriginalURL, 137 clearOriginalURLs, 138 resolveSourceMapURL, 139 };