Bug 1103993 - 3 - Only refresh the rule/computed views when active; r=bgrins r=ochameau
authorPatrick Brosset <pbrosset@mozilla.com>
Wed, 10 Dec 2014 09:09:19 +0100
changeset 219029 e0bcde09cbeb2cf2e8cc36d1087e9330944221b4
parent 218984 219d81afdd503110fd28def92d7a2076b715f78c
child 219030 1ee8cdb3f47c2b88b93925fa5d5910dcbe079e23
push id27953
push userryanvm@gmail.com
push dateWed, 10 Dec 2014 20:52:05 +0000
treeherdermozilla-central@7900b789d1fc [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbgrins, ochameau
bugs1103993
milestone37.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 1103993 - 3 - Only refresh the rule/computed views when active; r=bgrins r=ochameau
browser/devtools/debugger/test/browser_dbg_pause-warning.js
browser/devtools/inspector/inspector-panel.js
browser/devtools/inspector/test/browser.ini
browser/devtools/inspector/test/browser_inspector_destroy-before-ready.js
browser/devtools/inspector/test/browser_inspector_pseudoclass-lock.js
browser/devtools/layoutview/test/head.js
browser/devtools/layoutview/view.js
browser/devtools/markupview/markup-view.js
browser/devtools/styleinspector/computed-view.js
browser/devtools/styleinspector/rule-view.js
browser/devtools/styleinspector/style-inspector.js
browser/devtools/styleinspector/test/browser.ini
browser/devtools/styleinspector/test/browser_computedview_refresh-on-style-change_02.js
browser/devtools/styleinspector/test/browser_styleinspector_refresh_when_active.js
browser/devtools/styleinspector/test/browser_styleinspector_tooltip-background-image.js
browser/devtools/styleinspector/test/browser_styleinspector_tooltip-longhand-fontfamily.js
browser/devtools/styleinspector/test/browser_styleinspector_tooltip-multiple-background-images.js
browser/devtools/styleinspector/test/browser_styleinspector_transform-highlighter-01.js
browser/devtools/styleinspector/test/browser_styleinspector_transform-highlighter-02.js
browser/devtools/styleinspector/test/head.js
browser/devtools/webconsole/test/browser_webconsole_bug_653531_highlighter_console_helper.js
toolkit/devtools/server/actors/styles.js
toolkit/devtools/styleinspector/css-logic.js
--- a/browser/devtools/debugger/test/browser_dbg_pause-warning.js
+++ b/browser/devtools/debugger/test/browser_dbg_pause-warning.js
@@ -22,17 +22,19 @@ function test() {
   });
 }
 
 function testPause() {
   gDebugger.gThreadClient.addOneTimeListener("paused", () => {
     ok(gTarget.isThreadPaused,
       "target.isThreadPaused has been updated to true.");
 
-    gToolbox.once("inspector-selected", testNotificationIsUp1);
+    gToolbox.once("inspector-selected").then(inspector => {
+      inspector.once("inspector-updated").then(testNotificationIsUp1);
+    });
     gToolbox.selectTool("inspector");
   });
 
   EventUtils.sendMouseEvent({ type: "mousedown" },
     gDebugger.document.getElementById("resume"),
     gDebugger);
 }
 
--- a/browser/devtools/inspector/inspector-panel.js
+++ b/browser/devtools/inspector/inspector-panel.js
@@ -413,19 +413,28 @@ InspectorPanel.prototype = {
     // client know.
     let selection = this.selection.nodeFront;
 
     // On any new selection made by the user, store the unique css selector
     // of the selected node so it can be restored after reload of the same page
     if (reason !== "navigateaway" &&
         this.canGetUniqueSelector &&
         this.selection.isElementNode()) {
-      selection.getUniqueSelector().then((selector) => {
+      selection.getUniqueSelector().then(selector => {
         this.selectionCssSelector = selector;
-      }).then(null, console.error);
+      }).then(null, e => {
+        // Only log this as an error if the panel hasn't been destroyed in the
+        // meantime.
+        if (!this._panelDestroyer) {
+          console.error(e);
+        } else {
+          console.warn("Could not set the unique selector for the newly "+
+            "selected node, the inspector was destroyed.");
+        }
+      });
     }
 
     let selfUpdate = this.updating("inspector-panel");
     Services.tm.mainThread.dispatch(() => {
       try {
         selfUpdate(selection);
       } catch(ex) {
         console.error(ex);
--- a/browser/devtools/inspector/test/browser.ini
+++ b/browser/devtools/inspector/test/browser.ini
@@ -25,16 +25,17 @@ support-files =
   head.js
 
 [browser_inspector_breadcrumbs.js]
 [browser_inspector_breadcrumbs_highlight_hover.js]
 [browser_inspector_delete-selected-node-01.js]
 [browser_inspector_delete-selected-node-02.js]
 [browser_inspector_delete-selected-node-03.js]
 [browser_inspector_destroy-after-navigation.js]
+[browser_inspector_destroy-before-ready.js]
 [browser_inspector_gcli-inspect-command.js]
 [browser_inspector_highlighter-01.js]
 [browser_inspector_highlighter-02.js]
 [browser_inspector_highlighter-03.js]
 [browser_inspector_highlighter-04.js]
 [browser_inspector_highlighter-by-type.js]
 [browser_inspector_highlighter-comments.js]
 [browser_inspector_highlighter-csstransform_01.js]
new file mode 100644
--- /dev/null
+++ b/browser/devtools/inspector/test/browser_inspector_destroy-before-ready.js
@@ -0,0 +1,26 @@
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Test that switching to the inspector panel and not waiting for it to be fully
+// loaded doesn't fail the test with unhandled rejected promises.
+
+add_task(function*() {
+  // At least one assertion is needed to avoid failing the test, but really,
+  // what we're interested in is just having the test pass when switching to the
+  // inspector.
+  ok(true);
+
+  yield addTab("data:text/html;charset=utf-8,test inspector destroy");
+
+  info("Open the toolbox on the debugger panel");
+  let target = TargetFactory.forTab(gBrowser.selectedTab);
+  let toolbox = yield gDevTools.showToolbox(target, "jsdebugger");
+
+  info("Switch to the inspector panel and immediately end the test");
+  let onInspectorSelected = toolbox.once("inspector-selected");
+  toolbox.selectTool("inspector");
+  let inspector = yield onInspectorSelected;
+});
--- a/browser/devtools/inspector/test/browser_inspector_pseudoclass-lock.js
+++ b/browser/devtools/inspector/test/browser_inspector_pseudoclass-lock.js
@@ -75,17 +75,16 @@ function* testNavigate(inspector, rulevi
   info("Make sure the pseudoclass is removed after navigating to a non-hierarchy node");
   res = yield executeInContent("Test:HasPseudoClassLock",
                                {pseudo: PSEUDO},
                                {node: getNode("#div-1")});
   ok(!res.data, "pseudo-class lock is removed after inspecting sibling node");
 
   yield selectNode("#div-1", inspector);
   yield togglePseudoClass(inspector);
-  yield inspector.once("computed-view-refreshed");
 }
 
 function* showPickerOn(selector, inspector) {
   let highlighter = inspector.toolbox.highlighter;
   let nodeFront = yield getNodeFront(selector, inspector);
   yield highlighter.showBoxModel(nodeFront);
 }
 
--- a/browser/devtools/layoutview/test/head.js
+++ b/browser/devtools/layoutview/test/head.js
@@ -227,20 +227,18 @@ let openLayoutView = Task.async(function
  * Wait for the layoutview-updated event and for all of the inspector's panels
  * to update too.
  * Use this to make sure the inspector is updated and ready after a change was
  * made in one of the layout-view editable fields.
  * @return a promise
  */
 function waitForUpdate(inspector) {
   let onLayoutView = inspector.once("layoutview-updated");
-  let onRuleView = inspector.once("rule-view-refreshed");
-  let onComputedView = inspector.once("computed-view-refreshed");
   let onInspector = inspector.once("inspector-updated");
-  return promise.all([onLayoutView, onRuleView, onComputedView, onInspector]);
+  return promise.all([onLayoutView, onInspector]);
 }
 
 /**
  * The addTest/runTests function couple provides a simple way to define
  * subsequent test cases in a test file. Example:
  *
  * addTest("what this test does", function*() {
  *   ... actual code for the test ...
--- a/browser/devtools/layoutview/view.js
+++ b/browser/devtools/layoutview/view.js
@@ -543,17 +543,19 @@ window.setPanel = function(panel) {
     getService(Ci.nsIXULChromeRegistry);
   let dir = chromeReg.isLocaleRTL("global");
   document.body.setAttribute("dir", dir ? "rtl" : "ltr");
 
   window.parent.postMessage("layoutview-ready", "*");
 };
 
 window.onunload = function() {
-  this.layoutview.destroy();
+  if (this.layoutview) {
+    this.layoutview.destroy();
+  }
   if (elts) {
     for (let i = 0; i < elts.length; i++) {
       let elt = elts[i];
       elt.removeEventListener("mouseover", onmouseover, true);
       elt.removeEventListener("mouseout", onmouseout, true);
     }
   }
 };
--- a/browser/devtools/markupview/markup-view.js
+++ b/browser/devtools/markupview/markup-view.js
@@ -742,16 +742,25 @@ MarkupView.prototype = {
       this.expandNode(parent);
     }
 
     return this._waitForChildren().then(() => {
       return this._ensureVisible(aNode);
     }).then(() => {
       // Why is this not working?
       this.layoutHelpers.scrollIntoViewIfNeeded(this.getContainer(aNode).editor.elt, centered);
+    }, e => {
+      // Only report this rejection as an error if the panel hasn't been
+      // destroyed in the meantime.
+      if (!this._destroyer) {
+        console.error(e);
+      } else {
+        console.warn("Could not show the node, the markup-view was destroyed " +
+          "while waiting for children");
+      }
     });
   },
 
   /**
    * Expand the container's children.
    */
   _expandContainer: function(aContainer) {
     return this._updateChildren(aContainer, {expand: true}).then(() => {
--- a/browser/devtools/styleinspector/computed-view.js
+++ b/browser/devtools/styleinspector/computed-view.js
@@ -583,17 +583,24 @@ CssHtmlTree.prototype = {
         CssHtmlTree.propertyNames.push(prop);
       }
     }
 
     CssHtmlTree.propertyNames.sort();
     CssHtmlTree.propertyNames.push.apply(CssHtmlTree.propertyNames,
       mozProps.sort());
 
-    this._createPropertyViews();
+    this._createPropertyViews().then(null, e => {
+      if (!this.styleInspector) {
+        console.warn("The creation of property views was cancelled because the " +
+          "computed-view was destroyed before it was done creating views");
+      } else {
+        console.error(e);
+      }
+    });
   },
 
   /**
    * Get a set of properties that have matched selectors.
    *
    * @return {Set} If a property name is in the set, it has matching selectors.
    */
   get matchedProperties()
--- a/browser/devtools/styleinspector/rule-view.js
+++ b/browser/devtools/styleinspector/rule-view.js
@@ -1455,20 +1455,22 @@ CssRuleView.prototype = {
   },
 
   _onSourcePrefChanged: function() {
     if (this.menuitemSources) {
       let isEnabled = Services.prefs.getBoolPref(PREF_ORIG_SOURCES);
       this.menuitemSources.setAttribute("checked", isEnabled);
     }
 
-    // update text of source links
-    for (let rule of this._elementStyle.rules) {
-      if (rule.editor) {
-        rule.editor.updateSourceLink();
+    // update text of source links if the rule-view is populated
+    if (this._elementStyle) {
+      for (let rule of this._elementStyle.rules) {
+        if (rule.editor) {
+          rule.editor.updateSourceLink();
+        }
       }
     }
   },
 
   destroy: function() {
     this.isDestroyed = true;
     this.clear();
 
--- a/browser/devtools/styleinspector/style-inspector.js
+++ b/browser/devtools/styleinspector/style-inspector.js
@@ -27,35 +27,45 @@ function RuleViewTool(inspector, window,
   this.doc.documentElement.appendChild(this.view.element);
 
   this.onLinkClicked = this.onLinkClicked.bind(this);
   this.onSelected = this.onSelected.bind(this);
   this.refresh = this.refresh.bind(this);
   this.clearUserProperties = this.clearUserProperties.bind(this);
   this.onPropertyChanged = this.onPropertyChanged.bind(this);
   this.onViewRefreshed = this.onViewRefreshed.bind(this);
+  this.onPanelSelected = this.onPanelSelected.bind(this);
 
   this.view.element.addEventListener("CssRuleViewChanged", this.onPropertyChanged);
   this.view.element.addEventListener("CssRuleViewRefreshed", this.onViewRefreshed);
   this.view.element.addEventListener("CssRuleViewCSSLinkClicked", this.onLinkClicked);
 
   this.inspector.selection.on("detached", this.onSelected);
   this.inspector.selection.on("new-node-front", this.onSelected);
   this.inspector.on("layout-change", this.refresh);
   this.inspector.selection.on("pseudoclass", this.refresh);
   this.inspector.target.on("navigate", this.clearUserProperties);
+  this.inspector.sidebar.on("ruleview-selected", this.onPanelSelected);
 
   this.onSelected();
 }
 
 
 RuleViewTool.prototype = {
+  isSidebarActive: function() {
+    return this.inspector.sidebar.getCurrentTabID() == "ruleview";
+  },
+
   onSelected: function(event) {
-    // Ignore the event if the view has been destroyed
-    if (!this.view) {
+    // Ignore the event if the view has been destroyed, or if it's inactive.
+    // But only if the current selection isn't null. If it's been set to null,
+    // let the update go through as this is needed to empty the view on navigation.
+    let isDestroyed = !this.view;
+    let isInactive = !this.isSidebarActive() && this.inspector.selection.nodeFront;
+    if (isDestroyed || isInactive) {
       return;
     }
 
     this.view.setPageStyle(this.inspector.pageStyle);
 
     if (!this.inspector.selection.isConnected() ||
         !this.inspector.selection.isElementNode()) {
       this.view.selectElement(null);
@@ -64,25 +74,35 @@ RuleViewTool.prototype = {
 
     if (!event || event == "new-node-front") {
       let done = this.inspector.updating("rule-view");
       this.view.selectElement(this.inspector.selection.nodeFront).then(done, done);
     }
   },
 
   refresh: function() {
-    this.view.refreshPanel();
+    if (this.isSidebarActive()) {
+      this.view.refreshPanel();
+    }
   },
 
   clearUserProperties: function() {
     if (this.view && this.view.store && this.view.store.userProperties) {
       this.view.store.userProperties.clear();
     }
   },
 
+  onPanelSelected: function() {
+    if (this.inspector.selection.nodeFront === this.view.viewedElement) {
+      this.refresh();
+    } else {
+      this.onSelected();
+    }
+  },
+
   onLinkClicked: function(event) {
     let rule = event.detail.rule;
     let sheet = rule.parentStyleSheet;
 
     // Chrome stylesheets are not listed in the style editor, so show
     // these sheets in the view source window instead.
     if (!sheet || sheet.isSystem) {
       let contentDoc = this.inspector.selection.document;
@@ -116,16 +136,17 @@ RuleViewTool.prototype = {
     this.inspector.emit("rule-view-refreshed");
   },
 
   destroy: function() {
     this.inspector.off("layout-change", this.refresh);
     this.inspector.selection.off("pseudoclass", this.refresh);
     this.inspector.selection.off("new-node-front", this.onSelected);
     this.inspector.target.off("navigate", this.clearUserProperties);
+    this.inspector.sidebar.off("ruleview-selected", this.onPanelSelected);
 
     this.view.element.removeEventListener("CssRuleViewCSSLinkClicked", this.onLinkClicked);
     this.view.element.removeEventListener("CssRuleViewChanged", this.onPropertyChanged);
     this.view.element.removeEventListener("CssRuleViewRefreshed", this.onViewRefreshed);
 
     this.doc.documentElement.removeChild(this.view.element);
 
     this.view.destroy();
@@ -137,33 +158,44 @@ RuleViewTool.prototype = {
 function ComputedViewTool(inspector, window, iframe) {
   this.inspector = inspector;
   this.doc = window.document;
 
   this.view = new ComputedView.CssHtmlTree(this, inspector.pageStyle);
 
   this.onSelected = this.onSelected.bind(this);
   this.refresh = this.refresh.bind(this);
+  this.onPanelSelected = this.onPanelSelected.bind(this);
 
   this.inspector.selection.on("detached", this.onSelected);
   this.inspector.selection.on("new-node-front", this.onSelected);
   this.inspector.on("layout-change", this.refresh);
   this.inspector.selection.on("pseudoclass", this.refresh);
+  this.inspector.sidebar.on("computedview-selected", this.onPanelSelected);
 
   this.view.selectElement(null);
 
   this.onSelected();
 }
 
 ComputedViewTool.prototype = {
+  isSidebarActive: function() {
+    return this.inspector.sidebar.getCurrentTabID() == "computedview";
+  },
+
   onSelected: function(event) {
-    // Ignore the event if the view has been destroyed
-    if (!this.view) {
+    // Ignore the event if the view has been destroyed, or if it's inactive.
+    // But only if the current selection isn't null. If it's been set to null,
+    // let the update go through as this is needed to empty the view on navigation.
+    let isDestroyed = !this.view;
+    let isInactive = !this.isSidebarActive() && this.inspector.selection.nodeFront;
+    if (isDestroyed || isInactive) {
       return;
     }
+
     this.view.setPageStyle(this.inspector.pageStyle);
 
     if (!this.inspector.selection.isConnected() ||
         !this.inspector.selection.isElementNode()) {
       this.view.selectElement(null);
       return;
     }
 
@@ -171,24 +203,35 @@ ComputedViewTool.prototype = {
       let done = this.inspector.updating("computed-view");
       this.view.selectElement(this.inspector.selection.nodeFront).then(() => {
         done();
       });
     }
   },
 
   refresh: function() {
-    this.view.refreshPanel();
+    if (this.isSidebarActive()) {
+      this.view.refreshPanel();
+    }
+  },
+
+  onPanelSelected: function() {
+    if (this.inspector.selection.nodeFront === this.view.viewedElement) {
+      this.refresh();
+    } else {
+      this.onSelected();
+    }
   },
 
   destroy: function() {
     this.inspector.off("layout-change", this.refresh);
     this.inspector.sidebar.off("computedview-selected", this.refresh);
     this.inspector.selection.off("pseudoclass", this.refresh);
     this.inspector.selection.off("new-node-front", this.onSelected);
+    this.inspector.sidebar.off("computedview-selected", this.onPanelSelected);
 
     this.view.destroy();
 
     this.view = this.cssLogic = this.cssHtmlTree = null;
     this.doc = this.inspector = null;
   }
 };
 
--- a/browser/devtools/styleinspector/test/browser.ini
+++ b/browser/devtools/styleinspector/test/browser.ini
@@ -32,17 +32,16 @@ support-files =
 [browser_computedview_matched-selectors-toggle.js]
 [browser_computedview_matched-selectors_01.js]
 [browser_computedview_matched-selectors_02.js]
 [browser_computedview_media-queries.js]
 [browser_computedview_no-results-placeholder.js]
 [browser_computedview_original-source-link.js]
 [browser_computedview_pseudo-element_01.js]
 [browser_computedview_refresh-on-style-change_01.js]
-[browser_computedview_refresh-on-style-change_02.js]
 [browser_computedview_search-filter.js]
 [browser_computedview_select-and-copy-styles.js]
 [browser_computedview_style-editor-link.js]
 [browser_ruleview_add-property-and-reselect.js]
 [browser_ruleview_add-property-cancel_01.js]
 [browser_ruleview_add-property-cancel_02.js]
 [browser_ruleview_add-property-cancel_03.js]
 [browser_ruleview_add-property_01.js]
@@ -108,16 +107,17 @@ skip-if = e10s # bug 1040670 Cannot open
 [browser_ruleview_urls-clickable.js]
 [browser_ruleview_user-agent-styles.js]
 [browser_ruleview_user-agent-styles-uneditable.js]
 [browser_ruleview_user-property-reset.js]
 [browser_styleinspector_context-menu-copy-color_01.js]
 [browser_styleinspector_context-menu-copy-color_02.js]
 [browser_styleinspector_csslogic-content-stylesheets.js]
 [browser_styleinspector_output-parser.js]
+[browser_styleinspector_refresh_when_active.js]
 [browser_styleinspector_tooltip-background-image.js]
 [browser_styleinspector_tooltip-closes-on-new-selection.js]
 [browser_styleinspector_tooltip-longhand-fontfamily.js]
 [browser_styleinspector_tooltip-multiple-background-images.js]
 [browser_styleinspector_tooltip-shorthand-fontfamily.js]
 [browser_styleinspector_tooltip-size.js]
 [browser_styleinspector_transform-highlighter-01.js]
 [browser_styleinspector_transform-highlighter-02.js]
deleted file mode 100644
--- a/browser/devtools/styleinspector/test/browser_computedview_refresh-on-style-change_02.js
+++ /dev/null
@@ -1,37 +0,0 @@
-/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
-/* Any copyright is dedicated to the Public Domain.
- http://creativecommons.org/publicdomain/zero/1.0/ */
-
-"use strict";
-
-// Test that the computed view refreshes when the current node has its style
-// changed, even if the view is not the active one
-
-const TESTCASE_URI = 'data:text/html;charset=utf-8,' +
-                     '<div id="testdiv" style="font-size:10px;">Test div!</div>';
-
-let test = asyncTest(function*() {
-  yield addTab(TESTCASE_URI);
-
-  info("Opening the computed view and selecting the test node");
-  let {toolbox, inspector, view} = yield openComputedView();
-  yield selectNode("#testdiv", inspector);
-
-  let fontSize = getComputedViewPropertyValue(view, "font-size");
-  is(fontSize, "10px", "The computed view shows the right font-size");
-
-  info("Now switching to the rule view");
-  yield openRuleView();
-
-  info("Changing the node's style and waiting for the update");
-  let onUpdated = inspector.once("computed-view-refreshed");
-  getNode("#testdiv").style.cssText = "font-size: 20px; color: blue; text-align: center";
-  yield onUpdated;
-
-  fontSize = getComputedViewPropertyValue(view, "font-size");
-  is(fontSize, "20px", "The computed view shows the updated font-size");
-  let color = getComputedViewPropertyValue(view, "color");
-  is(color, "#00F", "The computed view also shows the color now");
-  let textAlign = getComputedViewPropertyValue(view, "text-align");
-  is(textAlign, "center", "The computed view also shows the text-align now");
-});
new file mode 100644
--- /dev/null
+++ b/browser/devtools/styleinspector/test/browser_styleinspector_refresh_when_active.js
@@ -0,0 +1,43 @@
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Test that the style-inspector views only refresh when they are active.
+
+const TEST_URL = 'data:text/html;charset=utf-8,' +
+                 '<div id="one" style="color:red;">one</div>' +
+                 '<div id="two" style="color:blue;">two</div>';
+
+let test = asyncTest(function*() {
+  yield addTab(TEST_URL);
+
+  info("Opening the rule-view and selecting test node one");
+  let {inspector, view: rView} = yield openRuleView();
+  yield selectNode("#one", inspector);
+
+  is(getRuleViewPropertyValue(rView, "element", "color"), "#F00",
+    "The rule-view shows the properties for test node one");
+
+  let cView = inspector.sidebar.getWindowForTab("computedview")["computedview"].view;
+  let prop = getComputedViewProperty(cView, "color");
+  ok(!prop, "The computed-view doesn't show the properties for test node one");
+
+  info("Switching to the computed-view");
+  let onComputedViewReady = inspector.once("computed-view-refreshed");
+  yield openComputedView();
+  yield onComputedViewReady;
+
+  ok(getComputedViewPropertyValue(cView, "color"), "#F00",
+    "The computed-view shows the properties for test node one");
+
+  info("Selecting test node two");
+  yield selectNode("#two", inspector);
+
+  ok(getComputedViewPropertyValue(cView, "color"), "#00F",
+    "The computed-view shows the properties for test node two");
+
+  is(getRuleViewPropertyValue(rView, "element", "color"), "#F00",
+    "The rule-view doesn't the properties for test node two");
+});
\ No newline at end of file
--- a/browser/devtools/styleinspector/test/browser_styleinspector_tooltip-background-image.js
+++ b/browser/devtools/styleinspector/test/browser_styleinspector_tooltip-background-image.js
@@ -37,17 +37,19 @@ let test = asyncTest(function*() {
   yield selectNode(".test-element", inspector);
   info("Testing the the background property on the .test-element rule");
   yield testDivRuleView(view);
 
   info("Testing that image preview tooltips show even when there are fields being edited");
   yield testTooltipAppearsEvenInEditMode(view);
 
   info("Switching over to the computed-view");
+  let onComputedViewReady = inspector.once("computed-view-refreshed");
   ({view} = yield openComputedView());
+  yield onComputedViewReady;
 
   info("Testing that the background-image computed style has a tooltip too");
   yield testComputedView(view);
 });
 
 function* testBodyRuleView(view) {
   info("Testing tooltips in the rule view");
   let panel = view.tooltips.previewTooltip.panel;
--- a/browser/devtools/styleinspector/test/browser_styleinspector_tooltip-longhand-fontfamily.js
+++ b/browser/devtools/styleinspector/test/browser_styleinspector_tooltip-longhand-fontfamily.js
@@ -27,17 +27,19 @@ let test = asyncTest(function*() {
   let {toolbox, inspector, view} = yield openRuleView();
 
   info("Selecting the test node");
   yield selectNode("#testElement", inspector);
 
   yield testRuleView(view, inspector.selection.nodeFront);
 
   info("Opening the computed view");
+  let onComputedViewReady = inspector.once("computed-view-refreshed");
   ({toolbox, inspector, view} = yield openComputedView());
+  yield onComputedViewReady;
 
   yield testComputedView(view, inspector.selection.nodeFront);
 
   yield testExpandedComputedViewProperty(view, inspector.selection.nodeFront);
 });
 
 function* testRuleView(ruleView, nodeFront) {
   info("Testing font-family tooltips in the rule view");
--- a/browser/devtools/styleinspector/test/browser_styleinspector_tooltip-multiple-background-images.js
+++ b/browser/devtools/styleinspector/test/browser_styleinspector_tooltip-multiple-background-images.js
@@ -27,27 +27,28 @@ let test = asyncTest(function* () {
 
   yield testRuleViewUrls();
   yield testComputedViewUrls();
 });
 
 function* testRuleViewUrls() {
   info("Testing tooltips in the rule view");
 
-  let { view, inspector } = yield openRuleView();
+  let {view, inspector} = yield openRuleView();
   yield selectNode("h1", inspector);
 
   let {valueSpan} = getRuleViewProperty(view, "h1", "background");
   yield performChecks(view, valueSpan);
 }
 
 function* testComputedViewUrls() {
   info("Testing tooltips in the computed view");
 
-  let {view} = yield openComputedView();
+  let {view, inspector} = yield openComputedView();
+  yield inspector.once("computed-view-refreshed");
   let {valueSpan} = getComputedViewProperty(view, "background-image");
 
   yield performChecks(view, valueSpan);
 }
 
 /**
  * A helper that checks url() tooltips contain correct images
  */
--- a/browser/devtools/styleinspector/test/browser_styleinspector_transform-highlighter-01.js
+++ b/browser/devtools/styleinspector/test/browser_styleinspector_transform-highlighter-01.js
@@ -15,27 +15,29 @@ const PAGE_CONTENT = [
   'Test the css transform highlighter'
 ].join("\n");
 
 const TYPE = "CssTransformHighlighter";
 
 let test = asyncTest(function*() {
   yield addTab("data:text/html;charset=utf-8," + PAGE_CONTENT);
 
-  let {view: rView} = yield openRuleView();
+  let {inspector, view: rView} = yield openRuleView();
   let overlay = rView.highlighters;
 
   ok(!overlay.highlighters[TYPE], "No highlighter exists in the rule-view");
   let h = yield overlay._getHighlighter(TYPE);
   ok(overlay.highlighters[TYPE], "The highlighter has been created in the rule-view");
   is(h, overlay.highlighters[TYPE], "The right highlighter has been created");
   let h2 = yield overlay._getHighlighter(TYPE);
   is(h, h2, "The same instance of highlighter is returned everytime in the rule-view");
 
+  let onComputedViewReady = inspector.once("computed-view-refreshed");
   let {view: cView} = yield openComputedView();
+  yield onComputedViewReady;
   overlay = cView.highlighters;
 
   ok(!overlay.highlighters[TYPE], "No highlighter exists in the computed-view");
   h = yield overlay._getHighlighter(TYPE);
   ok(overlay.highlighters[TYPE], "The highlighter has been created in the computed-view");
   is(h, overlay.highlighters[TYPE], "The right highlighter has been created");
   h2 = yield overlay._getHighlighter(TYPE);
   is(h, h2, "The same instance of highlighter is returned everytime in the computed-view");
--- a/browser/devtools/styleinspector/test/browser_styleinspector_transform-highlighter-02.js
+++ b/browser/devtools/styleinspector/test/browser_styleinspector_transform-highlighter-02.js
@@ -17,18 +17,17 @@ const PAGE_CONTENT = [
   'Test the css transform highlighter'
 ].join("\n");
 
 let TYPE = "CssTransformHighlighter";
 
 let test = asyncTest(function*() {
   yield addTab("data:text/html;charset=utf-8," + PAGE_CONTENT);
 
-
-  let {view: rView} = yield openRuleView();
+  let {inspector, view: rView} = yield openRuleView();
   let hs = rView.highlighters;
 
   ok(!hs.highlighters[TYPE], "No highlighter exists in the rule-view (1)");
   ok(!hs.promises[TYPE], "No highlighter is being created in the rule-view (1)");
 
   info("Faking a mousemove on a non-transform property");
   let {valueSpan} = getRuleViewProperty(rView, "body", "color");
   hs._onMouseMove({target: valueSpan});
@@ -37,17 +36,19 @@ let test = asyncTest(function*() {
 
   info("Faking a mousemove on a transform property");
   ({valueSpan} = getRuleViewProperty(rView, "body", "transform"));
   hs._onMouseMove({target: valueSpan});
   ok(hs.promises[TYPE], "The highlighter is being initialized");
   let h = yield hs.promises[TYPE];
   is(h, hs.highlighters[TYPE], "The initialized highlighter is the right one");
 
+  let onComputedViewReady = inspector.once("computed-view-refreshed");
   let {view: cView} = yield openComputedView();
+  yield onComputedViewReady;
   hs = cView.highlighters;
 
   ok(!hs.highlighters[TYPE], "No highlighter exists in the computed-view (1)");
   ok(!hs.promises[TYPE], "No highlighter is being created in the computed-view (1)");
 
   info("Faking a mousemove on a non-transform property");
   ({valueSpan} = getComputedViewProperty(cView, "color"));
   hs._onMouseMove({target: valueSpan});
--- a/browser/devtools/styleinspector/test/head.js
+++ b/browser/devtools/styleinspector/test/head.js
@@ -38,16 +38,17 @@ registerCleanupFunction(function*() {
 
 // Set the testing flag on gDevTools and reset it when the test ends
 gDevTools.testing = true;
 registerCleanupFunction(() => gDevTools.testing = false);
 
 // Clean-up all prefs that might have been changed during a test run
 // (safer here because if the test fails, then the pref is never reverted)
 registerCleanupFunction(() => {
+  Services.prefs.clearUserPref("devtools.inspector.activeSidebar");
   Services.prefs.clearUserPref("devtools.dump.emit");
   Services.prefs.clearUserPref("devtools.defaultColorUnit");
 });
 
 /**
  * The functions found below are here to ease test development and maintenance.
  * Most of these functions are stateless and will require some form of context
  * (the instance of the current toolbox, or inspector panel for instance).
--- a/browser/devtools/webconsole/test/browser_webconsole_bug_653531_highlighter_console_helper.js
+++ b/browser/devtools/webconsole/test/browser_webconsole_bug_653531_highlighter_console_helper.js
@@ -48,32 +48,36 @@ function createDocument() {
   setupHighlighterTests();
 }
 
 function setupHighlighterTests() {
   ok(h1, "we have the header node");
   openInspector(runSelectionTests);
 }
 
-function runSelectionTests(aInspector) {
+let runSelectionTests = Task.async(function*(aInspector) {
   inspector = aInspector;
 
+  let onPickerStarted = inspector.toolbox.once("picker-started");
   inspector.toolbox.highlighterUtils.startPicker();
-  inspector.toolbox.once("picker-started", () => {
-    info("Picker mode started, now clicking on H1 to select that node");
-    executeSoon(() => {
-      h1.scrollIntoView();
-      EventUtils.synthesizeMouseAtCenter(h1, {}, content);
-      inspector.toolbox.once("picker-stopped", () => {
-        info("Picker mode stopped, H1 selected, now switching to the console");
-        openConsole(gBrowser.selectedTab).then(performWebConsoleTests);
-      });
-    });
-  });
-}
+  yield onPickerStarted;
+
+  info("Picker mode started, now clicking on H1 to select that node");
+  h1.scrollIntoView();
+  let onPickerStopped = inspector.toolbox.once("picker-stopped");
+  let onInspectorUpdated = inspector.once("inspector-updated");
+  EventUtils.synthesizeMouseAtCenter(h1, {}, content);
+  yield onPickerStopped;
+  yield onInspectorUpdated;
+
+  info("Picker mode stopped, H1 selected, now switching to the console");
+  let hud = yield openConsole(gBrowser.selectedTab);
+
+  performWebConsoleTests(hud);
+});
 
 function performWebConsoleTests(hud) {
   let target = TargetFactory.forTab(gBrowser.selectedTab);
   let jsterm = hud.jsterm;
   outputNode = hud.outputNode;
 
   jsterm.clearOutput();
   jsterm.execute("$0", onNodeOutput);
--- a/toolkit/devtools/server/actors/styles.js
+++ b/toolkit/devtools/server/actors/styles.js
@@ -123,16 +123,33 @@ var PageStyleActor = protocol.ActorClass
     this.refMap = new Map;
 
     this.onFrameUnload = this.onFrameUnload.bind(this);
     events.on(this.inspector.tabActor, "will-navigate", this.onFrameUnload);
   },
 
   get conn() this.inspector.conn,
 
+  form: function(detail) {
+    if (detail === "actorid") {
+      return this.actorID;
+    }
+
+    return {
+      actor: this.actorID,
+      traits: {
+        // Whether the actor has had bug 1103993 fixed, which means that the
+        // getApplied method calls cssLogic.highlight(node) to recreate the
+        // style cache. Clients requesting getApplied from actors that have not
+        // been fixed must make sure cssLogic.highlight(node) was called before.
+        getAppliedCreatesStyleCache: true
+      }
+    };
+  },
+
   /**
    * Return or create a StyleRuleActor for the given item.
    * @param item Either a CSSStyleRule or a DOM element.
    */
   _styleRef: function(item) {
     if (this.refMap.has(item)) {
       return this.refMap.get(item);
     }
@@ -411,16 +428,17 @@ var PageStyleActor = protocol.ActorClass
    *     'user': Include properties from user style sheets.
    *     'ua': Include properties from user and user-agent sheets.
    *     Default value is 'ua'
    *   `inherited`: Include styles inherited from parent nodes.
    *   `matchedSeletors`: Include an array of specific selectors that
    *     caused this rule to match its node.
    */
   getApplied: method(function(node, options) {
+    this.cssLogic.highlight(node.rawNode);
     let entries = [];
     entries = entries.concat(this._getAllElementRules(node, undefined, options));
     return this.getAppliedProps(node, entries, options);
   }, {
     request: {
       node: Arg(0, "domnode"),
       inherited: Option(1, "boolean"),
       matchedSelectors: Option(1, "boolean"),
@@ -798,37 +816,51 @@ exports.PageStyleActor = PageStyleActor;
  * Front object for the PageStyleActor
  */
 var PageStyleFront = protocol.FrontClass(PageStyleActor, {
   initialize: function(conn, form, ctx, detail) {
     protocol.Front.prototype.initialize.call(this, conn, form, ctx, detail);
     this.inspector = this.parent();
   },
 
+  form: function(form, detail) {
+    if (detail === "actorid") {
+      this.actorID = form;
+      return;
+    }
+    this._form = form;
+  },
+
   destroy: function() {
     protocol.Front.prototype.destroy.call(this);
   },
 
   get walker() {
     return this.inspector.walker;
   },
 
   getMatchedSelectors: protocol.custom(function(node, property, options) {
     return this._getMatchedSelectors(node, property, options).then(ret => {
       return ret.matched;
     });
   }, {
     impl: "_getMatchedSelectors"
   }),
 
-  getApplied: protocol.custom(function(node, options={}) {
-    return this._getApplied(node, options).then(ret => {
-      return ret.entries;
-    });
-  }, {
+  getApplied: protocol.custom(Task.async(function*(node, options={}) {
+    // If the getApplied method doesn't recreate the style cache itself, this
+    // means a call to cssLogic.highlight is required before trying to access
+    // the applied rules. Issue a request to getLayout if this is the case.
+    // See https://bugzilla.mozilla.org/show_bug.cgi?id=1103993#c16.
+    if (!this._form.traits || !this._form.traits.getAppliedCreatesStyleCache) {
+      yield this.getLayout(node);
+    }
+    let ret = yield this._getApplied(node, options);
+    return ret.entries;
+  }), {
     impl: "_getApplied"
   }),
 
   addNewRule: protocol.custom(function(node) {
     return this._addNewRule(node).then(ret => {
       return ret.entries[0];
     });
   }, {
--- a/toolkit/devtools/styleinspector/css-logic.js
+++ b/toolkit/devtools/styleinspector/css-logic.js
@@ -159,16 +159,20 @@ CssLogic.prototype = {
     if (!aViewedElement) {
       this.viewedElement = null;
       this.viewedDocument = null;
       this._computedStyle = null;
       this.reset();
       return;
     }
 
+    if (aViewedElement === this.viewedElement) {
+      return;
+    }
+
     this.viewedElement = aViewedElement;
 
     let doc = this.viewedElement.ownerDocument;
     if (doc != this.viewedDocument) {
       // New document: clear/rebuild the cache.
       this.viewedDocument = doc;
 
       // Hunt down top level stylesheets, and cache them.