merge autoland to mozilla-central. r=merge a=merge
authorSebastian Hengst <archaeopteryx@coole-files.de>
Sun, 04 Jun 2017 20:07:23 +0200
changeset 410319 78450dd743b92638cdcf70b16c0850214782eff4
parent 410303 302eb1670544f4e8d0347de03526c4fd160480e1 (current diff)
parent 410318 ae2ff08a40b08c7d4712d22b6d830439ed1058a7 (diff)
child 410330 8a3aa1701537ea6b8334f432cd030d260d492fa3
push id7391
push usermtabara@mozilla.com
push dateMon, 12 Jun 2017 13:08:53 +0000
treeherdermozilla-beta@2191d7f87e2e [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge, merge
milestone55.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 autoland to mozilla-central. r=merge a=merge MozReview-Commit-ID: HcM5jRYwTVm
--- a/browser/components/extensions/ext-devtools-inspectedWindow.js
+++ b/browser/components/extensions/ext-devtools-inspectedWindow.js
@@ -20,37 +20,57 @@ this.devtools_inspectedWindow = class ex
       // If there is not yet a front instance, then a lazily cloned target for the context is
       // retrieved using the DevtoolsParentContextsManager helper (which is an asynchronous operation,
       // because the first time that the target has been cloned, it is not ready to be used to create
       // the front instance until it is connected to the remote debugger successfully).
       const clonedTarget = await getDevToolsTargetForContext(context);
       return new WebExtensionInspectedWindowFront(clonedTarget.client, clonedTarget.form);
     }
 
+    function getToolboxOptions() {
+      const options = {};
+      const toolbox = context.devToolsToolbox;
+      const selectedNode = toolbox.selection;
+
+      if (selectedNode && selectedNode.nodeFront) {
+        // If there is a selected node in the inspector, we hand over
+        // its actor id to the eval request in order to provide the "$0" binding.
+        options.toolboxSelectedNodeActorID = selectedNode.nodeFront.actorID;
+      }
+
+      // Provide the console actor ID to implement the "inspect" binding.
+      options.toolboxConsoleActorID = toolbox.target.form.consoleActor;
+
+      return options;
+    }
+
     // TODO(rpl): retrive a more detailed callerInfo object, like the filename and
     // lineNumber of the actual extension called, in the child process.
     const callerInfo = {
       addonId: context.extension.id,
       url: context.extension.baseURI.spec,
     };
 
     return {
       devtools: {
         inspectedWindow: {
           async eval(expression, options) {
             if (!waitForInspectedWindowFront) {
               waitForInspectedWindowFront = getInspectedWindowFront();
             }
 
             const front = await waitForInspectedWindowFront;
-            return front.eval(callerInfo, expression, options || {}).then(evalResult => {
-              // TODO(rpl): check for additional undocumented behaviors on chrome
-              // (e.g. if we should also print error to the console or set lastError?).
-              return new SpreadArgs([evalResult.value, evalResult.exceptionInfo]);
-            });
+
+            const evalOptions = Object.assign({}, options, getToolboxOptions());
+
+            const evalResult = await front.eval(callerInfo, expression, evalOptions);
+
+            // TODO(rpl): check for additional undocumented behaviors on chrome
+            // (e.g. if we should also print error to the console or set lastError?).
+            return new SpreadArgs([evalResult.value, evalResult.exceptionInfo]);
           },
           async reload(options) {
             const {ignoreCache, userAgent, injectedScript} = options || {};
 
             if (!waitForInspectedWindowFront) {
               waitForInspectedWindowFront = getInspectedWindowFront();
             }
 
--- a/browser/components/extensions/test/browser/browser-common.ini
+++ b/browser/components/extensions/test/browser/browser-common.ini
@@ -56,16 +56,17 @@ skip-if = (os == 'win' && !debug) # bug 
 [browser_ext_contextMenus_commands.js]
 [browser_ext_contextMenus_icons.js]
 [browser_ext_contextMenus_onclick.js]
 [browser_ext_contextMenus_radioGroups.js]
 [browser_ext_contextMenus_uninstall.js]
 [browser_ext_contextMenus_urlPatterns.js]
 [browser_ext_currentWindow.js]
 [browser_ext_devtools_inspectedWindow.js]
+[browser_ext_devtools_inspectedWindow_eval_bindings.js]
 [browser_ext_devtools_inspectedWindow_reload.js]
 [browser_ext_devtools_network.js]
 [browser_ext_devtools_page.js]
 [browser_ext_devtools_panel.js]
 [browser_ext_geckoProfiler_symbolicate.js]
 [browser_ext_getViews.js]
 [browser_ext_identity_indication.js]
 [browser_ext_incognito_views.js]
copy from browser/components/extensions/test/browser/browser_ext_devtools_inspectedWindow.js
copy to browser/components/extensions/test/browser/browser_ext_devtools_inspectedWindow_eval_bindings.js
--- a/browser/components/extensions/test/browser/browser_ext_devtools_inspectedWindow.js
+++ b/browser/components/extensions/test/browser/browser_ext_devtools_inspectedWindow_eval_bindings.js
@@ -1,126 +1,21 @@
 /* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
 /* vim: set sts=2 sw=2 et tw=80: */
 "use strict";
 
-XPCOMUtils.defineLazyModuleGetter(this, "gDevTools",
-                                  "resource://devtools/client/framework/gDevTools.jsm");
-XPCOMUtils.defineLazyModuleGetter(this, "devtools",
-                                  "resource://devtools/shared/Loader.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "DevToolsShim",
+                                  "chrome://devtools-shim/content/DevToolsShim.jsm");
 
 /**
  * this test file ensures that:
  *
- * - the devtools page gets only a subset of the runtime API namespace.
- * - devtools.inspectedWindow.tabId is the same tabId that we can retrieve
- *   in the background page using the tabs API namespace.
- * - devtools API is available in the devtools page sub-frames when a valid
- *   extension URL has been loaded.
- * - devtools.inspectedWindow.eval:
- *   - returns a serialized version of the evaluation result.
- *   - returns the expected error object when the return value serialization raises a
- *     "TypeError: cyclic object value" exception.
- *   - returns the expected exception when an exception has been raised from the evaluated
- *     javascript code.
+ * - devtools.inspectedWindow.eval provides the expected $0 and inspect bindings
  */
-add_task(async function test_devtools_inspectedWindow_tabId() {
-  let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, "http://mochi.test:8888/");
-
-  async function background() {
-    browser.test.assertEq(undefined, browser.devtools,
-                          "No devtools APIs should be available in the background page");
-
-    const tabs = await browser.tabs.query({active: true, lastFocusedWindow: true});
-    browser.test.sendMessage("current-tab-id", tabs[0].id);
-  }
-
-  function devtools_page() {
-    browser.test.assertEq(undefined, browser.runtime.getBackgroundPage,
-      "The `runtime.getBackgroundPage` API method should be missing in a devtools_page context"
-    );
-
-    try {
-      let tabId = browser.devtools.inspectedWindow.tabId;
-      browser.test.sendMessage("inspectedWindow-tab-id", tabId);
-    } catch (err) {
-      browser.test.sendMessage("inspectedWindow-tab-id", undefined);
-      throw err;
-    }
-  }
-
-  function devtools_page_iframe() {
-    try {
-      let tabId = browser.devtools.inspectedWindow.tabId;
-      browser.test.sendMessage("devtools_page_iframe.inspectedWindow-tab-id", tabId);
-    } catch (err) {
-      browser.test.fail(`Error: ${err} :: ${err.stack}`);
-      browser.test.sendMessage("devtools_page_iframe.inspectedWindow-tab-id", undefined);
-    }
-  }
-
-  let extension = ExtensionTestUtils.loadExtension({
-    background,
-    manifest: {
-      devtools_page: "devtools_page.html",
-    },
-    files: {
-      "devtools_page.html": `<!DOCTYPE html>
-      <html>
-       <head>
-         <meta charset="utf-8">
-       </head>
-       <body>
-         <iframe src="/devtools_page_iframe.html"></iframe>
-         <script src="devtools_page.js"></script>
-       </body>
-      </html>`,
-      "devtools_page.js": devtools_page,
-      "devtools_page_iframe.html": `<!DOCTYPE html>
-      <html>
-       <head>
-         <meta charset="utf-8">
-       </head>
-       <body>
-         <script src="devtools_page_iframe.js"></script>
-       </body>
-      </html>`,
-      "devtools_page_iframe.js": devtools_page_iframe,
-    },
-  });
-
-  await extension.startup();
-
-  let backgroundPageCurrentTabId = await extension.awaitMessage("current-tab-id");
-
-  let target = devtools.TargetFactory.forTab(tab);
-
-  await gDevTools.showToolbox(target, "webconsole");
-  info("developer toolbox opened");
-
-  let devtoolsInspectedWindowTabId = await extension.awaitMessage("inspectedWindow-tab-id");
-
-  is(devtoolsInspectedWindowTabId, backgroundPageCurrentTabId,
-     "Got the expected tabId from devtool.inspectedWindow.tabId");
-
-  let devtoolsPageIframeTabId = await extension.awaitMessage("devtools_page_iframe.inspectedWindow-tab-id");
-
-  is(devtoolsPageIframeTabId, backgroundPageCurrentTabId,
-     "Got the expected tabId from devtool.inspectedWindow.tabId called in a devtool_page iframe");
-
-  await gDevTools.closeToolbox(target);
-
-  await target.destroy();
-
-  await extension.unload();
-
-  await BrowserTestUtils.removeTab(tab);
-});
-
-add_task(async function test_devtools_inspectedWindow_eval() {
+add_task(async function test_devtools_inspectedWindow_eval_bindings() {
   const TEST_TARGET_URL = "http://mochi.test:8888/";
   let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, TEST_TARGET_URL);
 
   function devtools_page() {
     browser.test.onMessage.addListener(async (msg, ...args) => {
       if (msg !== "inspectedWindow-eval-request") {
         browser.test.fail(`Unexpected test message received: ${msg}`);
         return;
@@ -154,84 +49,103 @@ add_task(async function test_devtools_in
        </body>
       </html>`,
       "devtools_page.js": devtools_page,
     },
   });
 
   await extension.startup();
 
-  let target = devtools.TargetFactory.forTab(tab);
+  const {gDevTools} = DevToolsShim;
 
-  await gDevTools.showToolbox(target, "webconsole");
-  info("developer toolbox opened");
+  const target = gDevTools.getTargetForTab(tab);
+  // Open the toolbox on the styleeditor, so that the inspector and the
+  // console panel have not been explicitly activated yet.
+  const toolbox = await gDevTools.showToolbox(target, "styleeditor");
+  info("Developer toolbox opened");
 
-  const evalTestCases = [
-    // Successful evaluation results.
-    {
-      args: ["window.location.href"],
-      expectedResults: {evalResult: TEST_TARGET_URL, errorResult: undefined},
-    },
+  // Test $0 binding with no selected node
+  info("Test inspectedWindow.eval $0 binding with no selected node");
+
+  const evalNoSelectedNodePromise = extension.awaitMessage(`inspectedWindow-eval-result`);
+  extension.sendMessage(`inspectedWindow-eval-request`, "$0");
+  const evalNoSelectedNodeResult = await evalNoSelectedNodePromise;
+
+  Assert.deepEqual(evalNoSelectedNodeResult,
+                   {evalResult: undefined, errorResult: undefined},
+                   "Got the expected eval result");
 
-    // Error evaluation results.
-    {
-      args: ["window"],
-      expectedResults: {
-        evalResult: undefined,
-        errorResult: {
-          isError: true,
-          code: "E_PROTOCOLERROR",
-          description: "Inspector protocol error: %s",
-          details: [
-            "TypeError: cyclic object value",
-          ],
-        },
-      },
-    },
+  // Test $0 binding with a selected node in the inspector.
+
+  await gDevTools.showToolbox(target, "inspector");
+  info("Toolbox switched to the inspector panel");
+
+  info("Test inspectedWindow.eval $0 binding with a selected node in the inspector");
+
+  const evalSelectedNodePromise = extension.awaitMessage(`inspectedWindow-eval-result`);
+  extension.sendMessage(`inspectedWindow-eval-request`, "$0 && $0.tagName");
+  const evalSelectedNodeResult = await evalSelectedNodePromise;
+
+  Assert.deepEqual(evalSelectedNodeResult,
+                   {evalResult: "BODY", errorResult: undefined},
+                   "Got the expected eval result");
+
+  // Test that inspect($0) switch the developer toolbox to the inspector.
+
+  await gDevTools.showToolbox(target, "styleeditor");
+
+  info("Toolbox switched back to the styleeditor panel");
 
-    // Exception evaluation results.
-    {
-      args: ["throw new Error('fake eval exception');"],
-      expectedResults: {
-        evalResult: undefined,
-        errorResult: {
-          isException: true,
-          value: /Error: fake eval exception\n.*moz-extension:\/\//,
-        },
-      },
+  const inspectorPanelSelectedPromise = (async () => {
+    const toolId = await new Promise(resolve => {
+      toolbox.once("select", (evt, toolId) => resolve(toolId));
+    });
 
-    },
-  ];
+    if (toolId === "inspector") {
+      const selectedNodeName = toolbox.selection.nodeFront &&
+                               toolbox.selection.nodeFront._form.nodeName;
+      is(selectedNodeName, "HTML", "The expected DOM node has been selected in the inspector");
+    } else {
+      throw new Error(`inspector panel expected, ${toolId} has been selected instead`);
+    }
+  })();
 
-  for (let testCase of evalTestCases) {
-    info(`test inspectedWindow.eval with ${JSON.stringify(testCase)}`);
+  info("Test inspectedWindow.eval inspect() binding called for a DOM element");
+  const inspectDOMNodePromise = extension.awaitMessage(`inspectedWindow-eval-result`);
+  extension.sendMessage(`inspectedWindow-eval-request`, "inspect(document.documentElement)");
+  await inspectDOMNodePromise;
 
-    const {args, expectedResults} = testCase;
+  info("Wait for the toolbox to switch to the inspector and the expected node has been selected");
+  await inspectorPanelSelectedPromise;
+  info("Toolbox has been switched to the inspector as expected");
 
-    extension.sendMessage(`inspectedWindow-eval-request`, ...args);
-
-    const {evalResult, errorResult} = await extension.awaitMessage(`inspectedWindow-eval-result`);
+  info("Test inspectedWindow.eval inspect() binding called for a JS object");
 
-    Assert.deepEqual(evalResult, expectedResults.evalResult, "Got the expected eval result");
+  const splitPanelOpenedPromise = (async () => {
+    await toolbox.once("split-console");
+    let jsterm = toolbox.getPanel("webconsole").hud.jsterm;
 
-    if (errorResult) {
-      for (const errorPropName of Object.keys(expectedResults.errorResult)) {
-        const expected = expectedResults.errorResult[errorPropName];
-        const actual = errorResult[errorPropName];
+    const options = await new Promise(resolve => {
+      jsterm.once("variablesview-open", (evt, view, options) => resolve(options));
+    });
 
-        if (expected instanceof RegExp) {
-          ok(expected.test(actual),
-             `Got exceptionInfo.${errorPropName} value ${actual} matches ${expected}`);
-        } else {
-          Assert.deepEqual(actual, expected,
-                           `Got the expected exceptionInfo.${errorPropName} value`);
-        }
-      }
-    }
-  }
+    const objectType = options.objectActor.type;
+    const objectPreviewProperties = options.objectActor.preview.ownProperties;
+    is(objectType, "object", "The inspected object has the expected type");
+    Assert.deepEqual(Object.keys(objectPreviewProperties), ["testkey"],
+                     "The inspected object has the expected preview properties");
+  })();
+
+  const inspectJSObjectPromise = extension.awaitMessage(`inspectedWindow-eval-result`);
+  extension.sendMessage(`inspectedWindow-eval-request`, "inspect({testkey: 'testvalue'})");
+  await inspectJSObjectPromise;
+
+  info("Wait for the split console to be opened and the JS object inspected");
+  await splitPanelOpenedPromise;
+  info("Split console has been opened as expected");
 
   await gDevTools.closeToolbox(target);
 
   await target.destroy();
 
   await extension.unload();
 
   await BrowserTestUtils.removeTab(tab);
--- a/devtools/client/framework/target.js
+++ b/devtools/client/framework/target.js
@@ -451,16 +451,20 @@ TabTarget.prototype = {
     };
 
     let onConsoleAttached = (response, consoleClient) => {
       if (!consoleClient) {
         this._remote.reject("Unable to attach to the console");
         return;
       }
       this.activeConsole = consoleClient;
+
+      this._onInspectObject = (event, packet) => this.emit("inspect-object", packet);
+      this.activeConsole.on("inspectObject", this._onInspectObject);
+
       this._remote.resolve(null);
     };
 
     let attachConsole = () => {
       this._client.attachConsole(this._form.consoleActor, [], onConsoleAttached);
     };
 
     if (this.isLocalTab) {
@@ -571,16 +575,19 @@ TabTarget.prototype = {
    */
   _teardownRemoteListeners: function () {
     this.client.removeListener("closed", this.destroy);
     this.client.removeListener("tabNavigated", this._onTabNavigated);
     this.client.removeListener("tabDetached", this._onTabDetached);
     this.client.removeListener("frameUpdate", this._onFrameUpdate);
     this.client.removeListener("newSource", this._onSourceUpdated);
     this.client.removeListener("updatedSource", this._onSourceUpdated);
+    if (this.activeConsole && this._onInspectObject) {
+      this.activeConsole.off("inspectObject", this._onInspectObject);
+    }
   },
 
   /**
    * Handle tabs events.
    */
   handleEvent: function (event) {
     switch (event.type) {
       case "TabClose":
--- a/devtools/client/framework/toolbox.js
+++ b/devtools/client/framework/toolbox.js
@@ -62,16 +62,20 @@ loader.lazyRequireGetter(this, "ToolboxB
   "devtools/client/definitions", true);
 loader.lazyRequireGetter(this, "SourceMapURLService",
   "devtools/client/framework/source-map-url-service", true);
 loader.lazyRequireGetter(this, "HUDService",
   "devtools/client/webconsole/hudservice");
 loader.lazyRequireGetter(this, "viewSource",
   "devtools/client/shared/view-source");
 
+loader.lazyGetter(this, "domNodeConstants", () => {
+  return require("devtools/shared/dom-node-constants");
+});
+
 loader.lazyGetter(this, "registerHarOverlay", () => {
   return require("devtools/client/netmonitor/src/har/toolbox-overlay").register;
 });
 
 /**
  * A "Toolbox" is the component that holds all the tools for one specific
  * target. Visually, it's a document that includes the tools tabs and all
  * the iframes where the tool panels will be living in.
@@ -129,16 +133,17 @@ function Toolbox(target, selectedTool, h
   this._onBottomHostWillChange = this._onBottomHostWillChange.bind(this);
   this._toggleMinimizeMode = this._toggleMinimizeMode.bind(this);
   this._onToolbarFocus = this._onToolbarFocus.bind(this);
   this._onToolbarArrowKeypress = this._onToolbarArrowKeypress.bind(this);
   this._onPickerClick = this._onPickerClick.bind(this);
   this._onPickerKeypress = this._onPickerKeypress.bind(this);
   this._onPickerStarted = this._onPickerStarted.bind(this);
   this._onPickerStopped = this._onPickerStopped.bind(this);
+  this._onInspectObject = this._onInspectObject.bind(this);
   this.selectTool = this.selectTool.bind(this);
 
   this._target.on("close", this.destroy);
 
   if (!selectedTool) {
     selectedTool = Services.prefs.getCharPref(this._prefs.LAST_TOOL);
   }
   this._defaultToolId = selectedTool;
@@ -147,16 +152,17 @@ function Toolbox(target, selectedTool, h
 
   this._isOpenDeferred = defer();
   this.isOpen = this._isOpenDeferred.promise;
 
   EventEmitter.decorate(this);
 
   this._target.on("navigate", this._refreshHostTitle);
   this._target.on("frame-update", this._updateFrames);
+  this._target.on("inspect-object", this._onInspectObject);
 
   this.on("host-changed", this._refreshHostTitle);
   this.on("select", this._refreshHostTitle);
 
   this.on("ready", this._showDevEditionPromo);
 
   gDevTools.on("tool-registered", this._toolRegistered);
   gDevTools.on("tool-unregistered", this._toolUnregistered);
@@ -417,16 +423,17 @@ Toolbox.prototype = {
         ]);
       }
 
       // Attach the thread
       this._threadClient = yield attachThread(this);
       yield domReady.promise;
 
       this.isReady = true;
+
       let framesPromise = this._listFrames();
 
       Services.prefs.addObserver("devtools.cache.disabled", this._applyCacheSettings);
       Services.prefs.addObserver("devtools.serviceWorkers.testing.enabled",
                                  this._applyServiceWorkersTestingSettings);
 
       this.textBoxContextMenuPopup =
         this.doc.getElementById("toolbox-textbox-context-popup");
@@ -2212,16 +2219,43 @@ Toolbox.prototype = {
           let autohide = !flags.testing;
           this._highlighter = yield this._inspector.getHighlighter(autohide);
         }
       }.bind(this));
     }
     return this._initInspector;
   },
 
+  _onInspectObject: function (evt, packet) {
+    this.inspectObjectActor(packet.objectActor, packet.inspectFromAnnotation);
+  },
+
+  inspectObjectActor: async function (objectActor, inspectFromAnnotation) {
+    if (objectActor.preview &&
+        objectActor.preview.nodeType === domNodeConstants.ELEMENT_NODE) {
+      // Open the inspector and select the DOM Element.
+      await this.loadTool("inspector");
+      const inspector = await this.getPanel("inspector");
+      const nodeFound = await inspector.inspectNodeActor(objectActor.actor,
+                                                         inspectFromAnnotation);
+      if (nodeFound) {
+        await this.selectTool("inspector");
+      }
+    } else if (objectActor.type !== "null" &&
+               objectActor.type !== "undefined") {
+      // Open then split console and inspect the object in the variables view,
+      // when the objectActor doesn't represent an undefined or null value.
+      await this.openSplitConsole();
+      const panel = this.getPanel("webconsole");
+      const jsterm = panel.hud.jsterm;
+
+      jsterm.inspectObjectActor(objectActor);
+    }
+  },
+
   /**
    * Destroy the inspector/walker/selection fronts
    * Returns a promise that resolves when the fronts are destroyed
    */
   destroyInspector: function () {
     if (this._destroyingInspector) {
       return this._destroyingInspector;
     }
@@ -2295,16 +2329,17 @@ Toolbox.prototype = {
     if (this._destroyer) {
       return this._destroyer;
     }
     let deferred = defer();
     this._destroyer = deferred.promise;
 
     this.emit("destroy");
 
+    this._target.off("inspect-object", this._onInspectObject);
     this._target.off("navigate", this._refreshHostTitle);
     this._target.off("frame-update", this._updateFrames);
     this.off("select", this._refreshHostTitle);
     this.off("host-changed", this._refreshHostTitle);
     this.off("ready", this._showDevEditionPromo);
 
     gDevTools.off("tool-registered", this._toolRegistered);
     gDevTools.off("tool-unregistered", this._toolUnregistered);
--- a/devtools/client/inspector/inspector.js
+++ b/devtools/client/inspector/inspector.js
@@ -2001,16 +2001,34 @@ Inspector.prototype = {
    *         The node to highlight.
    * @param  {Object} options
    *         Options passed to the highlighter actor.
    */
   onShowBoxModelHighlighterForNode(nodeFront, options) {
     let toolbox = this.toolbox;
     toolbox.highlighterUtils.highlightNodeFront(nodeFront, options);
   },
+
+  async inspectNodeActor(nodeActor, inspectFromAnnotation) {
+    const nodeFront = await this.walker.getNodeActorFromObjectActor(nodeActor);
+    if (!nodeFront) {
+      console.error("The object cannot be linked to the inspector, the " +
+                    "corresponding nodeFront could not be found.");
+      return false;
+    }
+
+    let isAttached = await this.walker.isInDOMTree(nodeFront);
+    if (!isAttached) {
+      console.error("Selected DOMNode is not attached to the document tree.");
+      return false;
+    }
+
+    await this.selection.setNodeFront(nodeFront, inspectFromAnnotation);
+    return true;
+  },
 };
 
 /**
  * Create a fake toolbox when running the inspector standalone, either in a chrome tab or
  * in a content tab.
  *
  * @param {Target} target to debug
  * @param {Function} createThreadClient
--- a/devtools/client/webconsole/jsterm.js
+++ b/devtools/client/webconsole/jsterm.js
@@ -337,21 +337,17 @@ JSTerm.prototype = {
       switch (helperResult.type) {
         case "clearOutput":
           this.clearOutput();
           break;
         case "clearHistory":
           this.clearHistory();
           break;
         case "inspectObject":
-          this.openVariablesView({
-            label:
-              VariablesView.getString(helperResult.object, { concise: true }),
-            objectActor: helperResult.object,
-          });
+          this.inspectObjectActor(helperResult.object);
           break;
         case "error":
           try {
             errorMessage = l10n.getStr(helperResult.message);
           } catch (ex) {
             errorMessage = helperResult.message;
           }
           break;
@@ -400,16 +396,23 @@ JSTerm.prototype = {
       msg._objectActors.add(response.exception.actor);
     }
 
     if (WebConsoleUtils.isActorGrip(result)) {
       msg._objectActors.add(result.actor);
     }
   },
 
+  inspectObjectActor: function (objectActor) {
+    return this.openVariablesView({
+      objectActor,
+      label: VariablesView.getString(objectActor, {concise: true}),
+    });
+  },
+
   /**
    * Execute a string. Execution happens asynchronously in the content process.
    *
    * @param string [executeString]
    *        The string you want to execute. If this is not provided, the current
    *        user input is used - taken from |this.getInputValue()|.
    * @param function [callback]
    *        Optional function to invoke when the result is displayed.
--- a/devtools/server/actors/webconsole.js
+++ b/devtools/server/actors/webconsole.js
@@ -535,16 +535,27 @@ WebConsoleActor.prototype =
    * This is undefined if no evaluations have been completed.
    *
    * @return object
    */
   getLastConsoleInputEvaluation: function () {
     return this._lastConsoleInputEvaluation;
   },
 
+  /**
+   * This helper is used by the WebExtensionInspectedWindowActor to
+   * inspect an object in the developer toolbox.
+   */
+  inspectObject(dbgObj, inspectFromAnnotation) {
+    this.conn.sendActorEvent(this.actorID, "inspectObject", {
+      objectActor: this.createValueGrip(dbgObj),
+      inspectFromAnnotation,
+    });
+  },
+
   // Request handlers for known packet types.
 
   /**
    * Handler for the "startListeners" request.
    *
    * @param object request
    *        The JSON request object received from the Web Console client.
    * @return object
--- a/devtools/server/actors/webextension-inspected-window.js
+++ b/devtools/server/actors/webextension-inspected-window.js
@@ -3,18 +3,21 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
 const protocol = require("devtools/shared/protocol");
 
 const {Ci, Cu, Cr} = require("chrome");
 
+const {DebuggerServer} = require("devtools/server/main");
 const Services = require("Services");
 
+loader.lazyGetter(this, "NodeActor", () => require("devtools/server/actors/inspector").NodeActor, true);
+
 const {
   XPCOMUtils,
 } = Cu.import("resource://gre/modules/XPCOMUtils.jsm", {});
 
 const {
   webExtensionInspectedWindowSpec,
 } = require("devtools/shared/specs/webextension-inspected-window");
 
@@ -192,16 +195,17 @@ var WebExtensionInspectedWindowActor = p
      */
     initialize(conn, tabActor) {
       protocol.Actor.prototype.initialize.call(this, conn);
       this.tabActor = tabActor;
     },
 
     destroy(conn) {
       protocol.Actor.prototype.destroy.call(this, conn);
+
       if (this.customizedReload) {
         this.customizedReload.stop(
           new Error("WebExtensionInspectedWindowActor destroyed")
         );
         delete this.customizedReload;
       }
 
       if (this._dbg) {
@@ -227,16 +231,68 @@ var WebExtensionInspectedWindowActor = p
     get window() {
       return this.tabActor.window;
     },
 
     get webNavigation() {
       return this.tabActor.webNavigation;
     },
 
+    createEvalBindings(dbgWindow, options) {
+      const bindings = Object.create(null);
+
+      let selectedDOMNode;
+
+      if (options.toolboxSelectedNodeActorID) {
+        let actor = DebuggerServer.searchAllConnectionsForActor(
+          options.toolboxSelectedNodeActorID
+        );
+        if (actor && actor instanceof NodeActor) {
+          selectedDOMNode = actor.rawNode;
+        }
+      }
+
+      Object.defineProperty(bindings, "$0", {
+        enumerable: true,
+        configurable: true,
+        get: () => {
+          if (selectedDOMNode && !Cu.isDeadWrapper(selectedDOMNode)) {
+            return dbgWindow.makeDebuggeeValue(selectedDOMNode);
+          }
+
+          return undefined;
+        },
+      });
+
+      // This function is used by 'eval' and 'reload' requests, but only 'eval'
+      // passes 'toolboxConsoleActor' from the client side in order to set
+      // the 'inspect' binding.
+      Object.defineProperty(bindings, "inspect", {
+        enumerable: true,
+        configurable: true,
+        value: dbgWindow.makeDebuggeeValue((object) => {
+          const dbgObj = dbgWindow.makeDebuggeeValue(object);
+
+          let consoleActor = DebuggerServer.searchAllConnectionsForActor(
+            options.toolboxConsoleActorID
+          );
+          if (consoleActor) {
+            consoleActor.inspectObject(dbgObj,
+                                       "webextension-devtools-inspectedWindow-eval");
+          } else {
+            // TODO(rpl): evaluate if it would be better to raise an exception
+            // to the caller code instead.
+            console.error("Toolbox Console RDP Actor not found");
+          }
+        }),
+      });
+
+      return bindings;
+    },
+
     /**
      * Reload the target tab, optionally bypass cache, customize the userAgent and/or
      * inject a script in targeted document or any of its sub-frame.
      *
      * @param {webExtensionCallerInfo} callerInfo
      *   the addonId and the url (the addon base url or the url of the actual caller
      *   filename and lineNumber) used to log useful debugging information in the
      *   produced error logs and eval stack trace.
@@ -346,29 +402,17 @@ var WebExtensionInspectedWindowActor = p
      *   Used in the CustomizedReload instances to evaluate the `injectedScript`
      *   javascript code in every sub-frame of the target window during the tab reload.
      *   NOTE: this parameter is not part of the RDP protocol exposed by this actor, when
      *   it is called over the remote debugging protocol the target window is always
      *   `tabActor.window`.
      */
     eval(callerInfo, expression, options, customTargetWindow) {
       const window = customTargetWindow || this.window;
-
-      if (Object.keys(options).length > 0) {
-        return {
-          exceptionInfo: {
-            isError: true,
-            code: "E_PROTOCOLERROR",
-            description: "Inspector protocol error: %s",
-            details: [
-              "The inspectedWindow.eval options are currently not supported",
-            ],
-          },
-        };
-      }
+      options = options || {};
 
       if (!window) {
         return {
           exceptionInfo: {
             isError: true,
             code: "E_PROTOCOLERROR",
             description: "Inspector protocol error: %s",
             details: [
@@ -389,24 +433,41 @@ var WebExtensionInspectedWindowActor = p
             description: "Inspector protocol error: %s",
             details: [
               "This target has a system principal. inspectedWindow.eval denied.",
             ],
           },
         };
       }
 
+      // Raise an error on the unsupported options.
+      if (options.frameURL || options.contextSecurityOrigin ||
+          options.useContentScriptContext) {
+        return {
+          exceptionInfo: {
+            isError: true,
+            code: "E_PROTOCOLERROR",
+            description: "Inspector protocol error: %s",
+            details: [
+              "The inspectedWindow.eval options are currently not supported",
+            ],
+          },
+        };
+      }
+
       const dbgWindow = this.dbg.makeGlobalObjectReference(window);
 
       let evalCalledFrom = callerInfo.url;
       if (callerInfo.lineNumber) {
         evalCalledFrom += `:${callerInfo.lineNumber}`;
       }
-      // TODO(rpl): add $0 and inspect(...) bindings (Bug 1300590)
-      const result = dbgWindow.executeInGlobalWithBindings(expression, {}, {
+
+      const bindings = this.createEvalBindings(dbgWindow, options);
+
+      const result = dbgWindow.executeInGlobalWithBindings(expression, bindings, {
         url: `debugger eval called from ${evalCalledFrom} - eval code`,
       });
 
       let evalResult;
 
       if (result) {
         if ("return" in result) {
           evalResult = result.return;
--- a/devtools/server/main.js
+++ b/devtools/server/main.js
@@ -1378,35 +1378,32 @@ var DebuggerServer = {
         for (let connID of Object.getOwnPropertyNames(this._connections)) {
           this._connections[connID].rootActor.removeActorByName(name);
         }
       }
     }
   },
 
   /**
-   * Called when DevTools are unloaded to remove the contend process server script for the
-   * list of scripts loaded for each new content process. Will also remove message
-   * listeners from already loaded scripts.
+   * Searches all active connections for an actor matching an ID.
+   *
+   * ⚠ TO BE USED ONLY FROM SERVER CODE OR TESTING ONLY! ⚠`
+   *
+   * This is helpful for some tests which depend on reaching into the server to check some
+   * properties of an actor, and it is also used by the actors related to the
+   * DevTools WebExtensions API to be able to interact with the actors created for the
+   * panels natively provided by the DevTools Toolbox.
    */
-  removeContentServerScript() {
-    Services.ppmm.removeDelayedProcessScript(CONTENT_PROCESS_DBG_SERVER_SCRIPT);
-    try {
-      Services.ppmm.broadcastAsyncMessage("debug:close-content-server");
-    } catch (e) {
-      // Nothing to do
-    }
-  },
-
-  /**
-   * ⚠ TESTING ONLY! ⚠ Searches all active connections for an actor matching an ID.
-   * This is helpful for some tests which depend on reaching into the server to check some
-   * properties of an actor.
-   */
-  _searchAllConnectionsForActor(actorID) {
+  searchAllConnectionsForActor(actorID) {
+    // NOTE: the actor IDs are generated with the following format:
+    //
+    //   `server${loaderID}.conn${ConnectionID}${ActorPrefix}${ActorID}`
+    //
+    // as an optimization we can come up with a regexp to query only
+    // the right connection via its id.
     for (let connID of Object.getOwnPropertyNames(this._connections)) {
       let actor = this._connections[connID].getActor(actorID);
       if (actor) {
         return actor;
       }
     }
     return null;
   },
--- a/devtools/server/tests/browser/browser_navigateEvents.js
+++ b/devtools/server/tests/browser/browser_navigateEvents.js
@@ -105,17 +105,17 @@ function getServerTabActor(callback) {
 
   // Connect to this tab
   let transport = DebuggerServer.connectPipe();
   client = new DebuggerClient(transport);
   connectDebuggerClient(client).then(form => {
     let actorID = form.actor;
     client.attachTab(actorID, function (response, tabClient) {
       // !Hack! Retrieve a server side object, the BrowserTabActor instance
-      let tabActor = DebuggerServer._searchAllConnectionsForActor(actorID);
+      let tabActor = DebuggerServer.searchAllConnectionsForActor(actorID);
       callback(tabActor);
     });
   });
 
   client.addListener("tabNavigated", function (event, packet) {
     assertEvent("tabNavigated", packet);
   });
 }
--- a/devtools/server/tests/mochitest/inspector-helpers.js
+++ b/devtools/server/tests/mochitest/inspector-helpers.js
@@ -122,17 +122,17 @@ function serverOwnershipSubtree(walker, 
   }
   return {
     name: actor.actorID,
     children: sortOwnershipChildren(children)
   };
 }
 
 function serverOwnershipTree(walker) {
-  let serverWalker = DebuggerServer._searchAllConnectionsForActor(walker.actorID);
+  let serverWalker = DebuggerServer.searchAllConnectionsForActor(walker.actorID);
 
   return {
     root: serverOwnershipSubtree(serverWalker, serverWalker.rootDoc),
     orphaned: [...serverWalker._orphaned]
               .map(o => serverOwnershipSubtree(serverWalker, o.rawNode)),
     retained: [...serverWalker._retainedOrphans]
               .map(o => serverOwnershipSubtree(serverWalker, o.rawNode))
   };
--- a/devtools/server/tests/mochitest/test_animation_actor-lifetime.html
+++ b/devtools/server/tests/mochitest/test_animation_actor-lifetime.html
@@ -40,17 +40,17 @@ window.onload = function () {
 
   addAsyncTest(function* testActorLifetime() {
     info("Testing animated node actor");
     let animatedNodeActor = yield gWalker.querySelector(gWalker.rootNode,
       ".animated");
     yield animationsFront.getAnimationPlayersForNode(animatedNodeActor);
 
     let animationsActor = DebuggerServer
-                          ._searchAllConnectionsForActor(animationsFront.actorID);
+                          .searchAllConnectionsForActor(animationsFront.actorID);
 
     is(animationsActor.actors.length, 1,
       "AnimationActor have 1 AnimationPlayerActors");
 
     info("Testing AnimationPlayerActors release");
     let stillNodeActor = yield gWalker.querySelector(gWalker.rootNode,
       ".still");
     yield animationsFront.getAnimationPlayersForNode(stillNodeActor);
--- a/devtools/server/tests/mochitest/test_inspector-anonymous.html
+++ b/devtools/server/tests/mochitest/test_inspector-anonymous.html
@@ -68,17 +68,17 @@ window.onload = function () {
     is(children.nodes.length, 2, "No native anon content for form control");
 
     runNextTest();
   });
 
   addAsyncTest(function* testNativeAnonymousStartingNode() {
     info("Tests attaching an element that a walker can't see.");
 
-    let serverWalker = DebuggerServer._searchAllConnectionsForActor(gWalker.actorID);
+    let serverWalker = DebuggerServer.searchAllConnectionsForActor(gWalker.actorID);
     let docwalker = new _documentWalker(
       gInspectee.querySelector("select"),
       gInspectee.defaultView,
       nodeFilterConstants.SHOW_ALL,
       () => {
         return nodeFilterConstants.FILTER_ACCEPT;
       }
     );
--- a/devtools/server/tests/mochitest/test_inspector-search.html
+++ b/devtools/server/tests/mochitest/test_inspector-search.html
@@ -43,17 +43,17 @@ window.onload = function () {
         inspector = InspectorFront(client, tab);
         resolve();
       });
     });
 
     let walkerFront = yield inspector.getWalker();
     ok(walkerFront, "getWalker() should return an actor.");
 
-    walkerActor = DebuggerServer._searchAllConnectionsForActor(walkerFront.actorID);
+    walkerActor = DebuggerServer.searchAllConnectionsForActor(walkerFront.actorID);
     ok(walkerActor,
       "Got a reference to the walker actor (" + walkerFront.actorID + ")");
 
     walkerSearch = walkerActor.walkerSearch;
 
     runNextTest();
   });
 
--- a/devtools/shared/client/main.js
+++ b/devtools/shared/client/main.js
@@ -175,16 +175,17 @@ const UnsolicitedNotifications = {
   "exitedFrame": "exitedFrame",
   "appOpen": "appOpen",
   "appClose": "appClose",
   "appInstall": "appInstall",
   "appUninstall": "appUninstall",
   "evaluationResult": "evaluationResult",
   "newSource": "newSource",
   "updatedSource": "updatedSource",
+  "inspectObject": "inspectObject"
 };
 
 /**
  * Set of pause types that are sent by the server and not as an immediate
  * response to a client request.
  */
 const UnsolicitedPauses = {
   "resumeLimit": "resumeLimit",
--- a/devtools/shared/fronts/inspector.js
+++ b/devtools/shared/fronts/inspector.js
@@ -427,17 +427,17 @@ const NodeFront = FrontClassWithSpec(nod
    * protocol.  If you depend on this you're likely to break soon.
    */
   rawNode: function (rawNode) {
     if (!this.isLocalToBeDeprecated()) {
       console.warn("Tried to use rawNode on a remote connection.");
       return null;
     }
     const { DebuggerServer } = require("devtools/server/main");
-    let actor = DebuggerServer._searchAllConnectionsForActor(this.actorID);
+    let actor = DebuggerServer.searchAllConnectionsForActor(this.actorID);
     if (!actor) {
       // Can happen if we try to get the raw node for an already-expired
       // actor.
       return null;
     }
     return actor.rawNode;
   }
 });
@@ -902,17 +902,17 @@ const WalkerFront = FrontClassWithSpec(w
   // XXX hack during transition to remote inspector: get a proper NodeFront
   // for a given local node.  Only works locally.
   frontForRawNode: function (rawNode) {
     if (!this.isLocal()) {
       console.warn("Tried to use frontForRawNode on a remote connection.");
       return null;
     }
     const { DebuggerServer } = require("devtools/server/main");
-    let walkerActor = DebuggerServer._searchAllConnectionsForActor(this.actorID);
+    let walkerActor = DebuggerServer.searchAllConnectionsForActor(this.actorID);
     if (!walkerActor) {
       throw Error("Could not find client side for actor " + this.actorID);
     }
     let nodeActor = walkerActor._ref(rawNode);
 
     // Pass the node through a read/write pair to create the client side actor.
     let nodeType = types.getType("domnode");
     let returnNode = nodeType.read(
@@ -922,17 +922,17 @@ const WalkerFront = FrontClassWithSpec(w
     for (let extraActor of extras) {
       top = nodeType.read(nodeType.write(extraActor, walkerActor), this);
     }
 
     if (top !== this.rootNode) {
       // Imported an already-orphaned node.
       this._orphaned.add(top);
       walkerActor._orphaned
-        .add(DebuggerServer._searchAllConnectionsForActor(top.actorID));
+        .add(DebuggerServer.searchAllConnectionsForActor(top.actorID));
     }
     return returnNode;
   },
 
   removeNode: custom(Task.async(function* (node) {
     let previousSibling = yield this.previousSibling(node);
     let nextSibling = yield this._removeNode(node);
     return {
--- a/devtools/shared/specs/webextension-inspected-window.js
+++ b/devtools/shared/specs/webextension-inspected-window.js
@@ -31,16 +31,24 @@ types.addDictType("webExtensionCallerInf
 
 /**
  * RDP type related to the inspectedWindow.eval method request.
  */
 types.addDictType("webExtensionEvalOptions", {
   frameURL: "nullable:string",
   contextSecurityOrigin: "nullable:string",
   useContentScriptContext: "nullable:boolean",
+
+  // The actor ID of the node selected in the inspector if any,
+  // used to provide the '$0' binding.
+  toolboxSelectedNodeActorID: "nullable:string",
+
+  // The actor ID of the console actor,
+  // used to provide the 'inspect' binding.
+  toolboxConsoleActorID: "nullable:string",
 });
 
 /**
  * RDP type related to the inspectedWindow.eval method result errors.
  *
  * This type has been modelled on the same data format
  * used in the corresponding chrome API method.
  */
--- a/devtools/shared/webconsole/client.js
+++ b/devtools/shared/webconsole/client.js
@@ -29,20 +29,22 @@ function WebConsoleClient(debuggerClient
   this.traits = response.traits || {};
   this.events = [];
   this._networkRequests = new Map();
 
   this.pendingEvaluationResults = new Map();
   this.onEvaluationResult = this.onEvaluationResult.bind(this);
   this.onNetworkEvent = this._onNetworkEvent.bind(this);
   this.onNetworkEventUpdate = this._onNetworkEventUpdate.bind(this);
+  this.onInspectObject = this._onInspectObject.bind(this);
 
   this._client.addListener("evaluationResult", this.onEvaluationResult);
   this._client.addListener("networkEvent", this.onNetworkEvent);
   this._client.addListener("networkEventUpdate", this.onNetworkEventUpdate);
+  this._client.addListener("inspectObject", this.onInspectObject);
   EventEmitter.decorate(this);
 }
 
 exports.WebConsoleClient = WebConsoleClient;
 
 WebConsoleClient.prototype = {
   _longStrings: null,
   traits: null,
@@ -169,16 +171,30 @@ WebConsoleClient.prototype = {
 
     this.emit("networkEventUpdate", {
       packet: packet,
       networkInfo
     });
   },
 
   /**
+   * The "inspectObject" message type handler. We just re-emit it so that
+   * the toolbox can listen to the event and decide how to handle it.
+   *
+   * @private
+   * @param string type
+   *        Message type.
+   * @param object packet
+   *        The message received from the server.
+   */
+  _onInspectObject: function (type, packet) {
+    this.emit("inspectObject", packet);
+  },
+
+  /**
    * Retrieve the cached messages from the server.
    *
    * @see this.CACHED_MESSAGES
    * @param array types
    *        The array of message types you want from the server. See
    *        this.CACHED_MESSAGES for known types.
    * @param function onResponse
    *        The function invoked when the response is received.
@@ -638,16 +654,17 @@ WebConsoleClient.prototype = {
    * @param function onResponse
    *        Function to invoke when the server response is received.
    */
   detach: function (onResponse) {
     this._client.removeListener("evaluationResult", this.onEvaluationResult);
     this._client.removeListener("networkEvent", this.onNetworkEvent);
     this._client.removeListener("networkEventUpdate",
                                 this.onNetworkEventUpdate);
+    this._client.removeListener("inspectObject", this.onInspectObject);
     this.stopListeners(null, onResponse);
     this._longStrings = null;
     this._client = null;
     this.pendingEvaluationResults.clear();
     this.pendingEvaluationResults = null;
     this.clearNetworkRequests();
     this._networkRequests = null;
   },
--- a/layout/base/ServoRestyleManager.cpp
+++ b/layout/base/ServoRestyleManager.cpp
@@ -278,17 +278,17 @@ UpdateFramePseudoElementStyles(nsIFrame*
     UpdateBlockFramePseudoElements(static_cast<nsBlockFrame*>(aFrame),
                                    aStyleSet,
                                    aChangeList);
   }
 
   UpdateBackdropIfNeeded(aFrame, aStyleSet, aChangeList);
 }
 
-void
+bool
 ServoRestyleManager::ProcessPostTraversal(Element* aElement,
                                           nsStyleContext* aParentContext,
                                           ServoStyleSet* aStyleSet,
                                           nsStyleChangeList& aChangeList)
 {
   nsIFrame* styleFrame = nsLayoutUtils::GetStyleFrame(aElement);
 
   // Grab the change hint from Servo.
@@ -314,17 +314,17 @@ ServoRestyleManager::ProcessPostTraversa
 
   // If our change hint is reconstruct, we delegate to the frame constructor,
   // which consumes the new style and expects the old style to be on the frame.
   //
   // XXXbholley: We should teach the frame constructor how to clear the dirty
   // descendants bit to avoid the traversal here.
   if (changeHint & nsChangeHint_ReconstructFrame) {
     ClearRestyleStateFromSubtree(aElement);
-    return;
+    return true;
   }
 
   // TODO(emilio): We could avoid some refcount traffic here, specially in the
   // ServoComputedValues case, which uses atomic refcounting.
   //
   // Hold the old style context alive, because it could become a dangling
   // pointer during the replacement. In practice it's not a huge deal (on
   // GetNextContinuationWithSameStyle the pointer is not dereferenced, only
@@ -409,62 +409,70 @@ ServoRestyleManager::ProcessPostTraversa
     AddLayerChangesForAnimation(styleFrame, aElement, aChangeList);
   }
 
   const bool descendantsNeedFrames =
     aElement->HasFlag(NODE_DESCENDANTS_NEED_FRAMES);
   const bool traverseElementChildren =
     aElement->HasDirtyDescendantsForServo() || descendantsNeedFrames;
   const bool traverseTextChildren = recreateContext || descendantsNeedFrames;
+  bool recreatedAnyContext = recreateContext;
   if (traverseElementChildren || traverseTextChildren) {
     nsStyleContext* upToDateContext =
       recreateContext ? newContext : oldStyleContext;
 
     StyleChildrenIterator it(aElement);
     TextPostTraversalState textState(
         *upToDateContext, *aStyleSet, displayContentsNode && recreateContext);
     for (nsIContent* n = it.GetNextChild(); n; n = it.GetNextChild()) {
       if (traverseElementChildren && n->IsElement()) {
-        ProcessPostTraversal(n->AsElement(), upToDateContext,
-                             aStyleSet, aChangeList);
+        recreatedAnyContext |=
+          ProcessPostTraversal(n->AsElement(), upToDateContext,
+                               aStyleSet, aChangeList);
       } else if (traverseTextChildren && n->IsNodeOfType(nsINode::eTEXT)) {
-        ProcessPostTraversalForText(n, aChangeList, textState);
+        recreatedAnyContext |=
+          ProcessPostTraversalForText(n, aChangeList, textState);
       }
     }
   }
 
   aElement->UnsetHasDirtyDescendantsForServo();
   aElement->UnsetFlags(NODE_DESCENDANTS_NEED_FRAMES);
+  return recreatedAnyContext;
 }
 
-void
+bool
 ServoRestyleManager::ProcessPostTraversalForText(
     nsIContent* aTextNode,
     nsStyleChangeList& aChangeList,
     TextPostTraversalState& aPostTraversalState)
 {
   // Handle lazy frame construction.
   if (aTextNode->HasFlag(NODE_NEEDS_FRAME)) {
     aChangeList.AppendChange(nullptr, aTextNode, nsChangeHint_ReconstructFrame);
-    return;
+    return true;
   }
 
   // Handle restyle.
   nsIFrame* primaryFrame = aTextNode->GetPrimaryFrame();
-  if (primaryFrame) {
-    RefPtr<nsStyleContext> oldStyleContext = primaryFrame->StyleContext();
-    nsStyleContext& newContext = aPostTraversalState.ComputeStyle(aTextNode);
-    aPostTraversalState.ComputeHintIfNeeded(
-        aTextNode, primaryFrame, newContext, aChangeList);
+  if (!primaryFrame) {
+    return false;
+  }
 
-    for (nsIFrame* f = primaryFrame; f;
-         f = GetNextContinuationWithSameStyle(f, oldStyleContext)) {
-      f->SetStyleContext(&newContext);
-    }
+  RefPtr<nsStyleContext> oldStyleContext = primaryFrame->StyleContext();
+  nsStyleContext& newContext = aPostTraversalState.ComputeStyle(aTextNode);
+  aPostTraversalState.ComputeHintIfNeeded(
+      aTextNode, primaryFrame, newContext, aChangeList);
+
+  for (nsIFrame* f = primaryFrame; f;
+       f = GetNextContinuationWithSameStyle(f, oldStyleContext)) {
+    f->SetStyleContext(&newContext);
   }
+
+  return true;
 }
 
 void
 ServoRestyleManager::ClearSnapshots()
 {
   for (auto iter = mSnapshots.Iter(); !iter.Done(); iter.Next()) {
     iter.Key()->UnsetFlags(ELEMENT_HAS_SNAPSHOT | ELEMENT_HANDLED_SNAPSHOT);
     iter.Remove();
@@ -574,18 +582,20 @@ ServoRestyleManager::DoProcessPendingRes
     if (!animationOnly) {
       ClearSnapshots();
     }
 
     // Recreate style contexts, and queue up change hints (which also handle
     // lazy frame construction).
     nsStyleChangeList currentChanges(StyleBackendType::Servo);
     DocumentStyleRootIterator iter(doc);
+    bool anyStyleChanged = false;
     while (Element* root = iter.GetNextStyleRoot()) {
-      ProcessPostTraversal(root, nullptr, styleSet, currentChanges);
+      anyStyleChanged |=
+        ProcessPostTraversal(root, nullptr, styleSet, currentChanges);
     }
 
     // Process the change hints.
     //
     // Unfortunately, the frame constructor can generate new change hints while
     // processing existing ones. We redirect those into a secondary queue and
     // iterate until there's nothing left.
     ReentrantChangeList newChanges;
@@ -604,17 +614,28 @@ ServoRestyleManager::DoProcessPendingRes
         }
         currentChanges.AppendChange(change.mContent->GetPrimaryFrame(),
                                     change.mContent, change.mHint);
       }
       newChanges.Clear();
     }
     mReentrantChanges = nullptr;
 
-    IncrementRestyleGeneration();
+
+    if (anyStyleChanged) {
+      // Maybe no styles changed when:
+      //
+      //  * Only explicit change hints were posted in the first place.
+      //  * When an attribute or state change in the content happens not to need
+      //    a restyle after all.
+      //
+      // In any case, we don't need to increment the restyle generation in that
+      // case.
+      IncrementRestyleGeneration();
+    }
   }
 
   FlushOverflowChangedTracker();
 
   if (!animationOnly) {
     ClearSnapshots();
     styleSet->AssertTreeIsClean();
     mHaveNonAnimationRestyles = false;
--- a/layout/base/ServoRestyleManager.h
+++ b/layout/base/ServoRestyleManager.h
@@ -111,25 +111,31 @@ public:
 protected:
   ~ServoRestyleManager() override
   {
     MOZ_ASSERT(!mReentrantChanges);
   }
 
 private:
   /**
-   * Performs post-Servo-traversal processing on this element and its descendants.
+   * Performs post-Servo-traversal processing on this element and its
+   * descendants.
+   *
+   * Returns whether any style did actually change. There may be cases where we
+   * didn't need to change any style after all, for example, when a content
+   * attribute changes that happens not to have any effect on the style of that
+   * element or any descendant or sibling.
    */
-  void ProcessPostTraversal(Element* aElement,
+  bool ProcessPostTraversal(Element* aElement,
                             nsStyleContext* aParentContext,
                             ServoStyleSet* aStyleSet,
                             nsStyleChangeList& aChangeList);
 
   struct TextPostTraversalState;
-  void ProcessPostTraversalForText(nsIContent* aTextNode,
+  bool ProcessPostTraversalForText(nsIContent* aTextNode,
                                    nsStyleChangeList& aChangeList,
                                    TextPostTraversalState& aState);
 
   inline ServoStyleSet* StyleSet() const
   {
     MOZ_ASSERT(PresContext()->StyleSet()->IsServo(),
                "ServoRestyleManager should only be used with a Servo-flavored "
                "style backend");
--- a/layout/base/nsPresContext.cpp
+++ b/layout/base/nsPresContext.cpp
@@ -2065,36 +2065,29 @@ nsPresContext::MediaFeatureValuesChanged
 
 void
 nsPresContext::MediaFeatureValuesChanged(nsRestyleHint aRestyleHint,
                                          nsChangeHint aChangeHint)
 {
   mPendingMediaFeatureValuesChanged = false;
 
   // MediumFeaturesChanged updates the applied rules, so it always gets called.
-  if (mShell) {
-    // XXXheycam ServoStyleSets don't support responding to medium
-    // changes yet.
-    if (mShell->StyleSet()->IsGecko()) {
-      if (mShell->StyleSet()->AsGecko()->MediumFeaturesChanged()) {
-        aRestyleHint |= eRestyle_Subtree;
-      }
-    } else {
-      NS_WARNING("stylo: ServoStyleSets don't support responding to medium "
-                 "changes yet. See bug 1290228.");
-      aRestyleHint |= eRestyle_Subtree;
-    }
+  if (mShell && mShell->StyleSet()->MediumFeaturesChanged()) {
+    aRestyleHint |= eRestyle_Subtree;
   }
 
-  if (mUsesViewportUnits && mPendingViewportChange) {
+  if (mPendingViewportChange &&
+      (mUsesViewportUnits || mDocument->IsStyledByServo())) {
     // Rebuild all style data without rerunning selector matching.
     //
-    // TODO(emilio, bug 1328652): We don't set mUsesViewportUnits in stylo yet.
-    // This is wallpapered given we assume medium feature changes
-    // unconditionally, but we need to fix this.
+    // FIXME(emilio, bug 1328652): We don't set mUsesViewportUnits in stylo yet,
+    // so assume the worst.
+    //
+    // Also, in this case we don't need to do a rebuild of the style data, only
+    // post a restyle.
     aRestyleHint |= eRestyle_ForceDescendants;
   }
 
   if (aRestyleHint || aChangeHint) {
     RebuildAllStyleData(aChangeHint, aRestyleHint);
   }
 
   mPendingViewportChange = false;
new file mode 100644
--- /dev/null
+++ b/layout/reftests/bugs/1367592-1-ref.html
@@ -0,0 +1,7 @@
+<!DOCTYPE html>
+<style>
+:root { font-size: 32px; }
+body { font-size: 16px; }
+.y { width: 1rem; height: 1rem; background-color: blue; }
+</style>
+<div><div class=y></div></div>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/bugs/1367592-1.html
@@ -0,0 +1,9 @@
+<!DOCTYPE html>
+<style>
+:root { font-size: 16px; }
+.x:root { font-size: 32px; }
+body { font-size: 16px; }
+.y { width: 1rem; height: 1rem; background-color: blue; }
+</style>
+<body onload="document.body.offsetWidth; document.documentElement.className = 'x';">
+<div><div class=y></div></div>
--- a/layout/reftests/bugs/reftest.list
+++ b/layout/reftests/bugs/reftest.list
@@ -2000,8 +2000,9 @@ fails-if(styloVsGecko) == 1322512-1.html
 == 1358375-3.html 1358375-ref.html
 == 1364280-1.html 1364280-1-ref.html
 == 1364280-2a.html 1364280-2-ref.html
 == 1364280-2b.html 1364280-2-ref.html
 == 1364280-2c.html 1364280-2-ref.html
 == 1364335.html 1364335-ref.html
 == 1364360-1.html 1364360-1-ref.html
 == 1366144.html 1366144-ref.html
+== 1367592-1.html 1367592-1-ref.html
--- a/layout/reftests/css-selectors/nth-child-1.html
+++ b/layout/reftests/css-selectors/nth-child-1.html
@@ -1,42 +1,40 @@
 <!DOCTYPE HTML>
 <html><head>
     <meta charset="utf-8">
     <title>Tests :nth-child(An+B) matching</title>
     <style type="text/css">
 
-    div :nth-child(+/**/3n-2)  { color:white; }
     div :nth-child(+3n/**/-2)  { background-color:black; }
     div :nth-child(+3n/**/-2)  { font-size:12px; }
     div :nth-child(+3n-/**/2)  { text-decoration: underline; }
     div :nth-child(+3n-2/**/)  { border-left-width: 1px; }
-    div :nth-child(+3/**/n-2) { border-right-width: 1px; }
     div :nth-child(+3n/**/-2) { border-top-width: 1px; }
     div :nth-child(+3n/**/-2) { border-bottom-width: 1px; }
-    div :nth-child(+3n-/**/2) { border-style: solid; }
-    div :nth-child(+3n-2/**/) { border-color: blue; }
+    div :nth-child(+3n-/**/2) { border-right-width: 1px; }
+    div :nth-child(+3n-2/**/) { border-style: solid; border-color: blue;}
 
     /* valid but will not match anything */
-    div :nth-child(-/**/n-2)  { color:red; }
     div :nth-child(-n/**/-2)  { color:red; }
     div :nth-child(-n/**/-2)  { color:red; }
     div :nth-child(-n-/**/2)  { color:red; }
     div :nth-child(-n-2/**/)  { color:red; }
-    div :nth-child(-1/**/n-2) { color:red; }
     div :nth-child(-1n/**/-2) { color:red; }
     div :nth-child(-1n/**/-2) { color:red; }
     div :nth-child(-1n-/**/2) { color:red; }
     div :nth-child(-1n-2/**/) { color:red; }
 
     /* invalid */
     div :nth-child(-/**/ n-2) { color:red; }
     div :nth-child(- /**/n-2) { color:red; }
     div :nth-child(+/**/ n-2) { color:red; }
     div :nth-child(+ /**/n-2) { color:red; }
+    div :nth-child(+3/**/n-2) { color:red; }
+    div :nth-child(-/**/n-2) {color: red;}
 
     </style>
 </head>
 <body>
 
 <div><x>x</x><x>x</x><x>x</x><x>x</x><x>x</x><x>x</x><x>x</x><x>x</x><x>x</x><x>x</x><x>x</x></div>
 
 </body>
--- a/layout/reftests/css-selectors/nth-child-2.html
+++ b/layout/reftests/css-selectors/nth-child-2.html
@@ -1,42 +1,40 @@
 <!DOCTYPE HTML>
 <html><head>
     <meta charset="utf-8">
     <title>Tests :nth-child(An+B) matching</title>
     <style type="text/css">
 
-    div :nth-child(+/**/3N-2)  { color:white; }
     div :nth-child(+3N/**/-2)  { background-color:black; }
     div :nth-child(+3N/**/-2)  { font-size:12px; }
     div :nth-child(+3N-/**/2)  { text-decoration: underline; }
     div :nth-child(+3N-2/**/)  { border-left-width: 1px; }
-    div :nth-child(+3/**/N-2) { border-right-width: 1px; }
     div :nth-child(+3N/**/-2) { border-top-width: 1px; }
     div :nth-child(+3N/**/-2) { border-bottom-width: 1px; }
-    div :nth-child(+3N-/**/2) { border-style: solid; }
-    div :nth-child(+3N-2/**/) { border-color: blue; }
+    div :nth-child(+3N-/**/2) { border-right-width: 1px; }
+    div :nth-child(+3N-2/**/) { border-style: solid; border-color: blue;}
 
     /* valid but will not match anything */
-    div :nth-child(-/**/N-2)  { color:red; }
     div :nth-child(-N/**/-2)  { color:red; }
     div :nth-child(-N/**/-2)  { color:red; }
     div :nth-child(-N-/**/2)  { color:red; }
     div :nth-child(-N-2/**/)  { color:red; }
-    div :nth-child(-1/**/N-2) { color:red; }
     div :nth-child(-1N/**/-2) { color:red; }
     div :nth-child(-1N/**/-2) { color:red; }
     div :nth-child(-1N-/**/2) { color:red; }
     div :nth-child(-1N-2/**/) { color:red; }
 
     /* invalid */
     div :nth-child(-/**/ N-2) { color:red; }
     div :nth-child(- /**/N-2) { color:red; }
     div :nth-child(+/**/ N-2) { color:red; }
     div :nth-child(+ /**/N-2) { color:red; }
+    div :nth-child(+3/**/N-2) { color:red; }
+    div :nth-child(-/**/N-2) {color: red;}
 
     </style>
 </head>
 <body>
 
 <div><x>x</x><x>x</x><x>x</x><x>x</x><x>x</x><x>x</x><x>x</x><x>x</x><x>x</x><x>x</x><x>x</x></div>
 
 </body>
--- a/layout/reftests/css-selectors/nth-child-ref.html
+++ b/layout/reftests/css-selectors/nth-child-ref.html
@@ -1,24 +1,22 @@
 <!DOCTYPE HTML>
 <html><head>
     <meta charset="utf-8">
     <title>Tests :nth-child(An+B) matching</title>
     <style type="text/css">
 
-    x { color:white; }
     x { background-color:black; }
     x { font-size:12px; }
     x { text-decoration: underline; }
     x { border-left-width: 1px; }
     x { border-right-width: 1px; }
     x { border-top-width: 1px; }
     x { border-bottom-width: 1px; }
-    x { border-style: solid; }
-    x { border-color: blue; }
+    x { border-style: solid; border-color: blue;}
 
     </style>
 </head>
 <body>
 
 <div><x>x</x><y>x</y><y>x</y><x>x</x><y>x</y><y>x</y><x>x</x><y>x</y><y>x</y><x>x</x><y>x</y></div>
 
 </body>
--- a/layout/reftests/css-selectors/reftest.list
+++ b/layout/reftests/css-selectors/reftest.list
@@ -1,6 +1,6 @@
 == state-dependent-in-any.html state-dependent-in-any-ref.html
 == attr-case-insensitive-1.html attr-case-insensitive-1-ref.html
 == sibling-combinators-on-anon-content-1.xhtml sibling-combinators-on-anon-content-ref.xhtml
 == sibling-combinators-on-anon-content-2.xhtml sibling-combinators-on-anon-content-ref.xhtml
-fails-if(styloVsGecko||stylo) == nth-child-1.html nth-child-ref.html
-fails-if(styloVsGecko||stylo) == nth-child-2.html nth-child-ref.html
+== nth-child-1.html nth-child-ref.html
+== nth-child-2.html nth-child-ref.html
--- a/layout/style/ServoBindingList.h
+++ b/layout/style/ServoBindingList.h
@@ -49,16 +49,18 @@ SERVO_BINDING_FUNC(Servo_StyleSheet_Clon
                    RawServoStyleSheetBorrowed sheet)
 SERVO_BINDING_FUNC(Servo_StyleSheet_SizeOfIncludingThis, size_t,
                    mozilla::MallocSizeOf malloc_size_of, RawServoStyleSheetBorrowed sheet)
 SERVO_BINDING_FUNC(Servo_StyleSet_Init, RawServoStyleSetOwned, RawGeckoPresContextOwned pres_context)
 SERVO_BINDING_FUNC(Servo_StyleSet_Clear, void,
                    RawServoStyleSetBorrowed set)
 SERVO_BINDING_FUNC(Servo_StyleSet_RebuildData, void,
                    RawServoStyleSetBorrowed set)
+SERVO_BINDING_FUNC(Servo_StyleSet_MediumFeaturesChanged, bool,
+                   RawServoStyleSetBorrowed set)
 SERVO_BINDING_FUNC(Servo_StyleSet_Drop, void, RawServoStyleSetOwned set)
 SERVO_BINDING_FUNC(Servo_StyleSet_AppendStyleSheet, void,
                    RawServoStyleSetBorrowed set,
                    RawServoStyleSheetBorrowed sheet,
                    uint64_t unique_id)
 SERVO_BINDING_FUNC(Servo_StyleSet_PrependStyleSheet, void,
                    RawServoStyleSetBorrowed set,
                    RawServoStyleSheetBorrowed sheet,
--- a/layout/style/ServoStyleSet.cpp
+++ b/layout/style/ServoStyleSet.cpp
@@ -128,16 +128,22 @@ ServoStyleSet::Shutdown()
 
 void
 ServoStyleSet::InvalidateStyleForCSSRuleChanges()
 {
   MOZ_ASSERT(StylistNeedsUpdate());
   mPresContext->RestyleManager()->AsServo()->PostRestyleEventForCSSRuleChanges();
 }
 
+bool
+ServoStyleSet::MediumFeaturesChanged() const
+{
+  return Servo_StyleSet_MediumFeaturesChanged(mRawSet.get());
+}
+
 size_t
 ServoStyleSet::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const
 {
   size_t n = aMallocSizeOf(this);
 
   // Measurement of the following members may be added later if DMD finds it is
   // worthwhile:
   // - mRawSet
--- a/layout/style/ServoStyleSet.h
+++ b/layout/style/ServoStyleSet.h
@@ -132,16 +132,18 @@ public:
     ForceAllStyleDirty();
   }
 
   bool StyleSheetsHaveChanged() const
   {
     return StylistNeedsUpdate();
   }
 
+  bool MediumFeaturesChanged() const;
+
   void InvalidateStyleForCSSRuleChanges();
 
   size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const;
   const RawServoStyleSet& RawSet() const { return *mRawSet; }
 
   bool GetAuthorStyleDisabled() const;
   nsresult SetAuthorStyleDisabled(bool aStyleDisabled);
 
--- a/layout/style/StyleSetHandle.h
+++ b/layout/style/StyleSetHandle.h
@@ -153,16 +153,17 @@ public:
     inline int32_t SheetCount(SheetType aType) const;
     inline StyleSheet* StyleSheetAt(SheetType aType, int32_t aIndex) const;
     inline nsresult RemoveDocStyleSheet(StyleSheet* aSheet);
     inline nsresult AddDocStyleSheet(StyleSheet* aSheet, nsIDocument* aDocument);
     inline void RecordStyleSheetChange(StyleSheet* aSheet, StyleSheet::ChangeType);
     inline void RecordShadowStyleChange(mozilla::dom::ShadowRoot* aShadowRoot);
     inline bool StyleSheetsHaveChanged() const;
     inline void InvalidateStyleForCSSRuleChanges();
+    inline bool MediumFeaturesChanged();
     inline already_AddRefed<nsStyleContext>
     ProbePseudoElementStyle(dom::Element* aParentElement,
                             mozilla::CSSPseudoElementType aType,
                             nsStyleContext* aParentContext);
     inline already_AddRefed<nsStyleContext>
     ProbePseudoElementStyle(dom::Element* aParentElement,
                             mozilla::CSSPseudoElementType aType,
                             nsStyleContext* aParentContext,
--- a/layout/style/StyleSetHandleInlines.h
+++ b/layout/style/StyleSetHandleInlines.h
@@ -234,17 +234,21 @@ StyleSetHandle::Ptr::RecordShadowStyleCh
   FORWARD(RecordShadowStyleChange, (aShadowRoot));
 }
 
 bool
 StyleSetHandle::Ptr::StyleSheetsHaveChanged() const
 {
   FORWARD(StyleSheetsHaveChanged, ());
 }
-
+bool
+StyleSetHandle::Ptr::MediumFeaturesChanged()
+{
+  FORWARD(MediumFeaturesChanged, ());
+}
 void
 StyleSetHandle::Ptr::InvalidateStyleForCSSRuleChanges()
 {
   FORWARD(InvalidateStyleForCSSRuleChanges, ());
 }
 
 // check whether there is ::before/::after style for an element
 already_AddRefed<nsStyleContext>
--- a/layout/style/nsCSSParser.cpp
+++ b/layout/style/nsCSSParser.cpp
@@ -6326,161 +6326,148 @@ CSSParserImpl::ParsePseudoClassWithIdent
   return eSelectorParsingStatus_Continue;
 }
 
 CSSParserImpl::nsSelectorParsingStatus
 CSSParserImpl::ParsePseudoClassWithNthPairArg(nsCSSSelector& aSelector,
                                               CSSPseudoClassType aType)
 {
   int32_t numbers[2] = { 0, 0 };
-  int32_t sign[2] = { 1, 1 };
-  bool hasSign[2] = { false, false };
   bool lookForB = true;
+  bool onlyN = false;
+  bool hasSign = false;
+  int sign = 1;
 
   // Follow the whitespace rules as proposed in
   // http://lists.w3.org/Archives/Public/www-style/2008Mar/0121.html
 
   if (! GetToken(true)) {
     REPORT_UNEXPECTED_EOF(PEPseudoClassArgEOF);
     return eSelectorParsingStatus_Error;
   }
 
-  if (mToken.IsSymbol('+') || mToken.IsSymbol('-')) {
-    hasSign[0] = true;
-    if (mToken.IsSymbol('-')) {
-      sign[0] = -1;
-    }
-    if (! GetToken(false)) {
-      REPORT_UNEXPECTED_EOF(PEPseudoClassArgEOF);
-      return eSelectorParsingStatus_Error;
-    }
-  }
-
   // A helper function that checks if the token starts with literal string
   // |aStr| using a case-insensitive match.
   auto TokenBeginsWith = [this] (const nsLiteralString& aStr) {
     return StringBeginsWith(mToken.mIdent, aStr,
                             nsASCIICaseInsensitiveStringComparator());
   };
 
+  if (mToken.IsSymbol('+') || mToken.IsSymbol('-')) {
+    // This can only be +n or -n, since +an, -an, +a, -a will all
+    // parse a number as the first token.
+    numbers[0] = mToken.IsSymbol('+') ? 1 : -1;
+    onlyN = true;
+
+    // consume the `n`
+    // We do not allow whitespace here
+    // https://drafts.csswg.org/css-syntax-3/#the-anb-type
+    if (! GetToken(false)) {
+      REPORT_UNEXPECTED_EOF(PEPseudoClassArgEOF);
+      return eSelectorParsingStatus_Error; // our caller calls SkipUntil(')')
+    }
+  }
+
   if (eCSSToken_Ident == mToken.mType || eCSSToken_Dimension == mToken.mType) {
     // The CSS tokenization doesn't handle :nth-child() containing - well:
     //   2n-1 is a dimension
     //   n-1 is an identifier
     // The easiest way to deal with that is to push everything from the
     // minus on back onto the scanner's pushback buffer.
     uint32_t truncAt = 0;
     if (TokenBeginsWith(NS_LITERAL_STRING("n-"))) {
       truncAt = 1;
-    } else if (TokenBeginsWith(NS_LITERAL_STRING("-n-")) && !hasSign[0]) {
+    } else if (TokenBeginsWith(NS_LITERAL_STRING("-n-"))) {
       truncAt = 2;
     }
     if (truncAt != 0) {
       mScanner->Backup(mToken.mIdent.Length() - truncAt);
       mToken.mIdent.Truncate(truncAt);
     }
   }
 
-  if (eCSSToken_Ident == mToken.mType) {
-    if (mToken.mIdent.LowerCaseEqualsLiteral("odd") && !hasSign[0]) {
-      numbers[0] = 2;
-      numbers[1] = 1;
+  if (onlyN) {
+    // If we parsed a + or -, check that the truncated
+    // token is an "n"
+    if (eCSSToken_Ident != mToken.mType || !mToken.mIdent.LowerCaseEqualsLiteral("n")) {
+      REPORT_UNEXPECTED_TOKEN(PEPseudoClassArgNotNth);
+      return eSelectorParsingStatus_Error;
+    }
+  } else {
+    if (eCSSToken_Ident == mToken.mType) {
+      if (mToken.mIdent.LowerCaseEqualsLiteral("odd")) {
+        numbers[0] = 2;
+        numbers[1] = 1;
+        lookForB = false;
+      }
+      else if (mToken.mIdent.LowerCaseEqualsLiteral("even")) {
+        numbers[0] = 2;
+        numbers[1] = 0;
+        lookForB = false;
+      }
+      else if (mToken.mIdent.LowerCaseEqualsLiteral("n")) {
+          numbers[0] = 1;
+      }
+      else if (mToken.mIdent.LowerCaseEqualsLiteral("-n")) {
+        numbers[0] = -1;
+      }
+      else {
+        REPORT_UNEXPECTED_TOKEN(PEPseudoClassArgNotNth);
+        return eSelectorParsingStatus_Error; // our caller calls SkipUntil(')')
+      }
+    }
+    else if (eCSSToken_Number == mToken.mType) {
+      if (!mToken.mIntegerValid) {
+        REPORT_UNEXPECTED_TOKEN(PEPseudoClassArgNotNth);
+        return eSelectorParsingStatus_Error; // our caller calls SkipUntil(')')
+      }
+
+      numbers[1] = mToken.mInteger;
       lookForB = false;
     }
-    else if (mToken.mIdent.LowerCaseEqualsLiteral("even") && !hasSign[0]) {
-      numbers[0] = 2;
-      numbers[1] = 0;
-      lookForB = false;
-    }
-    else if (mToken.mIdent.LowerCaseEqualsLiteral("n")) {
-      numbers[0] = sign[0];
-    }
-    else if (mToken.mIdent.LowerCaseEqualsLiteral("-n") && !hasSign[0]) {
-      numbers[0] = -1;
-    }
+    else if (eCSSToken_Dimension == mToken.mType) {
+      if (!mToken.mIntegerValid || !mToken.mIdent.LowerCaseEqualsLiteral("n")) {
+        REPORT_UNEXPECTED_TOKEN(PEPseudoClassArgNotNth);
+        return eSelectorParsingStatus_Error; // our caller calls SkipUntil(')')
+      }
+      numbers[0] = mToken.mInteger;
+    }
+    // XXX If it's a ')', is that valid?  (as 0n+0)
     else {
       REPORT_UNEXPECTED_TOKEN(PEPseudoClassArgNotNth);
-      return eSelectorParsingStatus_Error; // our caller calls SkipUntil(')')
-    }
-  }
-  else if (eCSSToken_Number == mToken.mType) {
-    if (!mToken.mIntegerValid) {
-      REPORT_UNEXPECTED_TOKEN(PEPseudoClassArgNotNth);
-      return eSelectorParsingStatus_Error; // our caller calls SkipUntil(')')
-    }
-    // for +-an case
-    if (mToken.mHasSign && hasSign[0]) {
-      REPORT_UNEXPECTED_TOKEN(PEPseudoClassArgNotNth);
+      UngetToken();
       return eSelectorParsingStatus_Error; // our caller calls SkipUntil(')')
     }
-    int32_t intValue = mToken.mInteger * sign[0];
-    // for -a/**/n case
-    if (! GetToken(false)) {
-      numbers[1] = intValue;
-      lookForB = false;
-    }
-    else {
-      if (eCSSToken_Ident == mToken.mType && mToken.mIdent.LowerCaseEqualsLiteral("n")) {
-        numbers[0] = intValue;
-      }
-      else if (eCSSToken_Ident == mToken.mType && TokenBeginsWith(NS_LITERAL_STRING("n-"))) {
-        numbers[0] = intValue;
-        mScanner->Backup(mToken.mIdent.Length() - 1);
-      }
-      else {
-        UngetToken();
-        numbers[1] = intValue;
-        lookForB = false;
-      }
-    }
-  }
-  else if (eCSSToken_Dimension == mToken.mType) {
-    if (!mToken.mIntegerValid || !mToken.mIdent.LowerCaseEqualsLiteral("n")) {
-      REPORT_UNEXPECTED_TOKEN(PEPseudoClassArgNotNth);
-      return eSelectorParsingStatus_Error; // our caller calls SkipUntil(')')
-    }
-    // for +-an case
-    if ( mToken.mHasSign && hasSign[0] ) {
-      REPORT_UNEXPECTED_TOKEN(PEPseudoClassArgNotNth);
-      return eSelectorParsingStatus_Error; // our caller calls SkipUntil(')')
-    }
-    numbers[0] = mToken.mInteger * sign[0];
-  }
-  // XXX If it's a ')', is that valid?  (as 0n+0)
-  else {
-    REPORT_UNEXPECTED_TOKEN(PEPseudoClassArgNotNth);
-    UngetToken();
-    return eSelectorParsingStatus_Error; // our caller calls SkipUntil(')')
   }
 
   if (! GetToken(true)) {
     REPORT_UNEXPECTED_EOF(PEPseudoClassArgEOF);
     return eSelectorParsingStatus_Error;
   }
   if (lookForB && !mToken.IsSymbol(')')) {
     // The '+' or '-' sign can optionally be separated by whitespace.
     // If it is separated by whitespace from what follows it, it appears
     // as a separate token rather than part of the number token.
     if (mToken.IsSymbol('+') || mToken.IsSymbol('-')) {
-      hasSign[1] = true;
+      hasSign = true;
       if (mToken.IsSymbol('-')) {
-        sign[1] = -1;
+        sign = -1;
       }
       if (! GetToken(true)) {
         REPORT_UNEXPECTED_EOF(PEPseudoClassArgEOF);
         return eSelectorParsingStatus_Error;
       }
     }
     if (eCSSToken_Number != mToken.mType ||
-        !mToken.mIntegerValid || mToken.mHasSign == hasSign[1]) {
+        !mToken.mIntegerValid || mToken.mHasSign == hasSign) {
       REPORT_UNEXPECTED_TOKEN(PEPseudoClassArgNotNth);
       UngetToken();
       return eSelectorParsingStatus_Error; // our caller calls SkipUntil(')')
     }
-    numbers[1] = mToken.mInteger * sign[1];
+    numbers[1] = mToken.mInteger * sign;
     if (! GetToken(true)) {
       REPORT_UNEXPECTED_EOF(PEPseudoClassArgEOF);
       return eSelectorParsingStatus_Error;
     }
   }
   if (!mToken.IsSymbol(')')) {
     REPORT_UNEXPECTED_TOKEN(PEPseudoClassNoClose);
     return eSelectorParsingStatus_Error; // our caller calls SkipUntil(')')
--- a/layout/style/test/stylo-failures.md
+++ b/layout/style/test/stylo-failures.md
@@ -135,15 +135,15 @@ to mochitest command.
     * test_property_syntax_errors.html `-webkit-gradient` [20]
 * test_specified_value_serialization.html `-webkit-radial-gradient`: bug 1367299 [1]
 * test_variables.html `var(--var6)`: irrelevant test for stylo bug 1367306 [1]
 
 ## Unknown / Unsure
 
 * test_selectors_on_anonymous_content.html: xbl and :nth-child [1]
 * test_parse_rule.html `rgb(0, 128, 0)`: color properties not getting computed [5]
-* test_selectors.html `:nth-child`: &lt;an+b&gt; parsing difference bug 1364009 [14]
+* test_selectors.html `:nth-child`: https://github.com/servo/rust-cssparser/issues/153 [4]
 
 ## Ignore
 
 * Ignore for now since should be mostly identical to test_value_storage.html
   * test_value_cloning.html [*]
   * test_value_computation.html [*]
--- a/layout/style/test/test_selectors.html
+++ b/layout/style/test/test_selectors.html
@@ -642,93 +642,93 @@ function run() {
     test_balanced_unparseable(":nth-child(2n+/**/-/**/2)");
     test_balanced_unparseable(":nth-child(2n-/**/+/**/2)");
     test_balanced_unparseable(":nth-child(2n-/**/-/**/2)");
     test_parseable(":nth-child(+/**/n+2)");
     test_parseable(":nth-child(+n/**/+2)");
     test_parseable(":nth-child(+n/**/+2)");
     test_parseable(":nth-child(+n+/**/2)");
     test_parseable(":nth-child(+n+2/**/)");
-    test_parseable(":nth-child(+1/**/n+2)");
+    test_balanced_unparseable(":nth-child(+1/**/n+2)");
     test_parseable(":nth-child(+1n/**/+2)");
     test_parseable(":nth-child(+1n/**/+2)");
     test_parseable(":nth-child(+1n+/**/2)");
     test_parseable(":nth-child(+1n+2/**/)");
     test_parseable(":nth-child(-/**/n+2)");
     test_parseable(":nth-child(-n/**/+2)");
     test_parseable(":nth-child(-n/**/+2)");
     test_parseable(":nth-child(-n+/**/2)");
     test_parseable(":nth-child(-n+2/**/)");
-    test_parseable(":nth-child(-1/**/n+2)");
+    test_balanced_unparseable(":nth-child(-1/**/n+2)");
     test_parseable(":nth-child(-1n/**/+2)");
     test_parseable(":nth-child(-1n/**/+2)");
     test_parseable(":nth-child(-1n+/**/2)");
     test_parseable(":nth-child(-1n+2/**/)");
     test_balanced_unparseable(":nth-child(-/**/ n+2)");
     test_balanced_unparseable(":nth-child(- /**/n+2)");
     test_balanced_unparseable(":nth-child(+/**/ n+2)");
     test_balanced_unparseable(":nth-child(+ /**/n+2)");
     test_parseable(":nth-child(+/**/n-2)");
     test_parseable(":nth-child(+n/**/-2)");
     test_parseable(":nth-child(+n/**/-2)");
     test_parseable(":nth-child(+n-/**/2)");
     test_parseable(":nth-child(+n-2/**/)");
-    test_parseable(":nth-child(+1/**/n-2)");
+    test_balanced_unparseable(":nth-child(+1/**/n-2)");
     test_parseable(":nth-child(+1n/**/-2)");
     test_parseable(":nth-child(+1n/**/-2)");
     test_parseable(":nth-child(+1n-/**/2)");
     test_parseable(":nth-child(+1n-2/**/)");
     test_parseable(":nth-child(-/**/n-2)");
     test_parseable(":nth-child(-n/**/-2)");
     test_parseable(":nth-child(-n/**/-2)");
     test_parseable(":nth-child(-n-/**/2)");
     test_parseable(":nth-child(-n-2/**/)");
-    test_parseable(":nth-child(-1/**/n-2)");
+    test_balanced_unparseable(":nth-child(-1/**/n-2)");
     test_parseable(":nth-child(-1n/**/-2)");
     test_parseable(":nth-child(-1n/**/-2)");
     test_parseable(":nth-child(-1n-/**/2)");
     test_parseable(":nth-child(-1n-2/**/)");
     test_balanced_unparseable(":nth-child(-/**/ n-2)");
     test_balanced_unparseable(":nth-child(- /**/n-2)");
     test_balanced_unparseable(":nth-child(+/**/ n-2)");
     test_balanced_unparseable(":nth-child(+ /**/n-2)");
     test_parseable(":nth-child(+/**/N-2)");
     test_parseable(":nth-child(+N/**/-2)");
     test_parseable(":nth-child(+N/**/-2)");
     test_parseable(":nth-child(+N-/**/2)");
     test_parseable(":nth-child(+N-2/**/)");
-    test_parseable(":nth-child(+1/**/N-2)");
+    test_balanced_unparseable(":nth-child(+1/**/N-2)");
     test_parseable(":nth-child(+1N/**/-2)");
     test_parseable(":nth-child(+1N/**/-2)");
     test_parseable(":nth-child(+1N-/**/2)");
     test_parseable(":nth-child(+1N-2/**/)");
     test_parseable(":nth-child(-/**/N-2)");
     test_parseable(":nth-child(-N/**/-2)");
     test_parseable(":nth-child(-N/**/-2)");
     test_parseable(":nth-child(-N-/**/2)");
     test_parseable(":nth-child(-N-2/**/)");
-    test_parseable(":nth-child(-1/**/N-2)");
+    test_balanced_unparseable(":nth-child(-1/**/N-2)");
     test_parseable(":nth-child(-1N/**/-2)");
     test_parseable(":nth-child(-1N/**/-2)");
     test_parseable(":nth-child(-1N-/**/2)");
     test_parseable(":nth-child(-1N-2/**/)");
     test_balanced_unparseable(":nth-child(-/**/ N-2)");
     test_balanced_unparseable(":nth-child(- /**/N-2)");
     test_balanced_unparseable(":nth-child(+/**/ N-2)");
     test_balanced_unparseable(":nth-child(+ /**/N-2)");
     test_parseable(":nth-child( +n + 1 )");
     test_parseable(":nth-child( +/**/n + 1 )");
-    test_parseable(":nth-child( -/**/2/**/n/**/+/**/4 )");
-    test_balanced_unparseable(":nth-child( -/**/ 2/**/n/**/+/**/4 )");
-    test_balanced_unparseable(":nth-child( -/**/2 /**/n/**/+/**/4 )");
-    test_balanced_unparseable(":nth-child( -/**/2/**/ n/**/+/**/4 )");
-    test_parseable(":nth-child( -/**/2/**/n /**/+/**/4 )");
-    test_parseable(":nth-child( -/**/2/**/n/**/ +/**/4 )");
-    test_parseable(":nth-child(+1/**/n-1)");
-    test_parseable(":nth-child(1/**/n-1)");
+    test_balanced_unparseable(":nth-child( -/**/2/**/n/**/+/**/4 )");
+    test_parseable(":nth-child( -2n/**/ + /**/4 )");
+    test_parseable(":nth-child( -2n/**/+/**/4 )");
+    test_parseable(":nth-child( -2n  /**/+/**/4 )");
+    test_parseable(":nth-child( -/**/n  /**/+ /**/ 4 )");
+    test_parseable(":nth-child( +/**/n  /**/+ /**/ 4 )");
+    test_balanced_unparseable(":nth-child(+1/**/n-1)");
+    test_balanced_unparseable(":nth-child(1/**/n-1)");
     // bug 876570
     test_balanced_unparseable(":nth-child(+2n-)");
     test_balanced_unparseable(":nth-child(+n-)");
     test_balanced_unparseable(":nth-child(-2n-)");
     test_balanced_unparseable(":nth-child(-n-)");
     test_balanced_unparseable(":nth-child(2n-)");
     test_balanced_unparseable(":nth-child(n-)");
     test_balanced_unparseable(":nth-child(+2n+)");
--- a/media/webrtc/trunk/webrtc/modules/video_capture/windows/BasePin.cpp
+++ b/media/webrtc/trunk/webrtc/modules/video_capture/windows/BasePin.cpp
@@ -1,16 +1,16 @@
 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* vim:set ts=2 sw=2 sts=2 et cindent: */
 /* 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/. */
 
 #include <assert.h>
-#include "nsAutoPtr.h"
+#include <algorithm>
 #include "BasePin.h"
 
 namespace mozilla {
 namespace media {
 
 // Implements IEnumMediaTypes for nsBasePin::EnumMediaTypes().
 // Does not support dynamic media types.
 //
@@ -209,17 +209,17 @@ BasePin::QueryPinInfo(PIN_INFO * aInfo)
   aInfo->pFilter = mFilter;
   if (mFilter) {
     mFilter->AddRef();
   }
 
   if (!mName.empty()) {
     // Copy at most (max_buffer_size - sizeof(WCHAR)). The -1 is there to
     // ensure we always have a null terminator.
-    unsigned int len = PR_MIN((MAX_PIN_NAME-1)*sizeof(WCHAR), (sizeof(WCHAR)*mName.length()));
+    size_t len = std::min<size_t>(MAX_PIN_NAME - 1, mName.length()) * sizeof(WCHAR);
     memcpy(aInfo->achName, mName.data(), len);
   }
 
   aInfo->dir = mDirection;
 
   return NOERROR;
 }
 
@@ -271,17 +271,17 @@ BasePin::EnumMediaTypes(IEnumMediaTypes 
     return E_POINTER;
 
   *aEnum = new mozilla::media::EnumMediaTypes(this);
 
   if (*aEnum == NULL)
     return E_OUTOFMEMORY;
 
   // Must addref, caller's responsibility to release.
-  NS_ADDREF(*aEnum);
+  (*aEnum)->AddRef();
 
   return S_OK;
 }
 
 
 // Base class returns an error; we expect sub-classes to override this.
 HRESULT
 BasePin::GetMediaType(int, MediaType*)
--- a/netwerk/protocol/http/HttpBackgroundChannelParent.cpp
+++ b/netwerk/protocol/http/HttpBackgroundChannelParent.cpp
@@ -96,17 +96,16 @@ HttpBackgroundChannelParent::Init(const 
 void
 HttpBackgroundChannelParent::LinkToChannel(HttpChannelParent* aChannelParent)
 {
   LOG(("HttpBackgroundChannelParent::LinkToChannel [this=%p channel=%p]\n",
        this, aChannelParent));
   AssertIsInMainProcess();
   MOZ_ASSERT(NS_IsMainThread());
 
-  MOZ_DIAGNOSTIC_ASSERT(mIPCOpened);
   if (!mIPCOpened) {
     return;
   }
 
   mChannelParent = aChannelParent;
 }
 
 void
--- a/servo/components/net/image_cache.rs
+++ b/servo/components/net/image_cache.rs
@@ -94,17 +94,17 @@ fn is_image_opaque(format: webrender_tra
         webrender_traits::ImageFormat::A8 => false,
         webrender_traits::ImageFormat::Invalid | webrender_traits::ImageFormat::RGBAF32 => unreachable!(),
     }
 }
 
 fn premultiply(data: &mut [u8]) {
     let length = data.len();
 
-    for i in Iterator::step_by(0..length, 4) {
+    for i in (0..length).step_by(4) {
         let b = data[i + 0] as u32;
         let g = data[i + 1] as u32;
         let r = data[i + 2] as u32;
         let a = data[i + 3] as u32;
 
         data[i + 0] = (b * a / 255) as u8;
         data[i + 1] = (g * a / 255) as u8;
         data[i + 2] = (r * a / 255) as u8;
--- a/servo/components/net/lib.rs
+++ b/servo/components/net/lib.rs
@@ -1,15 +1,15 @@
 /* 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/. */
 
 #![deny(unsafe_code)]
 #![feature(box_syntax)]
-#![feature(iterator_step_by)]
+#![feature(step_by)]
 
 extern crate base64;
 extern crate brotli;
 extern crate cookie as cookie_rs;
 extern crate devtools_traits;
 extern crate flate2;
 extern crate hyper;
 extern crate hyper_openssl;
--- a/servo/components/net_traits/image/base.rs
+++ b/servo/components/net_traits/image/base.rs
@@ -37,17 +37,17 @@ pub struct ImageMetadata {
 
 // FIXME: Images must not be copied every frame. Instead we should atomically
 // reference count them.
 
 // TODO(pcwalton): Speed up with SIMD, or better yet, find some way to not do this.
 fn byte_swap_and_premultiply(data: &mut [u8]) {
     let length = data.len();
 
-    for i in Iterator::step_by(0..length, 4) {
+    for i in (0..length).step_by(4) {
         let r = data[i + 2];
         let g = data[i + 1];
         let b = data[i + 0];
 
         data[i + 0] = r;
         data[i + 1] = g;
         data[i + 2] = b;
     }
--- a/servo/components/net_traits/lib.rs
+++ b/servo/components/net_traits/lib.rs
@@ -1,14 +1,14 @@
 /* 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/. */
 
 #![feature(box_syntax)]
-#![feature(iterator_step_by)]
+#![feature(step_by)]
 
 #![deny(unsafe_code)]
 
 extern crate cookie as cookie_rs;
 extern crate heapsize;
 #[macro_use]
 extern crate heapsize_derive;
 extern crate hyper;
--- a/servo/components/script_plugins/unrooted_must_root.rs
+++ b/servo/components/script_plugins/unrooted_must_root.rs
@@ -135,26 +135,26 @@ impl<'a, 'tcx> LateLintPass<'a, 'tcx> fo
             visit::FnKind::Method(n, _, _, _) => {
                 &*n.as_str() == "new" || n.as_str().starts_with("new_")
             }
             visit::FnKind::Closure(_) => return,
         };
 
         if !in_derive_expn(span) {
             let def_id = cx.tcx.hir.local_def_id(id);
-            let sig = cx.tcx.type_of(def_id).fn_sig();
+            let ty = cx.tcx.type_of(def_id);
 
-            for (arg, ty) in decl.inputs.iter().zip(sig.inputs().0.iter()) {
+            for (arg, ty) in decl.inputs.iter().zip(ty.fn_args().0.iter()) {
                 if is_unrooted_ty(cx, ty, false) {
                     cx.span_lint(UNROOTED_MUST_ROOT, arg.span, "Type must be rooted")
                 }
             }
 
             if !in_new_function {
-                if is_unrooted_ty(cx, sig.output().0, false) {
+                if is_unrooted_ty(cx, ty.fn_ret().0, false) {
                     cx.span_lint(UNROOTED_MUST_ROOT, decl.output.span(), "Type must be rooted")
                 }
             }
         }
 
         let mut visitor = FnDefVisitor {
             cx: cx,
             in_new_function: in_new_function,
@@ -213,14 +213,21 @@ impl<'a, 'b, 'tcx> visit::Visitor<'tcx> 
                             pat.span,
                             &format!("Expression of type {:?} must be rooted", ty))
             }
         }
 
         visit::walk_pat(self, pat);
     }
 
-    fn visit_ty(&mut self, _: &'tcx hir::Ty) {}
+    fn visit_fn(&mut self, kind: visit::FnKind<'tcx>, decl: &'tcx hir::FnDecl,
+                body: hir::BodyId, span: codemap::Span, id: ast::NodeId) {
+        if let visit::FnKind::Closure(_) = kind {
+            visit::walk_fn(self, kind, decl, body, span, id);
+        }
+    }
 
+    fn visit_foreign_item(&mut self, _: &'tcx hir::ForeignItem) {}
+    fn visit_ty(&mut self, _: &'tcx hir::Ty) { }
     fn nested_visit_map<'this>(&'this mut self) -> hir::intravisit::NestedVisitorMap<'this, 'tcx> {
         hir::intravisit::NestedVisitorMap::OnlyBodies(&self.cx.tcx.hir)
     }
 }
--- a/servo/components/style/data.rs
+++ b/servo/components/style/data.rs
@@ -4,17 +4,17 @@
 
 //! Per-node data used in style calculation.
 
 use arrayvec::ArrayVec;
 use context::SharedStyleContext;
 use dom::TElement;
 use properties::{AnimationRules, ComputedValues, PropertyDeclarationBlock};
 use properties::longhands::display::computed_value as display;
-use restyle_hints::{HintComputationContext, RestyleReplacements, RestyleHint};
+use restyle_hints::{CascadeHint, HintComputationContext, RestyleReplacements, RestyleHint};
 use rule_tree::StrongRuleNode;
 use selector_parser::{EAGER_PSEUDO_COUNT, PseudoElement, RestyleDamage};
 use selectors::matching::VisitedHandlingMode;
 use shared_lock::{Locked, StylesheetGuards};
 use std::fmt;
 use stylearc::Arc;
 use traversal::TraversalFlags;
 
@@ -409,16 +409,21 @@ impl StoredRestyleHint {
         self.0.has_animation_hint()
     }
 
     /// Returns true if the hint indicates the current element must be
     /// recascaded.
     pub fn has_recascade_self(&self) -> bool {
         self.0.has_recascade_self()
     }
+
+    /// Insert the specified `CascadeHint`.
+    pub fn insert_cascade_hint(&mut self, cascade_hint: CascadeHint) {
+        self.0.insert_cascade_hint(cascade_hint);
+    }
 }
 
 impl Default for StoredRestyleHint {
     fn default() -> Self {
         StoredRestyleHint::empty()
     }
 }
 
--- a/servo/components/style/gecko/data.rs
+++ b/servo/components/style/gecko/data.rs
@@ -4,17 +4,16 @@
 
 //! Data needed to style a Gecko document.
 
 use Atom;
 use atomic_refcell::{AtomicRef, AtomicRefCell, AtomicRefMut};
 use dom::TElement;
 use fnv::FnvHashMap;
 use gecko::rules::{CounterStyleRule, FontFaceRule};
-use gecko::wrapper::GeckoElement;
 use gecko_bindings::bindings::RawServoStyleSet;
 use gecko_bindings::structs::RawGeckoPresContextOwned;
 use gecko_bindings::structs::nsIDocument;
 use gecko_bindings::sugar::ownership::{HasBoxFFI, HasFFI, HasSimpleFFI};
 use media_queries::Device;
 use properties::ComputedValues;
 use shared_lock::{Locked, StylesheetGuards, SharedRwLockReadGuard};
 use stylearc::Arc;
@@ -65,25 +64,16 @@ impl PerDocumentStyleData {
 
     /// Get an mutable reference to this style data.
     pub fn borrow_mut(&self) -> AtomicRefMut<PerDocumentStyleDataImpl> {
         self.0.borrow_mut()
     }
 }
 
 impl PerDocumentStyleDataImpl {
-    /// Reset the device state because it may have changed.
-    ///
-    /// Implies also a stylesheet flush.
-    pub fn reset_device(&mut self, guard: &SharedRwLockReadGuard) {
-        self.stylist.device_mut().reset();
-        self.stylesheets.force_dirty();
-        self.flush_stylesheets::<GeckoElement>(guard, None);
-    }
-
     /// Recreate the style data if the stylesheets have changed.
     pub fn flush_stylesheets<E>(&mut self,
                                 guard: &SharedRwLockReadGuard,
                                 document_element: Option<E>)
         where E: TElement,
     {
         if !self.stylesheets.has_changed() {
             return;
--- a/servo/components/style/gecko/media_queries.rs
+++ b/servo/components/style/gecko/media_queries.rs
@@ -15,17 +15,17 @@ use gecko_bindings::structs::{nsCSSKeywo
 use gecko_bindings::structs::{nsMediaExpression_Range, nsMediaFeature};
 use gecko_bindings::structs::{nsMediaFeature_ValueType, nsMediaFeature_RangeType, nsMediaFeature_RequirementFlags};
 use gecko_bindings::structs::RawGeckoPresContextOwned;
 use media_queries::MediaType;
 use parser::ParserContext;
 use properties::{ComputedValues, StyleBuilder};
 use properties::longhands::font_size;
 use std::fmt::{self, Write};
-use std::sync::atomic::{AtomicIsize, Ordering};
+use std::sync::atomic::{AtomicBool, AtomicIsize, Ordering};
 use str::starts_with_ignore_ascii_case;
 use string_cache::Atom;
 use style_traits::ToCss;
 use style_traits::viewport::ViewportConstraints;
 use stylearc::Arc;
 use values::{CSSFloat, specified};
 use values::computed::{self, ToComputedValue};
 
@@ -42,30 +42,34 @@ pub struct Device {
     /// This is set when computing the style of the root
     /// element, and used for rem units in other elements.
     ///
     /// When computing the style of the root element, there can't be any
     /// other style being computed at the same time, given we need the style of
     /// the parent to compute everything else. So it is correct to just use
     /// a relaxed atomic here.
     root_font_size: AtomicIsize,
+    /// Whether any styles computed in the document relied on the root font-size
+    /// by using rem units.
+    used_root_font_size: AtomicBool,
 }
 
 unsafe impl Sync for Device {}
 unsafe impl Send for Device {}
 
 impl Device {
     /// Trivially constructs a new `Device`.
     pub fn new(pres_context: RawGeckoPresContextOwned) -> Self {
         assert!(!pres_context.is_null());
         Device {
             pres_context: pres_context,
             default_values: ComputedValues::default_values(unsafe { &*pres_context }),
             viewport_override: None,
             root_font_size: AtomicIsize::new(font_size::get_initial_value().0 as isize), // FIXME(bz): Seems dubious?
+            used_root_font_size: AtomicBool::new(false),
         }
     }
 
     /// Tells the device that a new viewport rule has been found, and stores the
     /// relevant viewport constraints.
     pub fn account_for_viewport_rule(&mut self,
                                      constraints: &ViewportConstraints) {
         self.viewport_override = Some(constraints.clone());
@@ -86,32 +90,46 @@ impl Device {
     /// Returns the default computed values as an `Arc`, in order to avoid
     /// clones.
     pub fn default_values_arc(&self) -> &Arc<ComputedValues> {
         &self.default_values
     }
 
     /// Get the font size of the root element (for rem)
     pub fn root_font_size(&self) -> Au {
+        self.used_root_font_size.store(true, Ordering::Relaxed);
         Au::new(self.root_font_size.load(Ordering::Relaxed) as i32)
     }
 
     /// Set the font size of the root element (for rem)
     pub fn set_root_font_size(&self, size: Au) {
         self.root_font_size.store(size.0 as isize, Ordering::Relaxed)
     }
 
+    /// Recreates the default computed values.
+    pub fn reset_computed_values(&mut self) {
+        // NB: A following stylesheet flush will populate this if appropriate.
+        self.viewport_override = None;
+        self.default_values = ComputedValues::default_values(unsafe { &*self.pres_context });
+        self.used_root_font_size.store(false, Ordering::Relaxed);
+    }
+
+    /// Returns whether we ever looked up the root font size of the Device.
+    pub fn used_root_font_size(&self) -> bool {
+        self.used_root_font_size.load(Ordering::Relaxed)
+    }
+
     /// Recreates all the temporary state that the `Device` stores.
     ///
     /// This includes the viewport override from `@viewport` rules, and also the
     /// default computed values.
     pub fn reset(&mut self) {
         // NB: A following stylesheet flush will populate this if appropriate.
         self.viewport_override = None;
-        self.default_values = ComputedValues::default_values(unsafe { &*self.pres_context });
+        self.reset_computed_values();
     }
 
     /// Returns the current media type of the device.
     pub fn media_type(&self) -> MediaType {
         unsafe {
             // FIXME(emilio): Gecko allows emulating random media with
             // mIsEmulatingMedia / mMediaEmulated . Refactor both sides so that
             // is supported (probably just making MediaType an Atom).
new file mode 100644
--- /dev/null
+++ b/servo/components/style/invalidation/media_queries.rs
@@ -0,0 +1,133 @@
+/* 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/. */
+
+//! Code related to the invalidation of media-query-affected rules.
+
+use context::QuirksMode;
+use fnv::FnvHashSet;
+use media_queries::Device;
+use shared_lock::SharedRwLockReadGuard;
+use stylesheets::{DocumentRule, ImportRule, MediaRule,  SupportsRule};
+use stylesheets::{NestedRuleIterationCondition, Stylesheet};
+
+/// A key for a given media query result.
+///
+/// NOTE: It happens to be the case that all the media lists we care about
+/// happen to have a stable address, so we can just use an opaque pointer to
+/// represent them.
+///
+/// Also, note that right now when a rule or stylesheet is removed, we do a full
+/// style flush, so there's no need to worry about other item created with the
+/// same pointer address.
+///
+/// If this changes, though, we may need to remove the item from the cache if
+/// present before it goes away.
+#[derive(Debug, Copy, Clone, Hash, Eq, PartialEq)]
+#[cfg_attr(feature = "servo", derive(HeapSizeOf))]
+pub struct MediaListKey(usize);
+
+/// A trait to get a given `MediaListKey` for a given item that can hold a
+/// `MediaList`.
+pub trait ToMediaListKey : Sized {
+    /// Get a `MediaListKey` for this item. This key needs to uniquely identify
+    /// the item.
+    #[allow(unsafe_code)]
+    fn to_media_list_key(&self) -> MediaListKey {
+        use std::mem;
+        MediaListKey(unsafe { mem::transmute(self as *const Self) })
+    }
+}
+
+impl ToMediaListKey for Stylesheet {}
+impl ToMediaListKey for ImportRule {}
+impl ToMediaListKey for MediaRule {}
+
+/// A struct that holds the result of a media query evaluation pass for the
+/// media queries that evaluated successfully.
+#[cfg_attr(feature = "servo", derive(HeapSizeOf))]
+pub struct EffectiveMediaQueryResults {
+    /// The set of media lists that matched last time.
+    set: FnvHashSet<MediaListKey>,
+}
+
+impl EffectiveMediaQueryResults {
+    /// Trivially constructs an empty `EffectiveMediaQueryResults`.
+    pub fn new() -> Self {
+        Self {
+            set: FnvHashSet::default(),
+        }
+    }
+
+    /// Resets the results, using an empty key.
+    pub fn clear(&mut self) {
+        self.set.clear()
+    }
+
+    /// Returns whether a given item was known to be effective when the results
+    /// were cached.
+    pub fn was_effective<T>(&self, item: &T) -> bool
+        where T: ToMediaListKey,
+    {
+        self.set.contains(&item.to_media_list_key())
+    }
+
+    /// Notices that an effective item has been seen, and caches it as matching.
+    pub fn saw_effective<T>(&mut self, item: &T)
+        where T: ToMediaListKey,
+    {
+        // NOTE(emilio): We can't assert that we don't cache the same item twice
+        // because of stylesheet reusing... shrug.
+        self.set.insert(item.to_media_list_key());
+    }
+}
+
+/// A filter that filters over effective rules, but allowing all potentially
+/// effective `@media` rules.
+pub struct PotentiallyEffectiveMediaRules;
+
+impl NestedRuleIterationCondition for PotentiallyEffectiveMediaRules {
+    fn process_import(
+        _: &SharedRwLockReadGuard,
+        _: &Device,
+        _: QuirksMode,
+        _: &ImportRule)
+        -> bool
+    {
+        true
+    }
+
+    fn process_media(
+        _: &SharedRwLockReadGuard,
+        _: &Device,
+        _: QuirksMode,
+        _: &MediaRule)
+        -> bool
+    {
+        true
+    }
+
+    /// Whether we should process the nested rules in a given `@-moz-document` rule.
+    fn process_document(
+        guard: &SharedRwLockReadGuard,
+        device: &Device,
+        quirks_mode: QuirksMode,
+        rule: &DocumentRule)
+        -> bool
+    {
+        use stylesheets::EffectiveRules;
+        EffectiveRules::process_document(guard, device, quirks_mode, rule)
+    }
+
+    /// Whether we should process the nested rules in a given `@supports` rule.
+    fn process_supports(
+        guard: &SharedRwLockReadGuard,
+        device: &Device,
+        quirks_mode: QuirksMode,
+        rule: &SupportsRule)
+        -> bool
+    {
+        use stylesheets::EffectiveRules;
+        EffectiveRules::process_supports(guard, device, quirks_mode, rule)
+    }
+}
--- a/servo/components/style/invalidation/mod.rs
+++ b/servo/components/style/invalidation/mod.rs
@@ -1,296 +1,8 @@
 /* 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/. */
 
-//! A collection of invalidations due to changes in which stylesheets affect a
-//! document.
-
-#![deny(unsafe_code)]
-
-use Atom;
-use data::StoredRestyleHint;
-use dom::{TElement, TNode};
-use fnv::FnvHashSet;
-use selector_parser::SelectorImpl;
-use selectors::parser::{Component, Selector};
-use shared_lock::SharedRwLockReadGuard;
-use stylesheets::{CssRule, Stylesheet};
-use stylist::Stylist;
-
-/// An invalidation scope represents a kind of subtree that may need to be
-/// restyled.
-#[derive(Debug, Hash, Eq, PartialEq)]
-enum InvalidationScope {
-    /// All the descendants of an element with a given id.
-    ID(Atom),
-    /// All the descendants of an element with a given class name.
-    Class(Atom),
-}
-
-impl InvalidationScope {
-    fn is_id(&self) -> bool {
-        matches!(*self, InvalidationScope::ID(..))
-    }
-
-    fn matches<E>(&self, element: E) -> bool
-        where E: TElement,
-    {
-        match *self {
-            InvalidationScope::Class(ref class) => {
-                element.has_class(class)
-            }
-            InvalidationScope::ID(ref id) => {
-                match element.get_id() {
-                    Some(element_id) => element_id == *id,
-                    None => false,
-                }
-            }
-        }
-    }
-}
-
-/// A set of invalidations due to stylesheet additions.
-///
-/// TODO(emilio): We might be able to do the same analysis for removals and
-/// media query changes too?
-pub struct StylesheetInvalidationSet {
-    /// The style scopes we know we have to restyle so far.
-    invalid_scopes: FnvHashSet<InvalidationScope>,
-    /// Whether the whole document should be invalid.
-    fully_invalid: bool,
-}
-
-impl StylesheetInvalidationSet {
-    /// Create an empty `StylesheetInvalidationSet`.
-    pub fn new() -> Self {
-        Self {
-            invalid_scopes: FnvHashSet::default(),
-            fully_invalid: false,
-        }
-    }
-
-    /// Mark the DOM tree styles' as fully invalid.
-    pub fn invalidate_fully(&mut self) {
-        debug!("StylesheetInvalidationSet::invalidate_fully");
-        self.invalid_scopes.clear();
-        self.fully_invalid = true;
-    }
-
-    /// Analyze the given stylesheet, and collect invalidations from their
-    /// rules, in order to avoid doing a full restyle when we style the document
-    /// next time.
-    pub fn collect_invalidations_for(
-        &mut self,
-        stylist: &Stylist,
-        stylesheet: &Stylesheet,
-        guard: &SharedRwLockReadGuard)
-    {
-        debug!("StylesheetInvalidationSet::collect_invalidations_for");
-        if self.fully_invalid {
-            debug!(" > Fully invalid already");
-            return;
-        }
-
-        if stylesheet.disabled() ||
-           !stylesheet.is_effective_for_device(stylist.device(), guard) {
-            debug!(" > Stylesheet was not effective");
-            return; // Nothing to do here.
-        }
-
-        for rule in stylesheet.effective_rules(stylist.device(), guard) {
-            self.collect_invalidations_for_rule(rule, guard);
-            if self.fully_invalid {
-                self.invalid_scopes.clear();
-                break;
-            }
-        }
-
-        debug!(" > resulting invalidations: {:?}", self.invalid_scopes);
-        debug!(" > fully_invalid: {}", self.fully_invalid);
-    }
-
-    /// Clears the invalidation set, invalidating elements as needed if
-    /// `document_element` is provided.
-    pub fn flush<E>(&mut self, document_element: Option<E>)
-        where E: TElement,
-    {
-        if let Some(e) = document_element {
-            self.process_invalidations_in_subtree(e);
-        }
-        self.invalid_scopes.clear();
-        self.fully_invalid = false;
-    }
-
-    /// Process style invalidations in a given subtree, that is, look for all
-    /// the relevant scopes in the subtree, and mark as dirty only the relevant
-    /// ones.
-    ///
-    /// Returns whether it invalidated at least one element's style.
-    #[allow(unsafe_code)]
-    fn process_invalidations_in_subtree<E>(&self, element: E) -> bool
-        where E: TElement,
-    {
-        let mut data = match element.mutate_data() {
-            Some(data) => data,
-            None => return false,
-        };
-
-        if !data.has_styles() {
-            return false;
-        }
-
-        if let Some(ref r) = data.get_restyle() {
-            if r.hint.contains_subtree() {
-                debug!("process_invalidations_in_subtree: {:?} was already invalid",
-                       element);
-                return false;
-            }
-        }
+//! Different bits of code related to invalidating style.
 
-        if self.fully_invalid {
-            debug!("process_invalidations_in_subtree: fully_invalid({:?})",
-                   element);
-            data.ensure_restyle().hint.insert(StoredRestyleHint::subtree());
-            return true;
-        }
-
-        for scope in &self.invalid_scopes {
-            if scope.matches(element) {
-                debug!("process_invalidations_in_subtree: {:?} matched {:?}",
-                       element, scope);
-                data.ensure_restyle().hint.insert(StoredRestyleHint::subtree());
-                return true;
-            }
-        }
-
-
-        let mut any_children_invalid = false;
-
-        for child in element.as_node().children() {
-            let child = match child.as_element() {
-                Some(e) => e,
-                None => continue,
-            };
-
-            any_children_invalid |= self.process_invalidations_in_subtree(child);
-        }
-
-        if any_children_invalid {
-            debug!("Children of {:?} changed, setting dirty descendants",
-                   element);
-            unsafe { element.set_dirty_descendants() }
-        }
-
-        return any_children_invalid
-    }
-
-    fn scan_component(
-        component: &Component<SelectorImpl>,
-        scope: &mut Option<InvalidationScope>)
-    {
-        match *component {
-            Component::Class(ref class) => {
-                if scope.as_ref().map_or(true, |s| !s.is_id()) {
-                    *scope = Some(InvalidationScope::Class(class.clone()));
-                }
-            }
-            Component::ID(ref id) => {
-                if scope.is_none() {
-                    *scope = Some(InvalidationScope::ID(id.clone()));
-                }
-            }
-            _ => {
-                // Ignore everything else, at least for now.
-            }
-        }
-    }
-
-    /// Collect a style scopes for a given selector.
-    ///
-    /// We look at the outermost class or id selector to the left of an ancestor
-    /// combinator, in order to restyle only a given subtree.
-    ///
-    /// We prefer id scopes to class scopes, and outermost scopes to innermost
-    /// scopes (to reduce the amount of traversal we need to do).
-    fn collect_scopes(&mut self, selector: &Selector<SelectorImpl>) {
-        debug!("StylesheetInvalidationSet::collect_scopes({:?})", selector);
-
-        let mut scope: Option<InvalidationScope> = None;
-
-        let mut scan = true;
-        let mut iter = selector.inner.complex.iter();
-
-        loop {
-            for component in &mut iter {
-                if scan {
-                    Self::scan_component(component, &mut scope);
-                }
-            }
-            match iter.next_sequence() {
-                None => break,
-                Some(combinator) => {
-                    scan = combinator.is_ancestor();
-                }
-            }
-        }
-
-        match scope {
-            Some(s) => {
-                debug!(" > Found scope: {:?}", s);
-                self.invalid_scopes.insert(s);
-            }
-            None => {
-                debug!(" > Scope not found");
-
-                // If we didn't find a scope, any element could match this, so
-                // let's just bail out.
-                self.fully_invalid = true;
-            }
-        }
-    }
-
-    /// Collects invalidations for a given CSS rule.
-    fn collect_invalidations_for_rule(
-        &mut self,
-        rule: &CssRule,
-        guard: &SharedRwLockReadGuard)
-    {
-        use stylesheets::CssRule::*;
-        debug!("StylesheetInvalidationSet::collect_invalidations_for_rule");
-        debug_assert!(!self.fully_invalid, "Not worth to be here!");
-
-        match *rule {
-            Style(ref lock) => {
-                let style_rule = lock.read_with(guard);
-                for selector in &style_rule.selectors.0 {
-                    self.collect_scopes(selector);
-                    if self.fully_invalid {
-                        return;
-                    }
-                }
-            }
-            Document(..) |
-            Namespace(..) |
-            Import(..) |
-            Media(..) |
-            Supports(..) => {
-                // Do nothing, relevant nested rules are visited as part of the
-                // iteration.
-            }
-            FontFace(..) |
-            CounterStyle(..) |
-            Keyframes(..) |
-            Page(..) |
-            Viewport(..) => {
-                debug!(" > Found unsupported rule, marking the whole subtree \
-                       invalid.");
-
-                // TODO(emilio): Can we do better here?
-                //
-                // At least in `@page`, we could check the relevant media, I
-                // guess.
-                self.fully_invalid = true;
-            }
-        }
-    }
-}
+pub mod media_queries;
+pub mod stylesheets;
copy from servo/components/style/invalidation/mod.rs
copy to servo/components/style/invalidation/stylesheets.rs
--- a/servo/components/style/matching.rs
+++ b/servo/components/style/matching.rs
@@ -73,27 +73,31 @@ pub enum ChildCascadeRequirement {
     /// we won't bother recomputing style for children, so we can skip cascading
     /// the new values into child elements.
     CanSkipCascade,
     /// Old and new computed values were different, so we must cascade the
     /// new values to children.
     ///
     /// FIXME(heycam) Although this is "must" cascade, in the future we should
     /// track whether child elements rely specifically on inheriting particular
-    /// property values.  When we do that, we can treat `MustCascade` as "must
-    /// cascade unless we know that changes to these properties can be
+    /// property values.  When we do that, we can treat `MustCascadeChildren` as
+    /// "must cascade unless we know that changes to these properties can be
     /// ignored".
-    MustCascade,
+    MustCascadeChildren,
+    /// The same as `MustCascadeChildren`, but for the entire subtree.  This is
+    /// used to handle root font-size updates needing to recascade the whole
+    /// document.
+    MustCascadeDescendants,
 }
 
 impl From<StyleChange> for ChildCascadeRequirement {
     fn from(change: StyleChange) -> ChildCascadeRequirement {
         match change {
             StyleChange::Unchanged => ChildCascadeRequirement::CanSkipCascade,
-            StyleChange::Changed => ChildCascadeRequirement::MustCascade,
+            StyleChange::Changed => ChildCascadeRequirement::MustCascadeChildren,
         }
     }
 }
 
 bitflags! {
     /// Flags that represent the result of replace_rules.
     pub flags RulesChanged: u8 {
         /// Normal rules are changed.
@@ -442,16 +446,32 @@ trait PrivateMatchMethods: TElement {
             ChildCascadeRequirement::CanSkipCascade;
         if cascade_visited.should_accumulate_damage() {
             child_cascade_requirement =
                 self.accumulate_damage(&context.shared,
                                        restyle,
                                        old_values.as_ref().map(|v| v.as_ref()),
                                        &new_values,
                                        None);
+
+            // Handle root font-size changes.
+            if self.is_root() && !self.is_native_anonymous() {
+                // The new root font-size has already been updated on the Device
+                // in properties::apply_declarations.
+                let device = context.shared.stylist.device();
+                let new_font_size = new_values.get_font().clone_font_size();
+
+                // If the root font-size changed since last time, and something
+                // in the document did use rem units, ensure we recascade the
+                // entire tree.
+                if old_values.map_or(false, |v| v.get_font().clone_font_size() != new_font_size) &&
+                   device.used_root_font_size() {
+                    child_cascade_requirement = ChildCascadeRequirement::MustCascadeDescendants;
+                }
+            }
         }
 
         // Set the new computed values.
         cascade_visited.set_values(primary_style, new_values);
 
         // Return whether the damage indicates we must cascade new inherited
         // values into children.
         child_cascade_requirement
@@ -659,17 +679,17 @@ trait PrivateMatchMethods: TElement {
                              shared_context: &SharedStyleContext,
                              restyle: &mut RestyleData,
                              old_values: &ComputedValues,
                              new_values: &Arc<ComputedValues>,
                              pseudo: Option<&PseudoElement>)
                              -> ChildCascadeRequirement {
         // Don't accumulate damage if we're in a restyle for reconstruction.
         if shared_context.traversal_flags.for_reconstruct() {
-            return ChildCascadeRequirement::MustCascade;
+            return ChildCascadeRequirement::MustCascadeChildren;
         }
 
         // If an ancestor is already getting reconstructed by Gecko's top-down
         // frame constructor, no need to apply damage.  Similarly if we already
         // have an explicitly stored ReconstructFrame hint.
         //
         // See https://bugzilla.mozilla.org/show_bug.cgi?id=1301258#c12
         // for followup work to make the optimization here more optimal by considering
@@ -1215,22 +1235,22 @@ pub trait MatchMethods : TElement {
                          shared_context: &SharedStyleContext,
                          restyle: Option<&mut RestyleData>,
                          old_values: Option<&ComputedValues>,
                          new_values: &Arc<ComputedValues>,
                          pseudo: Option<&PseudoElement>)
                          -> ChildCascadeRequirement {
         let restyle = match restyle {
             Some(r) => r,
-            None => return ChildCascadeRequirement::MustCascade,
+            None => return ChildCascadeRequirement::MustCascadeChildren,
         };
 
         let old_values = match old_values {
             Some(v) => v,
-            None => return ChildCascadeRequirement::MustCascade,
+            None => return ChildCascadeRequirement::MustCascadeChildren,
         };
 
         // ::before and ::after are element-backed in Gecko, so they do the
         // damage calculation for themselves, when there's an actual pseudo.
         let is_existing_before_or_after =
             cfg!(feature = "gecko") &&
             pseudo.map_or(false, |p| p.is_before_or_after()) &&
             self.existing_style_for_restyle_damage(old_values, pseudo)
--- a/servo/components/style/restyle_hints.rs
+++ b/servo/components/style/restyle_hints.rs
@@ -441,16 +441,22 @@ impl RestyleHint {
     /// Unions the specified `RestyleHint` into this one.
     #[inline]
     pub fn insert(&mut self, other: Self) {
         // A later patch should make it worthwhile to have an insert() function
         // that consumes its argument.
         self.insert_from(&other)
     }
 
+    /// Inserts the specified `CascadeHint`.
+    #[inline]
+    pub fn insert_cascade_hint(&mut self, cascade_hint: CascadeHint) {
+        self.recascade.insert(cascade_hint);
+    }
+
     /// Returns whether this `RestyleHint` represents at least as much restyle
     /// work as the specified one.
     #[inline]
     pub fn contains(&self, other: &Self) -> bool {
         self.match_under_self.contains(other.match_under_self) &&
         (self.match_later_siblings & other.match_later_siblings) == other.match_later_siblings &&
         self.recascade.contains(other.recascade) &&
         self.replacements.contains(other.replacements)
--- a/servo/components/style/servo/media_queries.rs
+++ b/servo/components/style/servo/media_queries.rs
@@ -9,17 +9,17 @@ use context::QuirksMode;
 use cssparser::{Parser, RGBA};
 use euclid::{Size2D, TypedSize2D};
 use font_metrics::ServoMetricsProvider;
 use media_queries::MediaType;
 use parser::ParserContext;
 use properties::{ComputedValues, StyleBuilder};
 use properties::longhands::font_size;
 use std::fmt;
-use std::sync::atomic::{AtomicIsize, Ordering};
+use std::sync::atomic::{AtomicBool, AtomicIsize, Ordering};
 use style_traits::{CSSPixel, ToCss};
 use style_traits::viewport::ViewportConstraints;
 use values::computed::{self, ToComputedValue};
 use values::specified;
 
 /// A device is a structure that represents the current media a given document
 /// is displayed in.
 ///
@@ -36,48 +36,59 @@ pub struct Device {
     /// element, and used for rem units in other elements
     ///
     /// When computing the style of the root element, there can't be any
     /// other style being computed at the same time, given we need the style of
     /// the parent to compute everything else. So it is correct to just use
     /// a relaxed atomic here.
     #[ignore_heap_size_of = "Pure stack type"]
     root_font_size: AtomicIsize,
+    /// Whether any styles computed in the document relied on the root font-size
+    /// by using rem units.
+    #[ignore_heap_size_of = "Pure stack type"]
+    used_root_font_size: AtomicBool,
 }
 
 impl Device {
     /// Trivially construct a new `Device`.
     pub fn new(media_type: MediaType,
                viewport_size: TypedSize2D<f32, CSSPixel>)
                -> Device {
         Device {
             media_type: media_type,
             viewport_size: viewport_size,
             root_font_size: AtomicIsize::new(font_size::get_initial_value().0 as isize), // FIXME(bz): Seems dubious?
+            used_root_font_size: AtomicBool::new(false),
         }
     }
 
     /// Return the default computed values for this device.
     pub fn default_computed_values(&self) -> &ComputedValues {
         // FIXME(bz): This isn't really right, but it's no more wrong
         // than what we used to do.  See
         // https://github.com/servo/servo/issues/14773 for fixing it properly.
         ComputedValues::initial_values()
     }
 
     /// Get the font size of the root element (for rem)
     pub fn root_font_size(&self) -> Au {
+        self.used_root_font_size.store(true, Ordering::Relaxed);
         Au::new(self.root_font_size.load(Ordering::Relaxed) as i32)
     }
 
     /// Set the font size of the root element (for rem)
     pub fn set_root_font_size(&self, size: Au) {
         self.root_font_size.store(size.0 as isize, Ordering::Relaxed)
     }
 
+    /// Returns whether we ever looked up the root font size of the Device.
+    pub fn used_root_font_size(&self) -> bool {
+        self.used_root_font_size.load(Ordering::Relaxed)
+    }
+
     /// Returns the viewport size of the current device in app units, needed,
     /// among other things, to resolve viewport units.
     #[inline]
     pub fn au_viewport_size(&self) -> Size2D<Au> {
         Size2D::new(Au::from_f32_px(self.viewport_size.width),
                     Au::from_f32_px(self.viewport_size.height))
     }
 
--- a/servo/components/style/stylesheet_set.rs
+++ b/servo/components/style/stylesheet_set.rs
@@ -1,16 +1,16 @@
 /* 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/. */
 
 //! A centralized set of stylesheets for a document.
 
 use dom::TElement;
-use invalidation::StylesheetInvalidationSet;
+use invalidation::stylesheets::StylesheetInvalidationSet;
 use shared_lock::SharedRwLockReadGuard;
 use std::slice;
 use stylearc::Arc;
 use stylesheets::Stylesheet;
 use stylist::Stylist;
 
 /// Entry for a StylesheetSet. We don't bother creating a constructor, because
 /// there's no sensible defaults for the member variables.
@@ -170,16 +170,21 @@ impl StylesheetSet {
         where E: TElement,
     {
         debug!("StylesheetSet::flush");
         debug_assert!(self.dirty);
 
         self.dirty = false;
         self.invalidations.flush(document_element);
 
+        self.iter()
+    }
+
+    /// Returns an iterator over the current list of stylesheets.
+    pub fn iter(&self) -> StylesheetIterator {
         StylesheetIterator(self.entries.iter())
     }
 
     /// Mark the stylesheets as dirty, because something external may have
     /// invalidated it.
     ///
     /// FIXME(emilio): Make this more granular.
     pub fn force_dirty(&mut self) {
--- a/servo/components/style/stylesheets.rs
+++ b/servo/components/style/stylesheets.rs
@@ -1043,39 +1043,36 @@ impl NestedRuleIterationCondition for Al
         _: &Device,
         _: QuirksMode,
         _: &ImportRule)
         -> bool
     {
         true
     }
 
-    /// Whether we should process the nested rules in a given `@media` rule.
     fn process_media(
         _: &SharedRwLockReadGuard,
         _: &Device,
         _: QuirksMode,
         _: &MediaRule)
         -> bool
     {
         true
     }
 
-    /// Whether we should process the nested rules in a given `@-moz-document` rule.
     fn process_document(
         _: &SharedRwLockReadGuard,
         _: &Device,
         _: QuirksMode,
         _: &DocumentRule)
         -> bool
     {
         true
     }
 
-    /// Whether we should process the nested rules in a given `@supports` rule.
     fn process_supports(
         _: &SharedRwLockReadGuard,
         _: &Device,
         _: QuirksMode,
         _: &SupportsRule)
         -> bool
     {
         true
@@ -1153,46 +1150,41 @@ impl<'a, 'b, C> Iterator for RulesIterat
                         nested_iter_finished = true;
                         continue
                     }
                 };
 
                 sub_iter = match *rule {
                     CssRule::Import(ref import_rule) => {
                         let import_rule = import_rule.read_with(self.guard);
-
-                        if C::process_import(self.guard, self.device, self.quirks_mode, import_rule) {
-                            Some(import_rule.stylesheet.rules.read_with(self.guard).0.iter())
-                        } else {
-                            None
+                        if !C::process_import(self.guard, self.device, self.quirks_mode, import_rule) {
+                            continue;
                         }
+                        Some(import_rule.stylesheet.rules.read_with(self.guard).0.iter())
                     }
                     CssRule::Document(ref doc_rule) => {
                         let doc_rule = doc_rule.read_with(self.guard);
-                        if C::process_document(self.guard, self.device, self.quirks_mode, doc_rule) {
-                            Some(doc_rule.rules.read_with(self.guard).0.iter())
-                        } else {
-                            None
+                        if !C::process_document(self.guard, self.device, self.quirks_mode, doc_rule) {
+                            continue;
                         }
+                        Some(doc_rule.rules.read_with(self.guard).0.iter())
                     }
                     CssRule::Media(ref lock) => {
                         let media_rule = lock.read_with(self.guard);
-                        if C::process_media(self.guard, self.device, self.quirks_mode, media_rule) {
-                            Some(media_rule.rules.read_with(self.guard).0.iter())
-                        } else {
-                            None
+                        if !C::process_media(self.guard, self.device, self.quirks_mode, media_rule) {
+                            continue;
                         }
+                        Some(media_rule.rules.read_with(self.guard).0.iter())
                     }
                     CssRule::Supports(ref lock) => {
                         let supports_rule = lock.read_with(self.guard);
-                        if C::process_supports(self.guard, self.device, self.quirks_mode, supports_rule) {
-                            Some(supports_rule.rules.read_with(self.guard).0.iter())
-                        } else {
-                            None
+                        if !C::process_supports(self.guard, self.device, self.quirks_mode, supports_rule) {
+                            continue;
                         }
+                        Some(supports_rule.rules.read_with(self.guard).0.iter())
                     }
                     CssRule::Namespace(_) |
                     CssRule::Style(_) |
                     CssRule::FontFace(_) |
                     CssRule::CounterStyle(_) |
                     CssRule::Viewport(_) |
                     CssRule::Keyframes(_) |
                     CssRule::Page(_) => None,
--- a/servo/components/style/stylist.rs
+++ b/servo/components/style/stylist.rs
@@ -9,16 +9,17 @@ use bit_vec::BitVec;
 use context::{QuirksMode, SharedStyleContext};
 use data::ComputedStyle;
 use dom::TElement;
 use element_state::ElementState;
 use error_reporting::RustLogReporter;
 use font_metrics::FontMetricsProvider;
 #[cfg(feature = "gecko")]
 use gecko_bindings::structs::{nsIAtom, StyleRuleInclusion};
+use invalidation::media_queries::EffectiveMediaQueryResults;
 use keyframes::KeyframesAnimation;
 use media_queries::Device;
 use properties::{self, CascadeFlags, ComputedValues};
 use properties::{AnimationRules, PropertyDeclarationBlock};
 #[cfg(feature = "servo")]
 use properties::INHERIT_ALL;
 use restyle_hints::{HintComputationContext, DependencySet, RestyleHint};
 use rule_tree::{CascadeLevel, RuleTree, StrongRuleNode, StyleSource};
@@ -34,19 +35,18 @@ use shared_lock::{Locked, SharedRwLockRe
 use sink::Push;
 use smallvec::{SmallVec, VecLike};
 #[cfg(feature = "servo")]
 use std::marker::PhantomData;
 use style_traits::viewport::ViewportConstraints;
 use stylearc::Arc;
 #[cfg(feature = "gecko")]
 use stylesheets::{CounterStyleRule, FontFaceRule};
-use stylesheets::{CssRule, DocumentRule, ImportRule, MediaRule, StyleRule, SupportsRule};
+use stylesheets::{CssRule, StyleRule};
 use stylesheets::{Stylesheet, Origin, UserAgentStylesheets};
-use stylesheets::NestedRuleIterationCondition;
 use thread_state;
 use viewport::{self, MaybeNew, ViewportRule};
 
 pub use ::fnv::FnvHashMap;
 
 /// List of applicable declaration. This is a transient structure that shuttles
 /// declarations between selector matching and inserting into the rule tree, and
 /// therefore we want to avoid heap-allocation where possible.
@@ -78,16 +78,19 @@ pub struct Stylist {
     /// On Servo, on the other hand, the device is a really cheap representation
     /// that is recreated each time some constraint changes and calling
     /// `set_device`.
     device: Device,
 
     /// Viewport constraints based on the current device.
     viewport_constraints: Option<ViewportConstraints>,
 
+    /// Effective media query results cached from the last rebuild.
+    effective_media_query_results: EffectiveMediaQueryResults,
+
     /// If true, the quirks-mode stylesheet is applied.
     quirks_mode: QuirksMode,
 
     /// If true, the device has changed, and the stylist needs to be updated.
     is_device_dirty: bool,
 
     /// If true, the stylist is in a cleared state (e.g. just-constructed, or
     /// had clear() called on it with no following rebuild()).
@@ -217,79 +220,29 @@ impl From<StyleRuleInclusion> for RuleIn
     fn from(value: StyleRuleInclusion) -> Self {
         match value {
             StyleRuleInclusion::All => RuleInclusion::All,
             StyleRuleInclusion::DefaultOnly => RuleInclusion::DefaultOnly,
         }
     }
 }
 
-/// A filter that filters over effective rules, but allowing all potentially
-/// effective `@media` rules.
-pub struct PotentiallyEffectiveMediaRules;
-
-impl NestedRuleIterationCondition for PotentiallyEffectiveMediaRules {
-    fn process_import(
-        _: &SharedRwLockReadGuard,
-        _: &Device,
-        _: QuirksMode,
-        _: &ImportRule)
-        -> bool
-    {
-        true
-    }
-
-    fn process_media(
-        _: &SharedRwLockReadGuard,
-        _: &Device,
-        _: QuirksMode,
-        _: &MediaRule)
-        -> bool
-    {
-        true
-    }
-
-    /// Whether we should process the nested rules in a given `@-moz-document` rule.
-    fn process_document(
-        guard: &SharedRwLockReadGuard,
-        device: &Device,
-        quirks_mode: QuirksMode,
-        rule: &DocumentRule)
-        -> bool
-    {
-        use stylesheets::EffectiveRules;
-        EffectiveRules::process_document(guard, device, quirks_mode, rule)
-    }
-
-    /// Whether we should process the nested rules in a given `@supports` rule.
-    fn process_supports(
-        guard: &SharedRwLockReadGuard,
-        device: &Device,
-        quirks_mode: QuirksMode,
-        rule: &SupportsRule)
-        -> bool
-    {
-        use stylesheets::EffectiveRules;
-        EffectiveRules::process_supports(guard, device, quirks_mode, rule)
-    }
-}
-
-
 impl Stylist {
     /// Construct a new `Stylist`, using given `Device` and `QuirksMode`.
     /// If more members are added here, think about whether they should
     /// be reset in clear().
     #[inline]
     pub fn new(device: Device, quirks_mode: QuirksMode) -> Self {
         let mut stylist = Stylist {
             viewport_constraints: None,
             device: device,
             is_device_dirty: true,
             is_cleared: true,
             quirks_mode: quirks_mode,
+            effective_media_query_results: EffectiveMediaQueryResults::new(),
 
             element_map: PerPseudoElementSelectorMap::new(),
             pseudos_map: Default::default(),
             animations: Default::default(),
             precomputed_pseudo_element_decls: Default::default(),
             rules_source_order: 0,
             rule_tree: RuleTree::new(),
             dependencies: DependencySet::new(),
@@ -350,16 +303,17 @@ impl Stylist {
     /// Stylist::new.
     pub fn clear(&mut self) {
         if self.is_cleared {
             return
         }
 
         self.is_cleared = true;
 
+        self.effective_media_query_results.clear();
         self.viewport_constraints = None;
         // preserve current device
         self.is_device_dirty = true;
         // preserve current quirks_mode value
         self.element_map = PerPseudoElementSelectorMap::new();
         self.pseudos_map = Default::default();
         self.animations.clear(); // Or set to Default::default()?
         self.precomputed_pseudo_element_decls = Default::default();
@@ -477,16 +431,18 @@ impl Stylist {
     fn add_stylesheet<'a>(&mut self,
                           stylesheet: &Stylesheet,
                           guard: &SharedRwLockReadGuard,
                           _extra_data: &mut ExtraStyleData<'a>) {
         if stylesheet.disabled() || !stylesheet.is_effective_for_device(&self.device, guard) {
             return;
         }
 
+        self.effective_media_query_results.saw_effective(stylesheet);
+
         for rule in stylesheet.effective_rules(&self.device, guard) {
             match *rule {
                 CssRule::Style(ref locked) => {
                     let style_rule = locked.read_with(&guard);
                     self.num_declarations += style_rule.block.read_with(&guard).len();
                     for selector in &style_rule.selectors.0 {
                         self.num_selectors += 1;
 
@@ -510,20 +466,27 @@ impl Stylist {
                         selector.visit(&mut AttributeAndStateDependencyVisitor {
                             attribute_dependencies: &mut self.attribute_dependencies,
                             style_attribute_dependency: &mut self.style_attribute_dependency,
                             state_dependencies: &mut self.state_dependencies,
                         });
                     }
                     self.rules_source_order += 1;
                 }
-                CssRule::Import(..) => {
-                    // effective_rules visits the inner stylesheet if
+                CssRule::Import(ref lock) => {
+                    let import_rule = lock.read_with(guard);
+                    self.effective_media_query_results.saw_effective(import_rule);
+
+                    // NOTE: effective_rules visits the inner stylesheet if
                     // appropriate.
                 }
+                CssRule::Media(ref lock) => {
+                    let media_rule = lock.read_with(guard);
+                    self.effective_media_query_results.saw_effective(media_rule);
+                }
                 CssRule::Keyframes(ref keyframes_rule) => {
                     let keyframes_rule = keyframes_rule.read_with(guard);
                     debug!("Found valid keyframes rule: {:?}", *keyframes_rule);
 
                     // Don't let a prefixed keyframes animation override a non-prefixed one.
                     let needs_insertion = keyframes_rule.vendor_prefix.is_none() ||
                         self.animations.get(keyframes_rule.name.as_atom()).map_or(true, |rule|
                             rule.vendor_prefix.is_some());
@@ -811,26 +774,60 @@ impl Stylist {
 
         self.viewport_constraints =
             ViewportConstraints::maybe_new(&device, &cascaded_rule, self.quirks_mode);
 
         if let Some(ref constraints) = self.viewport_constraints {
             device.account_for_viewport_rule(constraints);
         }
 
-        self.is_device_dirty |= stylesheets.iter().any(|stylesheet| {
-            let mq = stylesheet.media.read_with(guard);
-            if mq.evaluate(&self.device, self.quirks_mode) != mq.evaluate(&device, self.quirks_mode) {
+        self.device = device;
+        let features_changed = self.media_features_change_changed_style(
+            stylesheets.iter(),
+            guard
+        );
+        self.is_device_dirty |= features_changed;
+    }
+
+    /// Returns whether, given a media feature change, any previously-applicable
+    /// style has become non-applicable, or vice-versa.
+    pub fn media_features_change_changed_style<'a, I>(
+        &self,
+        stylesheets: I,
+        guard: &SharedRwLockReadGuard,
+    ) -> bool
+        where I: Iterator<Item = &'a Arc<Stylesheet>>
+    {
+        use invalidation::media_queries::PotentiallyEffectiveMediaRules;
+
+        debug!("Stylist::media_features_change_changed_style");
+
+        for stylesheet in stylesheets {
+            let effective_now =
+                stylesheet.media.read_with(guard)
+                    .evaluate(&self.device, self.quirks_mode);
+
+            let effective_then =
+                self.effective_media_query_results.was_effective(&**stylesheet);
+
+            if effective_now != effective_then {
+                debug!(" > Stylesheet changed -> {}, {}",
+                       effective_then, effective_now);
                 return true
             }
 
+            if !effective_now {
+                continue;
+            }
+
             let mut iter =
                 stylesheet.iter_rules::<PotentiallyEffectiveMediaRules>(
                     &self.device,
-                    guard);
+                    guard
+                );
 
             while let Some(rule) = iter.next() {
                 match *rule {
                     CssRule::Style(..) |
                     CssRule::Namespace(..) |
                     CssRule::FontFace(..) |
                     CssRule::CounterStyle(..) |
                     CssRule::Supports(..) |
@@ -839,44 +836,52 @@ impl Stylist {
                     CssRule::Viewport(..) |
                     CssRule::Document(..) => {
                         // Not affected by device changes.
                         continue;
                     }
                     CssRule::Import(ref lock) => {
                         let import_rule = lock.read_with(guard);
                         let mq = import_rule.stylesheet.media.read_with(guard);
-                        let effective_now = mq.evaluate(&self.device, self.quirks_mode);
-                        if effective_now != mq.evaluate(&device, self.quirks_mode) {
+                        let effective_now =
+                            mq.evaluate(&self.device, self.quirks_mode);
+                        let effective_then =
+                            self.effective_media_query_results.was_effective(import_rule);
+                        if effective_now != effective_then {
+                            debug!(" > @import rule changed {} -> {}",
+                                   effective_then, effective_now);
                             return true;
                         }
 
                         if !effective_now {
                             iter.skip_children();
                         }
                     }
                     CssRule::Media(ref lock) => {
                         let media_rule = lock.read_with(guard);
                         let mq = media_rule.media_queries.read_with(guard);
-                        let effective_now = mq.evaluate(&self.device, self.quirks_mode);
-                        if effective_now != mq.evaluate(&device, self.quirks_mode) {
+                        let effective_now =
+                            mq.evaluate(&self.device, self.quirks_mode);
+                        let effective_then =
+                            self.effective_media_query_results.was_effective(media_rule);
+                        if effective_now != effective_then {
+                            debug!(" > @media rule changed {} -> {}",
+                                   effective_then, effective_now);
                             return true;
                         }
 
                         if !effective_now {
                             iter.skip_children();
                         }
                     }
                 }
             }
+        }
 
-            return false;
-        });
-
-        self.device = device;
+        return false;
     }
 
     /// Returns the viewport constraints that apply to this document because of
     /// a @viewport rule.
     pub fn viewport_constraints(&self) -> Option<&ViewportConstraints> {
         self.viewport_constraints.as_ref()
     }
 
--- a/servo/components/style/traversal.rs
+++ b/servo/components/style/traversal.rs
@@ -4,17 +4,18 @@
 
 //! Traversing the DOM tree; the bloom filter.
 
 use atomic_refcell::AtomicRefCell;
 use context::{SharedStyleContext, StyleContext, ThreadLocalStyleContext};
 use data::{ElementData, ElementStyles, StoredRestyleHint};
 use dom::{DirtyDescendants, NodeInfo, OpaqueNode, TElement, TNode};
 use matching::{ChildCascadeRequirement, MatchMethods};
-use restyle_hints::{HintComputationContext, RestyleHint};
+use restyle_hints::{CascadeHint, HintComputationContext, RECASCADE_SELF};
+use restyle_hints::{RECASCADE_DESCENDANTS, RestyleHint};
 use selector_parser::RestyleDamage;
 use sharing::{StyleSharingBehavior, StyleSharingTarget};
 #[cfg(feature = "servo")] use servo_config::opts;
 use smallvec::SmallVec;
 use std::borrow::BorrowMut;
 
 /// A per-traversal-level chunk of data. This is sent down by the traversal, and
 /// currently only holds the dom depth for the bloom filter.
@@ -667,26 +668,29 @@ pub fn recalc_style_at<E, D>(traversal: 
     context.thread_local.statistics.elements_traversed += 1;
     debug_assert!(!element.has_snapshot() || element.handled_snapshot(),
                   "Should've handled snapshots here already");
     debug_assert!(data.get_restyle().map_or(true, |r| {
         !r.has_sibling_invalidations()
     }), "Should've computed the final hint and handled later_siblings already");
 
     let compute_self = !element.has_current_styles(data);
-    let mut inherited_style_changed = false;
+    let mut cascade_hint = CascadeHint::empty();
 
     debug!("recalc_style_at: {:?} (compute_self={:?}, dirty_descendants={:?}, data={:?})",
            element, compute_self, element.has_dirty_descendants(), data);
 
     // Compute style for this element if necessary.
     if compute_self {
         match compute_style(traversal, traversal_data, context, element, data) {
-            ChildCascadeRequirement::MustCascade => {
-                inherited_style_changed = true;
+            ChildCascadeRequirement::MustCascadeChildren => {
+                cascade_hint |= RECASCADE_SELF;
+            }
+            ChildCascadeRequirement::MustCascadeDescendants => {
+                cascade_hint |= RECASCADE_SELF | RECASCADE_DESCENDANTS;
             }
             ChildCascadeRequirement::CanSkipCascade => {}
         };
 
         // If we're restyling this element to display:none, throw away all style
         // data in the subtree, notify the caller to early-return.
         if data.styles().is_display_none() {
             debug!("{:?} style is display:none - clearing data from descendants.",
@@ -703,25 +707,23 @@ pub fn recalc_style_at<E, D>(traversal: 
             debug_assert!(context.shared.traversal_flags.for_animation_only() ||
                           !r.hint.has_animation_hint(),
                           "animation restyle hint should be handled during \
                            animation-only restyles");
             r.hint.propagate(&context.shared.traversal_flags)
         },
     };
 
-    if inherited_style_changed {
-        // FIXME(bholley): Need to handle explicitly-inherited reset properties
-        // somewhere.
-        propagated_hint.insert(StoredRestyleHint::recascade_self());
-    }
+    // FIXME(bholley): Need to handle explicitly-inherited reset properties
+    // somewhere.
+    propagated_hint.insert_cascade_hint(cascade_hint);
 
-    trace!("propagated_hint={:?}, inherited_style_changed={:?}, \
+    trace!("propagated_hint={:?}, cascade_hint={:?}, \
             is_display_none={:?}, implementing_pseudo={:?}",
-           propagated_hint, inherited_style_changed,
+           propagated_hint, cascade_hint,
            data.styles().is_display_none(),
            element.implemented_pseudo_element());
     debug_assert!(element.has_current_styles(data) ||
                   context.shared.traversal_flags.for_animation_only(),
                   "Should have computed style or haven't yet valid computed \
                    style in case of animation-only restyle");
 
     let has_dirty_descendants_for_this_restyle =
--- a/servo/ports/geckolib/glue.rs
+++ b/servo/ports/geckolib/glue.rs
@@ -743,16 +743,42 @@ pub extern "C" fn Servo_StyleSet_AppendS
         &data.stylist,
         sheet,
         unique_id,
         &guard);
     data.clear_stylist();
 }
 
 #[no_mangle]
+pub extern "C" fn Servo_StyleSet_MediumFeaturesChanged(
+    raw_data: RawServoStyleSetBorrowed,
+) -> bool {
+    let global_style_data = &*GLOBAL_STYLE_DATA;
+    let guard = global_style_data.shared_lock.read();
+
+    // NOTE(emilio): We don't actually need to flush the stylist here and ensure
+    // it's up to date.
+    //
+    // In case it isn't we would trigger a rebuild + restyle as needed too.
+    //
+    // We need to ensure the default computed values are up to date though,
+    // because those can influence the result of media query evaluation.
+    //
+    // FIXME(emilio, bug 1369984): do the computation conditionally, to do it
+    // less often.
+    let mut data = PerDocumentStyleData::from_ffi(raw_data).borrow_mut();
+
+    data.stylist.device_mut().reset_computed_values();
+    data.stylist.media_features_change_changed_style(
+        data.stylesheets.iter(),
+        &guard,
+    )
+}
+
+#[no_mangle]
 pub extern "C" fn Servo_StyleSet_PrependStyleSheet(raw_data: RawServoStyleSetBorrowed,
                                                    raw_sheet: RawServoStyleSheetBorrowed,
                                                    unique_id: u64) {
     let global_style_data = &*GLOBAL_STYLE_DATA;
     let mut data = PerDocumentStyleData::from_ffi(raw_data).borrow_mut();
     let mut data = &mut *data;
     let sheet = HasArcFFI::as_arc(&raw_sheet);
     let guard = global_style_data.shared_lock.read();
@@ -1403,18 +1429,21 @@ pub extern "C" fn Servo_StyleSet_Init(pr
     let data = Box::new(PerDocumentStyleData::new(pres_context));
     data.into_ffi()
 }
 
 #[no_mangle]
 pub extern "C" fn Servo_StyleSet_RebuildData(raw_data: RawServoStyleSetBorrowed) {
     let global_style_data = &*GLOBAL_STYLE_DATA;
     let guard = global_style_data.shared_lock.read();
+
     let mut data = PerDocumentStyleData::from_ffi(raw_data).borrow_mut();
-    data.reset_device(&guard);
+    data.stylist.device_mut().reset();
+    data.stylesheets.force_dirty();
+    data.flush_stylesheets::<GeckoElement>(&guard, None);
 }
 
 #[no_mangle]
 pub extern "C" fn Servo_StyleSet_Clear(raw_data: RawServoStyleSetBorrowed) {
     let mut data = PerDocumentStyleData::from_ffi(raw_data).borrow_mut();
     data.clear_stylist();
 }
 
--- a/servo/rust-commit-hash
+++ b/servo/rust-commit-hash
@@ -1,1 +1,1 @@
-6165203c48420c6f77ea22113eb4ff66931410c3
+03bed655142dd5e42ba4539de53b3663d8a123e0
--- a/servo/rust-stable-version
+++ b/servo/rust-stable-version
@@ -1,1 +1,1 @@
-1.17.0
+1.16.0