diff --git a/browser/devtools/shared/profiler/frame-utils.js b/browser/devtools/shared/profiler/frame-utils.js index 5d8b1f3a7030..c878274ecaae 100644 --- a/browser/devtools/shared/profiler/frame-utils.js +++ b/browser/devtools/shared/profiler/frame-utils.js @@ -9,37 +9,122 @@ loader.lazyRequireGetter(this, "Services"); loader.lazyRequireGetter(this, "CATEGORY_OTHER", "devtools/shared/profiler/global", true); +// 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_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_0 = "0".charCodeAt(0); +const CHAR_CODE_9 = "9".charCodeAt(0); + +const CHAR_CODE_LPAREN = "(".charCodeAt(0); +const CHAR_CODE_COLON = ":".charCodeAt(0); +const CHAR_CODE_SLASH = "/".charCodeAt(0); + // The cache used in the `nsIURL` function. const gNSURLStore = new Map(); -const CHROME_SCHEMES = ["chrome://", "resource://", "jar:file://"]; -const CONTENT_SCHEMES = ["http://", "https://", "file://", "app://"]; /** * Parses the raw location of this function call to retrieve the actual * function name, source url, host name, line and column. */ -exports.parseLocation = function parseLocation (frame) { +exports.parseLocation = function parseLocation(location, fallbackLine, fallbackColumn) { // Parse the `location` for the function name, source url, line, column etc. - let lineAndColumn = frame.location.match(/((:\d+)*)\)?$/)[1]; - let [, line, column] = lineAndColumn.split(":"); - line = line || frame.line; - column = column || frame.column; - let firstParenIndex = frame.location.indexOf("("); - let lineAndColumnIndex = frame.location.indexOf(lineAndColumn); - let resource = frame.location.substring(firstParenIndex + 1, lineAndColumnIndex); + let line, column, url; + + // These two indices are used to extract the resource substring, which is + // location[firstParenIndex + 1 .. lineAndColumnIndex]. + // + // The resource substring is extracted iff a line number was found. There + // may be no parentheses, in which case the substring starts at 0. + // + // For example, take "foo (bar.js:1)". + // ^ ^ + // | -----+ + // +-------+ | + // | | + // firstParenIndex will point to -+ | + // | + // lineAndColumnIndex will point to --+ + // + // For an example without parentheses, take "bar.js:2". + // ^ ^ + // | | + // firstParenIndex will point to -----------+ | + // | + // lineAndColumIndex will point to ----------------+ + let firstParenIndex = -1; + let lineAndColumnIndex = -1; + + // Compute firstParenIndex and lineAndColumnIndex. If lineAndColumnIndex is + // found, also extract the line and column. + for (let i = 0; i < location.length; i++) { + let c = location.charCodeAt(i); + + // The url and line information might be inside parentheses. + if (c === CHAR_CODE_LPAREN) { + if (firstParenIndex < 0) { + firstParenIndex = i; + } + continue; + } + + // Look for numbers after colons, twice. Firstly for the line, secondly + // for the column. + if (c === CHAR_CODE_COLON) { + if (isNumeric(location.charCodeAt(i + 1))) { + // If we found a line number, remember when it starts. + if (lineAndColumnIndex < 0) { + lineAndColumnIndex = i; + } + + let start = ++i; + let length = 1; + while (isNumeric(location.charCodeAt(++i))) { + length++; + } + + if (!line) { + line = location.substr(start, length); + } else if (!column) { + column = location.substr(start, length); + } + + break; + } + } + } + + let uri; + if (lineAndColumnIndex > 0) { + let resource = location.substring(firstParenIndex + 1, lineAndColumnIndex); + url = resource.split(" -> ").pop(); + if (url) { + uri = nsIURL(url); + } + } - let url = resource.split(" -> ").pop(); - let uri = nsIURL(url); let functionName, fileName, hostName; // If the URI digged out from the `location` is valid, this is a JS frame. if (uri) { - functionName = frame.location.substring(0, firstParenIndex - 1); + functionName = location.substring(0, firstParenIndex - 1); fileName = (uri.fileName + (uri.ref ? "#" + uri.ref : "")) || "/"; hostName = getHost(url, uri.host); } else { - functionName = frame.location; + functionName = location; url = null; } @@ -51,37 +136,38 @@ exports.parseLocation = function parseLocation (frame) { line: line, column: column }; -}, - -/** - * Determines if the given frame is the (root) frame. - * - * @param object frame - * @return boolean - */ -exports.isRoot = function isRoot({ location }) { - return location === "(root)"; }; /** -* Checks if the specified function represents a chrome or content frame. -* -* @param object frame -* The { category, location } properties of the frame. -* @return boolean -* True if a content frame, false if a chrome frame. -*/ -exports.isContent = function isContent (frame) { - if (exports.isRoot(frame)) { - return true; + * Checks if the specified function represents a chrome or content frame. + * + * @param string location + * The location of the frame. + * @param number category [optional] + * If a chrome frame, the category. + * @return boolean + * True if a content frame, false if a chrome frame. + */ +function isContent({ location, category }) { + // Only C++ stack frames have associated category information. + if (category) { + return false; } - // Only C++ stack frames have associated category information. - const { category, location } = frame; - return !!(!category && - !CHROME_SCHEMES.find(e => location.includes(e)) && - CONTENT_SCHEMES.find(e => location.includes(e))); + // Locations in frames with function names look like: + // "functionName (foo://bar)". + // Look for the starting left parenthesis, then try to match a + // scheme name. + for (let i = 0; i < location.length; i++) { + if (location.charCodeAt(i) === CHAR_CODE_LPAREN) { + return isContentScheme(location, i + 1); + } + } + + // If there was no left parenthesis, try matching from the start. + return isContentScheme(location, 0); } +exports.isContent = isContent; /** * This filters out platform data frames in a sample. With latest performance @@ -149,15 +235,106 @@ function nsIURL(url) { } gNSURLStore.set(url, uri); return uri; -} +}; /** * Takes a `host` string from an nsIURL instance and * returns the same string, or null, if it's an invalid host. */ function getHost (url, hostName) { - if (CHROME_SCHEMES.find(e => url.indexOf(e) === 0)) { - return null; - } - return hostName; + return isChromeScheme(url) ? null : hostName; +} + +// 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) { + return location.charCodeAt(++i) === CHAR_CODE_COLON && + location.charCodeAt(++i) === CHAR_CODE_SLASH && + location.charCodeAt(++i) === CHAR_CODE_SLASH; +} + +function isContentScheme(location, i) { + let firstChar = location.charCodeAt(i); + + switch (firstChar) { + case CHAR_CODE_H: // "http://" or "https://" + if (location.charCodeAt(++i) === CHAR_CODE_T && + location.charCodeAt(++i) === CHAR_CODE_T && + location.charCodeAt(++i) === CHAR_CODE_P) { + if (location.charCodeAt(i) === CHAR_CODE_S) { + ++i; + } + return isColonSlashSlash(location, i); + } + return false; + + case CHAR_CODE_F: // "file://" + 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; + + case CHAR_CODE_A: // "app://" + 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) { + let firstChar = location.charCodeAt(i); + + switch (firstChar) { + case CHAR_CODE_C: // "chrome://" + 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; + + case CHAR_CODE_R: // "resource://" + 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; + + case CHAR_CODE_J: // "jar:file://" + 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; + } +} + +function isNumeric(c) { + return c >= CHAR_CODE_0 && c <= CHAR_CODE_9; }