add_task(function* () { let parsed = sourceUtils.parseURL("https://foo.com:8888/boo/bar.js?q=query"); equal(parsed.fileName, "bar.js", "parseURL parsed valid fileName"); equal(parsed.host, "foo.com:8888", "parseURL parsed valid host"); equal(parsed.hostname, "foo.com", "parseURL parsed valid hostname"); equal(parsed.port, "8888", "parseURL parsed valid port"); equal(parsed.href, "https://foo.com:8888/boo/bar.js?q=query", "parseURL parsed valid href"); parsed = sourceUtils.parseURL("https://foo.com"); equal(parsed.host, "foo.com", "parseURL parsed valid host when no port given"); equal(parsed.hostname, "foo.com", "parseURL parsed valid hostname when no port given"); equal(sourceUtils.parseURL("self-hosted"), null, "parseURL returns `null` for invalid URLs"); });
/** * Parses the raw location of this function call to retrieve the actual * function name, source url, host name, line and column. */ function parseLocation(location, fallbackLine, fallbackColumn) { // Parse the `location` for the function name, source url, line, column etc. let line, column, url; // These two indices are used to extract the resource substring, which is // location[parenIndex + 1 .. lineAndColumnIndex]. // // There are 3 variants of location strings in the profiler (with optional // column numbers): // 1) "name (resource:line)" // 2) "resource:line" // 3) "resource" // // For example for (1), take "foo (bar.js:1)". // ^ ^ // | | // | | // | | // parenIndex will point to ------+ | // | // lineAndColumnIndex will point to -----+ // // For an example without parentheses, take "bar.js:2". // ^ ^ // | | // parenIndex will point to ----------------+ | // | // lineAndColumIndex will point to ----------------+ // // To parse, we look for the last occurrence of the string ' ('. // // For 1), all occurrences of space ' ' characters in the resource string // are urlencoded, so the last occurrence of ' (' is the separator between // the function name and the resource. // // For 2) and 3), there can be no occurences of ' (' since ' ' characters // are urlencoded in the resource string. // // XXX: Note that 3) is ambiguous with Gecko Profiler marker locations like // "EnterJIT". We can't distinguish the two, so we treat 3) like a function // name. let parenIndex = -1; let lineAndColumnIndex = -1; let lastCharCode = location.charCodeAt(location.length - 1); let i; if (lastCharCode === CHAR_CODE_RPAREN) { // Case 1) i = location.length - 2; } else if (isNumeric(lastCharCode)) { // Case 2) i = location.length - 1; } else { // Case 3) i = 0; } if (i !== 0) { // Look for a :number. let end = i; while (isNumeric(location.charCodeAt(i))) { i--; } if (location.charCodeAt(i) === CHAR_CODE_COLON) { column = location.substr(i + 1, end - i); i--; } // Look for a preceding :number. end = i; while (isNumeric(location.charCodeAt(i))) { i--; } // If two were found, the first is the line and the second is the // column. If only a single :number was found, then it is the line number. if (location.charCodeAt(i) === CHAR_CODE_COLON) { line = location.substr(i + 1, end - i); lineAndColumnIndex = i; i--; } else { lineAndColumnIndex = i + 1; line = column; column = undefined; } } // Look for the last occurrence of ' (' in case 1). if (lastCharCode === CHAR_CODE_RPAREN) { for (; i >= 0; i--) { if (location.charCodeAt(i) === CHAR_CODE_LPAREN && i > 0 && location.charCodeAt(i - 1) === CHAR_CODE_SPACE) { parenIndex = i; break; } } } let parsedUrl; if (lineAndColumnIndex > 0) { let resource = location.substring(parenIndex + 1, lineAndColumnIndex); url = resource.split(" -> ").pop(); if (url) { parsedUrl = parseURL(url); } } let functionName, fileName, port, host; line = line || fallbackLine; column = column || fallbackColumn; // If the URL digged out from the `location` is valid, this is a JS frame. if (parsedUrl) { functionName = location.substring(0, parenIndex - 1); fileName = parsedUrl.fileName; port = parsedUrl.port; host = parsedUrl.host; // Check for the case of the filename containing eval // e.g. "file.js%20line%2065%20%3E%20eval" let evalIndex = fileName.indexOf(EVAL_TOKEN); if (evalIndex !== -1 && evalIndex === (fileName.length - EVAL_TOKEN.length)) { // Match the filename let evalLine = line; let [, _fileName, , _line] = fileName.match(/(.+)(%20line%20(\d+)%20%3E%20eval)/) || []; fileName = `${_fileName} (eval:${evalLine})`; line = _line; assert(_fileName !== undefined, "Filename could not be found from an eval location site"); assert(_line !== undefined, "Line could not be found from an eval location site"); // Match the url as well [, url] = url.match(/(.+)( line (\d+) > eval)/) || []; assert(url !== undefined, "The URL could not be parsed correctly from an eval location site"); } } else { functionName = location; url = null; } return { functionName, fileName, host, port, url, line, column }; }
render() { let frame, isSourceMapped; const { onClick, showFunctionName, showAnonymousFunctionName, showHost, showEmptyPathAsHost, showFullSourceUrl } = this.props; if (this.state && this.state.isSourceMapped && this.state.frame) { frame = this.state.frame; isSourceMapped = this.state.isSourceMapped; } else { frame = this.props.frame; } // If the resource was loaded by browser-loader.js, `frame.source` looks like: // resource://devtools/shared/base-loader.js -> resource://devtools/path/to/file.js . // What's needed is only the last part after " -> ". const source = frame.source ? String(frame.source).split(" -> ").pop() : ""; const line = frame.line != void 0 ? Number(frame.line) : null; const column = frame.column != void 0 ? Number(frame.column) : null; const { short, long, host } = getSourceNames(source); const unicodeShort = getUnicodeUrlPath(short); const unicodeLong = getUnicodeUrl(long); const unicodeHost = host ? getUnicodeHostname(host) : ""; // Reparse the URL to determine if we should link this; `getSourceNames` // has already cached this indirectly. We don't want to attempt to // link to "self-hosted" and "(unknown)". However, we do want to link // to Scratchpad URIs. // Source mapped sources might not necessary linkable, but they // are still valid in the debugger. const isLinkable = !!(isScratchpadScheme(source) || parseURL(source)) || isSourceMapped; const elements = []; const sourceElements = []; let sourceEl; let tooltip = unicodeLong; // Exclude all falsy values, including `0`, as line numbers start with 1. if (line) { tooltip += `:${line}`; // Intentionally exclude 0 if (column) { tooltip += `:${column}`; } } const attributes = { "data-url": long, className: "frame-link", }; if (showFunctionName) { let functionDisplayName = frame.functionDisplayName; if (!functionDisplayName && showAnonymousFunctionName) { functionDisplayName = webl10n.getStr("stacktrace.anonymousFunction"); } if (functionDisplayName) { elements.push( dom.span({ key: "function-display-name", className: "frame-link-function-display-name", }, functionDisplayName), " " ); } } let displaySource = showFullSourceUrl ? unicodeLong : unicodeShort; if (isSourceMapped) { displaySource = getSourceMappedFile(displaySource); } else if (showEmptyPathAsHost && (displaySource === "" || displaySource === "/")) { displaySource = host; } sourceElements.push(dom.span({ key: "filename", className: "frame-link-filename", }, displaySource)); // If we have a line number > 0. if (line) { let lineInfo = `:${line}`; // Add `data-line` attribute for testing attributes["data-line"] = line; // Intentionally exclude 0 if (column) { lineInfo += `:${column}`; // Add `data-column` attribute for testing attributes["data-column"] = column; } sourceElements.push(dom.span({ key: "line", className: "frame-link-line" }, lineInfo)); } // Inner el is useful for achieving ellipsis on the left and correct LTR/RTL // ordering. See CSS styles for frame-link-source-[inner] and bug 1290056. const sourceInnerEl = dom.span({ key: "source-inner", className: "frame-link-source-inner", title: isLinkable ? l10n.getFormatStr("frame.viewsourceindebugger", tooltip) : tooltip, }, sourceElements); // If source is not a URL (self-hosted, eval, etc.), don't make // it an anchor link, as we can't link to it. if (isLinkable) { sourceEl = dom.a({ onClick: e => { e.preventDefault(); e.stopPropagation(); onClick(this.getSourceForClick({...frame, source})); }, href: source, className: "frame-link-source", draggable: false, }, sourceInnerEl); } else { sourceEl = dom.span({ key: "source", className: "frame-link-source", }, sourceInnerEl); } elements.push(sourceEl); if (showHost && unicodeHost) { elements.push(" "); elements.push(dom.span({ key: "host", className: "frame-link-host", }, unicodeHost)); } return dom.span(attributes, ...elements); }
} else { frame = this.props.frame; } let source = frame.source ? String(frame.source) : ""; let line = frame.line != void 0 ? Number(frame.line) : null; let column = frame.column != void 0 ? Number(frame.column) : null; const { short, long, host } = getSourceNames(source); // Reparse the URL to determine if we should link this; `getSourceNames` // has already cached this indirectly. We don't want to attempt to // link to "self-hosted" and "(unknown)". However, we do want to link // to Scratchpad URIs. // Source mapped sources might not necessary linkable, but they // are still valid in the debugger. const isLinkable = !!(isScratchpadScheme(source) || parseURL(source)) || isSourceMapped; const elements = []; const sourceElements = []; let sourceEl; let tooltip = long; // If the source is linkable and line > 0 const shouldDisplayLine = isLinkable && line; // Exclude all falsy values, including `0`, as even // a number 0 for line doesn't make sense, and should not be displayed. // If source isn't linkable, don't attempt to append line and column // info, as this probably doesn't make sense. if (shouldDisplayLine) {
/** * Parses the raw location of this function call to retrieve the actual * function name, source url, host name, line and column. */ function parseLocation(location, fallbackLine, fallbackColumn) { // Parse the `location` for the function name, source url, line, column etc. let line, column, url; // These two indices are used to extract the resource substring, which is // location[parenIndex + 1 .. lineAndColumnIndex]. // // There are 3 variants of location strings in the profiler (with optional // column numbers): // 1) "name (resource:line)" // 2) "resource:line" // 3) "resource" // // For example for (1), take "foo (bar.js:1)". // ^ ^ // | | // | | // | | // parenIndex will point to ------+ | // | // lineAndColumnIndex will point to -----+ // // For an example without parentheses, take "bar.js:2". // ^ ^ // | | // parenIndex will point to ----------------+ | // | // lineAndColumIndex will point to ----------------+ // // To parse, we look for the last occurrence of the string ' ('. // // For 1), all occurrences of space ' ' characters in the resource string // are urlencoded, so the last occurrence of ' (' is the separator between // the function name and the resource. // // For 2) and 3), there can be no occurences of ' (' since ' ' characters // are urlencoded in the resource string. // // XXX: Note that 3) is ambiguous with SPS marker locations like // "EnterJIT". We can't distinguish the two, so we treat 3) like a function // name. let parenIndex = -1; let lineAndColumnIndex = -1; let lastCharCode = location.charCodeAt(location.length - 1); let i; if (lastCharCode === CHAR_CODE_RPAREN) { // Case 1) i = location.length - 2; } else if (isNumeric(lastCharCode)) { // Case 2) i = location.length - 1; } else { // Case 3) i = 0; } if (i !== 0) { // Look for a :number. let end = i; while (isNumeric(location.charCodeAt(i))) { i--; } if (location.charCodeAt(i) === CHAR_CODE_COLON) { column = location.substr(i + 1, end - i); i--; } // Look for a preceding :number. end = i; while (isNumeric(location.charCodeAt(i))) { i--; } // If two were found, the first is the line and the second is the // column. If only a single :number was found, then it is the line number. if (location.charCodeAt(i) === CHAR_CODE_COLON) { line = location.substr(i + 1, end - i); lineAndColumnIndex = i; i--; } else { lineAndColumnIndex = i + 1; line = column; column = undefined; } } // Look for the last occurrence of ' (' in case 1). if (lastCharCode === CHAR_CODE_RPAREN) { for (; i >= 0; i--) { if (location.charCodeAt(i) === CHAR_CODE_LPAREN && i > 0 && location.charCodeAt(i - 1) === CHAR_CODE_SPACE) { parenIndex = i; break; } } } let parsedUrl; if (lineAndColumnIndex > 0) { let resource = location.substring(parenIndex + 1, lineAndColumnIndex); url = resource.split(" -> ").pop(); if (url) { parsedUrl = parseURL(url); } } let functionName, fileName, port, host; line = line || fallbackLine; column = column || fallbackColumn; // If the URL digged out from the `location` is valid, this is a JS frame. if (parsedUrl) { functionName = location.substring(0, parenIndex - 1); fileName = parsedUrl.fileName; port = parsedUrl.port; host = parsedUrl.host; } else { functionName = location; url = null; } return { functionName, fileName, host, port, url, line, column }; };