source-utils.js 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329
  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. "use strict";
  5. const { LocalizationHelper } = require("devtools/shared/l10n");
  6. const l10n = new LocalizationHelper("devtools/client/locales/components.properties");
  7. const UNKNOWN_SOURCE_STRING = l10n.getStr("frame.unknownSource");
  8. // Character codes used in various parsing helper functions.
  9. const CHAR_CODE_A = "a".charCodeAt(0);
  10. const CHAR_CODE_C = "c".charCodeAt(0);
  11. const CHAR_CODE_D = "d".charCodeAt(0);
  12. const CHAR_CODE_E = "e".charCodeAt(0);
  13. const CHAR_CODE_F = "f".charCodeAt(0);
  14. const CHAR_CODE_H = "h".charCodeAt(0);
  15. const CHAR_CODE_I = "i".charCodeAt(0);
  16. const CHAR_CODE_J = "j".charCodeAt(0);
  17. const CHAR_CODE_L = "l".charCodeAt(0);
  18. const CHAR_CODE_M = "m".charCodeAt(0);
  19. const CHAR_CODE_O = "o".charCodeAt(0);
  20. const CHAR_CODE_P = "p".charCodeAt(0);
  21. const CHAR_CODE_R = "r".charCodeAt(0);
  22. const CHAR_CODE_S = "s".charCodeAt(0);
  23. const CHAR_CODE_T = "t".charCodeAt(0);
  24. const CHAR_CODE_U = "u".charCodeAt(0);
  25. const CHAR_CODE_COLON = ":".charCodeAt(0);
  26. const CHAR_CODE_SLASH = "/".charCodeAt(0);
  27. const CHAR_CODE_CAP_S = "S".charCodeAt(0);
  28. // The cache used in the `parseURL` function.
  29. const gURLStore = new Map();
  30. // The cache used in the `getSourceNames` function.
  31. const gSourceNamesStore = new Map();
  32. /**
  33. * Takes a string and returns an object containing all the properties
  34. * available on an URL instance, with additional properties (fileName),
  35. * Leverages caching.
  36. *
  37. * @param {String} location
  38. * @return {Object?} An object containing most properties available
  39. * in https://developer.mozilla.org/en-US/docs/Web/API/URL
  40. */
  41. function parseURL(location) {
  42. let url = gURLStore.get(location);
  43. if (url !== void 0) {
  44. return url;
  45. }
  46. try {
  47. url = new URL(location);
  48. // The callers were generally written to expect a URL from
  49. // sdk/url, which is subtly different. So, work around some
  50. // important differences here.
  51. url = {
  52. href: url.href,
  53. protocol: url.protocol,
  54. host: url.host,
  55. hostname: url.hostname,
  56. port: url.port || null,
  57. pathname: url.pathname,
  58. search: url.search,
  59. hash: url.hash,
  60. username: url.username,
  61. password: url.password,
  62. origin: url.origin,
  63. };
  64. // Definitions:
  65. // Example: https://foo.com:8888/file.js
  66. // `hostname`: "foo.com"
  67. // `host`: "foo.com:8888"
  68. let isChrome = isChromeScheme(location);
  69. url.fileName = url.pathname ?
  70. (url.pathname.slice(url.pathname.lastIndexOf("/") + 1) || "/") :
  71. "/";
  72. if (isChrome) {
  73. url.hostname = null;
  74. url.host = null;
  75. }
  76. gURLStore.set(location, url);
  77. return url;
  78. } catch (e) {
  79. gURLStore.set(location, null);
  80. return null;
  81. }
  82. }
  83. /**
  84. * Parse a source into a short and long name as well as a host name.
  85. *
  86. * @param {String} source
  87. * The source to parse. Can be a URI or names like "(eval)" or
  88. * "self-hosted".
  89. * @return {Object}
  90. * An object with the following properties:
  91. * - {String} short: A short name for the source.
  92. * - "http://page.com/test.js#go?q=query" -> "test.js"
  93. * - {String} long: The full, long name for the source, with
  94. hash/query stripped.
  95. * - "http://page.com/test.js#go?q=query" -> "http://page.com/test.js"
  96. * - {String?} host: If available, the host name for the source.
  97. * - "http://page.com/test.js#go?q=query" -> "page.com"
  98. */
  99. function getSourceNames(source) {
  100. let data = gSourceNamesStore.get(source);
  101. if (data) {
  102. return data;
  103. }
  104. let short, long, host;
  105. const sourceStr = source ? String(source) : "";
  106. // If `data:...` uri
  107. if (isDataScheme(sourceStr)) {
  108. let commaIndex = sourceStr.indexOf(",");
  109. if (commaIndex > -1) {
  110. // The `short` name for a data URI becomes `data:` followed by the actual
  111. // encoded content, omitting the MIME type, and charset.
  112. short = `data:${sourceStr.substring(commaIndex + 1)}`.slice(0, 100);
  113. let result = { short, long: sourceStr };
  114. gSourceNamesStore.set(source, result);
  115. return result;
  116. }
  117. }
  118. // If Scratchpad URI, like "Scratchpad/1"; no modifications,
  119. // and short/long are the same.
  120. if (isScratchpadScheme(sourceStr)) {
  121. let result = { short: sourceStr, long: sourceStr };
  122. gSourceNamesStore.set(source, result);
  123. return result;
  124. }
  125. const parsedUrl = parseURL(sourceStr);
  126. if (!parsedUrl) {
  127. // Malformed URI.
  128. long = sourceStr;
  129. short = sourceStr.slice(0, 100);
  130. } else {
  131. host = parsedUrl.host;
  132. long = parsedUrl.href;
  133. if (parsedUrl.hash) {
  134. long = long.replace(parsedUrl.hash, "");
  135. }
  136. if (parsedUrl.search) {
  137. long = long.replace(parsedUrl.search, "");
  138. }
  139. short = parsedUrl.fileName;
  140. // If `short` is just a slash, and we actually have a path,
  141. // strip the slash and parse again to get a more useful short name.
  142. // e.g. "http://foo.com/bar/" -> "bar", rather than "/"
  143. if (short === "/" && parsedUrl.pathname !== "/") {
  144. short = parseURL(long.replace(/\/$/, "")).fileName;
  145. }
  146. }
  147. if (!short) {
  148. if (!long) {
  149. long = UNKNOWN_SOURCE_STRING;
  150. }
  151. short = long.slice(0, 100);
  152. }
  153. let result = { short, long, host };
  154. gSourceNamesStore.set(source, result);
  155. return result;
  156. }
  157. // For the functions below, we assume that we will never access the location
  158. // argument out of bounds, which is indeed the vast majority of cases.
  159. //
  160. // They are written this way because they are hot. Each frame is checked for
  161. // being content or chrome when processing the profile.
  162. function isColonSlashSlash(location, i = 0) {
  163. return location.charCodeAt(++i) === CHAR_CODE_COLON &&
  164. location.charCodeAt(++i) === CHAR_CODE_SLASH &&
  165. location.charCodeAt(++i) === CHAR_CODE_SLASH;
  166. }
  167. /**
  168. * Checks for a Scratchpad URI, like "Scratchpad/1"
  169. */
  170. function isScratchpadScheme(location, i = 0) {
  171. return location.charCodeAt(i) === CHAR_CODE_CAP_S &&
  172. location.charCodeAt(++i) === CHAR_CODE_C &&
  173. location.charCodeAt(++i) === CHAR_CODE_R &&
  174. location.charCodeAt(++i) === CHAR_CODE_A &&
  175. location.charCodeAt(++i) === CHAR_CODE_T &&
  176. location.charCodeAt(++i) === CHAR_CODE_C &&
  177. location.charCodeAt(++i) === CHAR_CODE_H &&
  178. location.charCodeAt(++i) === CHAR_CODE_P &&
  179. location.charCodeAt(++i) === CHAR_CODE_A &&
  180. location.charCodeAt(++i) === CHAR_CODE_D &&
  181. location.charCodeAt(++i) === CHAR_CODE_SLASH;
  182. }
  183. function isDataScheme(location, i = 0) {
  184. return location.charCodeAt(i) === CHAR_CODE_D &&
  185. location.charCodeAt(++i) === CHAR_CODE_A &&
  186. location.charCodeAt(++i) === CHAR_CODE_T &&
  187. location.charCodeAt(++i) === CHAR_CODE_A &&
  188. location.charCodeAt(++i) === CHAR_CODE_COLON;
  189. }
  190. function isContentScheme(location, i = 0) {
  191. let firstChar = location.charCodeAt(i);
  192. switch (firstChar) {
  193. // "http://" or "https://"
  194. case CHAR_CODE_H:
  195. if (location.charCodeAt(++i) === CHAR_CODE_T &&
  196. location.charCodeAt(++i) === CHAR_CODE_T &&
  197. location.charCodeAt(++i) === CHAR_CODE_P) {
  198. if (location.charCodeAt(i + 1) === CHAR_CODE_S) {
  199. ++i;
  200. }
  201. return isColonSlashSlash(location, i);
  202. }
  203. return false;
  204. // "file://"
  205. case CHAR_CODE_F:
  206. if (location.charCodeAt(++i) === CHAR_CODE_I &&
  207. location.charCodeAt(++i) === CHAR_CODE_L &&
  208. location.charCodeAt(++i) === CHAR_CODE_E) {
  209. return isColonSlashSlash(location, i);
  210. }
  211. return false;
  212. // "app://"
  213. case CHAR_CODE_A:
  214. if (location.charCodeAt(++i) == CHAR_CODE_P &&
  215. location.charCodeAt(++i) == CHAR_CODE_P) {
  216. return isColonSlashSlash(location, i);
  217. }
  218. return false;
  219. default:
  220. return false;
  221. }
  222. }
  223. function isChromeScheme(location, i = 0) {
  224. let firstChar = location.charCodeAt(i);
  225. switch (firstChar) {
  226. // "chrome://"
  227. case CHAR_CODE_C:
  228. if (location.charCodeAt(++i) === CHAR_CODE_H &&
  229. location.charCodeAt(++i) === CHAR_CODE_R &&
  230. location.charCodeAt(++i) === CHAR_CODE_O &&
  231. location.charCodeAt(++i) === CHAR_CODE_M &&
  232. location.charCodeAt(++i) === CHAR_CODE_E) {
  233. return isColonSlashSlash(location, i);
  234. }
  235. return false;
  236. // "resource://"
  237. case CHAR_CODE_R:
  238. if (location.charCodeAt(++i) === CHAR_CODE_E &&
  239. location.charCodeAt(++i) === CHAR_CODE_S &&
  240. location.charCodeAt(++i) === CHAR_CODE_O &&
  241. location.charCodeAt(++i) === CHAR_CODE_U &&
  242. location.charCodeAt(++i) === CHAR_CODE_R &&
  243. location.charCodeAt(++i) === CHAR_CODE_C &&
  244. location.charCodeAt(++i) === CHAR_CODE_E) {
  245. return isColonSlashSlash(location, i);
  246. }
  247. return false;
  248. // "jar:file://"
  249. case CHAR_CODE_J:
  250. if (location.charCodeAt(++i) === CHAR_CODE_A &&
  251. location.charCodeAt(++i) === CHAR_CODE_R &&
  252. location.charCodeAt(++i) === CHAR_CODE_COLON &&
  253. location.charCodeAt(++i) === CHAR_CODE_F &&
  254. location.charCodeAt(++i) === CHAR_CODE_I &&
  255. location.charCodeAt(++i) === CHAR_CODE_L &&
  256. location.charCodeAt(++i) === CHAR_CODE_E) {
  257. return isColonSlashSlash(location, i);
  258. }
  259. return false;
  260. default:
  261. return false;
  262. }
  263. }
  264. /**
  265. * A utility method to get the file name from a sourcemapped location
  266. * The sourcemap location can be in any form. This method returns a
  267. * formatted file name for different cases like Windows or OSX.
  268. * @param source
  269. * @returns String
  270. */
  271. function getSourceMappedFile(source) {
  272. // If sourcemapped source is a OSX path, return
  273. // the characters after last "/".
  274. // If sourcemapped source is a Windowss path, return
  275. // the characters after last "\\".
  276. if (source.lastIndexOf("/") >= 0) {
  277. source = source.slice(source.lastIndexOf("/") + 1);
  278. } else if (source.lastIndexOf("\\") >= 0) {
  279. source = source.slice(source.lastIndexOf("\\") + 1);
  280. }
  281. return source;
  282. }
  283. exports.parseURL = parseURL;
  284. exports.getSourceNames = getSourceNames;
  285. exports.isScratchpadScheme = isScratchpadScheme;
  286. exports.isChromeScheme = isChromeScheme;
  287. exports.isContentScheme = isContentScheme;
  288. exports.isDataScheme = isDataScheme;
  289. exports.getSourceMappedFile = getSourceMappedFile;