summaryrefslogtreecommitdiffstats
path: root/devtools/client/shared/source-utils.js
diff options
context:
space:
mode:
Diffstat (limited to 'devtools/client/shared/source-utils.js')
-rw-r--r--devtools/client/shared/source-utils.js328
1 files changed, 328 insertions, 0 deletions
diff --git a/devtools/client/shared/source-utils.js b/devtools/client/shared/source-utils.js
new file mode 100644
index 000000000..974fd272d
--- /dev/null
+++ b/devtools/client/shared/source-utils.js
@@ -0,0 +1,328 @@
+/* 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 { LocalizationHelper } = require("devtools/shared/l10n");
+
+const l10n = new LocalizationHelper("devtools/client/locales/components.properties");
+const UNKNOWN_SOURCE_STRING = l10n.getStr("frame.unknownSource");
+
+// Character codes used in various parsing helper functions.
+const CHAR_CODE_A = "a".charCodeAt(0);
+const CHAR_CODE_C = "c".charCodeAt(0);
+const CHAR_CODE_D = "d".charCodeAt(0);
+const CHAR_CODE_E = "e".charCodeAt(0);
+const CHAR_CODE_F = "f".charCodeAt(0);
+const CHAR_CODE_H = "h".charCodeAt(0);
+const CHAR_CODE_I = "i".charCodeAt(0);
+const CHAR_CODE_J = "j".charCodeAt(0);
+const CHAR_CODE_L = "l".charCodeAt(0);
+const CHAR_CODE_M = "m".charCodeAt(0);
+const CHAR_CODE_O = "o".charCodeAt(0);
+const CHAR_CODE_P = "p".charCodeAt(0);
+const CHAR_CODE_R = "r".charCodeAt(0);
+const CHAR_CODE_S = "s".charCodeAt(0);
+const CHAR_CODE_T = "t".charCodeAt(0);
+const CHAR_CODE_U = "u".charCodeAt(0);
+const CHAR_CODE_COLON = ":".charCodeAt(0);
+const CHAR_CODE_SLASH = "/".charCodeAt(0);
+const CHAR_CODE_CAP_S = "S".charCodeAt(0);
+
+// The cache used in the `parseURL` function.
+const gURLStore = new Map();
+// The cache used in the `getSourceNames` function.
+const gSourceNamesStore = new Map();
+
+/**
+ * Takes a string and returns an object containing all the properties
+ * available on an URL instance, with additional properties (fileName),
+ * Leverages caching.
+ *
+ * @param {String} location
+ * @return {Object?} An object containing most properties available
+ * in https://developer.mozilla.org/en-US/docs/Web/API/URL
+ */
+
+function parseURL(location) {
+ let url = gURLStore.get(location);
+
+ if (url !== void 0) {
+ return url;
+ }
+
+ try {
+ url = new URL(location);
+ // The callers were generally written to expect a URL from
+ // sdk/url, which is subtly different. So, work around some
+ // important differences here.
+ url = {
+ href: url.href,
+ protocol: url.protocol,
+ host: url.host,
+ hostname: url.hostname,
+ port: url.port || null,
+ pathname: url.pathname,
+ search: url.search,
+ hash: url.hash,
+ username: url.username,
+ password: url.password,
+ origin: url.origin,
+ };
+
+ // Definitions:
+ // Example: https://foo.com:8888/file.js
+ // `hostname`: "foo.com"
+ // `host`: "foo.com:8888"
+ let isChrome = isChromeScheme(location);
+
+ url.fileName = url.pathname ?
+ (url.pathname.slice(url.pathname.lastIndexOf("/") + 1) || "/") :
+ "/";
+
+ if (isChrome) {
+ url.hostname = null;
+ url.host = null;
+ }
+
+ gURLStore.set(location, url);
+ return url;
+ } catch (e) {
+ gURLStore.set(location, null);
+ return null;
+ }
+}
+
+/**
+ * Parse a source into a short and long name as well as a host name.
+ *
+ * @param {String} source
+ * The source to parse. Can be a URI or names like "(eval)" or
+ * "self-hosted".
+ * @return {Object}
+ * An object with the following properties:
+ * - {String} short: A short name for the source.
+ * - "http://page.com/test.js#go?q=query" -> "test.js"
+ * - {String} long: The full, long name for the source, with
+ hash/query stripped.
+ * - "http://page.com/test.js#go?q=query" -> "http://page.com/test.js"
+ * - {String?} host: If available, the host name for the source.
+ * - "http://page.com/test.js#go?q=query" -> "page.com"
+ */
+function getSourceNames(source) {
+ let data = gSourceNamesStore.get(source);
+
+ if (data) {
+ return data;
+ }
+
+ let short, long, host;
+ const sourceStr = source ? String(source) : "";
+
+ // If `data:...` uri
+ if (isDataScheme(sourceStr)) {
+ let commaIndex = sourceStr.indexOf(",");
+ if (commaIndex > -1) {
+ // The `short` name for a data URI becomes `data:` followed by the actual
+ // encoded content, omitting the MIME type, and charset.
+ short = `data:${sourceStr.substring(commaIndex + 1)}`.slice(0, 100);
+ let result = { short, long: sourceStr };
+ gSourceNamesStore.set(source, result);
+ return result;
+ }
+ }
+
+ // If Scratchpad URI, like "Scratchpad/1"; no modifications,
+ // and short/long are the same.
+ if (isScratchpadScheme(sourceStr)) {
+ let result = { short: sourceStr, long: sourceStr };
+ gSourceNamesStore.set(source, result);
+ return result;
+ }
+
+ const parsedUrl = parseURL(sourceStr);
+
+ if (!parsedUrl) {
+ // Malformed URI.
+ long = sourceStr;
+ short = sourceStr.slice(0, 100);
+ } else {
+ host = parsedUrl.host;
+
+ long = parsedUrl.href;
+ if (parsedUrl.hash) {
+ long = long.replace(parsedUrl.hash, "");
+ }
+ if (parsedUrl.search) {
+ long = long.replace(parsedUrl.search, "");
+ }
+
+ short = parsedUrl.fileName;
+ // If `short` is just a slash, and we actually have a path,
+ // strip the slash and parse again to get a more useful short name.
+ // e.g. "http://foo.com/bar/" -> "bar", rather than "/"
+ if (short === "/" && parsedUrl.pathname !== "/") {
+ short = parseURL(long.replace(/\/$/, "")).fileName;
+ }
+ }
+
+ if (!short) {
+ if (!long) {
+ long = UNKNOWN_SOURCE_STRING;
+ }
+ short = long.slice(0, 100);
+ }
+
+ let result = { short, long, host };
+ gSourceNamesStore.set(source, result);
+ return result;
+}
+
+// For the functions below, we assume that we will never access the location
+// argument out of bounds, which is indeed the vast majority of cases.
+//
+// They are written this way because they are hot. Each frame is checked for
+// being content or chrome when processing the profile.
+
+function isColonSlashSlash(location, i = 0) {
+ return location.charCodeAt(++i) === CHAR_CODE_COLON &&
+ location.charCodeAt(++i) === CHAR_CODE_SLASH &&
+ location.charCodeAt(++i) === CHAR_CODE_SLASH;
+}
+
+/**
+ * Checks for a Scratchpad URI, like "Scratchpad/1"
+ */
+function isScratchpadScheme(location, i = 0) {
+ return location.charCodeAt(i) === CHAR_CODE_CAP_S &&
+ location.charCodeAt(++i) === CHAR_CODE_C &&
+ location.charCodeAt(++i) === CHAR_CODE_R &&
+ location.charCodeAt(++i) === CHAR_CODE_A &&
+ location.charCodeAt(++i) === CHAR_CODE_T &&
+ location.charCodeAt(++i) === CHAR_CODE_C &&
+ location.charCodeAt(++i) === CHAR_CODE_H &&
+ location.charCodeAt(++i) === CHAR_CODE_P &&
+ location.charCodeAt(++i) === CHAR_CODE_A &&
+ location.charCodeAt(++i) === CHAR_CODE_D &&
+ location.charCodeAt(++i) === CHAR_CODE_SLASH;
+}
+
+function isDataScheme(location, i = 0) {
+ return location.charCodeAt(i) === CHAR_CODE_D &&
+ location.charCodeAt(++i) === CHAR_CODE_A &&
+ location.charCodeAt(++i) === CHAR_CODE_T &&
+ location.charCodeAt(++i) === CHAR_CODE_A &&
+ location.charCodeAt(++i) === CHAR_CODE_COLON;
+}
+
+function isContentScheme(location, i = 0) {
+ let firstChar = location.charCodeAt(i);
+
+ switch (firstChar) {
+ // "http://" or "https://"
+ case CHAR_CODE_H:
+ if (location.charCodeAt(++i) === CHAR_CODE_T &&
+ location.charCodeAt(++i) === CHAR_CODE_T &&
+ location.charCodeAt(++i) === CHAR_CODE_P) {
+ if (location.charCodeAt(i + 1) === CHAR_CODE_S) {
+ ++i;
+ }
+ return isColonSlashSlash(location, i);
+ }
+ return false;
+
+ // "file://"
+ case CHAR_CODE_F:
+ if (location.charCodeAt(++i) === CHAR_CODE_I &&
+ location.charCodeAt(++i) === CHAR_CODE_L &&
+ location.charCodeAt(++i) === CHAR_CODE_E) {
+ return isColonSlashSlash(location, i);
+ }
+ return false;
+
+ // "app://"
+ case CHAR_CODE_A:
+ if (location.charCodeAt(++i) == CHAR_CODE_P &&
+ location.charCodeAt(++i) == CHAR_CODE_P) {
+ return isColonSlashSlash(location, i);
+ }
+ return false;
+
+ default:
+ return false;
+ }
+}
+
+function isChromeScheme(location, i = 0) {
+ let firstChar = location.charCodeAt(i);
+
+ switch (firstChar) {
+ // "chrome://"
+ case CHAR_CODE_C:
+ if (location.charCodeAt(++i) === CHAR_CODE_H &&
+ location.charCodeAt(++i) === CHAR_CODE_R &&
+ location.charCodeAt(++i) === CHAR_CODE_O &&
+ location.charCodeAt(++i) === CHAR_CODE_M &&
+ location.charCodeAt(++i) === CHAR_CODE_E) {
+ return isColonSlashSlash(location, i);
+ }
+ return false;
+
+ // "resource://"
+ case CHAR_CODE_R:
+ if (location.charCodeAt(++i) === CHAR_CODE_E &&
+ location.charCodeAt(++i) === CHAR_CODE_S &&
+ location.charCodeAt(++i) === CHAR_CODE_O &&
+ location.charCodeAt(++i) === CHAR_CODE_U &&
+ location.charCodeAt(++i) === CHAR_CODE_R &&
+ location.charCodeAt(++i) === CHAR_CODE_C &&
+ location.charCodeAt(++i) === CHAR_CODE_E) {
+ return isColonSlashSlash(location, i);
+ }
+ return false;
+
+ // "jar:file://"
+ case CHAR_CODE_J:
+ if (location.charCodeAt(++i) === CHAR_CODE_A &&
+ location.charCodeAt(++i) === CHAR_CODE_R &&
+ location.charCodeAt(++i) === CHAR_CODE_COLON &&
+ location.charCodeAt(++i) === CHAR_CODE_F &&
+ location.charCodeAt(++i) === CHAR_CODE_I &&
+ location.charCodeAt(++i) === CHAR_CODE_L &&
+ location.charCodeAt(++i) === CHAR_CODE_E) {
+ return isColonSlashSlash(location, i);
+ }
+ return false;
+
+ default:
+ return false;
+ }
+}
+
+/**
+ * A utility method to get the file name from a sourcemapped location
+ * The sourcemap location can be in any form. This method returns a
+ * formatted file name for different cases like Windows or OSX.
+ * @param source
+ * @returns String
+ */
+function getSourceMappedFile(source) {
+ // If sourcemapped source is a OSX path, return
+ // the characters after last "/".
+ // If sourcemapped source is a Windowss path, return
+ // the characters after last "\\".
+ if (source.lastIndexOf("/") >= 0) {
+ source = source.slice(source.lastIndexOf("/") + 1);
+ } else if (source.lastIndexOf("\\") >= 0) {
+ source = source.slice(source.lastIndexOf("\\") + 1);
+ }
+ return source;
+}
+
+exports.parseURL = parseURL;
+exports.getSourceNames = getSourceNames;
+exports.isScratchpadScheme = isScratchpadScheme;
+exports.isChromeScheme = isChromeScheme;
+exports.isContentScheme = isContentScheme;
+exports.isDataScheme = isDataScheme;
+exports.getSourceMappedFile = getSourceMappedFile;