/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ /* vim: set ft=javascript ts=2 et sw=2 tw=80: */ /* 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/. */ const {Cc, Cu, Ci} = require("chrome"); const PSEUDO_CLASSES = [":hover", ":active", ":focus"]; const ENSURE_SELECTION_VISIBLE_DELAY = 50; // ms Cu.import("resource://gre/modules/XPCOMUtils.jsm"); Cu.import("resource:///modules/devtools/DOMHelpers.jsm"); Cu.import("resource://gre/modules/Services.jsm"); Cu.import("resource:///modules/devtools/ViewHelpers.jsm"); const ELLIPSIS = Services.prefs.getComplexValue("intl.ellipsis", Ci.nsIPrefLocalizedString).data; const MAX_LABEL_LENGTH = 40; let promise = require("resource://gre/modules/Promise.jsm").Promise; const LOW_PRIORITY_ELEMENTS = { "HEAD": true, "BASE": true, "BASEFONT": true, "ISINDEX": true, "LINK": true, "META": true, "SCRIPT": true, "STYLE": true, "TITLE": true, }; function resolveNextTick(value) { let deferred = promise.defer(); Services.tm.mainThread.dispatch(() => { try { deferred.resolve(value); } catch(ex) { console.error(ex); } }, Ci.nsIThread.DISPATCH_NORMAL); return deferred.promise; } /////////////////////////////////////////////////////////////////////////// //// HTML Breadcrumbs /** * Display the ancestors of the current node and its children. * Only one "branch" of children are displayed (only one line). * * FIXME: Bug 822388 - Use the BreadcrumbsWidget in the Inspector. * * Mechanism: * . If no nodes displayed yet: * then display the ancestor of the selected node and the selected node; * else select the node; * . If the selected node is the last node displayed, append its first (if any). */ function HTMLBreadcrumbs(aInspector) { this.inspector = aInspector; this.selection = this.inspector.selection; this.chromeWin = this.inspector.panelWin; this.chromeDoc = this.inspector.panelDoc; this.DOMHelpers = new DOMHelpers(this.chromeWin); this._init(); } exports.HTMLBreadcrumbs = HTMLBreadcrumbs; HTMLBreadcrumbs.prototype = { get walker() this.inspector.walker, _init: function BC__init() { this.container = this.chromeDoc.getElementById("inspector-breadcrumbs"); // These separators are used for CSS purposes only, and are positioned // off screen, but displayed with -moz-element. this.separators = this.chromeDoc.createElement("box"); this.separators.className = "breadcrumb-separator-container"; this.separators.innerHTML = "" + "" + ""; this.container.parentNode.appendChild(this.separators); this.container.addEventListener("mousedown", this, true); this.container.addEventListener("keypress", this, true); this.container.addEventListener("mouseover", this, true); this.container.addEventListener("mouseleave", this, true); // We will save a list of already displayed nodes in this array. this.nodeHierarchy = []; // Last selected node in nodeHierarchy. this.currentIndex = -1; // By default, hide the arrows. We let the show them // in case of overflow. this.container.removeAttribute("overflows"); this.container._scrollButtonUp.collapsed = true; this.container._scrollButtonDown.collapsed = true; this.onscrollboxreflow = () => { if (this.container._scrollButtonDown.collapsed) this.container.removeAttribute("overflows"); else this.container.setAttribute("overflows", true); }; this.container.addEventListener("underflow", this.onscrollboxreflow, false); this.container.addEventListener("overflow", this.onscrollboxreflow, false); this.update = this.update.bind(this); this.updateSelectors = this.updateSelectors.bind(this); this.selection.on("new-node-front", this.update); this.selection.on("pseudoclass", this.updateSelectors); this.selection.on("attribute-changed", this.updateSelectors); this.inspector.on("markupmutation", this.update); this.update(); }, /** * Include in a promise's then() chain to reject the chain * when the breadcrumbs' selection has changed while the promise * was outstanding. */ selectionGuard: function() { let selection = this.selection.nodeFront; return (result) => { if (selection != this.selection.nodeFront) { return promise.reject("selection-changed"); } return result; } }, /** * Warn if rejection was caused by selection change, print an error otherwise. */ selectionGuardEnd: function(err) { if (err === "selection-changed") { console.warn("Asynchronous operation was aborted as selection changed."); } else { console.error(err); } }, /** * Build a string that represents the node: tagName#id.class1.class2. * * @param aNode The node to pretty-print * @returns a string */ prettyPrintNodeAsText: function BC_prettyPrintNodeText(aNode) { let text = aNode.tagName.toLowerCase(); if (aNode.isPseudoElement) { text = aNode.isBeforePseudoElement ? "::before" : "::after"; } if (aNode.id) { text += "#" + aNode.id; } if (aNode.className) { let classList = aNode.className.split(/\s+/); for (let i = 0; i < classList.length; i++) { text += "." + classList[i]; } } for (let pseudo of aNode.pseudoClassLocks) { text += pseudo; } return text; }, /** * Build