Bug 1106272 - Part 1: Remove the selector highlight on hover and add a selector icon instead. r=miker
authorPatrick Brosset <pbrosset@mozilla.com>
Tue, 24 Mar 2015 14:42:27 +0100
changeset 266188 b078b546a896d3656428d7a5c76469471149c8d7
parent 266187 37c1fdfd86ba15b53dca42be7c55a8ba5c74407b
child 266189 bcfa33e0315c7d037b006c09540db2fe4cc7b3f0
push id830
push userraliiev@mozilla.com
push dateFri, 19 Jun 2015 19:24:37 +0000
treeherdermozilla-release@932614382a68 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmiker
bugs1106272
milestone39.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1106272 - Part 1: Remove the selector highlight on hover and add a selector icon instead. r=miker
browser/devtools/styleinspector/rule-view.js
browser/devtools/styleinspector/style-inspector-overlays.js
browser/themes/shared/devtools/ruleview.css
toolkit/locales/en-US/chrome/global/devtools/styleinspector.properties
--- a/browser/devtools/styleinspector/rule-view.js
+++ b/browser/devtools/styleinspector/rule-view.js
@@ -1202,16 +1202,97 @@ CssRuleView.prototype = {
       popupset = doc.createElementNS(XUL_NS, "popupset");
       doc.documentElement.appendChild(popupset);
     }
 
     popupset.appendChild(this._contextmenu);
   },
 
   /**
+   * Get an instance of SelectorHighlighter (used to highlight nodes that match
+   * selectors in the rule-view). A new instance is only created the first time
+   * this function is called. The same instance will then be returned.
+   * @return {Promise} Resolves to the instance of the highlighter.
+   */
+  getSelectorHighlighter: Task.async(function*() {
+    let utils = this.inspector.toolbox.highlighterUtils;
+    if (!utils.supportsCustomHighlighters()) {
+      return null;
+    }
+
+    if (this.selectorHighlighter) {
+      return this.selectorHighlighter;
+    }
+
+    try {
+      let h = yield utils.getHighlighterByType("SelectorHighlighter");
+      return this.selectorHighlighter = h;
+    } catch (e) {
+      // The SelectorHighlighter type could not be created in the current target.
+      // It could be an older server, or a XUL page.
+      return null;
+    }
+  }),
+
+  /**
+   * Highlight/unhighlight all the nodes that match a given set of selectors
+   * inside the document of the current selected node.
+   * Only one selector can be highlighted at a time, so calling the method a
+   * second time with a different selector will first unhighlight the previously
+   * highlighted nodes.
+   * Calling the method a second time with the same selector will just
+   * unhighlight the highlighted nodes.
+   *
+   * @param {DOMNode} The icon that was clicked to toggle the selector. The
+   * class 'highlighted' will be added when the selector is highlighted.
+   * @param {String} The selector used to find nodes in the page.
+   */
+  toggleSelectorHighlighter: function(selectorIcon, selector) {
+    if (this.lastSelectorIcon) {
+      this.lastSelectorIcon.classList.remove("highlighted");
+    }
+    selectorIcon.classList.remove("highlighted");
+
+    this.unhighlightSelector().then(() => {
+      if (selector !== this.highlightedSelector) {
+        this.highlightedSelector = selector;
+        selectorIcon.classList.add("highlighted");
+        this.lastSelectorIcon = selectorIcon;
+        this.highlightSelector(selector).catch(Cu.reportError);
+      } else {
+        this.highlightedSelector = null;
+      }
+    }, Cu.reportError);
+  },
+
+  highlightSelector: Task.async(function*(selector) {
+    let node = this.inspector.selection.nodeFront;
+
+    let highlighter = yield this.getSelectorHighlighter();
+    if (!highlighter) {
+      return;
+    }
+
+    yield highlighter.show(node, {
+      hideInfoBar: true,
+      hideGuides: true,
+      selector
+    });
+  }),
+
+  unhighlightSelector: Task.async(function*() {
+    let highlighter = yield this.getSelectorHighlighter();
+    if (!highlighter) {
+      return;
+    }
+
+    yield highlighter.hide();
+  }),
+
+  /**
    * Update the context menu. This means enabling or disabling menuitems as
    * appropriate.
    */
   _contextMenuUpdate: function() {
     let win = this.doc.defaultView;
 
     // Copy selection.
     let selection = win.getSelection();
@@ -1639,16 +1720,18 @@ CssRuleView.prototype = {
       this.element.removeChild(this.element.lastChild);
     }
   },
 
   /**
    * Clear the rule view.
    */
   clear: function() {
+    this.lastSelectorIcon = null;
+
     this._clearRules();
     this._viewedElement = null;
 
     if (this._elementStyle) {
       this._elementStyle.destroy();
       this._elementStyle = null;
     }
   },
@@ -1831,16 +1914,17 @@ CssRuleView.prototype = {
  * @param {Rule} aRule
  *        The Rule object we're editing.
  * @constructor
  */
 function RuleEditor(aRuleView, aRule) {
   this.ruleView = aRuleView;
   this.doc = this.ruleView.doc;
   this.rule = aRule;
+
   this.isEditable = !aRule.isSystem;
   // Flag that blocks updates of the selector and properties when it is
   // being edited
   this.isEditing = false;
 
   this._onNewProperty = this._onNewProperty.bind(this);
   this._newPropertyDestroy = this._newPropertyDestroy.bind(this);
   this._onSelectorDone = this._onSelectorDone.bind(this);
@@ -1898,16 +1982,30 @@ RuleEditor.prototype = {
     });
 
     let header = createChild(code, "div", {});
 
     this.selectorContainer = createChild(header, "span", {
       class: "ruleview-selectorcontainer"
     });
 
+    if (this.rule.domRule.type !== Ci.nsIDOMCSSRule.KEYFRAME_RULE &&
+        this.rule.domRule.selectors) {
+      let selector = this.rule.domRule.selectors.join(", ");
+
+      let selectorHighlighter = createChild(header, "span", {
+        class: "ruleview-selectorhighlighter" +
+               (this.ruleView.highlightedSelector === selector ? " highlighted": ""),
+        title: CssLogic.l10n("rule.selectorHighlighter.tooltip")
+      });
+      selectorHighlighter.addEventListener("click", () => {
+        this.ruleView.toggleSelectorHighlighter(selectorHighlighter, selector);
+      });
+    }
+
     this.selectorText = createChild(this.selectorContainer, "span", {
       class: "ruleview-selector theme-fg-color3"
     });
 
     if (this.isSelectorEditable) {
       this.selectorContainer.addEventListener("click", aEvent => {
         // Clicks within the selector shouldn't propagate any further.
         aEvent.stopPropagation();
--- a/browser/devtools/styleinspector/style-inspector-overlays.js
+++ b/browser/devtools/styleinspector/style-inspector-overlays.js
@@ -24,24 +24,16 @@ Cu.import("resource://gre/modules/Task.j
 Cu.import("resource://gre/modules/Services.jsm");
 
 const PREF_IMAGE_TOOLTIP_SIZE = "devtools.inspector.imagePreviewTooltipSize";
 
 // Types of existing tooltips
 const TOOLTIP_IMAGE_TYPE = "image";
 const TOOLTIP_FONTFAMILY_TYPE = "font-family";
 
-// Types of existing highlighters
-const HIGHLIGHTER_TRANSFORM_TYPE = "CssTransformHighlighter";
-const HIGHLIGHTER_SELECTOR_TYPE = "SelectorHighlighter";
-const HIGHLIGHTER_TYPES = [
-  HIGHLIGHTER_TRANSFORM_TYPE,
-  HIGHLIGHTER_SELECTOR_TYPE
-];
-
 // Types of nodes in the rule/computed-view
 const VIEW_NODE_SELECTOR_TYPE = exports.VIEW_NODE_SELECTOR_TYPE = 1;
 const VIEW_NODE_PROPERTY_TYPE = exports.VIEW_NODE_PROPERTY_TYPE = 2;
 const VIEW_NODE_VALUE_TYPE = exports.VIEW_NODE_VALUE_TYPE = 3;
 const VIEW_NODE_IMAGE_URL_TYPE = exports.VIEW_NODE_IMAGE_URL_TYPE = 4;
 
 /**
  * Manages all highlighters in the style-inspector.
@@ -116,35 +108,27 @@ HighlightersOverlay.prototype = {
     this._lastHovered = event.target;
 
     let nodeInfo = this.view.getNodeInfo(event.target);
     if (!nodeInfo) {
       return;
     }
 
     // Choose the type of highlighter required for the hovered node
-    let type, options;
+    let type;
     if (this._isRuleViewTransform(nodeInfo) ||
         this._isComputedViewTransform(nodeInfo)) {
-      type = HIGHLIGHTER_TRANSFORM_TYPE;
-    } else if (nodeInfo.type === VIEW_NODE_SELECTOR_TYPE) {
-      type = HIGHLIGHTER_SELECTOR_TYPE;
-      options = {
-        selector: nodeInfo.value,
-        hideInfoBar: true,
-        showOnly: "border",
-        region: "border"
-      };
+      type = "CssTransformHighlighter";
     }
 
     if (type) {
       this.highlighterShown = type;
       let node = this.view.inspector.selection.nodeFront;
       this._getHighlighter(type).then(highlighter => {
-        highlighter.show(node, options);
+        highlighter.show(node);
       });
     }
   },
 
   _onMouseLeave: function(event) {
     this._lastHovered = null;
     this._hideCurrent();
   },
--- a/browser/themes/shared/devtools/ruleview.css
+++ b/browser/themes/shared/devtools/ruleview.css
@@ -227,8 +227,24 @@
 
 .ruleview-selector {
   word-wrap: break-word;
 }
 
 .ruleview-selector-separator, .ruleview-selector-unmatched {
   color: #888;
 }
+
+.ruleview-selectorhighlighter {
+  background: url("chrome://browser/skin/devtools/vview-open-inspector.png") no-repeat 0 0;
+  padding-left: 16px;
+  margin-left: 5px;
+  cursor: pointer;
+}
+
+.ruleview-selectorhighlighter:hover {
+  background-position: -32px 0;
+}
+
+.ruleview-selectorhighlighter:active,
+.ruleview-selectorhighlighter.highlighted {
+  background-position: -16px 0;
+}
--- a/toolkit/locales/en-US/chrome/global/devtools/styleinspector.properties
+++ b/toolkit/locales/en-US/chrome/global/devtools/styleinspector.properties
@@ -63,16 +63,20 @@ helpLinkTitle=Read the documentation for
 # entered into the rule view a warning icon is displayed. This text is used for
 # the title attribute of the warning icon.
 rule.warning.title=Invalid property value
 
 # LOCALIZATION NOTE (ruleView.empty): Text displayed when the highlighter is
 # first opened and there's no node selected in the rule view.
 rule.empty=No element selected.
 
+# LOCALIZATION NOTE (ruleView.selectorHighlighter.tooltip): Text displayed in a
+# tooltip when the mouse is over a selector highlighter icon in the rule view.
+rule.selectorHighlighter.tooltip=Highlight all elements matching this selector
+
 # LOCALIZATION NOTE (ruleView.contextmenu.selectAll): Text displayed in the
 # rule view context menu.
 ruleView.contextmenu.selectAll=Select all
 
 # LOCALIZATION NOTE (ruleView.contextmenu.selectAll.accessKey): Access key for
 # the rule view context menu "Select all" entry.
 ruleView.contextmenu.selectAll.accessKey=A