123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378 |
- /* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
- /* 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/. */
- /* globals document, window */
- /* import-globals-from ./debugger-controller.js */
- "use strict";
- // Maps known URLs to friendly source group names and put them at the
- // bottom of source list.
- var KNOWN_SOURCE_GROUPS = {
- "Add-on SDK": "resource://gre/modules/commonjs/",
- };
- KNOWN_SOURCE_GROUPS[L10N.getStr("anonymousSourcesLabel")] = "anonymous";
- var XULUtils = {
- /**
- * Create <command> elements within `commandset` with event handlers
- * bound to the `command` event
- *
- * @param commandset HTML Element
- * A <commandset> element
- * @param commands Object
- * An object where keys specify <command> ids and values
- * specify event handlers to be bound on the `command` event
- */
- addCommands: function (commandset, commands) {
- Object.keys(commands).forEach(name => {
- let node = document.createElement("command");
- node.id = name;
- // XXX bug 371900: the command element must have an oncommand
- // attribute as a string set by `setAttribute` for keys to use it
- node.setAttribute("oncommand", " ");
- node.addEventListener("command", commands[name]);
- commandset.appendChild(node);
- });
- }
- };
- // Used to detect minification for automatic pretty printing
- const SAMPLE_SIZE = 50; // no of lines
- const INDENT_COUNT_THRESHOLD = 5; // percentage
- const CHARACTER_LIMIT = 250; // line character limit
- /**
- * Utility functions for handling sources.
- */
- var SourceUtils = {
- _labelsCache: new Map(), // Can't use WeakMaps because keys are strings.
- _groupsCache: new Map(),
- _minifiedCache: new Map(),
- /**
- * Returns true if the specified url and/or content type are specific to
- * javascript files.
- *
- * @return boolean
- * True if the source is likely javascript.
- */
- isJavaScript: function (aUrl, aContentType = "") {
- return (aUrl && /\.jsm?$/.test(this.trimUrlQuery(aUrl))) ||
- aContentType.includes("javascript");
- },
- /**
- * Determines if the source text is minified by using
- * the percentage indented of a subset of lines
- *
- * @return object
- * A promise that resolves to true if source text is minified.
- */
- isMinified: function (key, text) {
- if (this._minifiedCache.has(key)) {
- return this._minifiedCache.get(key);
- }
- let isMinified;
- let lineEndIndex = 0;
- let lineStartIndex = 0;
- let lines = 0;
- let indentCount = 0;
- let overCharLimit = false;
- // Strip comments.
- text = text.replace(/\/\*[\S\s]*?\*\/|\/\/(.+|\n)/g, "");
- while (lines++ < SAMPLE_SIZE) {
- lineEndIndex = text.indexOf("\n", lineStartIndex);
- if (lineEndIndex == -1) {
- break;
- }
- if (/^\s+/.test(text.slice(lineStartIndex, lineEndIndex))) {
- indentCount++;
- }
- // For files with no indents but are not minified.
- if ((lineEndIndex - lineStartIndex) > CHARACTER_LIMIT) {
- overCharLimit = true;
- break;
- }
- lineStartIndex = lineEndIndex + 1;
- }
- isMinified =
- ((indentCount / lines) * 100) < INDENT_COUNT_THRESHOLD || overCharLimit;
- this._minifiedCache.set(key, isMinified);
- return isMinified;
- },
- /**
- * Clears the labels, groups and minify cache, populated by methods like
- * SourceUtils.getSourceLabel or Source Utils.getSourceGroup.
- * This should be done every time the content location changes.
- */
- clearCache: function () {
- this._labelsCache.clear();
- this._groupsCache.clear();
- this._minifiedCache.clear();
- },
- /**
- * Gets a unique, simplified label from a source url.
- *
- * @param string aUrl
- * The source url.
- * @return string
- * The simplified label.
- */
- getSourceLabel: function (aUrl) {
- let cachedLabel = this._labelsCache.get(aUrl);
- if (cachedLabel) {
- return cachedLabel;
- }
- let sourceLabel = null;
- for (let name of Object.keys(KNOWN_SOURCE_GROUPS)) {
- if (aUrl.startsWith(KNOWN_SOURCE_GROUPS[name])) {
- sourceLabel = aUrl.substring(KNOWN_SOURCE_GROUPS[name].length);
- }
- }
- if (!sourceLabel) {
- sourceLabel = this.trimUrl(aUrl);
- }
- let unicodeLabel = NetworkHelper.convertToUnicode(unescape(sourceLabel));
- this._labelsCache.set(aUrl, unicodeLabel);
- return unicodeLabel;
- },
- /**
- * Gets as much information as possible about the hostname and directory paths
- * of an url to create a short url group identifier.
- *
- * @param string aUrl
- * The source url.
- * @return string
- * The simplified group.
- */
- getSourceGroup: function (aUrl) {
- let cachedGroup = this._groupsCache.get(aUrl);
- if (cachedGroup) {
- return cachedGroup;
- }
- try {
- // Use an nsIURL to parse all the url path parts.
- var uri = Services.io.newURI(aUrl, null, null).QueryInterface(Ci.nsIURL);
- } catch (e) {
- // This doesn't look like a url, or nsIURL can't handle it.
- return "";
- }
- let groupLabel = uri.prePath;
- for (let name of Object.keys(KNOWN_SOURCE_GROUPS)) {
- if (aUrl.startsWith(KNOWN_SOURCE_GROUPS[name])) {
- groupLabel = name;
- }
- }
- let unicodeLabel = NetworkHelper.convertToUnicode(unescape(groupLabel));
- this._groupsCache.set(aUrl, unicodeLabel);
- return unicodeLabel;
- },
- /**
- * Trims the url by shortening it if it exceeds a certain length, adding an
- * ellipsis at the end.
- *
- * @param string aUrl
- * The source url.
- * @param number aLength [optional]
- * The expected source url length.
- * @param number aSection [optional]
- * The section to trim. Supported values: "start", "center", "end"
- * @return string
- * The shortened url.
- */
- trimUrlLength: function (aUrl, aLength, aSection) {
- aLength = aLength || SOURCE_URL_DEFAULT_MAX_LENGTH;
- aSection = aSection || "end";
- if (aUrl.length > aLength) {
- switch (aSection) {
- case "start":
- return ELLIPSIS + aUrl.slice(-aLength);
- break;
- case "center":
- return aUrl.substr(0, aLength / 2 - 1) + ELLIPSIS + aUrl.slice(-aLength / 2 + 1);
- break;
- case "end":
- return aUrl.substr(0, aLength) + ELLIPSIS;
- break;
- }
- }
- return aUrl;
- },
- /**
- * Trims the query part or reference identifier of a url string, if necessary.
- *
- * @param string aUrl
- * The source url.
- * @return string
- * The shortened url.
- */
- trimUrlQuery: function (aUrl) {
- let length = aUrl.length;
- let q1 = aUrl.indexOf("?");
- let q2 = aUrl.indexOf("&");
- let q3 = aUrl.indexOf("#");
- let q = Math.min(q1 != -1 ? q1 : length,
- q2 != -1 ? q2 : length,
- q3 != -1 ? q3 : length);
- return aUrl.slice(0, q);
- },
- /**
- * Trims as much as possible from a url, while keeping the label unique
- * in the sources container.
- *
- * @param string | nsIURL aUrl
- * The source url.
- * @param string aLabel [optional]
- * The resulting label at each step.
- * @param number aSeq [optional]
- * The current iteration step.
- * @return string
- * The resulting label at the final step.
- */
- trimUrl: function (aUrl, aLabel, aSeq) {
- if (!(aUrl instanceof Ci.nsIURL)) {
- try {
- // Use an nsIURL to parse all the url path parts.
- aUrl = Services.io.newURI(aUrl, null, null).QueryInterface(Ci.nsIURL);
- } catch (e) {
- // This doesn't look like a url, or nsIURL can't handle it.
- return aUrl;
- }
- }
- if (!aSeq) {
- let name = aUrl.fileName;
- if (name) {
- // This is a regular file url, get only the file name (contains the
- // base name and extension if available).
- // If this url contains an invalid query, unfortunately nsIURL thinks
- // it's part of the file extension. It must be removed.
- aLabel = aUrl.fileName.replace(/\&.*/, "");
- } else {
- // This is not a file url, hence there is no base name, nor extension.
- // Proceed using other available information.
- aLabel = "";
- }
- aSeq = 1;
- }
- // If we have a label and it doesn't only contain a query...
- if (aLabel && aLabel.indexOf("?") != 0) {
- // A page may contain multiple requests to the same url but with different
- // queries. It is *not* redundant to show each one.
- if (!DebuggerView.Sources.getItemForAttachment(e => e.label == aLabel)) {
- return aLabel;
- }
- }
- // Append the url query.
- if (aSeq == 1) {
- let query = aUrl.query;
- if (query) {
- return this.trimUrl(aUrl, aLabel + "?" + query, aSeq + 1);
- }
- aSeq++;
- }
- // Append the url reference.
- if (aSeq == 2) {
- let ref = aUrl.ref;
- if (ref) {
- return this.trimUrl(aUrl, aLabel + "#" + aUrl.ref, aSeq + 1);
- }
- aSeq++;
- }
- // Prepend the url directory.
- if (aSeq == 3) {
- let dir = aUrl.directory;
- if (dir) {
- return this.trimUrl(aUrl, dir.replace(/^\//, "") + aLabel, aSeq + 1);
- }
- aSeq++;
- }
- // Prepend the hostname and port number.
- if (aSeq == 4) {
- let host;
- try {
- // Bug 1261860: jar: URLs throw when accessing `hostPost`
- host = aUrl.hostPort;
- } catch (e) {}
- if (host) {
- return this.trimUrl(aUrl, host + "/" + aLabel, aSeq + 1);
- }
- aSeq++;
- }
- // Use the whole url spec but ignoring the reference.
- if (aSeq == 5) {
- return this.trimUrl(aUrl, aUrl.specIgnoringRef, aSeq + 1);
- }
- // Give up.
- return aUrl.spec;
- },
- parseSource: function (aDebuggerView, aParser) {
- let editor = aDebuggerView.editor;
- let contents = editor.getText();
- let location = aDebuggerView.Sources.selectedValue;
- let parsedSource = aParser.get(contents, location);
- return parsedSource;
- },
- findIdentifier: function (aEditor, parsedSource, x, y) {
- let editor = aEditor;
- // Calculate the editor's line and column at the current x and y coords.
- let hoveredPos = editor.getPositionFromCoords({ left: x, top: y });
- let hoveredOffset = editor.getOffset(hoveredPos);
- let hoveredLine = hoveredPos.line;
- let hoveredColumn = hoveredPos.ch;
- let scriptInfo = parsedSource.getScriptInfo(hoveredOffset);
- // If the script length is negative, we're not hovering JS source code.
- if (scriptInfo.length == -1) {
- return;
- }
- // Using the script offset, determine the actual line and column inside the
- // script, to use when finding identifiers.
- let scriptStart = editor.getPosition(scriptInfo.start);
- let scriptLineOffset = scriptStart.line;
- let scriptColumnOffset = (hoveredLine == scriptStart.line ? scriptStart.ch : 0);
- let scriptLine = hoveredLine - scriptLineOffset;
- let scriptColumn = hoveredColumn - scriptColumnOffset;
- let identifierInfo = parsedSource.getIdentifierAt({
- line: scriptLine + 1,
- column: scriptColumn,
- scriptIndex: scriptInfo.index
- });
- return identifierInfo;
- }
- };
|