michael@0: /* -*- Mode: Javascript; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ michael@0: /* vim: set ft=javascript ts=2 et sw=2 tw=80: */ michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: const {Cc, Cu, Ci} = require("chrome"); michael@0: michael@0: const PSEUDO_CLASSES = [":hover", ":active", ":focus"]; michael@0: const ENSURE_SELECTION_VISIBLE_DELAY = 50; // ms michael@0: michael@0: Cu.import("resource://gre/modules/XPCOMUtils.jsm"); michael@0: Cu.import("resource:///modules/devtools/DOMHelpers.jsm"); michael@0: Cu.import("resource://gre/modules/Services.jsm"); michael@0: Cu.import("resource:///modules/devtools/ViewHelpers.jsm"); michael@0: const ELLIPSIS = Services.prefs.getComplexValue("intl.ellipsis", Ci.nsIPrefLocalizedString).data; michael@0: const MAX_LABEL_LENGTH = 40; michael@0: michael@0: let promise = require("devtools/toolkit/deprecated-sync-thenables"); michael@0: michael@0: const LOW_PRIORITY_ELEMENTS = { michael@0: "HEAD": true, michael@0: "BASE": true, michael@0: "BASEFONT": true, michael@0: "ISINDEX": true, michael@0: "LINK": true, michael@0: "META": true, michael@0: "SCRIPT": true, michael@0: "STYLE": true, michael@0: "TITLE": true, michael@0: }; michael@0: michael@0: function resolveNextTick(value) { michael@0: let deferred = promise.defer(); michael@0: Services.tm.mainThread.dispatch(() => { michael@0: try { michael@0: deferred.resolve(value); michael@0: } catch(ex) { michael@0: console.error(ex); michael@0: } michael@0: }, Ci.nsIThread.DISPATCH_NORMAL); michael@0: return deferred.promise; michael@0: } michael@0: michael@0: /////////////////////////////////////////////////////////////////////////// michael@0: //// HTML Breadcrumbs michael@0: michael@0: /** michael@0: * Display the ancestors of the current node and its children. michael@0: * Only one "branch" of children are displayed (only one line). michael@0: * michael@0: * FIXME: Bug 822388 - Use the BreadcrumbsWidget in the Inspector. michael@0: * michael@0: * Mechanism: michael@0: * . If no nodes displayed yet: michael@0: * then display the ancestor of the selected node and the selected node; michael@0: * else select the node; michael@0: * . If the selected node is the last node displayed, append its first (if any). michael@0: */ michael@0: function HTMLBreadcrumbs(aInspector) michael@0: { michael@0: this.inspector = aInspector; michael@0: this.selection = this.inspector.selection; michael@0: this.chromeWin = this.inspector.panelWin; michael@0: this.chromeDoc = this.inspector.panelDoc; michael@0: this.DOMHelpers = new DOMHelpers(this.chromeWin); michael@0: this._init(); michael@0: } michael@0: michael@0: exports.HTMLBreadcrumbs = HTMLBreadcrumbs; michael@0: michael@0: HTMLBreadcrumbs.prototype = { michael@0: get walker() this.inspector.walker, michael@0: michael@0: _init: function BC__init() michael@0: { michael@0: this.container = this.chromeDoc.getElementById("inspector-breadcrumbs"); michael@0: michael@0: // These separators are used for CSS purposes only, and are positioned michael@0: // off screen, but displayed with -moz-element. michael@0: this.separators = this.chromeDoc.createElement("box"); michael@0: this.separators.className = "breadcrumb-separator-container"; michael@0: this.separators.innerHTML = michael@0: "" + michael@0: "" + michael@0: ""; michael@0: this.container.parentNode.appendChild(this.separators); michael@0: michael@0: this.container.addEventListener("mousedown", this, true); michael@0: this.container.addEventListener("keypress", this, true); michael@0: michael@0: // We will save a list of already displayed nodes in this array. michael@0: this.nodeHierarchy = []; michael@0: michael@0: // Last selected node in nodeHierarchy. michael@0: this.currentIndex = -1; michael@0: michael@0: // By default, hide the arrows. We let the show them michael@0: // in case of overflow. michael@0: this.container.removeAttribute("overflows"); michael@0: this.container._scrollButtonUp.collapsed = true; michael@0: this.container._scrollButtonDown.collapsed = true; michael@0: michael@0: this.onscrollboxreflow = function() { michael@0: if (this.container._scrollButtonDown.collapsed) michael@0: this.container.removeAttribute("overflows"); michael@0: else michael@0: this.container.setAttribute("overflows", true); michael@0: }.bind(this); michael@0: michael@0: this.container.addEventListener("underflow", this.onscrollboxreflow, false); michael@0: this.container.addEventListener("overflow", this.onscrollboxreflow, false); michael@0: michael@0: this.update = this.update.bind(this); michael@0: this.updateSelectors = this.updateSelectors.bind(this); michael@0: this.selection.on("new-node-front", this.update); michael@0: this.selection.on("pseudoclass", this.updateSelectors); michael@0: this.selection.on("attribute-changed", this.updateSelectors); michael@0: this.inspector.on("markupmutation", this.update); michael@0: this.update(); michael@0: }, michael@0: michael@0: /** michael@0: * Include in a promise's then() chain to reject the chain michael@0: * when the breadcrumbs' selection has changed while the promise michael@0: * was outstanding. michael@0: */ michael@0: selectionGuard: function() { michael@0: let selection = this.selection.nodeFront; michael@0: return (result) => { michael@0: if (selection != this.selection.nodeFront) { michael@0: return promise.reject("selection-changed"); michael@0: } michael@0: return result; michael@0: } michael@0: }, michael@0: michael@0: /** michael@0: * Print any errors (except selection guard errors). michael@0: */ michael@0: selectionGuardEnd: function(err) { michael@0: if (err != "selection-changed") { michael@0: console.error(err); michael@0: } michael@0: promise.reject(err); michael@0: }, michael@0: michael@0: /** michael@0: * Build a string that represents the node: tagName#id.class1.class2. michael@0: * michael@0: * @param aNode The node to pretty-print michael@0: * @returns a string michael@0: */ michael@0: prettyPrintNodeAsText: function BC_prettyPrintNodeText(aNode) michael@0: { michael@0: let text = aNode.tagName.toLowerCase(); michael@0: if (aNode.id) { michael@0: text += "#" + aNode.id; michael@0: } michael@0: michael@0: if (aNode.className) { michael@0: let classList = aNode.className.split(/\s+/); michael@0: for (let i = 0; i < classList.length; i++) { michael@0: text += "." + classList[i]; michael@0: } michael@0: } michael@0: michael@0: for (let pseudo of aNode.pseudoClassLocks) { michael@0: text += pseudo; michael@0: } michael@0: michael@0: return text; michael@0: }, michael@0: michael@0: michael@0: /** michael@0: * Build