summaryrefslogtreecommitdiffstats
path: root/devtools/client/shared/source-map-loader/utils/fetchSourceMap.js
blob: 8cf7ab31a9b8bfb17b6e55f04f07a7114895f469 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
/* 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 {
  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 };