/* -*- 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/. */ "use strict"; const {Cu, Ci} = require("chrome"); Cu.import("resource://gre/modules/XPCOMUtils.jsm"); Cu.import("resource://gre/modules/Services.jsm"); const promise = require("promise"); const ENSURE_SELECTION_VISIBLE_DELAY = 50; // ms const ELLIPSIS = Services.prefs.getComplexValue("intl.ellipsis", Ci.nsIPrefLocalizedString).data; const MAX_LABEL_LENGTH = 40; const LOW_PRIORITY_ELEMENTS = { "HEAD": true, "BASE": true, "BASEFONT": true, "ISINDEX": true, "LINK": true, "META": true, "SCRIPT": true, "STYLE": true, "TITLE": true }; /** * 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). * * @param {InspectorPanel} inspector The inspector hosting this widget. */ function HTMLBreadcrumbs(inspector) { this.inspector = inspector; this.selection = this.inspector.selection; this.chromeWin = this.inspector.panelWin; this.chromeDoc = this.inspector.panelDoc; this._init(); } exports.HTMLBreadcrumbs = HTMLBreadcrumbs; HTMLBreadcrumbs.prototype = { get walker() { return this.inspector.walker; }, _init: function() { 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. * @param {Error} err */ 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 {NodeFront} node The node to pretty-print * @return {String} */ prettyPrintNodeAsText: function(node) { let text = node.tagName.toLowerCase(); if (node.isPseudoElement) { text = node.isBeforePseudoElement ? "::before" : "::after"; } if (node.id) { text += "#" + node.id; } if (node.className) { let classList = node.className.split(/\s+/); for (let i = 0; i < classList.length; i++) { text += "." + classList[i]; } } for (let pseudo of node.pseudoClassLocks) { text += pseudo; } return text; }, /** * Build