merge fx-team to m-c
authorRob Campbell <rcampbell@mozilla.com>
Tue, 18 Oct 2011 14:43:50 -0300
changeset 78931 7ed661aa832d8e0ffd0789f00de602956de3bb21
parent 78926 9e6aa5ee6425233b428323bb9c9fef2c1c625982 (current diff)
parent 78930 aa254d59e1660aa6832147522ad4e3b46185dae6 (diff)
child 78932 7f6ff9f22d6f3e4c20174f6757e073f68023ff28
child 79115 7b05a3d1f56e054d21716fabaebd6076756020b0
push id2769
push usermak77@bonardo.net
push dateWed, 19 Oct 2011 09:50:42 +0000
treeherdermozilla-inbound@3b2891425788 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
milestone10.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
merge fx-team to m-c
browser/base/content/test/tabview/browser_tabview_bug595020.js
--- a/browser/base/content/browser-tabview.js
+++ b/browser/base/content/browser-tabview.js
@@ -47,17 +47,16 @@ let TabView = {
   _initFrameCallbacks: [],
   _lastSessionGroupName: null,
   PREF_BRANCH: "browser.panorama.",
   PREF_FIRST_RUN: "browser.panorama.experienced_first_run",
   PREF_STARTUP_PAGE: "browser.startup.page",
   PREF_RESTORE_ENABLED_ONCE: "browser.panorama.session_restore_enabled_once",
   GROUPS_IDENTIFIER: "tabview-groups",
   VISIBILITY_IDENTIFIER: "tabview-visibility",
-  LAST_SESSION_GROUP_NAME_IDENTIFIER: "tabview-last-session-group-name",
 
   // ----------
   get windowTitle() {
     delete this.windowTitle;
     let brandBundle = document.getElementById("bundle_brand");
     let brandShortName = brandBundle.getString("brandShortName");
     let title = gNavigatorBundle.getFormattedString("tabView2.title", [brandShortName]);
     return this.windowTitle = title;
@@ -138,20 +137,16 @@ let TabView = {
         this._tabCloseEventListener = function(event) {
           if (!self._window && gBrowser.visibleTabs.length == 0)
             self._closedLastVisibleTabBeforeFrameInitialized = true;
         };
         gBrowser.tabContainer.addEventListener(
           "TabShow", this._tabShowEventListener, false);
         gBrowser.tabContainer.addEventListener(
           "TabClose", this._tabCloseEventListener, false);
-
-       // grab the last used group title
-       this._lastSessionGroupName = sessionstore.getWindowValue(window,
-         this.LAST_SESSION_GROUP_NAME_IDENTIFIER);
       }
     }
 
     Services.prefs.addObserver(this.PREF_BRANCH, this, false);
 
     this._initialized = true;
   },
 
@@ -274,40 +269,16 @@ let TabView = {
 
   // ----------
   toggle: function TabView_toggle() {
     if (this.isVisible())
       this.hide();
     else 
       this.show();
   },
-  
-  getActiveGroupName: function TabView_getActiveGroupName() {
-    if (!this._window)
-      return this._lastSessionGroupName;
-
-    // We get the active group this way, instead of querying
-    // GroupItems.getActiveGroupItem() because the tabSelect event
-    // will not have happened by the time the browser tries to
-    // update the title.
-    let groupItem = null;
-    let activeTab = window.gBrowser.selectedTab;
-    let activeTabItem = activeTab._tabViewTabItem;
-
-    if (activeTab.pinned) {
-      // It's an app tab, so it won't have a .tabItem. However, its .parent
-      // will already be set as the active group. 
-      groupItem = this._window.GroupItems.getActiveGroupItem();
-    } else if (activeTabItem) {
-      groupItem = activeTabItem.parent;
-    }
-
-    // groupItem may still be null, if the active tab is an orphan.
-    return groupItem ? groupItem.getTitle() : "";
-  },
 
   // ----------
   updateContextMenu: function TabView_updateContextMenu(tab, popup) {
     let separator = document.getElementById("context_tabViewNamedGroups");
     let isEmpty = true;
 
     while (popup.firstChild && popup.firstChild != separator)
       popup.removeChild(popup.firstChild);
--- a/browser/base/content/tabbrowser.xml
+++ b/browser/base/content/tabbrowser.xml
@@ -808,22 +808,16 @@
                             aBrowser.currentURI);
                 if (uri.scheme == "about")
                   newTitle = uri.spec + sep + newTitle;
                 else
                   newTitle = uri.prePath + sep + newTitle;
               }
             } catch (e) {}
 
-            if ("TabView" in window) {
-              let groupName = TabView.getActiveGroupName();
-              if (groupName)
-                newTitle = groupName + sep + newTitle;
-            }
-
             return newTitle;
           ]]>
         </body>
       </method>
 
       <method name="updateTitlebar">
         <body>
           <![CDATA[
--- a/browser/base/content/tabview/storage.js
+++ b/browser/base/content/tabview/storage.js
@@ -210,25 +210,16 @@ let Storage = {
   // Function: saveVisibilityData
   // Saves visibility for the given window.
   saveVisibilityData: function Storage_saveVisibilityData(win, data) {
     this._sessionStore.setWindowValue(
       win, win.TabView.VISIBILITY_IDENTIFIER, data);
   },
 
   // ----------
-  // Function: saveActiveGroupName
-  // Saves the active group's name for the given window.
-  saveActiveGroupName: function Storage_saveActiveGroupName(win) {
-    let groupName = win.TabView.getActiveGroupName();
-    this._sessionStore.setWindowValue(
-      win, win.TabView.LAST_SESSION_GROUP_NAME_IDENTIFIER, groupName);
-  },
-
-  // ----------
   // Function: saveData
   // Generic routine for saving data to a window.
   saveData: function Storage_saveData(win, id, data) {
     try {
       this._sessionStore.setWindowValue(win, id, JSON.stringify(data));
     } catch (e) {
       Utils.log("Error in saveData: "+e);
     }
--- a/browser/base/content/tabview/ui.js
+++ b/browser/base/content/tabview/ui.js
@@ -290,17 +290,16 @@ let UI = {
         self.isDOMWindowClosing = true;
 
         if (self.isTabViewVisible())
           GroupItems.removeHiddenGroups();
 
         TabItems.saveAll();
         TabItems.saveAllThumbnails({synchronously: true});
 
-        Storage.saveActiveGroupName(gWindow);
         self._save();
       }, false);
 
       // ___ load frame script
       let frameScript = "chrome://browser/content/tabview-content.js";
       gWindow.messageManager.loadFrameScript(frameScript, true);
 
       // ___ Done
--- a/browser/base/content/test/tabview/Makefile.in
+++ b/browser/base/content/test/tabview/Makefile.in
@@ -55,17 +55,16 @@ include $(topsrcdir)/config/rules.mk
                  browser_tabview_bug587503.js \
                  browser_tabview_bug587990.js \
                  browser_tabview_bug588265.js \
                  browser_tabview_bug589324.js \
                  browser_tabview_bug590606.js \
                  browser_tabview_bug591706.js \
                  browser_tabview_bug593283.js \
                  browser_tabview_bug594958.js \
-                 browser_tabview_bug595020.js \
                  browser_tabview_bug595191.js \
                  browser_tabview_bug595436.js \
                  browser_tabview_bug595518.js \
                  browser_tabview_bug595521.js \
                  browser_tabview_bug595560.js \
                  browser_tabview_bug595601.js \
                  browser_tabview_bug595804.js \
                  browser_tabview_bug595930.js \
deleted file mode 100644
--- a/browser/base/content/test/tabview/browser_tabview_bug595020.js
+++ /dev/null
@@ -1,39 +0,0 @@
-/* Any copyright is dedicated to the Public Domain.
-   http://creativecommons.org/publicdomain/zero/1.0/ */
-
-let ss = Cc["@mozilla.org/browser/sessionstore;1"].getService(Ci.nsISessionStore);
-
-let stateStartup = {windows:[
-  {tabs:[{entries:[{url:"about:home"}]}], extData:{"tabview-last-session-group-name":"title"}}
-]};
-
-function test() {
-  let assertWindowTitle = function (win, title) {
-    let browser = win.gBrowser.tabs[0].linkedBrowser;
-    let winTitle = win.gBrowser.getWindowTitleForBrowser(browser);
-
-    info('window title is: "' + winTitle + '"');
-    is(winTitle.indexOf(title), 0, "title starts with '" + title + "'");
-  };
-
-  let testGroupNameChange = function (win) {
-    showTabView(function () {
-      let cw = win.TabView.getContentWindow();
-      let groupItem = cw.GroupItems.groupItems[0];
-      groupItem.setTitle("new-title");
-
-      hideTabView(function () {
-        assertWindowTitle(win, "new-title");
-        finish();
-      }, win);
-    }, win);
-  };
-
-  waitForExplicitFinish();
-
-  newWindowWithState(stateStartup, function (win) {
-    registerCleanupFunction(function () win.close());
-    assertWindowTitle(win, "title");
-    testGroupNameChange(win);
-  });
-}
--- a/browser/devtools/styleinspector/CssHtmlTree.jsm
+++ b/browser/devtools/styleinspector/CssHtmlTree.jsm
@@ -279,17 +279,16 @@ CssHtmlTree.prototype = {
    *
    * @param {Event} aEvent the DOM Event object.
    */
   onlyUserStylesChanged: function CssHtmltree_onlyUserStylesChanged(aEvent)
   {
     this.cssLogic.sourceFilter = this.showOnlyUserStyles ?
                                  CssLogic.FILTER.ALL :
                                  CssLogic.FILTER.UA;
-    
     this.refreshPanel();
   },
 
   /**
    * Provide access to the path to get from document.body to the selected
    * element.
    *
    * @return {array} the array holding the path from document.body to the
@@ -439,21 +438,37 @@ PropertyView.prototype = {
    * An easy way to access the CssPropertyInfo behind this PropertyView.
    */
   get propertyInfo()
   {
     return this.tree.cssLogic.getPropertyInfo(this.name);
   },
 
   /**
+   * Does the property have any matched selectors?
+   */
+  get hasMatchedSelectors()
+  {
+    return this.propertyInfo.hasMatchedSelectors();
+  },
+
+  /**
+   * Does the property have any unmatched selectors?
+   */
+  get hasUnmatchedSelectors()
+  {
+    return this.propertyInfo.hasUnmatchedSelectors();
+  },
+
+  /**
    * Should this property be visible?
    */
   get visible()
   {
-    if (this.tree.showOnlyUserStyles && this.matchedSelectorCount == 0) {
+    if (this.tree.showOnlyUserStyles && !this.hasMatchedSelectors) {
       return false;
     }
 
     let searchTerm = this.tree.searchField.value.toLowerCase();
     if (searchTerm && this.name.toLowerCase().indexOf(searchTerm) == -1 &&
       this.value.toLowerCase().indexOf(searchTerm) == -1) {
       return false;
     }
@@ -465,32 +480,16 @@ PropertyView.prototype = {
    * Returns the className that should be assigned to the propertyView.
    */
   get className()
   {
     return this.visible ? "property-view" : "property-view-hidden";
   },
 
   /**
-   * The number of matched selectors.
-   */
-  get matchedSelectorCount()
-  {
-    return this.propertyInfo.matchedSelectors.length;
-  },
-
-  /**
-   * The number of unmatched selectors.
-   */
-  get unmatchedSelectorCount()
-  {
-    return this.propertyInfo.unmatchedSelectors.length;
-  },
-
-  /**
    * Refresh the panel's CSS property value.
    */
   refresh: function PropertyView_refresh()
   {
     this.element.className = this.className;
 
     if (this.prevViewedElement != this.tree.viewedElement) {
       this._matchedSelectorViews = null;
@@ -515,83 +514,48 @@ PropertyView.prototype = {
     this.refreshUnmatchedSelectors();
   },
 
   /**
    * Refresh the panel matched rules.
    */
   refreshMatchedSelectors: function PropertyView_refreshMatchedSelectors()
   {
-    this.matchedSelectorsTitleNode.innerHTML = this.matchedSelectorTitle();
-    this.matchedSelectorsContainer.hidden = this.matchedSelectorCount == 0;
+    let hasMatchedSelectors = this.hasMatchedSelectors;
+    this.matchedSelectorsContainer.hidden = !hasMatchedSelectors;
 
-    if (this.matchedExpanded && this.matchedSelectorCount > 0) {
+    if (this.matchedExpanded && hasMatchedSelectors) {
       CssHtmlTree.processTemplate(this.templateMatchedSelectors,
         this.matchedSelectorTable, this);
       this.matchedExpander.setAttribute("open", "");
     } else {
       this.matchedSelectorTable.innerHTML = "";
       this.matchedExpander.removeAttribute("open");
     }
   },
 
   /**
    * Refresh the panel unmatched rules.
    */
-  refreshUnmatchedSelectors: function PropertyView_refreshUnmatchedSelectors() {
-    this.unmatchedSelectorsTitleNode.innerHTML = this.unmatchedSelectorTitle();
-    this.unmatchedSelectorsContainer.hidden = this.unmatchedSelectorCount == 0;
+  refreshUnmatchedSelectors: function PropertyView_refreshUnmatchedSelectors()
+  {
+    let hasUnmatchedSelectors = this.hasUnmatchedSelectors;
+    this.unmatchedSelectorsContainer.hidden = !hasUnmatchedSelectors;
 
-    if (this.unmatchedExpanded && this.unmatchedSelectorCount > 0) {
+    if (this.unmatchedExpanded && hasUnmatchedSelectors) {
       CssHtmlTree.processTemplate(this.templateUnmatchedSelectors,
           this.unmatchedSelectorTable, this);
       this.unmatchedExpander.setAttribute("open", "");
     } else {
       this.unmatchedSelectorTable.innerHTML = "";
       this.unmatchedExpander.removeAttribute("open");
     }
   },
 
   /**
-   * Compute the title of the matched selector expander. The title includes the
-   * number of selectors that match the currently selected element.
-   *
-   * @return {string} The rule title.
-   */
-  matchedSelectorTitle: function PropertyView_matchedSelectorTitle()
-  {
-    let result = "";
-
-    if (this.matchedSelectorCount > 0) {
-      let str = CssHtmlTree.l10n("property.numberOfMatchedSelectors");
-      result = PluralForm.get(this.matchedSelectorCount, str)
-                         .replace("#1", this.matchedSelectorCount);
-    }
-    return result;
-  },
-
-  /**
-   * Compute the title of the unmatched selector expander. The title includes
-   * the number of selectors that match the currently selected element.
-   *
-   * @return {string} The rule title.
-   */
-  unmatchedSelectorTitle: function PropertyView_unmatchedSelectorTitle()
-  {
-    let result = "";
-
-    if (this.unmatchedSelectorCount > 0) {
-      let str = CssHtmlTree.l10n("property.numberOfUnmatchedSelectors");
-      result = PluralForm.get(this.unmatchedSelectorCount, str)
-                         .replace("#1", this.unmatchedSelectorCount);
-    }
-    return result;
-  },
-
-  /**
    * Provide access to the matched SelectorViews that we are currently
    * displaying.
    */
   get matchedSelectorViews()
   {
     if (!this._matchedSelectorViews) {
       this._matchedSelectorViews = [];
       this.propertyInfo.matchedSelectors.forEach(
--- a/browser/devtools/styleinspector/CssLogic.jsm
+++ b/browser/devtools/styleinspector/CssLogic.jsm
@@ -211,17 +211,18 @@ CssLogic.prototype = {
    * @returns {string} The source filter being used.
    */
   get sourceFilter() {
     return this._sourceFilter;
   },
 
   /**
    * Source filter. Only display properties coming from the given source (web
-   * address).
+   * address). Note that in order to avoid information overload we DO NOT show
+   * unmatched system rules.
    * @see CssLogic.FILTER.*
    */
   set sourceFilter(aValue) {
     let oldValue = this._sourceFilter;
     this._sourceFilter = aValue;
 
     let ruleCount = 0;
 
@@ -411,16 +412,37 @@ CssLogic.prototype = {
   forEachSheet: function CssLogic_forEachSheet(aCallback, aScope)
   {
     for each (let sheet in this._sheets) {
       sheet.forEach(aCallback, aScope);
     }
   },
 
   /**
+   * Process *some* cached stylesheets in the document using your callback. The
+   * callback function should return true in order to halt processing.
+   *
+   * @param {function} aCallback the function you want executed for some of the
+   * CssSheet objects cached.
+   * @param {object} aScope the scope you want for the callback function. aScope
+   * will be the this object when aCallback executes.
+   * @return {Boolean} true if aCallback returns true during any iteration,
+   * otherwise false is returned.
+   */
+  forSomeSheets: function CssLogic_forSomeSheets(aCallback, aScope)
+  {
+    for each (let sheets in this._sheets) {
+      if (sheets.some(aCallback, aScope)) {
+        return true;
+      }
+    }
+    return false;
+  },
+
+  /**
    * Get the number nsIDOMCSSRule objects in the document, counted from all of
    * the stylesheets. System sheets are excluded. If a filter is active, this
    * tells only the number of nsIDOMCSSRule objects inside the selected
    * CSSStyleSheet.
    *
    * WARNING: This only provides an estimate of the rule count, and the results
    * could change at a later date. Todo remove this
    *
@@ -549,43 +571,116 @@ CssLogic.prototype = {
    * @param {object} aScope the scope you want for the callback function. aScope
    * will be the this object when aCallback executes.
    */
   processUnmatchedSelectors: function CL_processUnmatchedSelectors(aCallback, aScope)
   {
     if (!this._matchedSelectors) {
       this.processMatchedSelectors();
     }
-
     if (this._unmatchedSelectors) {
       if (aCallback) {
         this._unmatchedSelectors.forEach(aCallback, aScope);
       }
       return;
     }
 
     this._unmatchedSelectors = [];
 
     this.forEachSheet(function (aSheet) {
+      // We do not show unmatched selectors from system stylesheets
       if (aSheet.systemSheet) {
         return;
       }
 
       aSheet.forEachRule(function (aRule) {
         aRule.selectors.forEach(function (aSelector) {
           if (aSelector._matchId != this._matchId) {
             this._unmatchedSelectors.push(aSelector);
             if (aCallback) {
               aCallback.call(aScope, aSelector);
             }
           }
         }, this);
       }, this);
     }, this);
   },
+
+  /**
+   * Check if the highlighted element or it's parents have matched selectors.
+   * If aCallback is provided then the domRules for the element are passed to
+   * the callback function.
+   *
+   * @param {function} [aCallback] Simple callback method
+   * @return {Boolean} true if the current element or it's parents have
+   * matching CssSelector objects, false otherwise
+   */
+  hasMatchedSelectors: function CL_hasMatchedSelectors(aCallback)
+  {
+    let domRules;
+    let element = this.viewedElement;
+    let matched = false;
+
+    do {
+      try {
+        domRules = this.domUtils.getCSSStyleRules(element);
+      } catch (ex) {
+        Services.console.
+            logStringMessage("CssLogic_hasMatchedSelectors error: " + ex);
+        continue;
+      }
+
+      if (domRules.Count() && (!aCallback || aCallback(domRules))) {
+        matched = true;
+        break;
+      }
+
+    } while ((element = element.parentNode) &&
+        element.nodeType === Ci.nsIDOMNode.ELEMENT_NODE);
+
+    return matched;
+  },
+
+  /**
+   * Check if the highlighted element or it's parents have unmatched selectors.
+   *
+   * @param {String} aProperty The CSS property to check against
+   * @return {Boolean} true if the current element or it's parents have
+   * unmatched CssSelector objects, false otherwise
+   */
+  hasUnmatchedSelectors: function CL_hasUnmatchedSelectors(aProperty)
+  {
+    return this.forSomeSheets(function (aSheet) {
+      // We do not show unmatched selectors from system stylesheets
+      if (aSheet.systemSheet) {
+        return false;
+      }
+
+      return aSheet.forSomeRules(function (aRule) {
+        if (aRule.getPropertyValue(aProperty)) {
+          let element = this.viewedElement;
+          let selectorText = aRule._domRule.selectorText;
+          let matches = false;
+
+          do {
+            if (element.mozMatchesSelector(selectorText)) {
+              matches = true;
+              break;
+            }
+          } while ((element = element.parentNode) &&
+                   element.nodeType === Ci.nsIDOMNode.ELEMENT_NODE);
+
+          if (!matches) {
+            // Now we know that there are rules but none match.
+            return true;
+          }
+        }
+      }, this);
+    }, this);
+  },
 };
 
 /**
  * If the element has an id, return '#id'. Otherwise return 'tagname[n]' where
  * n is the index of this element in its siblings.
  * <p>A technically more 'correct' output from the no-id case might be:
  * 'tagname:nth-of-type(n)' however this is unlikely to be more understood
  * and it is longer.
@@ -909,16 +1004,46 @@ CssSheet.prototype = {
       }
     }
 
     Array.prototype.forEach.call(domRules, _iterator, this);
 
     this._ruleCount = ruleCount;
   },
 
+  /**
+   * Process *some* rules in this stylesheet using your callback function. Your
+   * function receives one argument: the CssRule object for each CSSStyleRule
+   * inside the stylesheet. In order to stop processing the callback function
+   * needs to return a value.
+   *
+   * Note that this method also iterates through @media rules inside the
+   * stylesheet.
+   *
+   * @param {function} aCallback the function you want to execute for each of
+   * the style rules.
+   * @param {object} aScope the scope you want for the callback function. aScope
+   * will be the this object when aCallback executes.
+   * @return {Boolean} true if aCallback returns true during any iteration,
+   * otherwise false is returned.
+   */
+  forSomeRules: function CssSheet_forSomeRules(aCallback, aScope)
+  {
+    let domRules = this.domSheet.cssRules;
+    function _iterator(aDomRule) {
+      if (aDomRule.type == Ci.nsIDOMCSSRule.STYLE_RULE) {
+        return aCallback.call(aScope, this.getRule(aDomRule));
+      } else if (aDomRule.type == Ci.nsIDOMCSSRule.MEDIA_RULE &&
+          aDomRule.cssRules && CssLogic.sheetMediaAllowed(aDomRule)) {
+        return Array.prototype.some.call(aDomRule.cssRules, _iterator, this);
+      }
+    }
+    return Array.prototype.some.call(domRules, _iterator, this);
+  },
+
   toString: function CssSheet_toString()
   {
     return "CssSheet[" + this.shortSource + "]";
   },
 };
 
 /**
  * Information about a single CSSStyleRule.
@@ -1259,16 +1384,19 @@ function CssPropertyInfo(aCssLogic, aPro
   // Additionally, only rules that come from allowed stylesheets are counted.
   this._matchedRuleCount = 0;
 
   // An array holding CssSelectorInfo objects for each of the matched selectors
   // that are inside a CSS rule. Only rules that hold the this.property are
   // counted. This includes rules that come from filtered stylesheets (those
   // that have sheetAllowed = false).
   this._matchedSelectors = null;
+  this._unmatchedSelectors = null;
+  this._hasMatchedSelectors = null;
+  this._hasUnmatchedSelectors = null;
 }
 
 CssPropertyInfo.prototype = {
   /**
    * Retrieve the computed style value for the current property, for the
    * highlighted element.
    *
    * @return {string} the computed style value for the current property, for the
@@ -1357,16 +1485,65 @@ CssPropertyInfo.prototype = {
     } else if (this.needRefilter) {
       this._refilterSelectors();
     }
 
     return this._unmatchedSelectors;
   },
 
   /**
+   * Check if the property has any matched selectors.
+   * 
+   * @return {Boolean} true if the current element or it's parents have
+   * matching CssSelector objects, false otherwise
+   */
+  hasMatchedSelectors: function CssPropertyInfo_hasMatchedSelectors()
+  {
+    if (this._hasMatchedSelectors === null) {
+      this._hasMatchedSelectors = this._cssLogic.hasMatchedSelectors(function(aDomRules) {
+        for (let i = 0; i < aDomRules.Count(); i++) {
+          let domRule = aDomRules.GetElementAt(i);
+
+          if (domRule.type !== Ci.nsIDOMCSSRule.STYLE_RULE) {
+            continue;
+          }
+
+          let domSheet = domRule.parentStyleSheet;
+          let systemSheet = CssLogic.isSystemStyleSheet(domSheet);
+          let filter = this._cssLogic.sourceFilter;
+          if (filter !== CssLogic.FILTER.UA && systemSheet) {
+            continue;
+          }
+
+          if (domRule.style.getPropertyValue(this.property)) {
+            return true;
+          }
+        }
+        return false;
+      }.bind(this));
+    }
+
+    return this._hasMatchedSelectors;
+  },
+
+  /**
+   * Check if the property has any matched selectors.
+   *
+   * @return {Boolean} true if the current element or it's parents have
+   * unmatched CssSelector objects, false otherwise
+   */
+  hasUnmatchedSelectors: function CssPropertyInfo_hasUnmatchedSelectors()
+  {
+    if (this._hasUnmatchedSelectors === null) {
+      this._hasUnmatchedSelectors = this._cssLogic.hasUnmatchedSelectors(this.property);
+    }
+    return this._hasUnmatchedSelectors;
+  },
+
+  /**
    * Find the selectors that match the highlighted element and its parents.
    * Uses CssLogic.processMatchedSelectors() to find the matched selectors,
    * passing in a reference to CssPropertyInfo._processMatchedSelector() to
    * create CssSelectorInfo objects, which we then sort
    * @private
    */
   _findMatchedSelectors: function CssPropertyInfo_findMatchedSelectors()
   {
@@ -1432,17 +1609,18 @@ CssPropertyInfo.prototype = {
 
     // Sort the selectors by specificity.
     this._unmatchedSelectors.sort(function(aSelectorInfo1, aSelectorInfo2) {
       return aSelectorInfo1.compareTo(aSelectorInfo2);
     });
   },
 
   /**
-   * Process an unmatched CssSelector object.
+   * Process an unmatched CssSelector object. Note that in order to avoid
+   * information overload we DO NOT show unmatched system rules.
    *
    * @private
    * @param {CssSelector} aSelector the unmatched CssSelector object.
    */
   _processUnmatchedSelector: function CPI_processUnmatchedSelector(aSelector)
   {
     let cssRule = aSelector._cssRule;
     let value = cssRule.getPropertyValue(this.property);
--- a/browser/devtools/styleinspector/csshtmltree.xhtml
+++ b/browser/devtools/styleinspector/csshtmltree.xhtml
@@ -125,29 +125,25 @@ To visually debug the templates without 
               href="${link}">${name}</a>
         </div>
         <div save="${valueNode}" class="property-value" dir="ltr">${value}</div>
       </div>
 
       <div save="${matchedSelectorsContainer}" class="rulelink" dir="${getRTLAttr}">
         <div onclick="${matchedSelectorsClick}" class="rule-matched">
           <div save="${matchedExpander}" class="expander"></div>
-          <div save="${matchedSelectorsTitleNode}">
-            ${matchedSelectorTitle(__element)}
-          </div>
+          <div save="${matchedSelectorsTitleNode}">&matchedSelectors;</div>
         </div>
         <table save="${matchedSelectorTable}" dir="${getRTLAttr}"></table>
       </div>
 
       <div save="${unmatchedSelectorsContainer}" class="rulelink" dir="${getRTLAttr}">
         <div onclick="${unmatchedSelectorsClick}" class="rule-unmatched">
           <div save="${unmatchedExpander}" class="expander"></div>
-          <div save="${unmatchedSelectorsTitleNode}">
-            ${unmatchedSelectorTitle(__element)}
-          </div>
+          <div save="${unmatchedSelectorsTitleNode}">&unmatchedSelectors;</div>
         </div>
         <table save="${unmatchedSelectorTable}" dir="${getRTLAttr}"></table>
       </div>
     </div>
   </div>
 
   <!--
   A templateMatchedSelectors sits inside each templateProperties showing the
--- a/browser/devtools/styleinspector/test/browser/browser_bug683672.js
+++ b/browser/devtools/styleinspector/test/browser/browser_bug683672.js
@@ -57,25 +57,18 @@ function testMatchedSelectors()
       "style inspector node matches the selected node");
 
   let propertyView = new PropertyView(htmlTree, "color");
   let numMatchedSelectors = propertyView.propertyInfo.matchedSelectors.length;
 
   is(numMatchedSelectors, 6,
       "CssLogic returns the correct number of matched selectors for div");
 
-  let returnedSelectorTitle = propertyView.matchedSelectorTitle();
-  let str = CssHtmlTree.l10n("property.numberOfMatchedSelectors");
-  let calculatedSelectorTitle = PluralForm.get(numMatchedSelectors, str)
-                                      .replace("#1", numMatchedSelectors);
-
-  info("returnedSelectorTitle: '" + returnedSelectorTitle + "'");
-
-  is(returnedSelectorTitle, calculatedSelectorTitle,
-      "returned title for matched selectors is correct");
+  is(propertyView.propertyInfo.hasMatchedSelectors(), true,
+      "hasMatchedSelectors returns true");
 }
 
 function testUnmatchedSelectors()
 {
   info("checking selector counts, unmatched rules and titles");
   let body = content.document.body;
   ok(body, "captain, we have a body");
 
@@ -88,25 +81,18 @@ function testUnmatchedSelectors()
       "style inspector node matches the selected node");
 
   let propertyView = new PropertyView(htmlTree, "color");
   let numUnmatchedSelectors = propertyView.propertyInfo.unmatchedSelectors.length;
 
   is(numUnmatchedSelectors, 13,
       "CssLogic returns the correct number of unmatched selectors for body");
 
-  let returnedSelectorTitle = propertyView.unmatchedSelectorTitle();
-  let str = CssHtmlTree.l10n("property.numberOfUnmatchedSelectors");
-  let calculatedSelectorTitle = PluralForm.get(numUnmatchedSelectors, str)
-                                      .replace("#1", numUnmatchedSelectors);
-
-  info("returnedSelectorTitle: '" + returnedSelectorTitle + "'");
-
-  is(returnedSelectorTitle, calculatedSelectorTitle,
-      "returned title for unmatched selectors is correct");
+  is(propertyView.propertyInfo.hasUnmatchedSelectors(), true,
+      "hasUnmatchedSelectors returns true");
 }
 
 function finishUp()
 {
   Services.obs.removeObserver(finishUp, "StyleInspector-closed", false);
   ok(!stylePanel.isOpen(), "style inspector is closed");
   doc = stylePanel = null;
   gBrowser.removeCurrentTab();
--- a/browser/locales/en-US/chrome/browser/styleinspector.dtd
+++ b/browser/locales/en-US/chrome/browser/styleinspector.dtd
@@ -8,8 +8,18 @@
   -  tree. -->
 <!ENTITY lookingAtLabel        "Looking at:">
 
 <!-- LOCALIZATION NOTE (helpLinkTitle): For each style property
   -  the user can hover it and get a help link button which allows one to
   -  quickly jump to the documentation from the Mozilla Developer Network site.
   -  This is the link title shown in the hover tooltip. -->
 <!ENTITY helpLinkTitle         "Read the documentation for this property">
+
+<!-- LOCALIZATION NOTE (matchedSelectors): For each style property the
+  -  panel shows whether there are any selectors that match the currently
+  -  selected element. -->
+<!ENTITY matchedSelectors      "Matched selectors">
+
+<!-- LOCALIZATION NOTE (unmatchedSelectors): For each style property
+  -  the panel shows whether there are any selectors that do not match the
+  -  currently selected element. -->
+<!ENTITY unmatchedSelectors    "Unmatched selectors">
--- a/browser/locales/en-US/chrome/browser/styleinspector.properties
+++ b/browser/locales/en-US/chrome/browser/styleinspector.properties
@@ -1,26 +1,13 @@
 # LOCALIZATION NOTE These strings are used inside the Style Inspector.
 
 # LOCALIZATION NOTE (panelTitle): This is the panel title
 panelTitle=Style Inspector
 
-# LOCALIZATION NOTE (property.numberOfMatchedSelectors): For each style property the
-# panel shows the number of selectors which match the currently selected
-# element, counted from all stylesheets in the web page inspected.
-# See: http://developer.mozilla.org/en/docs/Localization_and_Plurals
-property.numberOfMatchedSelectors=1 matched selector;#1 matched selectors
-
-# LOCALIZATION NOTE (property.numberOfUnmatchedSelectors): For each style
-# property the panel shows the number of selectors which do not match the
-# currently selected element, counted from all stylesheets in the web page
-# inspected.
-# See: http://developer.mozilla.org/en/docs/Localization_and_Plurals
-property.numberOfUnmatchedSelectors=1 unmatched selector;#1 unmatched selectors
-
 # LOCALIZATION NOTE (rule.status): For each style property the panel shows
 # the rules which hold that specific property. For every rule, the rule status
 # is also displayed: a rule can be the best match, a match, a parent match, or a
 # rule did not match the element the user has highlighted.
 rule.status.BEST=Best Match
 rule.status.MATCHED=Matched
 rule.status.PARENT_MATCH=Parent Match
 rule.status.UNMATCHED=Unmatched
--- a/browser/themes/gnomestripe/browser/jar.mn
+++ b/browser/themes/gnomestripe/browser/jar.mn
@@ -81,16 +81,17 @@ browser.jar:
   skin/classic/browser/tabview/edit-light.png         (tabview/edit-light.png)
   skin/classic/browser/tabview/search.png             (tabview/search.png)  
   skin/classic/browser/tabview/stack-expander.png     (tabview/stack-expander.png)
   skin/classic/browser/tabview/tabview.png            (tabview/tabview.png)
   skin/classic/browser/tabview/tabview.css            (tabview/tabview.css)
   skin/classic/browser/devtools/arrows.png            (devtools/arrows.png)
   skin/classic/browser/devtools/search.png            (devtools/search.png)
   skin/classic/browser/devtools/csshtmltree.css       (devtools/csshtmltree.css)
+  skin/classic/browser/devtools/gcli.css              (devtools/gcli.css)
 #ifdef MOZ_SERVICES_SYNC
   skin/classic/browser/sync-16-throbber.png
   skin/classic/browser/sync-16.png
   skin/classic/browser/sync-24-throbber.png
   skin/classic/browser/sync-32.png
   skin/classic/browser/sync-bg.png
   skin/classic/browser/sync-desktopIcon.png
   skin/classic/browser/sync-mobileIcon.png
--- a/browser/themes/pinstripe/browser/jar.mn
+++ b/browser/themes/pinstripe/browser/jar.mn
@@ -120,16 +120,17 @@ browser.jar:
   skin/classic/browser/tabview/edit-light.png               (tabview/edit-light.png)
   skin/classic/browser/tabview/search.png                   (tabview/search.png)
   skin/classic/browser/tabview/stack-expander.png           (tabview/stack-expander.png)
   skin/classic/browser/tabview/tabview.png                  (tabview/tabview.png)
   skin/classic/browser/tabview/tabview.css                  (tabview/tabview.css)
   skin/classic/browser/devtools/arrows.png                  (devtools/arrows.png)
   skin/classic/browser/devtools/search.png                  (devtools/search.png)
   skin/classic/browser/devtools/csshtmltree.css             (devtools/csshtmltree.css)
+  skin/classic/browser/devtools/gcli.css                    (devtools/gcli.css)
 #ifdef MOZ_SERVICES_SYNC
   skin/classic/browser/sync-throbber.png
   skin/classic/browser/sync-16.png
   skin/classic/browser/sync-32.png
   skin/classic/browser/sync-bg.png
   skin/classic/browser/sync-desktopIcon.png
   skin/classic/browser/sync-mobileIcon.png
   skin/classic/browser/sync-notification-24.png