diff -r 000000000000 -r 6474c204b198 browser/devtools/inspector/breadcrumbs.js --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/browser/devtools/inspector/breadcrumbs.js Wed Dec 31 06:09:35 2014 +0100 @@ -0,0 +1,708 @@ +/* -*- Mode: Javascript; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 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("devtools/toolkit/deprecated-sync-thenables"); + +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); + + // 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 = function() { + if (this.container._scrollButtonDown.collapsed) + this.container.removeAttribute("overflows"); + else + this.container.setAttribute("overflows", true); + }.bind(this); + + 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; + } + }, + + /** + * Print any errors (except selection guard errors). + */ + selectionGuardEnd: function(err) { + if (err != "selection-changed") { + console.error(err); + } + promise.reject(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.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