merge mozilla-central to mozilla-inbound. r=merge a=merge
authorSebastian Hengst <archaeopteryx@coole-files.de>
Sat, 09 Sep 2017 11:47:13 +0200
changeset 429409 296f26c8422126c7e80812173ebc38e548566234
parent 429408 27f3e967a340906521a1a50e670ce09074bc31a7 (current diff)
parent 429382 c71b01e993510268bab7d60154b2f80692fd507d (diff)
child 429410 0298cb04eb7a794501556f2dd8b64f27736d300a
push id7761
push userjlund@mozilla.com
push dateFri, 15 Sep 2017 00:19:52 +0000
treeherdermozilla-beta@c38455951db4 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge, merge
milestone57.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 mozilla-central to mozilla-inbound. r=merge a=merge
--- a/accessible/generic/DocAccessible.cpp
+++ b/accessible/generic/DocAccessible.cpp
@@ -525,17 +525,17 @@ DocAccessible::RelativeBounds(nsIFrame**
 
   nsRect bounds;
   while (document) {
     nsIPresShell *presShell = document->GetShell();
     if (!presShell)
       return nsRect();
 
     nsRect scrollPort;
-    nsIScrollableFrame* sf = presShell->GetRootScrollFrameAsScrollableExternal();
+    nsIScrollableFrame* sf = presShell->GetRootScrollFrameAsScrollable();
     if (sf) {
       scrollPort = sf->GetScrollPortRect();
     } else {
       nsIFrame* rootFrame = presShell->GetRootFrame();
       if (!rootFrame)
         return nsRect();
 
       scrollPort = rootFrame->GetRect();
--- a/addon-sdk/source/lib/sdk/base64.js
+++ b/addon-sdk/source/lib/sdk/base64.js
@@ -5,20 +5,17 @@
 "use strict";
 
 module.metadata = {
   "stability": "unstable"
 };
 
 const { Cu } = require("chrome");
 
-// Passing an empty object as second argument to avoid scope's pollution
-// (devtools loader injects these symbols as global and prevent using
-// const here)
-var { atob, btoa } = Cu.import("resource://gre/modules/Services.jsm", {});
+Cu.importGlobalProperties(["atob", "btoa"]);
 
 function isUTF8(charset) {
   let type = typeof charset;
 
   if (type === "undefined")
     return false;
 
   if (type === "string" && charset.toLowerCase() === "utf-8")
--- a/addon-sdk/source/lib/sdk/core/promise.js
+++ b/addon-sdk/source/lib/sdk/core/promise.js
@@ -83,20 +83,22 @@ function getEnvironment (callback) {
   let Cu, _exports, _module, _require;
 
   // CommonJS / SDK
   if (typeof(require) === 'function') {
     Cu = require('chrome').Cu;
     _exports = exports;
     _module = module;
     _require = require;
-  }
   // JSM
-  else if (String(this).indexOf('BackstagePass') >= 0) {
-    Cu = this['Components'].utils;
+  } else if (typeof(this.__URI__) === "string") {
+    // Intentionally bypass the scan_for_bad_chrome checker in a way
+    // that works with shared JSM globals.
+    let comp = Components;
+    Cu = comp.utils;
     _exports = this.Promise = {};
     _module = { uri: __URI__, id: 'promise/core' };
     _require = uri => {
       let imports = {};
       Cu.import(uri, imports);
       return imports;
     };
     this.EXPORTED_SYMBOLS = ['Promise'];
--- a/addon-sdk/source/lib/toolkit/require.js
+++ b/addon-sdk/source/lib/toolkit/require.js
@@ -72,20 +72,20 @@ const make = (exports, rootURI, componen
 
 // If loaded in the context of commonjs module, reload as JSM into an
 // exports object.
 if (typeof(require) === "function" && typeof(module) === "object") {
   require("chrome").Cu.import(module.uri, module.exports);
 }
 // If loaded in the context of JSM make a loader & require and define
 // new symbols as exported ones.
-else if (typeof(__URI__) === "string" && this["Components"]) {
+else if (typeof(this.__URI__) === "string" && Components) {
   const builtin = Object.keys(this);
   const uri = __URI__.replace("toolkit/require.js", "");
-  make(this, uri, this["Components"]);
+  make(this, uri, Components);
 
   this.EXPORTED_SYMBOLS = Object.
                             keys(this).
                             filter($ => builtin.indexOf($) < 0);
 }
 else {
   throw Error("Loading require.js in this environment isn't supported")
 }
--- a/addon-sdk/source/test/addons/places/lib/favicon-helpers.js
+++ b/addon-sdk/source/test/addons/places/lib/favicon-helpers.js
@@ -4,17 +4,17 @@
 
 const { Cc, Ci, Cu } = require('chrome');
 const { Loader } = require('sdk/test/loader');
 const loader = Loader(module);
 const httpd = loader.require('./httpd');
 const { pathFor } = require('sdk/system');
 const { startServerAsync } = httpd;
 const basePath = pathFor('ProfD');
-const { atob } = Cu.import("resource://gre/modules/Services.jsm", {});
+Cu.importGlobalProperties(["atob"]);
 const historyService = Cc["@mozilla.org/browser/nav-history-service;1"]
                        .getService(Ci.nsINavHistoryService);
 const { events } = require('sdk/places/events');
 const { OS } = Cu.import("resource://gre/modules/osfile.jsm", {});
 
 function onFaviconChange (url) {
   return new Promise(resolve => {
     function handler ({data, type}) {
--- a/browser/base/content/browser.css
+++ b/browser/base/content/browser.css
@@ -678,18 +678,18 @@ html|input.urlbar-input[textoverflow]:no
 }
 
 #DateTimePickerPanel[active="true"] {
   -moz-binding: url("chrome://global/content/bindings/datetimepopup.xml#datetime-popup");
 }
 
 #urlbar[pageproxystate=invalid] > #page-action-buttons > .urlbar-page-action,
 #identity-box.chromeUI ~ #page-action-buttons > .urlbar-page-action:not(#star-button-box),
-#urlbar[usertyping] > .urlbar-textbox-container > .urlbar-history-dropmarker,
-.urlbar-go-button[pageproxystate="valid"],
+.urlbar-history-dropmarker[usertyping],
+.urlbar-go-button:not([usertyping]),
 .urlbar-go-button:not([parentfocused="true"]),
 #urlbar[pageproxystate="invalid"] > #identity-box > #blocked-permissions-container,
 #urlbar[pageproxystate="invalid"] > #identity-box > #notification-popup-box,
 #urlbar[pageproxystate="invalid"] > #identity-box > #identity-icon-labels {
   display: none;
 }
 
 #identity-box {
--- a/browser/base/content/test/urlbar/browser_locationBarCommand.js
+++ b/browser/base/content/test/urlbar/browser_locationBarCommand.js
@@ -1,19 +1,18 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 const TEST_VALUE = "example.com";
 const START_VALUE = "example.org";
 
 add_task(async function setup() {
-  Services.prefs.setBoolPref("browser.altClickSave", true);
-
-  registerCleanupFunction(() => {
-    Services.prefs.clearUserPref("browser.altClickSave");
+  await SpecialPowers.pushPrefEnv({
+    set: [["browser.altClickSave", true],
+          ["browser.urlbar.autoFill", false]],
   });
 });
 
 add_task(async function alt_left_click_test() {
   info("Running test: Alt left click");
 
   // Monkey patch saveURL() to avoid dealing with file save code paths.
   let oldSaveURL = saveURL;
@@ -153,22 +152,25 @@ add_task(async function load_in_new_tab_
 
     // Cleanup.
     gBrowser.removeCurrentTab();
     gBrowser.removeCurrentTab();
   }
 });
 
 function triggerCommand(shouldClick, event) {
-  gURLBar.value = TEST_VALUE;
   gURLBar.focus();
+  gURLBar.value = "";
+  for (let c of TEST_VALUE) {
+    EventUtils.synthesizeKey(c, {});
+  }
 
   if (shouldClick) {
-    is(gURLBar.getAttribute("pageproxystate"), "invalid",
-       "page proxy state must be invalid for go button to be visible");
+    ok(gURLBar.hasAttribute("usertyping"),
+       "usertyping attribute must be set for the go button to be visible");
 
     EventUtils.synthesizeMouseAtCenter(gURLBar.goButton, event);
   } else {
     EventUtils.synthesizeKey("VK_RETURN", event);
   }
 }
 
 function promiseLoadStarted() {
--- a/browser/base/content/test/urlbar/browser_locationBarExternalLoad.js
+++ b/browser/base/content/test/urlbar/browser_locationBarExternalLoad.js
@@ -1,26 +1,33 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 add_task(async function() {
+  await SpecialPowers.pushPrefEnv({
+    set: [["browser.urlbar.autoFill", false]],
+  });
   const url = "data:text/html,<body>hi";
   await testURL(url, urlEnter);
   await testURL(url, urlClick);
 });
 
 function urlEnter(url) {
   gURLBar.value = url;
   gURLBar.focus();
   EventUtils.synthesizeKey("VK_RETURN", {});
 }
 
 function urlClick(url) {
-  gURLBar.value = url;
   gURLBar.focus();
+  gURLBar.value = "";
+  for (let c of url) {
+    EventUtils.synthesizeKey(c, {});
+  }
+
   EventUtils.synthesizeMouseAtCenter(gURLBar.goButton, {});
 }
 
 function promiseNewTabSwitched() {
   return new Promise(resolve => {
     gBrowser.addEventListener("TabSwitchDone", function() {
       executeSoon(resolve);
     }, {once: true});
--- a/browser/base/content/urlbarBindings.xml
+++ b/browser/base/content/urlbarBindings.xml
@@ -40,23 +40,23 @@ file, You can obtain one at http://mozil
                       class="autocomplete-textbox urlbar-input textbox-input uri-element-right-align"
                       allowevents="true"
                       inputmode="url"
                       xbl:inherits="tooltiptext=inputtooltiptext,value,maxlength,disabled,size,readonly,placeholder,tabindex,accesskey,focused,textoverflow"/>
           <xul:image anonid="urlbar-go-button"
                      class="urlbar-go-button urlbar-icon"
                      onclick="gURLBar.handleCommand(event);"
                      tooltiptext="&goEndCap.tooltip;"
-                     xbl:inherits="pageproxystate,parentfocused=focused"/>
+                     xbl:inherits="pageproxystate,parentfocused=focused,usertyping"/>
         </xul:hbox>
         <xul:dropmarker anonid="historydropmarker"
                         class="autocomplete-history-dropmarker urlbar-history-dropmarker urlbar-icon"
                         tooltiptext="&urlbar.openHistoryPopup.tooltip;"
                         allowevents="true"
-                        xbl:inherits="open,enablehistory,parentfocused=focused"/>
+                        xbl:inherits="open,enablehistory,parentfocused=focused,usertyping"/>
         <children includes="hbox"/>
       </xul:hbox>
       <xul:popupset anonid="popupset"
                     class="autocomplete-result-popupset"/>
       <children includes="toolbarbutton"/>
     </content>
 
     <implementation implements="nsIObserver, nsIDOMEventListener">
--- a/browser/components/extensions/ext-tabs.js
+++ b/browser/components/extensions/ext-tabs.js
@@ -433,17 +433,20 @@ this.tabs = class extends ExtensionAPI {
 
           if (updateProperties.url !== null) {
             let url = context.uri.resolve(updateProperties.url);
 
             if (!context.checkLoadURL(url, {dontReportErrors: true})) {
               return Promise.reject({message: `Illegal URL: ${url}`});
             }
 
-            nativeTab.linkedBrowser.loadURI(url);
+            let flags = updateProperties.loadReplace
+              ? Ci.nsIWebNavigation.LOAD_FLAGS_REPLACE_HISTORY
+              : Ci.nsIWebNavigation.LOAD_FLAGS_NONE;
+            nativeTab.linkedBrowser.loadURIWithFlags(url, {flags});
           }
 
           if (updateProperties.active !== null) {
             if (updateProperties.active) {
               tabbrowser.selectedTab = nativeTab;
             } else {
               // Not sure what to do here? Which tab should we select?
             }
--- a/browser/components/extensions/schemas/tabs.json
+++ b/browser/components/extensions/schemas/tabs.json
@@ -743,16 +743,21 @@
                 "optional": true,
                 "description": "Whether the tab should be muted."
               },
               "openerTabId": {
                 "type": "integer",
                 "minimum": 0,
                 "optional": true,
                 "description": "The ID of the tab that opened this tab. If specified, the opener tab must be in the same window as this tab."
+              },
+              "loadReplace": {
+                "type": "boolean",
+                "optional": true,
+                "description": "Whether the load should replace the current history entry for the tab."
               }
             }
           },
           {
             "type": "function",
             "name": "callback",
             "optional": true,
             "parameters": [
--- a/browser/components/extensions/test/browser/browser_ext_tabs_executeScript_runAt.js
+++ b/browser/components/extensions/test/browser/browser_ext_tabs_executeScript_runAt.js
@@ -20,31 +20,36 @@ add_task(async function testExecuteScrip
   async function background() {
     let tab;
 
     const BASE = "http://mochi.test:8888/browser/browser/components/extensions/test/browser/";
     const URL = BASE + "file_iframe_document.sjs";
 
     const MAX_TRIES = 10;
 
+    let onUpdatedPromise = (tabId, url, status) => {
+      return new Promise(resolve => {
+        browser.tabs.onUpdated.addListener(function listener(_, changed, tab) {
+          if (tabId == tab.id && changed.status == status && tab.url == url) {
+            browser.tabs.onUpdated.removeListener(listener);
+            resolve();
+          }
+        });
+      });
+    };
+
     try {
       [tab] = await browser.tabs.query({active: true, currentWindow: true});
 
       let success = false;
       for (let tries = 0; !success && tries < MAX_TRIES; tries++) {
         let url = `${URL}?r=${Math.random()}`;
 
-        let loadingPromise = new Promise(resolve => {
-          browser.tabs.onUpdated.addListener(function listener(tabId, changed, tab_) {
-            if (tabId == tab.id && changed.status == "loading" && tab_.url == url) {
-              browser.tabs.onUpdated.removeListener(listener);
-              resolve();
-            }
-          });
-        });
+        let loadingPromise = onUpdatedPromise(tab.id, url, "loading");
+        let completePromise = onUpdatedPromise(tab.id, url, "complete");
 
         // TODO: Test allFrames and frameId.
 
         await browser.tabs.update({url});
         await loadingPromise;
 
         let states = await Promise.all([
           // Send the executeScript requests in the reverse order that we expect
@@ -69,25 +74,27 @@ add_task(async function testExecuteScrip
         ].reverse());
 
         browser.test.log(`Got states: ${states}`);
 
         // Make sure that none of our scripts executed earlier than expected,
         // regardless of retries.
         browser.test.assertTrue(states[1] == "interactive" || states[1] == "complete",
                                 `document_end state is valid: ${states[1]}`);
-        browser.test.assertTrue(states[2] == "complete",
+        browser.test.assertTrue(states[2] == "interactive" || states[2] == "complete",
                                 `document_idle state is valid: ${states[2]}`);
 
         // If we have the earliest valid states for each script, we're done.
         // Otherwise, try again.
         success = (states[0] == "loading" &&
                    states[1] == "interactive" &&
-                   states[2] == "complete" &&
-                   states[3] == "complete");
+                   states[2] == "interactive" &&
+                   states[3] == "interactive");
+
+        await completePromise;
       }
 
       browser.test.assertTrue(success, "Got the earliest expected states at least once");
 
       browser.test.notifyPass("executeScript-runAt");
     } catch (e) {
       browser.test.fail(`Error: ${e} :: ${e.stack}`);
       browser.test.notifyFail("executeScript-runAt");
--- a/browser/components/extensions/test/browser/browser_ext_tabs_update_url.js
+++ b/browser/components/extensions/test/browser/browser_ext_tabs_update_url.js
@@ -1,12 +1,17 @@
 /* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
 /* vim: set sts=2 sw=2 et tw=80: */
 "use strict";
 
+XPCOMUtils.defineLazyModuleGetter(this, "SessionStore",
+                                  "resource:///modules/sessionstore/SessionStore.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "TabStateFlusher",
+                                  "resource:///modules/sessionstore/TabStateFlusher.jsm");
+
 async function testTabsUpdateURL(existentTabURL, tabsUpdateURL, isErrorExpected) {
   let extension = ExtensionTestUtils.loadExtension({
     manifest: {
       "permissions": ["tabs"],
     },
 
     files: {
       "tab.html": `
@@ -103,8 +108,78 @@ add_task(async function() {
         .map((check) => Object.assign({}, check, {existentTabURL: "about:blank"}));
 
   for (let {existentTabURL, tabsUpdateURL, isErrorExpected} of testCases) {
     await testTabsUpdateURL(existentTabURL, tabsUpdateURL, isErrorExpected);
   }
 
   info("done");
 });
+
+add_task(async function test_update_reload() {
+  const URL = "https://example.com/";
+
+  let extension = ExtensionTestUtils.loadExtension({
+    manifest: {
+      "permissions": ["tabs", "history"],
+    },
+
+    background() {
+      browser.test.onMessage.addListener(async (cmd, ...args) => {
+        const result = await browser.tabs[cmd](...args);
+        browser.test.sendMessage("result", result);
+      });
+
+      browser.history.onVisited.addListener(data => {
+        browser.test.sendMessage("historyAdded");
+      });
+    },
+  });
+
+  let win = await BrowserTestUtils.openNewBrowserWindow();
+  let tabBrowser = win.gBrowser.selectedBrowser;
+  await BrowserTestUtils.loadURI(tabBrowser, URL);
+  await BrowserTestUtils.browserLoaded(tabBrowser, false, URL);
+  let tab = win.gBrowser.selectedTab;
+
+  async function getTabHistory() {
+    await TabStateFlusher.flush(tabBrowser);
+    return JSON.parse(SessionStore.getTabState(tab));
+  }
+
+  await extension.startup();
+  extension.sendMessage("query", {url: URL});
+  let tabs = await extension.awaitMessage("result");
+  let tabId = tabs[0].id;
+
+  let history = await getTabHistory();
+  is(history.entries.length, 1,
+     "Tab history contains the expected number of entries.");
+  is(history.entries[0].url, URL,
+    `Tab history contains the expected entry: URL.`);
+
+  extension.sendMessage("update", tabId, {url: `${URL}1/`});
+  await Promise.all([
+    extension.awaitMessage("result"),
+    extension.awaitMessage("historyAdded"),
+  ]);
+
+  history = await getTabHistory();
+  is(history.entries.length, 2,
+     "Tab history contains the expected number of entries.");
+  is(history.entries[1].url, `${URL}1/`,
+    `Tab history contains the expected entry: ${URL}1/.`);
+
+  extension.sendMessage("update", tabId, {url: `${URL}2/`, loadReplace: true});
+  await Promise.all([
+    extension.awaitMessage("result"),
+    extension.awaitMessage("historyAdded"),
+  ]);
+
+  history = await getTabHistory();
+  is(history.entries.length, 2,
+     "Tab history contains the expected number of entries.");
+  is(history.entries[1].url, `${URL}2/`,
+    `Tab history contains the expected entry: ${URL}2/.`);
+
+  await extension.unload();
+  await BrowserTestUtils.closeWindow(win);
+});
--- a/browser/components/nsBrowserGlue.js
+++ b/browser/components/nsBrowserGlue.js
@@ -915,17 +915,17 @@ BrowserGlue.prototype = {
 
     PageActions.init();
 
     this._firstWindowTelemetry(aWindow);
     this._firstWindowLoaded();
   },
 
   _sendMediaTelemetry() {
-    let win = RecentWindow.getMostRecentBrowserWindow();
+    let win = Services.appShell.hiddenDOMWindow;
     let v = win.document.createElementNS("http://www.w3.org/1999/xhtml", "video");
     v.reportCanPlayTelemetry();
   },
 
   /**
    * Application shutdown handler.
    */
   _onQuitApplicationGranted() {
--- a/browser/locales/en-US/installer/nsisstrings.properties
+++ b/browser/locales/en-US/installer/nsisstrings.properties
@@ -24,19 +24,23 @@ INSTALLER_WIN_CAPTION=$BrandShortName In
 # the string fit in the 3 lines of space available.
 STUB_CLEANUP_PAVEOVER_HEADER=$BrandShortName is already installed.\nLet's update it.
 STUB_CLEANUP_REINSTALL_HEADER=$BrandShortName has been installed before.\nLet's get you a new copy.
 STUB_CLEANUP_PAVEOVER_BUTTON=&Update
 STUB_CLEANUP_REINSTALL_BUTTON=Re-&install
 STUB_CLEANUP_CHECKBOX_LABEL=&Restore default settings and remove old add-ons for optimal performance
 
 STUB_INSTALLING_LABEL=Now installing
+STUB_INSTALLING_LABEL2=Now installing…
 STUB_BLURB1=Fast, responsive online experiences
 STUB_BLURB2=Compatibility with more of your favorite sites
 STUB_BLURB3=Built-in privacy tools for safer browsing
+STUB_BLURB_FIRST1=The fastest, most responsive $BrandShortName yet
+STUB_BLURB_SECOND1=Faster page loading and tab switching
+STUB_BLURB_THIRD1=Powerful private browsing
 STUB_BLURB_FOOTER2=Built for people, not for profit
 
 WARN_MIN_SUPPORTED_OSVER_MSG=Sorry, $BrandShortName can't be installed. This version of $BrandShortName requires ${MinSupportedVer} or newer. Please click the OK button for additional information.
 WARN_MIN_SUPPORTED_CPU_MSG=Sorry, $BrandShortName can't be installed. This version of $BrandShortName requires a processor with ${MinSupportedCPU} support. Please click the OK button for additional information.
 WARN_MIN_SUPPORTED_OSVER_CPU_MSG=Sorry, $BrandShortName can't be installed. This version of $BrandShortName requires ${MinSupportedVer} or newer and a processor with ${MinSupportedCPU} support. Please click the OK button for additional information.
 WARN_WRITE_ACCESS_QUIT=You don't have access to write to the installation directory
 WARN_DISK_SPACE_QUIT=You don't have sufficient disk space to install.
 WARN_MANUALLY_CLOSE_APP_LAUNCH=$BrandShortName is already running.\n\nPlease close $BrandShortName prior to launching the version you have just installed.
--- a/browser/modules/test/browser/browser_UsageTelemetry_content_aboutHome.js
+++ b/browser/modules/test/browser/browser_UsageTelemetry_content_aboutHome.js
@@ -35,24 +35,23 @@ add_task(async function setup() {
 
   // Make sure to restore the engine once we're done.
   registerCleanupFunction(async function() {
     Services.search.currentEngine = originalEngine;
     Services.search.removeEngine(engineDefault);
     Services.search.removeEngine(engineOneOff);
     await PlacesTestUtils.clearHistory();
     Services.telemetry.setEventRecordingEnabled("navigation", false);
+    Services.prefs.clearUserPref("browser.newtabpage.activity-stream.aboutHome.enabled");
   });
 });
 
 add_task(async function test_abouthome_simpleQuery() {
   // Make sure Activity Stream about:home is disabled.
-  await SpecialPowers.pushPrefEnv({set: [
-    ["browser.newtabpage.activity-stream.aboutHome.enabled", false]
-  ]});
+  Services.prefs.setBoolPref("browser.newtabpage.activity-stream.aboutHome.enabled", false);
 
   // Let's reset the counts.
   Services.telemetry.clearScalars();
   Services.telemetry.clearEvents();
   let search_hist = getAndClearKeyedHistogram("SEARCH_COUNTS");
 
   let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser);
 
@@ -64,16 +63,21 @@ add_task(async function test_abouthome_s
     }, true, true);
   });
 
   info("Load about:home.");
   tab.linkedBrowser.loadURI("about:home");
   info("Wait for AboutHomeLoadSnippetsCompleted.");
   await promiseAboutHomeLoaded;
 
+  info("Wait for ContentSearchUI search provider to initialize.");
+  await ContentTask.spawn(tab.linkedBrowser, null, async function() {
+    await ContentTaskUtils.waitForCondition(() => content.wrappedJSObject.gContentSearchController.defaultEngine);
+  });
+
   info("Trigger a simple serch, just test + enter.");
   let p = BrowserTestUtils.browserLoaded(tab.linkedBrowser);
   await typeInSearchField(tab.linkedBrowser, "test query", "searchText");
   await BrowserTestUtils.synthesizeKey("VK_RETURN", {}, tab.linkedBrowser);
   await p;
 
   // Check if the scalars contain the expected values.
   const scalars = getParentProcessScalars(Ci.nsITelemetry.DATASET_RELEASE_CHANNEL_OPTIN, true, false);
@@ -89,40 +93,43 @@ add_task(async function test_abouthome_s
   events = (events.parent || []).filter(e => e[1] == "navigation" && e[2] == "search");
   checkEvents(events, [["navigation", "search", "about_home", "enter", {engine: "other-MozSearch"}]]);
 
   await BrowserTestUtils.removeTab(tab);
 });
 
 add_task(async function test_abouthome_activitystream_simpleQuery() {
   // Make sure Activity Stream about:home is enabled.
-  await SpecialPowers.pushPrefEnv({set: [
-    ["browser.newtabpage.activity-stream.aboutHome.enabled", true]
-  ]});
+  Services.prefs.setBoolPref("browser.newtabpage.activity-stream.aboutHome.enabled", true);
 
   // Let's reset the counts.
   Services.telemetry.clearScalars();
   Services.telemetry.clearEvents();
   let search_hist = getAndClearKeyedHistogram("SEARCH_COUNTS");
 
   let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser);
 
   info("Setup waiting for search input to initialise.");
   let promiseAboutHomeSearchLoaded = new Promise(resolve => {
     tab.linkedBrowser.addEventListener("ContentSearchClient", function loadListener(event) {
       tab.linkedBrowser.removeEventListener("ContentSearchClient", loadListener, true);
-      resolve();
+      executeSoon(resolve);
     }, true, true);
   });
 
   info("Load about:home.");
   tab.linkedBrowser.loadURI("about:home");
   info("Wait for ActivityStream search input.");
   await promiseAboutHomeSearchLoaded;
 
+  info("Wait for ContentSearchUI search provider to initialize.");
+  await ContentTask.spawn(tab.linkedBrowser, null, async function() {
+    await ContentTaskUtils.waitForCondition(() => content.wrappedJSObject.gContentSearchController.defaultEngine);
+  });
+
   info("Trigger a simple serch, just test + enter.");
   let p = BrowserTestUtils.browserLoaded(tab.linkedBrowser);
   await typeInSearchField(tab.linkedBrowser, "test query", "newtab-search-text");
   await BrowserTestUtils.synthesizeKey("VK_RETURN", {}, tab.linkedBrowser);
   await p;
 
   // Check if the scalars contain the expected values.
   const scalars = getParentProcessScalars(Ci.nsITelemetry.DATASET_RELEASE_CHANNEL_OPTIN, true, false);
--- a/browser/themes/shared/toolbarbuttons.inc.css
+++ b/browser/themes/shared/toolbarbuttons.inc.css
@@ -1,21 +1,24 @@
 /* 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/. */
 
 :root {
-  --toolbarbutton-hover-background: hsla(240,5%,5%,.1);
-  --toolbarbutton-active-background: hsla(240,5%,5%,.15);
-
   --toolbarbutton-hover-transition-duration: 150ms;
   --toolbarbutton-active-transition-duration: 10ms;
 
   --toolbarbutton-inner-padding: 6px;
 
+  /* These hover and active colors should work on both light and dark
+     backgrounds. We'll later set colors that cater for light and dark
+     backgrounds specifically when we can detect them. */
+  --toolbarbutton-hover-background: hsla(0,0%,70%,.4);
+  --toolbarbutton-active-background: hsla(0,0%,70%,.6);
+
   --backbutton-background: hsla(0,100%,100%,.8);
   --backbutton-hover-background: var(--backbutton-background);
   --backbutton-active-background: var(--toolbarbutton-active-background);
   --backbutton-border-color: hsla(240,5%,5%,.3);
 
   /* This default value of --toolbarbutton-height is defined to prevent
      CSS errors for an invalid variable. The value should not get used,
      as a more specific value should be set when the value will be used. */
@@ -25,16 +28,25 @@
 :root[uidensity=compact] {
   --toolbarbutton-inner-padding: 5px;
 }
 
 :root[uidensity=touch] {
   --toolbarbutton-inner-padding: 9px;
 }
 
+/* We use :-moz-lwtheme-* for toolbarbuttons that aren't inside a toolbar, and
+   [brighttext] to cater for OS themes where :-moz-lwtheme-* doesn't apply. */
+:root:-moz-lwtheme-darktext,
+toolbar:not([brighttext]) {
+  --toolbarbutton-hover-background: hsla(240,5%,5%,.1);
+  --toolbarbutton-active-background: hsla(240,5%,5%,.15);
+}
+
+:root:-moz-lwtheme-brighttext,
 toolbar[brighttext] {
   --toolbarbutton-hover-background: hsla(0,0%,100%,.2);
   --toolbarbutton-active-background: hsla(0,0%,100%,.3);
 
   --backbutton-background: var(--toolbarbutton-hover-background);
   --backbutton-hover-background: var(--toolbarbutton-active-background);
   --backbutton-active-background: hsla(0,0%,100%,.4);
 }
--- a/caps/tests/mochitest/test_addonMayLoad.html
+++ b/caps/tests/mochitest/test_addonMayLoad.html
@@ -10,17 +10,17 @@ https://bugzilla.mozilla.org/show_bug.cg
   <link rel="stylesheet" type="text/css" href="chrome://global/skin"/>
   <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"/>
   <script type="application/javascript">
 
   /** Test for Bug 1180921 **/
   const Cc = Components.classes;
   const Ci = Components.interfaces;
   const Cu = Components.utils;
-  let module = Cu.import("resource://gre/modules/Services.jsm", window);
+  let module = Cu.getGlobalForObject(Cu.import("resource://gre/modules/Services.jsm", window));
   let ssm = Services.scriptSecurityManager;
 
   function StubPolicy(id, subdomain) {
     /* globals MatchPatternSet */
     return new module.WebExtensionPolicy({
       id,
       mozExtensionHostname: id,
       baseURL: `file:///{id}`,
--- a/caps/tests/mochitest/test_extensionURL.html
+++ b/caps/tests/mochitest/test_extensionURL.html
@@ -8,17 +8,17 @@ https://bugzilla.mozilla.org/show_bug.cg
   <title>Test for Bug 1161831</title>
   <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
   <script type="application/javascript">
 
   /** Test for Bug 1161831 **/
   SimpleTest.waitForExplicitFinish();
 
-  let module = SpecialPowers.Cu.import("resource://gre/modules/Services.jsm", {});
+  let module = SpecialPowers.Cu.getGlobalForObject(SpecialPowers.Cu.import("resource://gre/modules/Services.jsm", {}));
   var {MatchGlob, MatchPatternSet, WebExtensionPolicy} = module;
 
   var policy1, policy2;
 
   var XPCOMUtils = SpecialPowers.Cu.import("resource://gre/modules/XPCOMUtils.jsm").XPCOMUtils;
   var resourceHandler = SpecialPowers.Services.io.getProtocolHandler("resource")
                                      .QueryInterface(SpecialPowers.Ci.nsISubstitutingProtocolHandler);
   var extensionHandler = SpecialPowers.Services.io.getProtocolHandler("moz-extension")
--- a/devtools/shared/builtin-modules.js
+++ b/devtools/shared/builtin-modules.js
@@ -14,17 +14,17 @@
  */
 
 const { Cu, CC, Cc, Ci } = require("chrome");
 const promise = Cu.import("resource://gre/modules/Promise.jsm", {}).Promise;
 const jsmScope = Cu.import("resource://gre/modules/Services.jsm", {});
 const { Services } = jsmScope;
 // Steal various globals only available in JSM scope (and not Sandbox one)
 const { PromiseDebugging, ChromeUtils, ThreadSafeChromeUtils, HeapSnapshot,
-        atob, btoa, TextEncoder, TextDecoder } = jsmScope;
+        atob, btoa, TextEncoder, TextDecoder } = Cu.getGlobalForObject(jsmScope);
 
 // Create a single Sandbox to access global properties needed in this module.
 // Sandbox are memory expensive, so we should create as little as possible.
 const { CSS, FileReader, indexedDB, URL } =
     Cu.Sandbox(CC("@mozilla.org/systemprincipal;1", "nsIPrincipal")(), {
       wantGlobalProperties: ["CSS", "FileReader", "indexedDB", "URL"]
     });
 
--- a/docshell/base/nsDocShell.cpp
+++ b/docshell/base/nsDocShell.cpp
@@ -9140,17 +9140,17 @@ nsDocShell::RestoreFromHistory()
     if (!newBounds.IsEmpty() && !newBounds.IsEqualEdges(oldBounds)) {
 #ifdef DEBUG_PAGE_CACHE
       printf("resize widget(%d, %d, %d, %d)\n", newBounds.x,
              newBounds.y, newBounds.width, newBounds.height);
 #endif
       mContentViewer->SetBounds(newBounds);
     } else {
       nsIScrollableFrame* rootScrollFrame =
-        shell->GetRootScrollFrameAsScrollableExternal();
+        shell->GetRootScrollFrameAsScrollable();
       if (rootScrollFrame) {
         rootScrollFrame->PostScrolledAreaEventForCurrentArea();
       }
     }
   }
 
   // The FinishRestore call below can kill these, null them out so we don't
   // have invalid pointer lying around.
@@ -13585,17 +13585,17 @@ nsDocShell::GetChildOffset(nsIDOMNode* a
 }
 
 nsIScrollableFrame*
 nsDocShell::GetRootScrollFrame()
 {
   nsCOMPtr<nsIPresShell> shell = GetPresShell();
   NS_ENSURE_TRUE(shell, nullptr);
 
-  return shell->GetRootScrollFrameAsScrollableExternal();
+  return shell->GetRootScrollFrameAsScrollable();
 }
 
 NS_IMETHODIMP
 nsDocShell::EnsureScriptEnvironment()
 {
   if (mScriptGlobal) {
     return NS_OK;
   }
--- a/dom/base/Selection.cpp
+++ b/dom/base/Selection.cpp
@@ -3876,42 +3876,32 @@ Selection::NotifySelectionListeners()
 
   short reason = frameSelection->PopReason();
   for (auto& listener : selectionListeners) {
     listener->NotifySelectionChanged(domdoc, this, reason);
   }
   return NS_OK;
 }
 
-NS_IMETHODIMP
+void
 Selection::StartBatchChanges()
 {
   if (mFrameSelection) {
     RefPtr<nsFrameSelection> frameSelection = mFrameSelection;
     frameSelection->StartBatchChanges();
   }
-  return NS_OK;
 }
 
-
-
-NS_IMETHODIMP
-Selection::EndBatchChanges()
-{
-  return EndBatchChangesInternal();
-}
-
-nsresult
-Selection::EndBatchChangesInternal(int16_t aReason)
+void
+Selection::EndBatchChanges(int16_t aReason)
 {
   if (mFrameSelection) {
     RefPtr<nsFrameSelection> frameSelection = mFrameSelection;
     frameSelection->EndBatchChanges(aReason);
   }
-  return NS_OK;
 }
 
 void
 Selection::AddSelectionChangeBlocker()
 {
   mSelectionChangeBlockerCount++;
 }
 
--- a/dom/base/Selection.h
+++ b/dom/base/Selection.h
@@ -67,17 +67,22 @@ public:
   Selection();
   explicit Selection(nsFrameSelection *aList);
 
   NS_DECL_CYCLE_COLLECTING_ISUPPORTS
   NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS_AMBIGUOUS(Selection, nsISelectionPrivate)
   NS_DECL_NSISELECTION
   NS_DECL_NSISELECTIONPRIVATE
 
-  nsresult EndBatchChangesInternal(int16_t aReason = nsISelectionListener::NO_REASON);
+  // match this up with EndbatchChanges. will stop ui updates while multiple
+  // selection methods are called
+  void StartBatchChanges();
+
+  // match this up with StartBatchChanges
+  void EndBatchChanges(int16_t aReason = nsISelectionListener::NO_REASON);
 
   nsIDocument* GetParentObject() const;
 
   // utility methods for scrolling the selection into view
   nsPresContext* GetPresContext() const;
   nsIPresShell* GetPresShell() const;
   nsFrameSelection* GetFrameSelection() const { return mFrameSelection; }
   // Returns a rect containing the selection region, and frame that that
@@ -528,17 +533,17 @@ public:
     if (mSelection) {
       mSelection->StartBatchChanges();
     }
   }
 
   ~SelectionBatcher()
   {
     if (mSelection) {
-      mSelection->EndBatchChangesInternal();
+      mSelection->EndBatchChanges();
     }
   }
 };
 
 class MOZ_RAII AutoHideSelectionChanges final
 {
 private:
   RefPtr<Selection> mSelection;
--- a/dom/base/nsISelectionPrivate.idl
+++ b/dom/base/nsISelectionPrivate.idl
@@ -31,26 +31,16 @@ native ScrollAxis(nsIPresShell::ScrollAx
 interface nsISelectionPrivate : nsISelection
  {
     const short ENDOFPRECEDINGLINE=0;
     const short STARTOFNEXTLINE=1;
 
     attribute boolean interlinePosition;
     [noscript] attribute nsIContent ancestorLimiter;
 
-    /* startBatchChanges
-       match this up with endbatchChanges. will stop ui updates while multiple selection methods are called
-    */
-    [noscript] void startBatchChanges();
-
-    /* endBatchChanges
-       match this up with startBatchChanges
-    */
-    [noscript] void endBatchChanges();
-
     DOMString  toStringWithFormat(in string formatType, in unsigned long flags, in int32_t wrapColumn);
     void  addSelectionListener(in nsISelectionListener newListener);
     void  removeSelectionListener(in nsISelectionListener listenerToRemove);
 
     /* Table selection stuff
        We should probably move this and table-related
        items in nsFrameSelection  to a
        new nsITableSelection interface
--- a/dom/base/nsObjectLoadingContent.cpp
+++ b/dom/base/nsObjectLoadingContent.cpp
@@ -1399,20 +1399,16 @@ nsObjectLoadingContent::MaybeRewriteYout
 
   // See if requester is planning on using the JS API.
   nsAutoCString uri;
   nsresult rv = aURI->GetSpec(uri);
   if (NS_FAILED(rv)) {
     return;
   }
 
-  if (uri.Find("enablejsapi=1", true, 0, -1) != kNotFound) {
-    return;
-  }
-
   // Some YouTube urls have parameters in path components, e.g.
   // http://youtube.com/embed/7LcUOEP7Brc&start=35. These URLs work with flash,
   // but break iframe/object embedding. If this situation occurs with rewritten
   // URLs, convert the parameters to query in order to make the video load
   // correctly as an iframe. In either case, warn about it in the
   // developer console.
   int32_t ampIndex = uri.FindChar('&', 0);
   bool replaceQuery = false;
--- a/dom/base/nsObjectLoadingContent.h
+++ b/dom/base/nsObjectLoadingContent.h
@@ -577,22 +577,22 @@ class nsObjectLoadingContent : public ns
      * Used for identifying whether we can rewrite a youtube flash embed to
      * possibly use HTML5 instead.
      *
      * Returns true if plugin.rewrite_youtube_embeds pref is true and the
      * element this nsObjectLoadingContent instance represents:
      *
      * - is an embed or object node
      * - has a URL pointing at the youtube.com domain, using "/v/" style video
-     *   path reference, and without enablejsapi=1 in the path
+     *   path reference.
      *
      * Having the enablejsapi flag means the document that contains the element
      * could possibly be manipulating the youtube video elsewhere on the page
-     * via javascript. We can't rewrite these kinds of elements without possibly
-     * breaking content, which we want to avoid.
+     * via javascript. In the context of embed elements, this usage has been
+     * deprecated by youtube, so we can just rewrite as normal.
      *
      * If we can rewrite the URL, we change the "/v/" to "/embed/", and change
      * our type to eType_Document so that we render similarly to an iframe
      * embed.
      */
     void MaybeRewriteYoutubeEmbed(nsIURI* aURI,
                                   nsIURI* aBaseURI,
                                   nsIURI** aRewrittenURI);
--- a/dom/events/ContentEventHandler.cpp
+++ b/dom/events/ContentEventHandler.cpp
@@ -3249,17 +3249,17 @@ ContentEventHandler::OnSelectionEvent(Wi
       } else {
         rv = mSelection->Extend(endNode, endNodeOffset);
       }
     }
   }
 
   // Pass the eSetSelection events reason along with the BatchChange-end
   // selection change notifications.
-  mSelection->EndBatchChangesInternal(aEvent->mReason);
+  mSelection->EndBatchChanges(aEvent->mReason);
   NS_ENSURE_SUCCESS(rv, rv);
 
   mSelection->ScrollIntoViewInternal(
     nsISelectionController::SELECTION_FOCUS_REGION,
     false, nsIPresShell::ScrollAxis(), nsIPresShell::ScrollAxis());
   aEvent->mSucceeded = true;
   return NS_OK;
 }
--- a/dom/ipc/ContentPrefs.cpp
+++ b/dom/ipc/ContentPrefs.cpp
@@ -112,16 +112,17 @@ const char* mozilla::dom::ContentPrefs::
   "javascript.options.strict.debug",
   "javascript.options.throw_on_asmjs_validation_failure",
   "javascript.options.throw_on_debuggee_would_run",
   "javascript.options.wasm",
   "javascript.options.wasm_baselinejit",
   "javascript.options.wasm_ionjit",
   "javascript.options.werror",
   "javascript.use_us_english_locale",
+  "jsloader.shareGlobal",
   "layout.idle_period.required_quiescent_frames",
   "layout.idle_period.time_limit",
   "layout.interruptible-reflow.enabled",
   "mathml.disabled",
   "media.apple.forcevda",
   "media.clearkey.persistent-license.enabled",
   "media.cubeb.backend",
   "media.cubeb.sandbox",
--- a/dom/tests/browser/browser_ConsoleAPI_originAttributes.js
+++ b/dom/tests/browser/browser_ConsoleAPI_originAttributes.js
@@ -1,15 +1,15 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 const ConsoleAPIStorage = Cc["@mozilla.org/consoleAPI-storage;1"]
       .getService(Ci.nsIConsoleAPIStorage);
 
-const {WebExtensionPolicy} = Cu.import("resource://gre/modules/Services.jsm", {});
+const {WebExtensionPolicy} = Cu.getGlobalForObject(Cu.import("resource://gre/modules/Services.jsm", {}));
 
 const FAKE_ADDON_ID = "test-webext-addon@mozilla.org";
 const EXPECTED_CONSOLE_ID = `addon/${FAKE_ADDON_ID}`;
 const EXPECTED_CONSOLE_MESSAGE_CONTENT = "fake-webext-addon-test-log-message";
 const ConsoleObserver = {
   QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver]),
 
   init() {
--- a/gfx/layers/apz/src/FocusState.cpp
+++ b/gfx/layers/apz/src/FocusState.cpp
@@ -69,63 +69,77 @@ FocusState::Update(uint64_t aRootLayerTr
       return;
     }
 
     const FocusTarget& target = currentNode->second;
 
     // Accumulate event listener flags on the path to the focus target
     mFocusHasKeyEventListeners |= target.mFocusHasKeyEventListeners;
 
-    switch (target.mType) {
-      case FocusTarget::eRefLayer: {
+    // Match on the data stored in mData
+    // The match functions return true or false depending on whether the
+    // enclosing method, FocusState::Update, should return or continue to the
+    // next iteration of the while loop, respectively.
+    struct FocusTargetDataMatcher {
+
+      FocusState& mFocusState;
+      const uint64_t mSequenceNumber;
+
+      bool match(const FocusTarget::NoFocusTarget& aNoFocusTarget) {
+        FS_LOG("Setting target to nil (reached a nil target)\n");
+
+        // Mark what sequence number this target has for debugging purposes so
+        // we can always accurately report on whether we are stale or not
+        mFocusState.mLastContentProcessedEvent = mSequenceNumber;
+        return true;
+      }
+
+      bool match(const FocusTarget::RefLayerId aRefLayerId) {
         // Guard against infinite loops
-        MOZ_ASSERT(mFocusLayersId != target.mData.mRefLayerId);
-        if (mFocusLayersId == target.mData.mRefLayerId) {
+        MOZ_ASSERT(mFocusState.mFocusLayersId != aRefLayerId);
+        if (mFocusState.mFocusLayersId == aRefLayerId) {
           FS_LOG("Setting target to nil (bailing out of infinite loop, lt=%" PRIu64 ")\n",
-                 mFocusLayersId);
-          return;
+                 mFocusState.mFocusLayersId);
+          return true;
         }
 
-        FS_LOG("Looking for target in lt=%" PRIu64 "\n", target.mData.mRefLayerId);
+        FS_LOG("Looking for target in lt=%" PRIu64 "\n", aRefLayerId);
 
         // The focus target is in a child layer tree
-        mFocusLayersId = target.mData.mRefLayerId;
-        break;
+        mFocusState.mFocusLayersId = aRefLayerId;
+        return false;
       }
-      case FocusTarget::eScrollLayer: {
+
+      bool match(const FocusTarget::ScrollTargets& aScrollTargets) {
         FS_LOG("Setting target to h=%" PRIu64 ", v=%" PRIu64 ", and seq=%" PRIu64 "\n",
-               target.mData.mScrollTargets.mHorizontal,
-               target.mData.mScrollTargets.mVertical,
-               target.mSequenceNumber);
+               aScrollTargets.mHorizontal,
+               aScrollTargets.mVertical,
+               mSequenceNumber);
 
         // This is the global focus target
-        mFocusHorizontalTarget = target.mData.mScrollTargets.mHorizontal;
-        mFocusVerticalTarget = target.mData.mScrollTargets.mVertical;
+        mFocusState.mFocusHorizontalTarget = aScrollTargets.mHorizontal;
+        mFocusState.mFocusVerticalTarget = aScrollTargets.mVertical;
 
         // Mark what sequence number this target has so we can determine whether
         // it is stale or not
-        mLastContentProcessedEvent = target.mSequenceNumber;
+        mFocusState.mLastContentProcessedEvent = mSequenceNumber;
 
         // If this focus state was just created and content has experienced more
         // events then us, then assume we were recreated and sync focus sequence
         // numbers.
-        if (mLastAPZProcessedEvent == 1 &&
-            mLastContentProcessedEvent > mLastAPZProcessedEvent) {
-          mLastAPZProcessedEvent = mLastContentProcessedEvent;
+        if (mFocusState.mLastAPZProcessedEvent == 1 &&
+            mFocusState.mLastContentProcessedEvent > mFocusState.mLastAPZProcessedEvent) {
+          mFocusState.mLastAPZProcessedEvent = mFocusState.mLastContentProcessedEvent;
         }
-        return;
+        return true;
       }
-      case FocusTarget::eNone: {
-        FS_LOG("Setting target to nil (reached a nil target)\n");
+    }; // struct FocusTargetDataMatcher
 
-        // Mark what sequence number this target has for debugging purposes so
-        // we can always accurately report on whether we are stale or not
-        mLastContentProcessedEvent = target.mSequenceNumber;
-        return;
-      }
+    if (target.mData.match(FocusTargetDataMatcher{*this, target.mSequenceNumber})) {
+      return;
     }
   }
 }
 
 std::unordered_set<uint64_t>
 FocusState::GetFocusTargetLayerIds() const
 {
   std::unordered_set<uint64_t> layersIds;
--- a/gfx/layers/apz/src/FocusTarget.cpp
+++ b/gfx/layers/apz/src/FocusTarget.cpp
@@ -95,45 +95,44 @@ static bool
 IsEditableNode(nsINode* aNode)
 {
   return aNode && aNode->IsEditable();
 }
 
 FocusTarget::FocusTarget()
   : mSequenceNumber(0)
   , mFocusHasKeyEventListeners(false)
-  , mType(FocusTarget::eNone)
+  , mData(AsVariant(NoFocusTarget()))
 {
 }
 
 FocusTarget::FocusTarget(nsIPresShell* aRootPresShell,
                          uint64_t aFocusSequenceNumber)
   : mSequenceNumber(aFocusSequenceNumber)
   , mFocusHasKeyEventListeners(false)
+  , mData(AsVariant(NoFocusTarget()))
 {
   MOZ_ASSERT(aRootPresShell);
   MOZ_ASSERT(NS_IsMainThread());
 
   // Key events can be retargeted to a child PresShell when there is an iframe
   nsCOMPtr<nsIPresShell> presShell = GetRetargetEventPresShell(aRootPresShell);
 
   if (!presShell) {
     FT_LOG("Creating nil target with seq=%" PRIu64 " (can't find retargeted presshell)\n",
            aFocusSequenceNumber);
 
-    mType = FocusTarget::eNone;
     return;
   }
 
   nsCOMPtr<nsIDocument> document = presShell->GetDocument();
   if (!document) {
     FT_LOG("Creating nil target with seq=%" PRIu64 " (no document)\n",
            aFocusSequenceNumber);
 
-    mType = FocusTarget::eNone;
     return;
   }
 
   // Find the focused content and use it to determine whether there are key event
   // listeners or whether key events will be targeted at a different process
   // through a remote browser.
   nsCOMPtr<nsIContent> focusedContent = presShell->GetFocusedContentInOurWindow();
   nsCOMPtr<nsIContent> keyEventTarget = focusedContent;
@@ -155,96 +154,81 @@ FocusTarget::FocusTarget(nsIPresShell* a
   // Check if the key event target is content editable or if the document
   // is in design mode.
   if (IsEditableNode(keyEventTarget) ||
       IsEditableNode(document)) {
     FT_LOG("Creating nil target with seq=%" PRIu64 ", kl=%d (disabling for editable node)\n",
            aFocusSequenceNumber,
            static_cast<int>(mFocusHasKeyEventListeners));
 
-    mType = FocusTarget::eNone;
     return;
   }
 
   // Check if the key event target is a remote browser
   if (TabParent* browserParent = TabParent::GetFrom(keyEventTarget)) {
     RenderFrameParent* rfp = browserParent->GetRenderFrame();
 
     // The globally focused element for scrolling is in a remote layer tree
     if (rfp) {
       FT_LOG("Creating reflayer target with seq=%" PRIu64 ", kl=%d, lt=%" PRIu64 "\n",
              aFocusSequenceNumber,
              mFocusHasKeyEventListeners,
              rfp->GetLayersId());
 
-      mType = FocusTarget::eRefLayer;
-      mData.mRefLayerId = rfp->GetLayersId();
+      mData = AsVariant<RefLayerId>(rfp->GetLayersId());
       return;
     }
 
     FT_LOG("Creating nil target with seq=%" PRIu64 ", kl=%d (remote browser missing layers id)\n",
            aFocusSequenceNumber,
            mFocusHasKeyEventListeners);
 
-    mType = FocusTarget::eNone;
     return;
   }
 
   // The content to scroll is either the focused element or the focus node of
   // the selection. It's difficult to determine if an element is an interactive
   // element requiring async keyboard scrolling to be disabled. So we only
   // allow async key scrolling based on the selection, which doesn't have
   // this problem and is more common.
   if (focusedContent) {
     FT_LOG("Creating nil target with seq=%" PRIu64 ", kl=%d (disabling for focusing an element)\n",
            aFocusSequenceNumber,
            mFocusHasKeyEventListeners);
 
-    mType = FocusTarget::eNone;
     return;
   }
 
   nsCOMPtr<nsIContent> selectedContent = presShell->GetSelectedContentForScrolling();
 
   // Gather the scrollable frames that would be scrolled in each direction
   // for this scroll target
   nsIScrollableFrame* horizontal =
     presShell->GetScrollableFrameToScrollForContent(selectedContent.get(),
                                                     nsIPresShell::eHorizontal);
   nsIScrollableFrame* vertical =
     presShell->GetScrollableFrameToScrollForContent(selectedContent.get(),
                                                     nsIPresShell::eVertical);
 
   // We might have the globally focused element for scrolling. Gather a ViewID for
   // the horizontal and vertical scroll targets of this element.
-  mType = FocusTarget::eScrollLayer;
-  mData.mScrollTargets.mHorizontal =
-    nsLayoutUtils::FindIDForScrollableFrame(horizontal);
-  mData.mScrollTargets.mVertical =
-    nsLayoutUtils::FindIDForScrollableFrame(vertical);
+  ScrollTargets target;
+  target.mHorizontal =  nsLayoutUtils::FindIDForScrollableFrame(horizontal);
+  target.mVertical = nsLayoutUtils::FindIDForScrollableFrame(vertical);
+  mData = AsVariant(target);
 
   FT_LOG("Creating scroll target with seq=%" PRIu64 ", kl=%d, h=%" PRIu64 ", v=%" PRIu64 "\n",
          aFocusSequenceNumber,
          mFocusHasKeyEventListeners,
-         mData.mScrollTargets.mHorizontal,
-         mData.mScrollTargets.mVertical);
+         target.mHorizontal,
+         target.mVertical);
 }
 
 bool
 FocusTarget::operator==(const FocusTarget& aRhs) const
 {
-  if (mSequenceNumber != aRhs.mSequenceNumber ||
-      mFocusHasKeyEventListeners != aRhs.mFocusHasKeyEventListeners ||
-      mType != aRhs.mType) {
-    return false;
-  }
-
-  if (mType == FocusTarget::eRefLayer) {
-      return mData.mRefLayerId == aRhs.mData.mRefLayerId;
-  } else if (mType == FocusTarget::eScrollLayer) {
-      return mData.mScrollTargets.mHorizontal == aRhs.mData.mScrollTargets.mHorizontal &&
-             mData.mScrollTargets.mVertical == aRhs.mData.mScrollTargets.mVertical;
-  }
-  return true;
+  return mSequenceNumber == aRhs.mSequenceNumber &&
+         mFocusHasKeyEventListeners == aRhs.mFocusHasKeyEventListeners &&
+         mData == aRhs.mData;
 }
 
 } // namespace layers
 } // namespace mozilla
--- a/gfx/layers/apz/src/FocusTarget.h
+++ b/gfx/layers/apz/src/FocusTarget.h
@@ -5,16 +5,17 @@
 
 #ifndef mozilla_layers_FocusTarget_h
 #define mozilla_layers_FocusTarget_h
 
 #include <stdint.h> // for int32_t, uint32_t
 
 #include "FrameMetrics.h"        // for FrameMetrics::ViewID
 #include "mozilla/DefineEnum.h"  // for MOZ_DEFINE_ENUM
+#include "mozilla/Variant.h"     // for Variant
 
 class nsIPresShell;
 
 namespace mozilla {
 namespace layers {
 
 /**
  * This class is used for communicating information about the currently focused
@@ -24,29 +25,33 @@ namespace layers {
  */
 class FocusTarget final
 {
 public:
   struct ScrollTargets
   {
     FrameMetrics::ViewID mHorizontal;
     FrameMetrics::ViewID mVertical;
+
+    bool operator==(const ScrollTargets& aRhs) const
+    {
+      return mHorizontal == aRhs.mHorizontal &&
+             mVertical == aRhs.mVertical;
+    }
   };
 
-  MOZ_DEFINE_ENUM_AT_CLASS_SCOPE(
-    FocusTargetType, (
-      eNone,
-      eRefLayer,
-      eScrollLayer
-  ));
+  typedef uint64_t RefLayerId;
 
-  union FocusTargetData
-  {
-    uint64_t      mRefLayerId;
-    ScrollTargets mScrollTargets;
+  // We need this to represent the case where mData has no focus target data
+  // because we can't have an empty variant
+  struct NoFocusTarget {
+    bool operator==(const NoFocusTarget& aRhs) const
+    {
+     return true;
+    }
   };
 
   FocusTarget();
 
   /**
    * Construct a focus target for the specified top level PresShell
    */
   FocusTarget(nsIPresShell* aRootPresShell,
@@ -57,16 +62,15 @@ public:
 public:
   // The content sequence number recorded at the time of this class's creation
   uint64_t mSequenceNumber;
 
   // Whether there are keydown, keypress, or keyup event listeners
   // in the event target chain of the focused element
   bool mFocusHasKeyEventListeners;
 
-  FocusTargetType mType;
-  FocusTargetData mData;
+  mozilla::Variant<RefLayerId, ScrollTargets, NoFocusTarget> mData;
 };
 
 } // namespace layers
 } // namespace mozilla
 
 #endif // mozilla_layers_FocusTarget_h
--- a/gfx/layers/ipc/LayersMessageUtils.h
+++ b/gfx/layers/ipc/LayersMessageUtils.h
@@ -435,54 +435,39 @@ struct ParamTraits<mozilla::layers::Focu
   static bool Read(const Message* aMsg, PickleIterator* aIter, paramType* aResult)
   {
     return ReadParam(aMsg, aIter, &aResult->mHorizontal) &&
            ReadParam(aMsg, aIter, &aResult->mVertical);
   }
 };
 
 template <>
-struct ParamTraits<mozilla::layers::FocusTarget::FocusTargetType>
-  : public ContiguousEnumSerializerInclusive<
-             mozilla::layers::FocusTarget::FocusTargetType,
-             mozilla::layers::FocusTarget::eNone,
-             mozilla::layers::FocusTarget::sHighestFocusTargetType>
+struct ParamTraits<mozilla::layers::FocusTarget::NoFocusTarget>
+  : public EmptyStructSerializer<mozilla::layers::FocusTarget::NoFocusTarget>
 {};
 
 template <>
 struct ParamTraits<mozilla::layers::FocusTarget>
 {
   typedef mozilla::layers::FocusTarget paramType;
 
   static void Write(Message* aMsg, const paramType& aParam)
   {
     WriteParam(aMsg, aParam.mSequenceNumber);
     WriteParam(aMsg, aParam.mFocusHasKeyEventListeners);
-    WriteParam(aMsg, aParam.mType);
-    if (aParam.mType == mozilla::layers::FocusTarget::eRefLayer) {
-      WriteParam(aMsg, aParam.mData.mRefLayerId);
-    } else if (aParam.mType == mozilla::layers::FocusTarget::eScrollLayer) {
-      WriteParam(aMsg, aParam.mData.mScrollTargets);
-    }
+    WriteParam(aMsg, aParam.mData);
   }
 
   static bool Read(const Message* aMsg, PickleIterator* aIter, paramType* aResult)
   {
     if (!ReadParam(aMsg, aIter, &aResult->mSequenceNumber) ||
         !ReadParam(aMsg, aIter, &aResult->mFocusHasKeyEventListeners) ||
-        !ReadParam(aMsg, aIter, &aResult->mType)) {
+        !ReadParam(aMsg, aIter, &aResult->mData)) {
       return false;
     }
-
-    if (aResult->mType == mozilla::layers::FocusTarget::eRefLayer) {
-      return ReadParam(aMsg, aIter, &aResult->mData.mRefLayerId);
-    } else if (aResult->mType == mozilla::layers::FocusTarget::eScrollLayer) {
-      return ReadParam(aMsg, aIter, &aResult->mData.mScrollTargets);
-    }
-
     return true;
   }
 };
 
 template <>
 struct ParamTraits<mozilla::layers::KeyboardScrollAction::KeyboardScrollActionType>
   : public ContiguousEnumSerializerInclusive<
              mozilla::layers::KeyboardScrollAction::KeyboardScrollActionType,
--- a/gfx/vr/gfxVROpenVR.cpp
+++ b/gfx/vr/gfxVROpenVR.cpp
@@ -52,24 +52,25 @@ VRDisplayOpenVR::VRDisplayOpenVR(::vr::I
   , mVRSystem(aVRSystem)
   , mVRChaperone(aVRChaperone)
   , mVRCompositor(aVRCompositor)
   , mIsPresenting(false)
 {
   MOZ_COUNT_CTOR_INHERITED(VRDisplayOpenVR, VRDisplayHost);
 
   mDisplayInfo.mDisplayName.AssignLiteral("OpenVR HMD");
-  mDisplayInfo.mIsConnected = true;
+  mDisplayInfo.mIsConnected = mVRSystem->IsTrackedDeviceConnected(::vr::k_unTrackedDeviceIndex_Hmd);
   mDisplayInfo.mIsMounted = false;
   mDisplayInfo.mCapabilityFlags = VRDisplayCapabilityFlags::Cap_None |
                                   VRDisplayCapabilityFlags::Cap_Orientation |
                                   VRDisplayCapabilityFlags::Cap_Position |
                                   VRDisplayCapabilityFlags::Cap_External |
                                   VRDisplayCapabilityFlags::Cap_Present |
                                   VRDisplayCapabilityFlags::Cap_StageParameters;
+  mIsHmdPresent = ::vr::VR_IsHmdPresent();
 
   ::vr::ETrackedPropertyError err;
   bool bHasProximitySensor = mVRSystem->GetBoolTrackedDeviceProperty(::vr::k_unTrackedDeviceIndex_Hmd, ::vr::Prop_ContainsProximitySensor_Bool, &err);
   if (err == ::vr::TrackedProp_Success && bHasProximitySensor) {
     mDisplayInfo.mCapabilityFlags |= VRDisplayCapabilityFlags::Cap_MountDetection;
   }
 
   mVRCompositor->SetTrackingSpace(::vr::TrackingUniverseSeated);
@@ -169,34 +170,54 @@ VRDisplayOpenVR::UpdateStageParameters()
 
 void
 VRDisplayOpenVR::ZeroSensor()
 {
   mVRSystem->ResetSeatedZeroPose();
   UpdateStageParameters();
 }
 
+bool
+VRDisplayOpenVR::GetIsHmdPresent()
+{
+  return mIsHmdPresent;
+}
+
 void
 VRDisplayOpenVR::PollEvents()
 {
   ::vr::VREvent_t event;
-  while (mVRSystem->PollNextEvent(&event, sizeof(event))) {
+  while (mVRSystem && mVRSystem->PollNextEvent(&event, sizeof(event))) {
     switch (event.eventType) {
       case ::vr::VREvent_TrackedDeviceUserInteractionStarted:
-        mDisplayInfo.mIsMounted = true;
+        if (event.trackedDeviceIndex == ::vr::k_unTrackedDeviceIndex_Hmd) {
+          mDisplayInfo.mIsMounted = true;
+        }
         break;
       case ::vr::VREvent_TrackedDeviceUserInteractionEnded:
-        mDisplayInfo.mIsMounted = false;
+        if (event.trackedDeviceIndex == ::vr::k_unTrackedDeviceIndex_Hmd) {
+          mDisplayInfo.mIsMounted = false;
+        }
+        break;
+      case ::vr::EVREventType::VREvent_TrackedDeviceActivated:
+        if (event.trackedDeviceIndex == ::vr::k_unTrackedDeviceIndex_Hmd) {
+          mDisplayInfo.mIsConnected = true;
+        }
+        break;
+      case ::vr::EVREventType::VREvent_TrackedDeviceDeactivated:
+        if (event.trackedDeviceIndex == ::vr::k_unTrackedDeviceIndex_Hmd) {
+          mDisplayInfo.mIsConnected = false;
+        }
         break;
       case ::vr::EVREventType::VREvent_DriverRequestedQuit:
       case ::vr::EVREventType::VREvent_Quit:
       case ::vr::EVREventType::VREvent_ProcessQuit:
       case ::vr::EVREventType::VREvent_QuitAcknowledged:
       case ::vr::EVREventType::VREvent_QuitAborted_UserPrompt:
-        mDisplayInfo.mIsConnected = false;
+        mIsHmdPresent = false;
         break;
       default:
         // ignore
         break;
     }
   }
 }
 
@@ -373,19 +394,18 @@ VRDisplayOpenVR::SubmitFrame(MacIOSurfac
   return result;
 }
 
 #endif
 
 void
 VRDisplayOpenVR::NotifyVSync()
 {
-  // We update mIsConneced once per frame.
-  mDisplayInfo.mIsConnected = ::vr::VR_IsHmdPresent();
-
+  // We check if HMD is available once per frame.
+  mIsHmdPresent = ::vr::VR_IsHmdPresent();
   // Make sure we respond to OpenVR events even when not presenting
   PollEvents();
 
   VRDisplayHost::NotifyVSync();
 }
 
 VRControllerOpenVR::VRControllerOpenVR(dom::GamepadHand aHand, uint32_t aDisplayID,
                                        uint32_t aNumButtons, uint32_t aNumTriggers,
@@ -586,17 +606,17 @@ VRSystemManagerOpenVR::Shutdown()
   RemoveControllers();
   mVRSystem = nullptr;
 }
 
 bool
 VRSystemManagerOpenVR::GetHMDs(nsTArray<RefPtr<VRDisplayHost>>& aHMDResult)
 {
   if (!::vr::VR_IsHmdPresent() ||
-      (mOpenVRHMD && !mOpenVRHMD->GetIsConnected())) {
+      (mOpenVRHMD && !mOpenVRHMD->GetIsHmdPresent())) {
     // OpenVR runtime could be quit accidentally,
     // and we make it re-initialize.
     mOpenVRHMD = nullptr;
     mVRSystem = nullptr;
   } else if (mOpenVRHMD == nullptr) {
     ::vr::HmdError err;
 
     ::vr::VR_Init(&err, ::vr::EVRApplicationType::VRApplication_Scene);
--- a/gfx/vr/gfxVROpenVR.h
+++ b/gfx/vr/gfxVROpenVR.h
@@ -26,16 +26,17 @@ namespace mozilla {
 namespace gfx {
 namespace impl {
 
 class VRDisplayOpenVR : public VRDisplayHost
 {
 public:
   virtual void NotifyVSync() override;
   void ZeroSensor() override;
+  bool GetIsHmdPresent();
 
 protected:
   virtual VRHMDSensorState GetSensorState() override;
   virtual void StartPresentation() override;
   virtual void StopPresentation() override;
 #if defined(XP_WIN)
   virtual bool SubmitFrame(mozilla::layers::TextureSourceD3D11* aSource,
                            const IntSize& aSize,
@@ -59,16 +60,17 @@ protected:
 
   // not owned by us; global from OpenVR
   ::vr::IVRSystem *mVRSystem;
   ::vr::IVRChaperone *mVRChaperone;
   ::vr::IVRCompositor *mVRCompositor;
 
   VRTelemetry mTelemetry;
   bool mIsPresenting;
+  bool mIsHmdPresent;
 
   void UpdateStageParameters();
   void PollEvents();
   bool SubmitFrame(void* aTextureHandle,
                    ::vr::ETextureType aTextureType,
                    const IntSize& aSize,
                    const gfx::Rect& aLeftEyeRect,
                    const gfx::Rect& aRightEyeRect);
--- a/gfx/webrender/src/frame.rs
+++ b/gfx/webrender/src/frame.rs
@@ -432,21 +432,16 @@ impl Frame {
             let display_list = context.scene.display_lists
                                       .get(&pipeline_id)
                                       .expect("No display list?!");
             CompositeOps::new(
                 stacking_context.filter_ops_for_compositing(display_list, filters, &context.scene.properties),
                 stacking_context.mix_blend_mode_for_compositing())
         };
 
-        if composition_operations.will_make_invisible() {
-            traversal.skip_current_stacking_context();
-            return;
-        }
-
         if stacking_context.scroll_policy == ScrollPolicy::Fixed {
             context.replacements.push((context_scroll_node_id,
                                        context.builder.current_reference_frame_id()));
         }
 
         // If we have a transformation, we establish a new reference frame. This means
         // that fixed position stacking contexts are positioned relative to us.
         let is_reference_frame = stacking_context.transform.is_some() ||
--- a/intl/l10n/L10nRegistry.jsm
+++ b/intl/l10n/L10nRegistry.jsm
@@ -31,17 +31,17 @@ Components.utils.importGlobalProperties(
  *     ]
  *
  * If the user will request:
  *   L10nRegistry.generateContexts(['de', 'en-US'], [
  *     '/browser/menu.ftl',
  *     '/platform/toolkit.ftl'
  *   ]);
  *
- * the generator will return an iterator over the following contexts:
+ * the generator will return an async iterator over the following contexts:
  *
  *   {
  *     locale: 'de',
  *     resources: [
  *       ['app', '/browser/menu.ftl'],
  *       ['app', '/platform/toolkit.ftl'],
  *     ]
  *   },
@@ -80,19 +80,19 @@ const L10nRegistry = {
   ctxCache: new Map(),
 
   /**
    * Based on the list of requested languages and resource Ids,
    * this function returns an lazy iterator over message context permutations.
    *
    * @param {Array} requestedLangs
    * @param {Array} resourceIds
-   * @returns {Iterator<MessageContext>}
+   * @returns {AsyncIterator<MessageContext>}
    */
-  * generateContexts(requestedLangs, resourceIds) {
+  async * generateContexts(requestedLangs, resourceIds) {
     const sourcesOrder = Array.from(this.sources.keys()).reverse();
     for (const locale of requestedLangs) {
       yield * generateContextsForLocale(locale, sourcesOrder, resourceIds);
     }
   },
 
   /**
    * Adds a new resource source to the L10nRegistry.
@@ -174,19 +174,19 @@ function generateContextID(locale, sourc
  * This function is called recursively to generate all possible permutations
  * and uses the last, optional parameter, to pass the already resolved
  * sources order.
  *
  * @param {String} locale
  * @param {Array} sourcesOrder
  * @param {Array} resourceIds
  * @param {Array} [resolvedOrder]
- * @returns {Iterator<MessageContext>}
+ * @returns {AsyncIterator<MessageContext>}
  */
-function* generateContextsForLocale(locale, sourcesOrder, resourceIds, resolvedOrder = []) {
+async function* generateContextsForLocale(locale, sourcesOrder, resourceIds, resolvedOrder = []) {
   const resolvedLength = resolvedOrder.length;
   const resourcesLength = resourceIds.length;
 
   // Inside that loop we have a list of resources and the sources for them, like this:
   //   ['test.ftl', 'menu.ftl', 'foo.ftl']
   //   ['app', 'platform', 'app']
   for (const sourceName of sourcesOrder) {
     const order = resolvedOrder.concat(sourceName);
@@ -197,150 +197,202 @@ function* generateContextsForLocale(loca
     // have to perform the I/O to learn.
     if (L10nRegistry.sources.get(sourceName).hasFile(locale, resourceIds[resolvedOrder.length]) === false) {
       continue;
     }
 
     // If the number of resolved sources equals the number of resources,
     // create the right context and return it if it loads.
     if (resolvedLength + 1 === resourcesLength) {
-      yield generateContext(locale, order, resourceIds);
+      const ctx = await generateContext(locale, order, resourceIds);
+      if (ctx !== null) {
+        yield ctx;
+      }
     } else {
       // otherwise recursively load another generator that walks over the
       // partially resolved list of sources.
       yield * generateContextsForLocale(locale, sourcesOrder, resourceIds, order);
     }
   }
 }
 
 /**
  * Generates a single MessageContext by loading all resources
  * from the listed sources for a given locale.
  *
+ * The function casts all error cases into a Promise that resolves with
+ * value `null`.
+ * This allows the caller to be an async generator without using
+ * try/catch clauses.
+ *
  * @param {String} locale
  * @param {Array} sourcesOrder
  * @param {Array} resourceIds
  * @returns {Promise<MessageContext>}
  */
-async function generateContext(locale, sourcesOrder, resourceIds) {
+function generateContext(locale, sourcesOrder, resourceIds) {
   const ctxId = generateContextID(locale, sourcesOrder, resourceIds);
-  if (!L10nRegistry.ctxCache.has(ctxId)) {
-    const ctx = new MessageContext(locale);
-    for (let i = 0; i < resourceIds.length; i++) {
-      const data = await L10nRegistry.sources.get(sourcesOrder[i]).fetchFile(locale, resourceIds[i]);
-      if (data === null) {
-        return false;
+  if (L10nRegistry.ctxCache.has(ctxId)) {
+    return L10nRegistry.ctxCache.get(ctxId);
+  }
+
+  const fetchPromises = resourceIds.map((resourceId, i) => {
+    return L10nRegistry.sources.get(sourcesOrder[i]).fetchFile(locale, resourceId);
+  });
+
+  const ctxPromise = Promise.all(fetchPromises).then(
+    dataSets => {
+      const ctx = new MessageContext(locale);
+      for (const data of dataSets) {
+        if (data === null) {
+          return null;
+        }
+        ctx.addMessages(data);
       }
-      ctx.addMessages(data);
-    }
-    L10nRegistry.ctxCache.set(ctxId, ctx);
-  }
-  return L10nRegistry.ctxCache.get(ctxId);
+      return ctx;
+    },
+    () => null
+  );
+  L10nRegistry.ctxCache.set(ctxId, ctxPromise);
+  return ctxPromise;
 }
 
 /**
  * This is a basic Source for L10nRegistry.
  * It registers its own locales and a pre-path, and when asked for a file
  * it attempts to download and cache it.
  *
  * The Source caches the downloaded files so any consecutive loads will
  * come from the cache.
  **/
 class FileSource {
+  /**
+   * @param {string}         name
+   * @param {Array<string>}  locales
+   * @param {string}         prePath
+   *
+   * @returns {IndexedFileSource}
+   */
   constructor(name, locales, prePath) {
     this.name = name;
     this.locales = locales;
     this.prePath = prePath;
+    this.indexed = false;
+
+    // The cache object stores information about the resources available
+    // in the Source.
+    //
+    // It can take one of three states:
+    //   * true - the resource is available but not fetched yet
+    //   * false - the resource is not available
+    //   * Promise - the resource has been fetched
+    //
+    // If the cache has no entry for a given path, that means that there
+    // is no information available about whether the resource is available.
+    //
+    // If the `indexed` property is set to `true` it will be treated as the
+    // resource not being available. Otherwise, the resource may be
+    // available and we do not have any information about it yet.
     this.cache = {};
   }
 
   getPath(locale, path) {
     return (this.prePath + path).replace(/\{locale\}/g, locale);
   }
 
   hasFile(locale, path) {
     if (!this.locales.includes(locale)) {
       return false;
     }
 
     const fullPath = this.getPath(locale, path);
     if (!this.cache.hasOwnProperty(fullPath)) {
-      return undefined;
+      return this.indexed ? false : undefined;
     }
-
-    if (this.cache[fullPath] === null) {
+    if (this.cache[fullPath] === false) {
       return false;
     }
+    if (this.cache[fullPath].then) {
+      return undefined;
+    }
     return true;
   }
 
-  async fetchFile(locale, path) {
+  fetchFile(locale, path) {
     if (!this.locales.includes(locale)) {
-      return null;
+      return Promise.reject(`The source has no resources for locale "${locale}"`);
     }
 
     const fullPath = this.getPath(locale, path);
-    if (this.hasFile(locale, path) === undefined) {
-      let file = await L10nRegistry.load(fullPath);
 
-      if (file === undefined) {
-        this.cache[fullPath] = null;
-      } else {
-        this.cache[fullPath] = file;
+    if (this.cache.hasOwnProperty(fullPath)) {
+      if (this.cache[fullPath] === false) {
+        return Promise.reject(`The source has no resources for path "${fullPath}"`);
+      }
+      if (this.cache[fullPath].then) {
+        return this.cache[fullPath];
+      }
+    } else {
+      if (this.indexed) {
+        return Promise.reject(`The source has no resources for path "${fullPath}"`);
       }
     }
-    return this.cache[fullPath];
+    return this.cache[fullPath] = L10nRegistry.load(fullPath).then(
+      data => {
+        return this.cache[fullPath] = data;
+      },
+      err => {
+        this.cache[fullPath] = false;
+        return Promise.reject(err);
+      }
+    );
   }
 }
 
 /**
  * This is an extension of the FileSource which should be used
  * for sources that can provide the list of files available in the source.
  *
  * This allows for a faster lookup in cases where the source does not
  * contain most of the files that the app will request for (e.g. an addon).
  **/
 class IndexedFileSource extends FileSource {
+  /**
+   * @param {string}         name
+   * @param {Array<string>}  locales
+   * @param {string}         prePath
+   * @param {Array<string>}  paths
+   *
+   * @returns {IndexedFileSource}
+   */
   constructor(name, locales, prePath, paths) {
     super(name, locales, prePath);
-    this.paths = paths;
-  }
-
-  hasFile(locale, path) {
-    if (!this.locales.includes(locale)) {
-      return false;
-    }
-    const fullPath = this.getPath(locale, path);
-    return this.paths.includes(fullPath);
-  }
-
-  async fetchFile(locale, path) {
-    if (!this.locales.includes(locale)) {
-      return null;
-    }
-
-    const fullPath = this.getPath(locale, path);
-    if (this.paths.includes(fullPath)) {
-      let file = await L10nRegistry.load(fullPath);
-
-      if (file === undefined) {
-        return null;
-      } else {
-        return file;
-      }
-    } else {
-      return null;
+    this.indexed = true;
+    for (const path of paths) {
+      this.cache[path] = true;
     }
   }
 }
 
 /**
+ * The low level wrapper around Fetch API. It unifies the error scenarios to
+ * always produce a promise rejection.
+ *
  * We keep it as a method to make it easier to override for testing purposes.
- **/
+ *
+ * @param {string} url
+ *
+ * @returns {Promise<string>}
+ */
 L10nRegistry.load = function(url) {
-  return fetch(url).then(data => data.text()).catch(() => undefined);
+  return fetch(url).then(response => {
+    if (!response.ok) {
+      return Promise.reject(response.statusText);
+    }
+    return response.text()
+  });
 };
 
 this.L10nRegistry = L10nRegistry;
 this.FileSource = FileSource;
 this.IndexedFileSource = IndexedFileSource;
 
 this.EXPORTED_SYMBOLS = ['L10nRegistry', 'FileSource', 'IndexedFileSource'];
--- a/intl/l10n/Localization.jsm
+++ b/intl/l10n/Localization.jsm
@@ -31,32 +31,32 @@ const ObserverService = Cc["@mozilla.org
 /**
  * CachedIterable caches the elements yielded by an iterable.
  *
  * It can be used to iterate over an iterable many times without depleting the
  * iterable.
  */
 class CachedIterable {
   constructor(iterable) {
-    if (!(Symbol.iterator in Object(iterable))) {
-      throw new TypeError('Argument must implement the iteration protocol.');
+    if (!(Symbol.asyncIterator in Object(iterable))) {
+      throw new TypeError('Argument must implement the async iteration protocol.');
     }
 
-    this.iterator = iterable[Symbol.iterator]();
+    this.iterator = iterable[Symbol.asyncIterator]();
     this.seen = [];
   }
 
-  [Symbol.iterator]() {
+  [Symbol.asyncIterator]() {
     const { seen, iterator } = this;
     let cur = 0;
 
     return {
-      next() {
+      async next() {
         if (seen.length <= cur) {
-          seen.push(iterator.next());
+          seen.push(await iterator.next());
         }
         return seen[cur++];
       }
     };
   }
 }
 
 /**
@@ -126,17 +126,17 @@ class Localization {
    *
    * @param   {Array<Array>}          keys    - Translation keys to format.
    * @param   {Function}              method  - Formatting function.
    * @returns {Promise<Array<string|Object>>}
    * @private
    */
   async formatWithFallback(keys, method) {
     const translations = [];
-    for (let ctx of this.ctxs) {
+    for await (let ctx of this.ctxs) {
       // This can operate on synchronous and asynchronous
       // contexts coming from the iterator.
       if (typeof ctx.then === 'function') {
         ctx = await ctx;
       }
       const errors = keysFromContext(method, ctx, keys, translations);
       if (!errors) {
         break;
--- a/intl/l10n/test/dom/test_domloc.xul
+++ b/intl/l10n/test/dom/test_domloc.xul
@@ -11,17 +11,17 @@
           src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" />
   <script type="application/javascript">
   <![CDATA[
   const { DOMLocalization } =
     Components.utils.import("resource://gre/modules/DOMLocalization.jsm", {});
   const { MessageContext } =
     Components.utils.import("resource://gre/modules/MessageContext.jsm", {});
 
-  function * generateMessages(locales, resourceIds) {
+  async function * generateMessages(locales, resourceIds) {
     const mc = new MessageContext(locales);
     mc.addMessages(`
 file-menu
     .label = File
     .accesskey = F
 new-tab
     .label = New Tab
     .accesskey = N
--- a/intl/l10n/test/dom/test_domloc_connectRoot.html
+++ b/intl/l10n/test/dom/test_domloc_connectRoot.html
@@ -5,17 +5,17 @@
   <title>Test DOMLocalization.prototype.connectRoot</title>
   <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
   <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
   <script type="application/javascript">
   "use strict";
   const { DOMLocalization } =
     Components.utils.import("resource://gre/modules/DOMLocalization.jsm", {});
 
-  function * mockGenerateMessages(locales, resourceIds) {
+  async function * mockGenerateMessages(locales, resourceIds) {
   }
 
   window.onload = async function () {
     SimpleTest.waitForExplicitFinish();
 
     const domLoc = new DOMLocalization(
       window,
       [],
--- a/intl/l10n/test/dom/test_domloc_disconnectRoot.html
+++ b/intl/l10n/test/dom/test_domloc_disconnectRoot.html
@@ -5,17 +5,17 @@
   <title>Test DOMLocalization.prototype.disconnectRoot</title>
   <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
   <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
   <script type="application/javascript">
   "use strict";
   const { DOMLocalization } =
     Components.utils.import("resource://gre/modules/DOMLocalization.jsm", {});
 
-  function * mockGenerateMessages(locales, resourceIds) {
+  async function * mockGenerateMessages(locales, resourceIds) {
   }
 
   window.onload = async function () {
     SimpleTest.waitForExplicitFinish();
 
     const domLoc = new DOMLocalization(
       window,
       [],
--- a/intl/l10n/test/dom/test_domloc_getAttributes.html
+++ b/intl/l10n/test/dom/test_domloc_getAttributes.html
@@ -5,17 +5,17 @@
   <title>Test DOMLocalization.prototype.getAttributes</title>
   <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
   <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
   <script type="application/javascript">
   "use strict";
   const { DOMLocalization } =
     Components.utils.import("resource://gre/modules/DOMLocalization.jsm", {});
 
-  function * mockGenerateMessages(locales, resourceIds) {}
+  async function * mockGenerateMessages(locales, resourceIds) {}
 
   window.onload = function () {
     SimpleTest.waitForExplicitFinish();
 
     const domLoc = new DOMLocalization(
       window,
       [],
       mockGenerateMessages
--- a/intl/l10n/test/dom/test_domloc_mutations.html
+++ b/intl/l10n/test/dom/test_domloc_mutations.html
@@ -7,17 +7,17 @@
   <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
   <script type="application/javascript">
   "use strict";
   const { DOMLocalization } =
     Components.utils.import("resource://gre/modules/DOMLocalization.jsm", {});
   const { MessageContext } =
     Components.utils.import("resource://gre/modules/MessageContext.jsm", {});
 
-  function * mockGenerateMessages(locales, resourceIds) {
+  async function * mockGenerateMessages(locales, resourceIds) {
     const mc = new MessageContext(locales);
     mc.addMessages('title = Hello World');
     mc.addMessages('title2 = Hello Another World');
     yield mc;
   }
 
   window.onload = async function () {
     SimpleTest.waitForExplicitFinish();
--- a/intl/l10n/test/dom/test_domloc_overlay.html
+++ b/intl/l10n/test/dom/test_domloc_overlay.html
@@ -7,17 +7,17 @@
   <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
   <script type="application/javascript">
   "use strict";
   const { DOMLocalization } =
     Components.utils.import("resource://gre/modules/DOMLocalization.jsm", {});
   const { MessageContext } =
     Components.utils.import("resource://gre/modules/MessageContext.jsm", {});
 
-  function * mockGenerateMessages(locales, resourceIds) {
+  async function * mockGenerateMessages(locales, resourceIds) {
     const mc = new MessageContext(locales);
     mc.addMessages('title = <strong>Hello</strong> World');
     mc.addMessages('title2 = This is <a>a link</a>!');
     yield mc;
   }
 
   window.onload = async function () {
     SimpleTest.waitForExplicitFinish();
--- a/intl/l10n/test/dom/test_domloc_overlay_missing_children.html
+++ b/intl/l10n/test/dom/test_domloc_overlay_missing_children.html
@@ -7,17 +7,17 @@
   <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
   <script type="application/javascript">
   "use strict";
   const { DOMLocalization } =
     Components.utils.import("resource://gre/modules/DOMLocalization.jsm", {});
   const { MessageContext } =
     Components.utils.import("resource://gre/modules/MessageContext.jsm", {});
 
-  function * mockGenerateMessages(locales, resourceIds) {
+  async function * mockGenerateMessages(locales, resourceIds) {
     const mc = new MessageContext(locales);
     mc.addMessages('title = Visit <a>Mozilla</a> or <a>Firefox</a> website!');
     yield mc;
   }
 
   window.onload = async function () {
     SimpleTest.waitForExplicitFinish();
 
--- a/intl/l10n/test/dom/test_domloc_overlay_repeated.html
+++ b/intl/l10n/test/dom/test_domloc_overlay_repeated.html
@@ -7,17 +7,17 @@
   <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
   <script type="application/javascript">
   "use strict";
   const { DOMLocalization } =
     Components.utils.import("resource://gre/modules/DOMLocalization.jsm", {});
   const { MessageContext } =
     Components.utils.import("resource://gre/modules/MessageContext.jsm", {});
 
-  function * mockGenerateMessages(locales, resourceIds) {
+  async function * mockGenerateMessages(locales, resourceIds) {
     const mc = new MessageContext(locales);
     mc.addMessages('title = Visit <a>Mozilla</a> or <a>Firefox</a> website!');
     yield mc;
   }
 
   window.onload = async function () {
     SimpleTest.waitForExplicitFinish();
 
--- a/intl/l10n/test/dom/test_domloc_setAttributes.html
+++ b/intl/l10n/test/dom/test_domloc_setAttributes.html
@@ -5,17 +5,17 @@
   <title>Test DOMLocalization.prototype.setAttributes</title>
   <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
   <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
   <script type="application/javascript">
   "use strict";
   const { DOMLocalization } =
     Components.utils.import("resource://gre/modules/DOMLocalization.jsm", {});
 
-  function * mockGenerateMessages(locales, resourceIds) {}
+  async function * mockGenerateMessages(locales, resourceIds) {}
 
   window.onload = function () {
     SimpleTest.waitForExplicitFinish();
 
     const domLoc = new DOMLocalization(
       window,
       [],
       mockGenerateMessages
--- a/intl/l10n/test/dom/test_domloc_translateElement.html
+++ b/intl/l10n/test/dom/test_domloc_translateElement.html
@@ -7,17 +7,17 @@
   <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
   <script type="application/javascript">
   "use strict";
   const { DOMLocalization } =
     Components.utils.import("resource://gre/modules/DOMLocalization.jsm", {});
   const { MessageContext } =
     Components.utils.import("resource://gre/modules/MessageContext.jsm", {});
 
-  function * mockGenerateMessages(locales, resourceIds) {
+  async function * mockGenerateMessages(locales, resourceIds) {
     const mc = new MessageContext(locales);
     mc.addMessages('title = Hello World');
     mc.addMessages('link\n    .title = Click me');
     yield mc;
   }
 
   window.onload = async function () {
     SimpleTest.waitForExplicitFinish();
--- a/intl/l10n/test/dom/test_domloc_translateFragment.html
+++ b/intl/l10n/test/dom/test_domloc_translateFragment.html
@@ -7,17 +7,17 @@
   <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
   <script type="application/javascript">
   "use strict";
   const { DOMLocalization } =
     Components.utils.import("resource://gre/modules/DOMLocalization.jsm", {});
   const { MessageContext } =
     Components.utils.import("resource://gre/modules/MessageContext.jsm", {});
 
-  function * mockGenerateMessages(locales, resourceIds) {
+  async function * mockGenerateMessages(locales, resourceIds) {
     const mc = new MessageContext(locales);
     mc.addMessages('title = Hello World');
     mc.addMessages('subtitle = Welcome to Fluent');
     yield mc;
   }
 
   window.onload = async function () {
     SimpleTest.waitForExplicitFinish();
--- a/intl/l10n/test/dom/test_domloc_translateRoots.html
+++ b/intl/l10n/test/dom/test_domloc_translateRoots.html
@@ -7,17 +7,17 @@
   <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
   <script type="application/javascript">
   "use strict";
   const { DOMLocalization } =
     Components.utils.import("resource://gre/modules/DOMLocalization.jsm", {});
   const { MessageContext } =
     Components.utils.import("resource://gre/modules/MessageContext.jsm", {});
 
-  function * mockGenerateMessages(locales, resourceIds) {
+  async function * mockGenerateMessages(locales, resourceIds) {
     const mc = new MessageContext(locales);
     mc.addMessages('title = Hello World');
     mc.addMessages('title2 = Hello Another World');
     yield mc;
   }
 
   window.onload = async function () {
     SimpleTest.waitForExplicitFinish();
--- a/intl/l10n/test/test_l10nregistry.js
+++ b/intl/l10n/test/test_l10nregistry.js
@@ -1,19 +1,23 @@
 /* Any copyrighequal dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 const {
   L10nRegistry,
   FileSource,
   IndexedFileSource
 } = Components.utils.import("resource://gre/modules/L10nRegistry.jsm", {});
+Components.utils.import("resource://gre/modules/Timer.jsm");
 
 let fs;
 L10nRegistry.load = async function(url) {
+  if (!fs.hasOwnProperty(url)) {
+    return Promise.reject('Resource unavailable');
+  }
   return fs[url];
 }
 
 add_task(function test_methods_presence() {
   equal(typeof L10nRegistry.generateContexts, "function");
   equal(typeof L10nRegistry.getAvailableLocales, "function");
   equal(typeof L10nRegistry.registerSource, "function");
   equal(typeof L10nRegistry.updateSource, "function");
@@ -22,24 +26,23 @@ add_task(function test_methods_presence(
 /**
  * This test tests generation of a proper context for a single
  * source scenario
  */
 add_task(async function test_methods_calling() {
   fs = {
     '/localization/en-US/browser/menu.ftl': 'key = Value',
   };
-  const originalLoad = L10nRegistry.load;
 
   const source = new FileSource('test', ['en-US'], '/localization/{locale}');
   L10nRegistry.registerSource(source);
 
   const ctxs = L10nRegistry.generateContexts(['en-US'], ['/browser/menu.ftl']);
 
-  const ctx = await ctxs.next().value;
+  const ctx = (await ctxs.next()).value;
 
   equal(ctx.hasMessage('key'), true);
 
   // cleanup
   L10nRegistry.sources.clear();
   L10nRegistry.ctxCache.clear();
 });
 
@@ -59,27 +62,27 @@ add_task(async function test_has_one_sou
 
   equal(L10nRegistry.sources.size, 1);
   equal(L10nRegistry.sources.has('app'), true);
 
 
   // returns a single context
 
   let ctxs = L10nRegistry.generateContexts(['en-US'], ['test.ftl']);
-  let ctx0 = await ctxs.next().value;
+  let ctx0 = (await ctxs.next()).value;
   equal(ctx0.hasMessage('key'), true);
 
-  equal(ctxs.next().done, true);
+  equal((await ctxs.next()).done, true);
 
 
   // returns no contexts for missing locale
 
   ctxs = L10nRegistry.generateContexts(['pl'], ['test.ftl']);
 
-  equal(ctxs.next().done, true);
+  equal((await ctxs.next()).done, true);
 
   // cleanup
   L10nRegistry.sources.clear();
   L10nRegistry.ctxCache.clear();
 });
 
 /**
  * This test verifies that public methods return expected values
@@ -102,41 +105,41 @@ add_task(async function test_has_two_sou
   equal(L10nRegistry.sources.size, 2);
   equal(L10nRegistry.sources.has('app'), true);
   equal(L10nRegistry.sources.has('platform'), true);
 
 
   // returns correct contexts for en-US
 
   let ctxs = L10nRegistry.generateContexts(['en-US'], ['test.ftl']);
-  let ctx0 = await ctxs.next().value;
+  let ctx0 = (await ctxs.next()).value;
 
   equal(ctx0.hasMessage('key'), true);
   let msg = ctx0.getMessage('key');
   equal(ctx0.format(msg), 'platform value');
 
-  equal(ctxs.next().done, true);
+  equal((await ctxs.next()).done, true);
 
 
   // returns correct contexts for [pl, en-US]
 
   ctxs = L10nRegistry.generateContexts(['pl', 'en-US'], ['test.ftl']);
-  ctx0 = await ctxs.next().value;
+  ctx0 = (await ctxs.next()).value;
   equal(ctx0.locales[0], 'pl');
   equal(ctx0.hasMessage('key'), true);
   let msg0 = ctx0.getMessage('key');
   equal(ctx0.format(msg0), 'app value');
 
-  let ctx1 = await ctxs.next().value;
+  let ctx1 = (await ctxs.next()).value;
   equal(ctx1.locales[0], 'en-US');
   equal(ctx1.hasMessage('key'), true);
   let msg1 = ctx1.getMessage('key');
   equal(ctx1.format(msg1), 'platform value');
 
-  equal(ctxs.next().done, true);
+  equal((await ctxs.next()).done, true);
 
   // cleanup
   L10nRegistry.sources.clear();
   L10nRegistry.ctxCache.clear();
 });
 
 /**
  * This test verifies that behavior specific to the IndexedFileSource
@@ -183,29 +186,29 @@ add_task(async function test_override() 
     '/app/data/locales/pl/test.ftl': 'key = value',
     '/data/locales/pl/test.ftl': 'key = addon value'
   };
 
   equal(L10nRegistry.sources.size, 2);
   equal(L10nRegistry.sources.has('langpack-pl'), true);
 
   let ctxs = L10nRegistry.generateContexts(['pl'], ['test.ftl']);
-  let ctx0 = await ctxs.next().value;
+  let ctx0 = (await ctxs.next()).value;
   equal(ctx0.locales[0], 'pl');
   equal(ctx0.hasMessage('key'), true);
   let msg0 = ctx0.getMessage('key');
   equal(ctx0.format(msg0), 'addon value');
 
-  let ctx1 = await ctxs.next().value;
+  let ctx1 = (await ctxs.next()).value;
   equal(ctx1.locales[0], 'pl');
   equal(ctx1.hasMessage('key'), true);
   let msg1 = ctx1.getMessage('key');
   equal(ctx1.format(msg1), 'value');
 
-  equal(ctxs.next().done, true);
+  equal((await ctxs.next()).done, true);
 
   // cleanup
   L10nRegistry.sources.clear();
   L10nRegistry.ctxCache.clear();
 });
 
 /**
  * This test verifies that new contexts are returned
@@ -216,32 +219,32 @@ add_task(async function test_updating() 
     '/data/locales/pl/test.ftl',
   ]);
   L10nRegistry.registerSource(oneSource);
   fs = {
     '/data/locales/pl/test.ftl': 'key = value'
   };
 
   let ctxs = L10nRegistry.generateContexts(['pl'], ['test.ftl']);
-  let ctx0 = await ctxs.next().value;
+  let ctx0 = (await ctxs.next()).value;
   equal(ctx0.locales[0], 'pl');
   equal(ctx0.hasMessage('key'), true);
   let msg0 = ctx0.getMessage('key');
   equal(ctx0.format(msg0), 'value');
 
 
   const newSource = new IndexedFileSource('langpack-pl', ['pl'], '/data/locales/{locale}/', [
     '/data/locales/pl/test.ftl'
   ]);
   fs['/data/locales/pl/test.ftl'] = 'key = new value';
   L10nRegistry.updateSource(newSource);
 
   equal(L10nRegistry.sources.size, 1);
   ctxs = L10nRegistry.generateContexts(['pl'], ['test.ftl']);
-  ctx0 = await ctxs.next().value;
+  ctx0 = (await ctxs.next()).value;
   msg0 = ctx0.getMessage('key');
   equal(ctx0.format(msg0), 'new value');
 
   // cleanup
   L10nRegistry.sources.clear();
   L10nRegistry.ctxCache.clear();
 });
 
@@ -262,51 +265,151 @@ add_task(async function test_removing() 
     '/app/data/locales/pl/test.ftl': 'key = value',
     '/data/locales/pl/test.ftl': 'key = addon value'
   };
 
   equal(L10nRegistry.sources.size, 2);
   equal(L10nRegistry.sources.has('langpack-pl'), true);
 
   let ctxs = L10nRegistry.generateContexts(['pl'], ['test.ftl']);
-  let ctx0 = await ctxs.next().value;
+  let ctx0 = (await ctxs.next()).value;
   equal(ctx0.locales[0], 'pl');
   equal(ctx0.hasMessage('key'), true);
   let msg0 = ctx0.getMessage('key');
   equal(ctx0.format(msg0), 'addon value');
 
-  let ctx1 = await ctxs.next().value;
+  let ctx1 = (await ctxs.next()).value;
   equal(ctx1.locales[0], 'pl');
   equal(ctx1.hasMessage('key'), true);
   let msg1 = ctx1.getMessage('key');
   equal(ctx1.format(msg1), 'value');
 
-  equal(ctxs.next().done, true);
+  equal((await ctxs.next()).done, true);
 
   // Remove langpack
 
   L10nRegistry.removeSource('langpack-pl');
 
   equal(L10nRegistry.sources.size, 1);
   equal(L10nRegistry.sources.has('langpack-pl'), false);
 
   ctxs = L10nRegistry.generateContexts(['pl'], ['test.ftl']);
-  ctx0 = await ctxs.next().value;
+  ctx0 = (await ctxs.next()).value;
   equal(ctx0.locales[0], 'pl');
   equal(ctx0.hasMessage('key'), true);
   msg0 = ctx0.getMessage('key');
   equal(ctx0.format(msg0), 'value');
 
-  equal(ctxs.next().done, true);
+  equal((await ctxs.next()).done, true);
 
   // Remove app source
 
   L10nRegistry.removeSource('app');
 
   equal(L10nRegistry.sources.size, 0);
 
   ctxs = L10nRegistry.generateContexts(['pl'], ['test.ftl']);
-  equal(ctxs.next().done, true);
+  equal((await ctxs.next()).done, true);
+
+  // cleanup
+  L10nRegistry.sources.clear();
+  L10nRegistry.ctxCache.clear();
+});
+
+/**
+ * This test verifies that the logic works correctly when there's a missing
+ * file in the FileSource scenario.
+ */
+add_task(async function test_missing_file() {
+  let oneSource = new FileSource('app', ['en-US'], './app/data/locales/{locale}/');
+  L10nRegistry.registerSource(oneSource);
+  let twoSource = new FileSource('platform', ['en-US'], './platform/data/locales/{locale}/');
+  L10nRegistry.registerSource(twoSource);
+
+  fs = {
+    './app/data/locales/en-US/test.ftl': 'key = value en-US',
+    './platform/data/locales/en-US/test.ftl': 'key = value en-US',
+    './platform/data/locales/en-US/test2.ftl': 'key2 = value2 en-US'
+  };
+
+
+  // has two sources
+
+  equal(L10nRegistry.sources.size, 2);
+  equal(L10nRegistry.sources.has('app'), true);
+  equal(L10nRegistry.sources.has('platform'), true);
+
+
+  // returns a single context
+
+  let ctxs = L10nRegistry.generateContexts(['en-US'], ['test.ftl', 'test2.ftl']);
+  let ctx0 = (await ctxs.next()).value;
+  let ctx1 = (await ctxs.next()).value;
+
+  equal((await ctxs.next()).done, true);
+
 
   // cleanup
   L10nRegistry.sources.clear();
   L10nRegistry.ctxCache.clear();
 });
+
+/**
+ * This test verifies that each file is that all files requested
+ * by a single context are fetched at the same time, even
+ * if one I/O is slow.
+ */
+add_task(async function test_parallel_io() {
+  /* eslint-disable mozilla/no-arbitrary-setTimeout */
+  let originalLoad = L10nRegistry.load;
+  let fetchIndex = new Map();
+
+  L10nRegistry.load = function(url) {
+    if (!fetchIndex.has(url)) {
+      fetchIndex.set(url, 0);
+    }
+    fetchIndex.set(url, fetchIndex.get(url) + 1);
+
+    if (url === '/en-US/slow-file.ftl') {
+      return new Promise((resolve, reject) => {
+        setTimeout(() => {
+          // Despite slow-file being the first on the list,
+          // by the time the it finishes loading, the other
+          // two files are already fetched.
+          equal(fetchIndex.get('/en-US/test.ftl'), 1);
+          equal(fetchIndex.get('/en-US/test2.ftl'), 1);
+
+          resolve('');
+        }, 10);
+      });
+    };
+    return Promise.resolve('');
+  }
+  let oneSource = new FileSource('app', ['en-US'], '/{locale}/');
+  L10nRegistry.registerSource(oneSource);
+
+  fs = {
+    '/en-US/test.ftl': 'key = value en-US',
+    '/en-US/test2.ftl': 'key2 = value2 en-US',
+    '/en-US/slow-file.ftl': 'key-slow = value slow en-US',
+  };
+
+  // returns a single context
+
+  let ctxs = L10nRegistry.generateContexts(['en-US'], ['slow-file.ftl', 'test.ftl', 'test2.ftl']);
+
+  equal(fetchIndex.size, 0);
+
+  let ctx0 = await ctxs.next();
+
+  equal(ctx0.done, false);
+
+  equal((await ctxs.next()).done, true);
+
+  // When requested again, the cache should make the load operation not
+  // increase the fetchedIndex count
+  let ctxs2= L10nRegistry.generateContexts(['en-US'], ['test.ftl', 'test2.ftl', 'slow-file.ftl']);
+
+  // cleanup
+  L10nRegistry.sources.clear();
+  L10nRegistry.ctxCache.clear();
+  L10nRegistry.load = originalLoad;
+});
--- a/intl/l10n/test/test_localization.js
+++ b/intl/l10n/test/test_localization.js
@@ -18,25 +18,25 @@ add_task(async function test_methods_cal
 
   const fs = {
     '/localization/de/browser/menu.ftl': 'key = [de] Value2',
     '/localization/en-US/browser/menu.ftl': 'key = [en] Value2\nkey2 = [en] Value3',
   };
   const originalLoad = L10nRegistry.load;
   const originalRequested = LocaleService.getRequestedLocales();
 
-  L10nRegistry.load = function(url) {
+  L10nRegistry.load = async function(url) {
     return fs[url];
   }
 
   const source = new FileSource('test', ['de', 'en-US'], '/localization/{locale}');
   L10nRegistry.registerSource(source);
 
-  function * generateMessages(resIds) {
-    yield * L10nRegistry.generateContexts(['de', 'en-US'], resIds);
+  async function * generateMessages(resIds) {
+    yield * await L10nRegistry.generateContexts(['de', 'en-US'], resIds);
   }
 
   const l10n = new Localization([
     '/browser/menu.ftl'
   ], generateMessages);
 
   let values = await l10n.formatValues([['key'], ['key2']]);
 
--- a/intl/locale/LocaleService.cpp
+++ b/intl/locale/LocaleService.cpp
@@ -526,19 +526,24 @@ LocaleService::IsAppLocaleRTL()
 }
 
 NS_IMETHODIMP
 LocaleService::Observe(nsISupports *aSubject, const char *aTopic,
                       const char16_t *aData)
 {
   MOZ_ASSERT(mIsServer, "This should only be called in the server mode.");
 
+  NS_ConvertUTF16toUTF8 pref(aData);
+
+  // This is a temporary solution until we get bug 1337078 landed.
+  if (pref.EqualsLiteral(ANDROID_OS_LOCALE_PREF)) {
+    OSPreferences::GetInstance()->Refresh();
+  }
   // At the moment the only thing we're observing are settings indicating
   // user requested locales.
-  NS_ConvertUTF16toUTF8 pref(aData);
   if (pref.EqualsLiteral(MATCH_OS_LOCALE_PREF) ||
       pref.EqualsLiteral(SELECTED_LOCALE_PREF) ||
       pref.EqualsLiteral(ANDROID_OS_LOCALE_PREF)) {
     OnRequestedLocalesChanged();
   }
   return NS_OK;
 }
 
--- a/intl/locale/OSPreferences.h
+++ b/intl/locale/OSPreferences.h
@@ -123,16 +123,29 @@ public:
    *
    * (See mozIOSPreferences.idl for a JS-callable version of this.)
    */
   bool GetRegionalPrefsLocales(nsTArray<nsCString>& aRetVal);
 
   static bool GetDateTimeConnectorPattern(const nsACString& aLocale,
                                           nsAString& aRetVal);
 
+  /**
+   * Triggers a refresh of retrieving data from host environment.
+   *
+   * If the result differs from the previous list, it will additionally
+   * trigger global events for changed values:
+   *
+   *  * SystemLocales: "intl:system-locales-changed"
+   *
+   * This method should not be called from anywhere except of per-platform
+   * hooks into OS events.
+   */
+  void Refresh();
+
 protected:
   nsTArray<nsCString> mSystemLocales;
   nsTArray<nsCString> mRegionalPrefsLocales;
 
 private:
   virtual ~OSPreferences() {};
 
   static StaticRefPtr<OSPreferences> sInstance;
@@ -182,27 +195,14 @@ private:
    * Callers should always be prepared to handle that scenario.
    *
    * The heuristic may depend on the OS API and HIG guidelines.
    */
   bool ReadDateTimePattern(DateTimeFormatStyle aDateFormatStyle,
                            DateTimeFormatStyle aTimeFormatStyle,
                            const nsACString& aLocale,
                            nsAString& aRetVal);
-
-  /**
-   * Triggers a refresh of retrieving data from host environment.
-   *
-   * If the result differs from the previous list, it will additionally
-   * trigger global events for changed values:
-   *
-   *  * SystemLocales: "intl:system-locales-changed"
-   *
-   * This method should not be called from anywhere except of per-platform
-   * hooks into OS events.
-   */
-  void Refresh();
 };
 
 } // intl
 } // namespace mozilla
 
 #endif /* mozilla_intl_IntlOSPreferences_h__ */
--- a/ipc/glue/IPCMessageUtils.h
+++ b/ipc/glue/IPCMessageUtils.h
@@ -281,16 +281,33 @@ struct PlainOldDataSerializer
     aMsg->WriteBytes(&aParam, sizeof(aParam));
   }
 
   static bool Read(const Message* aMsg, PickleIterator* aIter, paramType* aResult) {
     return aMsg->ReadBytesInto(aIter, aResult, sizeof(paramType));
   }
 };
 
+/**
+ * A helper class for serializing empty structs. Since the struct is empty there
+ * is nothing to write, and a priori we know the result of the read.
+ */
+template <typename T>
+struct EmptyStructSerializer
+{
+  typedef T paramType;
+
+  static void Write(Message* aMsg, const paramType& aParam) {}
+
+  static bool Read(const Message* aMsg, PickleIterator* aIter, paramType* aResult) {
+    *aResult = {};
+    return true;
+  }
+};
+
 template<>
 struct ParamTraits<int8_t>
 {
   typedef int8_t paramType;
 
   static void Write(Message* aMsg, const paramType& aParam)
   {
     aMsg->WriteBytes(&aParam, sizeof(aParam));
@@ -917,17 +934,17 @@ struct ParamTraits<mozilla::Variant<Ts..
     void match(const T& t) {
       WriteParam(msg, t);
     }
   };
 
   static void Write(Message* msg, const paramType& param)
   {
     WriteParam(msg, param.tag);
-    param.match(VariantWriter(msg));
+    param.match(VariantWriter{msg});
   }
 
   // Because VariantReader is a nested struct, we need the dummy template
   // parameter to avoid making VariantReader<0> an explicit specialization,
   // which is not allowed for a nested class template
   template<size_t N, typename dummy = void>
   struct VariantReader
   {
@@ -940,22 +957,22 @@ struct ParamTraits<mozilla::Variant<Ts..
       // subtract one to look at N - 1, the first valid tag.  This means our
       // comparisons are off by 1.  If we get to N = 0 then we have failed to
       // find a match to the tag.
       if (tag == N - 1) {
         // Recall, even though the template parameter is N, we are
         // actually interested in the N - 1 tag.
         typename mozilla::detail::Nth<N - 1, Ts...>::Type val;
         if (ReadParam(msg, iter, &val)) {
-          *result = paramType::AsVariant(val);
+          *result = mozilla::AsVariant(val);
           return true;
         }
         return false;
       } else {
-        return Next::Read(msg, iter, tag);
+        return Next::Read(msg, iter, tag, result);
       }
     }
 
   }; // VariantReader<N>
 
   // Since we are conditioning on tag = N - 1 in the preceding specialization,
   // if we get to `VariantReader<0, dummy>` we have failed to find
   // a matching tag.
--- a/js/xpconnect/loader/ScriptPreloader.cpp
+++ b/js/xpconnect/loader/ScriptPreloader.cpp
@@ -1,16 +1,17 @@
 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
 /* vim: set ts=8 sts=4 et sw=4 tw=99: */
 /* 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 "ScriptPreloader-inl.h"
 #include "mozilla/ScriptPreloader.h"
+#include "mozJSComponentLoader.h"
 #include "mozilla/loader/ScriptCacheActors.h"
 
 #include "mozilla/URLPreloader.h"
 
 #include "mozilla/ArrayUtils.h"
 #include "mozilla/ClearOnShutdown.h"
 #include "mozilla/FileUtils.h"
 #include "mozilla/Logging.h"
@@ -902,35 +903,41 @@ ScriptPreloader::FinishPendingParses(Mon
 
 void
 ScriptPreloader::DoFinishOffThreadDecode()
 {
     mFinishDecodeRunnablePending = false;
     MaybeFinishOffThreadDecode();
 }
 
+JSObject*
+ScriptPreloader::CompilationScope(JSContext* cx)
+{
+    return mozJSComponentLoader::Get()->CompilationScope(cx);
+}
+
 void
 ScriptPreloader::MaybeFinishOffThreadDecode()
 {
     if (!mToken) {
         return;
     }
 
     auto cleanup = MakeScopeExit([&] () {
         mToken = nullptr;
         mParsingSources.clear();
         mParsingScripts.clear();
 
         DecodeNextBatch(OFF_THREAD_CHUNK_SIZE);
     });
 
-    AutoJSAPI jsapi;
-    MOZ_RELEASE_ASSERT(jsapi.Init(xpc::CompilationScope()));
+    AutoSafeJSAPI jsapi;
+    JSContext* cx = jsapi.cx();
 
-    JSContext* cx = jsapi.cx();
+    JSAutoCompartment ac(cx, CompilationScope(cx));
     JS::Rooted<JS::ScriptVector> jsScripts(cx, JS::ScriptVector(cx));
 
     // If this fails, we still need to mark the scripts as finished. Any that
     // weren't successfully compiled in this operation (which should never
     // happen under ordinary circumstances) will be re-decoded on the main
     // thread, and raise the appropriate errors when they're executed.
     //
     // The exception from the off-thread decode operation will be reported when
@@ -988,19 +995,19 @@ ScriptPreloader::DecodeNextBatch(size_t 
         script->remove();
         size += script->mSize;
     }
 
     if (size == 0 && mPendingScripts.isEmpty()) {
         return;
     }
 
-    AutoJSAPI jsapi;
-    MOZ_RELEASE_ASSERT(jsapi.Init(xpc::CompilationScope()));
+    AutoSafeJSAPI jsapi;
     JSContext* cx = jsapi.cx();
+    JSAutoCompartment ac(cx, CompilationScope(cx));
 
     JS::CompileOptions options(cx, JSVERSION_DEFAULT);
     options.setNoScriptRval(true)
            .setSourceIsLazy(true);
 
     if (!JS::CanCompileOffThread(cx, options, size) ||
         !JS::DecodeMultiOffThreadScripts(cx, options, mParsingSources,
                                          OffThreadDecodeCallback,
--- a/js/xpconnect/loader/ScriptPreloader.h
+++ b/js/xpconnect/loader/ScriptPreloader.h
@@ -385,16 +385,21 @@ private:
     JSScript* WaitForCachedScript(JSContext* cx, CachedScript* script);
 
     void DecodeNextBatch(size_t chunkSize);
 
     static void OffThreadDecodeCallback(void* token, void* context);
     void MaybeFinishOffThreadDecode();
     void DoFinishOffThreadDecode();
 
+    // Returns the global scope object for off-thread compilation. When global
+    // sharing is enabled in the component loader, this should be the shared
+    // module global. Otherwise, it should be the XPConnect compilation scope.
+    JSObject* CompilationScope(JSContext* cx);
+
     size_t ShallowHeapSizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf)
     {
         return (mallocSizeOf(this) + mScripts.ShallowSizeOfExcludingThis(mallocSizeOf) +
                 mallocSizeOf(mSaveThread.get()) + mallocSizeOf(mProfD.get()));
     }
 
     using ScriptHash = nsClassHashtable<nsCStringHashKey, CachedScript>;
 
--- a/js/xpconnect/loader/mozJSComponentLoader.cpp
+++ b/js/xpconnect/loader/mozJSComponentLoader.cpp
@@ -196,17 +196,19 @@ ReportOnCallerUTF8(JSCLContextHelper& he
     return NS_OK;
 }
 
 mozJSComponentLoader::mozJSComponentLoader()
     : mModules(16),
       mImports(16),
       mInProgressImports(16),
       mLocations(16),
-      mInitialized(false)
+      mInitialized(false),
+      mShareLoaderGlobal(false),
+      mLoaderGlobal(dom::RootingCx())
 {
     MOZ_ASSERT(!sSelf, "mozJSComponentLoader should be a singleton");
 
     sSelf = this;
 }
 
 #define ENSURE_DEP(name) { nsresult rv = Ensure##name(); NS_ENSURE_SUCCESS(rv, rv); }
 #define ENSURE_DEPS(...) MOZ_FOR_EACH(ENSURE_DEP, (), (__VA_ARGS__));
@@ -294,16 +296,20 @@ mozJSComponentLoader::sSelf;
 NS_IMPL_ISUPPORTS(mozJSComponentLoader,
                   mozilla::ModuleLoader,
                   xpcIJSModuleLoader,
                   nsIObserver)
 
 nsresult
 mozJSComponentLoader::ReallyInit()
 {
+    MOZ_ASSERT(!mInitialized);
+
+    mShareLoaderGlobal = Preferences::GetBool("jsloader.shareGlobal");
+
     nsresult rv;
     nsCOMPtr<nsIObserverService> obsSvc =
         do_GetService(kObserverServiceContractID, &rv);
     NS_ENSURE_SUCCESS(rv, rv);
 
     rv = obsSvc->AddObserver(this, "xpcom-shutdown-loaders", false);
     NS_ENSURE_SUCCESS(rv, rv);
 
@@ -423,17 +429,23 @@ mozJSComponentLoader::LoadModule(FileLoc
     // The hash owns the ModuleEntry now, forget about it
     return entry.forget();
 }
 
 void
 mozJSComponentLoader::FindTargetObject(JSContext* aCx,
                                        MutableHandleObject aTargetObject)
 {
-    aTargetObject.set(CurrentGlobalOrNull(aCx));
+    aTargetObject.set(js::GetJSMEnvironmentOfScriptedCaller(aCx));
+
+    // The above could fail if the scripted caller is not a
+    // component/JSM (it could be a DOM scope, for instance).
+    if (!aTargetObject) {
+        aTargetObject.set(CurrentGlobalOrNull(aCx));
+    }
 }
 
 // This requires that the keys be strings and the values be pointers.
 template <class Key, class Data, class UserData>
 static size_t
 SizeOfTableExcludingThis(const nsBaseHashtable<Key, Data, UserData>& aTable,
                          MallocSizeOf aMallocSizeOf)
 {
@@ -453,17 +465,17 @@ mozJSComponentLoader::SizeOfIncludingThi
     n += SizeOfTableExcludingThis(mImports, aMallocSizeOf);
     n += mLocations.ShallowSizeOfExcludingThis(aMallocSizeOf);
     n += SizeOfTableExcludingThis(mInProgressImports, aMallocSizeOf);
     return n;
 }
 
 void
 mozJSComponentLoader::CreateLoaderGlobal(JSContext* aCx,
-                                         nsACString& aLocation,
+                                         const nsACString& aLocation,
                                          JSAddonId* aAddonID,
                                          MutableHandleObject aGlobal)
 {
     RefPtr<BackstagePass> backstagePass;
     nsresult rv = NS_NewBackstagePass(getter_AddRefs(backstagePass));
     NS_ENSURE_SUCCESS_VOID(rv);
 
     CompartmentOptions options;
@@ -503,38 +515,108 @@ mozJSComponentLoader::CreateLoaderGlobal
 
     // Set the location information for the new global, so that tools like
     // about:memory may use that information
     xpc::SetLocationForGlobal(global, aLocation);
 
     aGlobal.set(global);
 }
 
+bool
+mozJSComponentLoader::ReuseGlobal(bool aIsAddon, nsIURI* aURI)
+{
+    if (aIsAddon || !mShareLoaderGlobal)
+        return false;
+
+    nsCString spec;
+    NS_ENSURE_SUCCESS(aURI->GetSpec(spec), false);
+
+    // The loader calls Object.freeze on global properties, which
+    // causes problems if the global is shared with other code.
+    if (spec.EqualsASCII("resource://gre/modules/commonjs/toolkit/loader.js")) {
+        return false;
+    }
+
+    // Various tests call addDebuggerToGlobal on the result of
+    // importing this JSM, which would be annoying to fix.
+    if (spec.EqualsASCII("resource://gre/modules/jsdebugger.jsm")) {
+        return false;
+    }
+
+    // Some SpecialPowers jsms call Cu.forcePermissiveCOWs(),
+    // which sets a per-compartment flag that disables certain
+    // security wrappers, so don't use the shared global for them
+    // to avoid breaking tests.
+    if (FindInReadable(NS_LITERAL_CSTRING("chrome://specialpowers/"), spec)) {
+        return false;
+    }
+
+    return true;
+}
+
+JSObject*
+mozJSComponentLoader::GetSharedGlobal(JSContext* aCx)
+{
+    if (!mLoaderGlobal) {
+        MOZ_ASSERT(mShareLoaderGlobal);
+
+        JS::RootedObject globalObj(aCx);
+        CreateLoaderGlobal(aCx, NS_LITERAL_CSTRING("shared JSM global"),
+                           nullptr, &globalObj);
+
+        // If we fail to create a module global this early, we're not going to
+        // get very far, so just bail out now.
+        MOZ_RELEASE_ASSERT(globalObj);
+        mLoaderGlobal = globalObj;
+
+        // AutoEntryScript required to invoke debugger hook, which is a
+        // Gecko-specific concept at present.
+        dom::AutoEntryScript aes(globalObj,
+                                 "component loader report global");
+        JS_FireOnNewGlobalObject(aes.cx(), globalObj);
+    }
+
+    return mLoaderGlobal;
+}
+
 JSObject*
 mozJSComponentLoader::PrepareObjectForLocation(JSContext* aCx,
                                                nsIFile* aComponentFile,
                                                nsIURI* aURI,
+                                               bool* aReuseGlobal,
                                                bool* aRealFile)
 {
     nsAutoCString nativePath;
     NS_ENSURE_SUCCESS(aURI->GetSpec(nativePath), nullptr);
 
-    RootedObject globalObj(aCx);
+    JSAddonId* addonId = MapURIToAddonID(aURI);
+    bool reuseGlobal = ReuseGlobal(!!addonId, aURI);
+
+    *aReuseGlobal = reuseGlobal;
 
-    CreateLoaderGlobal(aCx, nativePath, MapURIToAddonID(aURI), &globalObj);
+    bool createdNewGlobal = false;
+    RootedObject globalObj(aCx);
+    if (reuseGlobal) {
+        globalObj = GetSharedGlobal(aCx);
+    } else if (!globalObj) {
+        CreateLoaderGlobal(aCx, nativePath, addonId, &globalObj);
+        createdNewGlobal = true;
+    }
 
     // |thisObj| is the object we set properties on for a particular .jsm.
-    // XXX Right now, thisObj is always globalObj, but if we start
-    // sharing globals between jsms, they won't be the same.
-    // See bug 1186409.
     RootedObject thisObj(aCx, globalObj);
     NS_ENSURE_TRUE(thisObj, nullptr);
 
     JSAutoCompartment ac(aCx, thisObj);
 
+    if (reuseGlobal) {
+        thisObj = js::NewJSMEnvironment(aCx);
+        NS_ENSURE_TRUE(thisObj, nullptr);
+    }
+
     *aRealFile = false;
 
     // need to be extra careful checking for URIs pointing to files
     // EnsureFile may not always get called, especially on resource URIs
     // so we need to call GetFile to make sure this is a valid file
     nsresult rv = NS_OK;
     nsCOMPtr<nsIFileURL> fileURL = do_QueryInterface(aURI, &rv);
     nsCOMPtr<nsIFile> testFile;
@@ -562,17 +644,17 @@ mozJSComponentLoader::PrepareObjectForLo
     // Expose the URI from which the script was imported through a special
     // variable that we insert into the JSM.
     RootedString exposedUri(aCx, JS_NewStringCopyN(aCx, nativePath.get(), nativePath.Length()));
     NS_ENSURE_TRUE(exposedUri, nullptr);
 
     if (!JS_DefineProperty(aCx, thisObj, "__URI__", exposedUri, 0))
         return nullptr;
 
-    {
+    if (createdNewGlobal) {
         // AutoEntryScript required to invoke debugger hook, which is a
         // Gecko-specific concept at present.
         dom::AutoEntryScript aes(globalObj,
                                  "component loader report global");
         JS_FireOnNewGlobalObject(aes.cx(), globalObj);
     }
 
     return thisObj;
@@ -591,20 +673,21 @@ mozJSComponentLoader::ObjectForLocation(
 
     dom::AutoJSAPI jsapi;
     jsapi.Init();
     JSContext* cx = jsapi.cx();
 
     bool realFile = false;
     nsresult rv = aInfo.EnsureURI();
     NS_ENSURE_SUCCESS(rv, rv);
+    bool reuseGlobal = false;
     RootedObject obj(cx, PrepareObjectForLocation(cx, aComponentFile, aInfo.URI(),
-                                                  &realFile));
+                                                  &reuseGlobal, &realFile));
     NS_ENSURE_TRUE(obj, NS_ERROR_FAILURE);
-    MOZ_ASSERT(JS_IsGlobalObject(obj));
+    MOZ_ASSERT(JS_IsGlobalObject(obj) == !reuseGlobal);
 
     JSAutoCompartment ac(cx, obj);
 
     RootedScript script(cx);
 
     nsAutoCString nativePath;
     rv = aInfo.URI()->GetSpec(nativePath);
     NS_ENSURE_SUCCESS(rv, rv);
@@ -657,17 +740,20 @@ mozJSComponentLoader::ObjectForLocation(
 
         if (realFile) {
             AutoMemMap map;
             MOZ_TRY(map.init(aComponentFile));
 
             // Note: exceptions will get handled further down;
             // don't early return for them here.
             auto buf = map.get<char>();
-            Compile(cx, options, buf.get(), map.size(), &script);
+            if (reuseGlobal)
+                CompileForNonSyntacticScope(cx, options, buf.get(), map.size(), &script);
+            else
+                Compile(cx, options, buf.get(), map.size(), &script);
         } else {
             rv = aInfo.EnsureScriptChannel();
             NS_ENSURE_SUCCESS(rv, rv);
             nsCOMPtr<nsIInputStream> scriptStream;
             rv = NS_MaybeOpenChannelUsingOpen2(aInfo.ScriptChannel(),
                    getter_AddRefs(scriptStream));
             NS_ENSURE_SUCCESS(rv, rv);
 
@@ -688,17 +774,20 @@ mozJSComponentLoader::ObjectForLocation(
 
             /* read the file in one swoop */
             rv = scriptStream->Read(buf.get(), len, &bytesRead);
             if (bytesRead != len)
                 return NS_BASE_STREAM_OSERROR;
 
             buf[len] = '\0';
 
-            Compile(cx, options, buf.get(), bytesRead, &script);
+            if (reuseGlobal)
+                CompileForNonSyntacticScope(cx, options, buf.get(), bytesRead, &script);
+            else
+                Compile(cx, options, buf.get(), bytesRead, &script);
         }
         // Propagate the exception, if one exists. Also, don't leave the stale
         // exception on this context.
         if (!script && aPropagateExceptions && jsapi.HasException()) {
             if (!jsapi.StealException(aException))
                 return NS_ERROR_OUT_OF_MEMORY;
         }
     }
@@ -731,18 +820,26 @@ mozJSComponentLoader::ObjectForLocation(
 
     {   // Scope for AutoEntryScript
 
         // We're going to run script via JS_ExecuteScript, so we need an
         // AutoEntryScript. This is Gecko-specific and not in any spec.
         dom::AutoEntryScript aes(CurrentGlobalOrNull(cx),
                                  "component loader load module");
         JSContext* aescx = aes.cx();
-        JS::RootedValue rval(cx);
-        if (!JS::CloneAndExecuteScript(aescx, script, &rval)) {
+
+        bool executeOk = false;
+        if (JS_IsGlobalObject(obj)) {
+            JS::RootedValue rval(cx);
+            executeOk = JS::CloneAndExecuteScript(aescx, script, &rval);
+        } else {
+            executeOk = js::ExecuteInJSMEnvironment(aescx, script, obj);
+        }
+
+        if (!executeOk) {
             if (aPropagateExceptions && aes.HasException()) {
                 // Ignore return value because we're returning an error code
                 // anyway.
                 Unused << aes.StealException(aException);
             }
             aObject.set(nullptr);
             aTableScript.set(nullptr);
             return NS_ERROR_FAILURE;
@@ -760,16 +857,28 @@ mozJSComponentLoader::ObjectForLocation(
     return NS_OK;
 }
 
 void
 mozJSComponentLoader::UnloadModules()
 {
     mInitialized = false;
 
+    if (mLoaderGlobal) {
+        dom::AutoJSAPI jsapi;
+        jsapi.Init();
+        JSContext* cx = jsapi.cx();
+        RootedObject global(cx, mLoaderGlobal);
+        JSAutoCompartment ac(cx, global);
+        MOZ_ASSERT(JS_HasExtensibleLexicalEnvironment(global));
+        JS_SetAllNonReservedSlotsToUndefined(cx, JS_ExtensibleLexicalEnvironment(global));
+        JS_SetAllNonReservedSlotsToUndefined(cx, global);
+        mLoaderGlobal = nullptr;
+    }
+
     mInProgressImports.Clear();
     mImports.Clear();
     mLocations.Clear();
 
     for (auto iter = mModules.Iter(); !iter.Done(); iter.Next()) {
         iter.Data()->Clear();
         iter.Remove();
     }
@@ -1125,16 +1234,19 @@ mozJSComponentLoader::Unload(const nsACS
     rv = info.EnsureKey();
     NS_ENSURE_SUCCESS(rv, rv);
     ModuleEntry* mod;
     if (mImports.Get(info.Key(), &mod)) {
         mLocations.Remove(mod->resolvedURL);
         mImports.Remove(info.Key());
     }
 
+    // If this is the last module to be unloaded, we will leak mLoaderGlobal
+    // until UnloadModules is called. So be it.
+
     return NS_OK;
 }
 
 NS_IMETHODIMP
 mozJSComponentLoader::Observe(nsISupports* subject, const char* topic,
                               const char16_t* data)
 {
     if (!strcmp(topic, "xpcom-shutdown-loaders")) {
--- a/js/xpconnect/loader/mozJSComponentLoader.h
+++ b/js/xpconnect/loader/mozJSComponentLoader.h
@@ -15,32 +15,38 @@
 #include "nsIObserver.h"
 #include "nsIURI.h"
 #include "xpcIJSModuleLoader.h"
 #include "nsClassHashtable.h"
 #include "nsDataHashtable.h"
 #include "jsapi.h"
 
 #include "xpcIJSGetFactory.h"
+#include "xpcpublic.h"
 
 class nsIFile;
 class nsIPrincipal;
 class nsIXPConnectJSObjectHolder;
 class ComponentLoaderInfo;
 
+namespace mozilla {
+    class ScriptPreloader;
+} // namespace mozilla
+
+
 /* 6bd13476-1dd2-11b2-bbef-f0ccb5fa64b6 (thanks, mozbot) */
 
 #define MOZJSCOMPONENTLOADER_CID                                              \
   {0x6bd13476, 0x1dd2, 0x11b2,                                                \
     { 0xbb, 0xef, 0xf0, 0xcc, 0xb5, 0xfa, 0x64, 0xb6 }}
 #define MOZJSCOMPONENTLOADER_CONTRACTID "@mozilla.org/moz/jsloader;1"
 
-class mozJSComponentLoader : public mozilla::ModuleLoader,
-                             public xpcIJSModuleLoader,
-                             public nsIObserver
+class mozJSComponentLoader final : public mozilla::ModuleLoader,
+                                   public xpcIJSModuleLoader,
+                                   public nsIObserver
 {
  public:
     NS_DECL_ISUPPORTS
     NS_DECL_XPCIJSMODULELOADER
     NS_DECL_NSIOBSERVER
 
     mozJSComponentLoader();
 
@@ -51,35 +57,58 @@ class mozJSComponentLoader : public mozi
                           JS::MutableHandleObject aTargetObject);
 
     static mozJSComponentLoader* Get() { return sSelf; }
 
     nsresult Import(const nsACString& aResourceURI, JS::HandleValue aTargetObj,
                     JSContext* aCx, uint8_t aArgc, JS::MutableHandleValue aRetval);
     nsresult Unload(const nsACString& aResourceURI);
     nsresult IsModuleLoaded(const nsACString& aResourceURI, bool* aRetval);
+    bool IsLoaderGlobal(JSObject* aObj) {
+        return mLoaderGlobal == aObj;
+    }
 
     size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf);
 
  protected:
     virtual ~mozJSComponentLoader();
 
+    friend class mozilla::ScriptPreloader;
+
+    JSObject* CompilationScope(JSContext* aCx)
+    {
+        if (mLoaderGlobal)
+            return mLoaderGlobal;
+        if ((mInitialized || NS_SUCCEEDED(ReallyInit())) &&
+            mShareLoaderGlobal)
+        {
+            return GetSharedGlobal(aCx);
+        }
+        return xpc::CompilationScope();
+    }
+
+ private:
     static mozJSComponentLoader* sSelf;
 
     nsresult ReallyInit();
     void UnloadModules();
 
     void CreateLoaderGlobal(JSContext* aCx,
-                            nsACString& aLocation,
+                            const nsACString& aLocation,
                             JSAddonId* aAddonID,
                             JS::MutableHandleObject aGlobal);
 
+    bool ReuseGlobal(bool aIsAddon, nsIURI* aComponent);
+
+    JSObject* GetSharedGlobal(JSContext* aCx);
+
     JSObject* PrepareObjectForLocation(JSContext* aCx,
                                        nsIFile* aComponentFile,
                                        nsIURI* aComponent,
+                                       bool* aReuseGlobal,
                                        bool* aRealFile);
 
     nsresult ObjectForLocation(ComponentLoaderInfo& aInfo,
                                nsIFile* aComponentFile,
                                JS::MutableHandleObject aObject,
                                JS::MutableHandleScript aTableScript,
                                char** location,
                                bool aCatchException,
@@ -143,18 +172,16 @@ class mozJSComponentLoader : public mozi
 
         nsCOMPtr<xpcIJSGetFactory> getfactoryobj;
         JS::PersistentRootedObject obj;
         JS::PersistentRootedScript thisObjectKey;
         char* location;
         nsCString resolvedURL;
     };
 
-    friend class ModuleEntry;
-
     static size_t DataEntrySizeOfExcludingThis(const nsACString& aKey, ModuleEntry* const& aData,
                                                mozilla::MallocSizeOf aMallocSizeOf, void* arg);
     static size_t ClassEntrySizeOfExcludingThis(const nsACString& aKey,
                                                 const nsAutoPtr<ModuleEntry>& aData,
                                                 mozilla::MallocSizeOf aMallocSizeOf, void* arg);
 
     // Modules are intentionally leaked, but still cleared.
     nsDataHashtable<nsCStringHashKey, ModuleEntry*> mModules;
@@ -163,11 +190,13 @@ class mozJSComponentLoader : public mozi
     nsDataHashtable<nsCStringHashKey, ModuleEntry*> mInProgressImports;
 
     // A map of on-disk file locations which are loaded as modules to the
     // pre-resolved URIs they were loaded from. Used to prevent the same file
     // from being loaded separately, from multiple URLs.
     nsClassHashtable<nsCStringHashKey, nsCString> mLocations;
 
     bool mInitialized;
+    bool mShareLoaderGlobal;
+    JS::PersistentRooted<JSObject*> mLoaderGlobal;
 };
 
 #endif
--- a/js/xpconnect/loader/mozJSSubScriptLoader.cpp
+++ b/js/xpconnect/loader/mozJSSubScriptLoader.cpp
@@ -162,31 +162,43 @@ PrepareScript(nsIURI* uri,
         return JS::Compile(cx, options, buf, len, script);
     }
     return JS::CompileForNonSyntacticScope(cx, options, buf, len, script);
 }
 
 static bool
 EvalScript(JSContext* cx,
            HandleObject targetObj,
+           HandleObject loadScope,
            MutableHandleValue retval,
            nsIURI* uri,
            bool startupCache,
            bool preloadCache,
            MutableHandleScript script)
 {
     if (JS_IsGlobalObject(targetObj)) {
         if (!JS::CloneAndExecuteScript(cx, script, retval)) {
             return false;
         }
     } else {
         JS::AutoObjectVector envChain(cx);
         if (!envChain.append(targetObj)) {
             return false;
         }
+        if (loadScope != targetObj &&
+            loadScope &&
+            !JS_IsGlobalObject(loadScope))
+        {
+            MOZ_DIAGNOSTIC_ASSERT(js::GetObjectCompartment(loadScope) ==
+                                  js::GetObjectCompartment(targetObj));
+
+            if (!envChain.append(loadScope)) {
+                return false;
+            }
+        }
         if (!JS::CloneAndExecuteScript(cx, envChain, script, retval)) {
             return false;
         }
     }
 
     JSAutoCompartment rac(cx, targetObj);
     if (!JS_WrapValue(cx, retval)) {
         return false;
@@ -236,59 +248,64 @@ class AsyncScriptLoader : public nsIIncr
 {
 public:
     NS_DECL_CYCLE_COLLECTING_ISUPPORTS
     NS_DECL_NSIINCREMENTALSTREAMLOADEROBSERVER
 
     NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(AsyncScriptLoader)
 
     AsyncScriptLoader(nsIChannel* aChannel, bool aWantReturnValue,
-                      JSObject* aTargetObj, const nsAString& aCharset,
-                      bool aCache, Promise* aPromise)
+                      JSObject* aTargetObj, JSObject* aLoadScope,
+                      const nsAString& aCharset, bool aCache,
+                      Promise* aPromise)
         : mChannel(aChannel)
         , mTargetObj(aTargetObj)
+        , mLoadScope(aLoadScope)
         , mPromise(aPromise)
         , mCharset(aCharset)
         , mWantReturnValue(aWantReturnValue)
         , mCache(aCache)
     {
         // Needed for the cycle collector to manage mTargetObj.
         mozilla::HoldJSObjects(this);
     }
 
 private:
     virtual ~AsyncScriptLoader() {
         mozilla::DropJSObjects(this);
     }
 
     RefPtr<nsIChannel> mChannel;
     Heap<JSObject*> mTargetObj;
+    Heap<JSObject*> mLoadScope;
     RefPtr<Promise> mPromise;
     nsString mCharset;
     bool mWantReturnValue;
     bool mCache;
 };
 
 NS_IMPL_CYCLE_COLLECTION_CLASS(AsyncScriptLoader)
 
 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(AsyncScriptLoader)
   NS_INTERFACE_MAP_ENTRY(nsIIncrementalStreamLoaderObserver)
 NS_INTERFACE_MAP_END
 
 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(AsyncScriptLoader)
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mPromise)
   tmp->mTargetObj = nullptr;
+  tmp->mLoadScope = nullptr;
 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
 
 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(AsyncScriptLoader)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPromise)
 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
 
 NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(AsyncScriptLoader)
   NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mTargetObj)
+  NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mLoadScope)
 NS_IMPL_CYCLE_COLLECTION_TRACE_END
 
 NS_IMPL_CYCLE_COLLECTING_ADDREF(AsyncScriptLoader)
 NS_IMPL_CYCLE_COLLECTING_RELEASE(AsyncScriptLoader)
 
 class MOZ_STACK_CLASS AutoRejectPromise
 {
 public:
@@ -363,37 +380,39 @@ AsyncScriptLoader::OnStreamComplete(nsII
     }
 
     RootedScript script(cx);
     nsAutoCString spec;
     nsresult rv = uri->GetSpec(spec);
     NS_ENSURE_SUCCESS(rv, rv);
 
     RootedObject targetObj(cx, mTargetObj);
+    RootedObject loadScope(cx, mLoadScope);
 
     if (!PrepareScript(uri, cx, targetObj, spec.get(), mCharset,
                        reinterpret_cast<const char*>(aBuf), aLength,
                        mWantReturnValue, &script))
     {
         return NS_OK;
     }
 
     JS::Rooted<JS::Value> retval(cx);
-    if (EvalScript(cx, targetObj, &retval, uri, mCache,
+    if (EvalScript(cx, targetObj, loadScope, &retval, uri, mCache,
                    mCache && !mWantReturnValue,
                    &script)) {
         autoPromise.ResolvePromise(retval);
     }
 
     return NS_OK;
 }
 
 nsresult
 mozJSSubScriptLoader::ReadScriptAsync(nsIURI* uri,
                                       HandleObject targetObj,
+                                      HandleObject loadScope,
                                       const nsAString& charset,
                                       nsIIOService* serv,
                                       bool wantReturnValue,
                                       bool cache,
                                       MutableHandleValue retval)
 {
     nsCOMPtr<nsIGlobalObject> globalObject = xpc::NativeGlobal(targetObj);
     ErrorResult result;
@@ -430,16 +449,17 @@ mozJSSubScriptLoader::ReadScriptAsync(ns
     }
 
     channel->SetContentType(NS_LITERAL_CSTRING("application/javascript"));
 
     RefPtr<AsyncScriptLoader> loadObserver =
         new AsyncScriptLoader(channel,
                               wantReturnValue,
                               targetObj,
+                              loadScope,
                               charset,
                               cache,
                               promise);
 
     nsCOMPtr<nsIIncrementalStreamLoader> loader;
     rv = NS_NewIncrementalStreamLoader(getter_AddRefs(loader), loadObserver);
     NS_ENSURE_SUCCESS(rv, rv);
 
@@ -548,28 +568,32 @@ mozJSSubScriptLoader::LoadSubScriptWithO
 nsresult
 mozJSSubScriptLoader::DoLoadSubScriptWithOptions(const nsAString& url,
                                                  LoadSubScriptOptions& options,
                                                  JSContext* cx,
                                                  MutableHandleValue retval)
 {
     nsresult rv = NS_OK;
     RootedObject targetObj(cx);
-    if (options.target) {
+    RootedObject loadScope(cx);
+    mozJSComponentLoader* loader = mozJSComponentLoader::Get();
+    loader->FindTargetObject(cx, &loadScope);
+
+    if (options.target)
         targetObj = options.target;
-    } else {
-        mozJSComponentLoader* loader = mozJSComponentLoader::Get();
-        loader->FindTargetObject(cx, &targetObj);
-        MOZ_ASSERT(JS_IsGlobalObject(targetObj));
-    }
+    else
+        targetObj = loadScope;
 
     targetObj = JS_FindCompilationScope(cx, targetObj);
-    if (!targetObj)
+    if (!targetObj || !loadScope)
         return NS_ERROR_FAILURE;
 
+    if (js::GetObjectCompartment(loadScope) != js::GetObjectCompartment(targetObj))
+        loadScope = nullptr;
+
     /* load up the url.  From here on, failures are reflected as ``custom''
      * js exceptions */
     nsCOMPtr<nsIURI> uri;
     nsAutoCString uriStr;
     nsAutoCString scheme;
 
     // Figure out who's calling us
     JS::AutoFilename filename;
@@ -651,27 +675,27 @@ mozJSSubScriptLoader::DoLoadSubScriptWit
         if (NS_FAILED(rv) || !script) {
             // ReadCachedScript may have set a pending exception.
             JS_ClearPendingException(cx);
         }
     }
 
     // If we are doing an async load, trigger it and bail out.
     if (!script && options.async) {
-        return ReadScriptAsync(uri, targetObj, options.charset, serv,
+        return ReadScriptAsync(uri, targetObj, loadScope, options.charset, serv,
                                options.wantReturnValue, !!cache, retval);
     }
 
     if (script) {
         // |script| came from the cache, so don't bother writing it
         // |back there.
         cache = nullptr;
     } else if (!ReadScript(uri, cx, targetObj, options.charset,
                         static_cast<const char*>(uriStr.get()), serv,
                         options.wantReturnValue, &script)) {
         return NS_OK;
     }
 
-    Unused << EvalScript(cx, targetObj, retval, uri, !!cache,
+    Unused << EvalScript(cx, targetObj, loadScope, retval, uri, !!cache,
                          !ignoreCache && !options.wantReturnValue,
                          &script);
     return NS_OK;
 }
--- a/js/xpconnect/loader/mozJSSubScriptLoader.h
+++ b/js/xpconnect/loader/mozJSSubScriptLoader.h
@@ -36,16 +36,17 @@ private:
     bool ReadScript(nsIURI* uri, JSContext* cx, JS::HandleObject targetObj,
                     const nsAString& charset, const char* uriStr,
                     nsIIOService* serv,
                     bool wantReturnValue,
                     JS::MutableHandleScript script);
 
     nsresult ReadScriptAsync(nsIURI* uri,
                              JS::HandleObject targetObj,
+                             JS::HandleObject loadScope,
                              const nsAString& charset,
                              nsIIOService* serv,
                              bool wantReturnValue,
                              bool cache,
                              JS::MutableHandleValue retval);
 
     nsresult DoLoadSubScriptWithOptions(const nsAString& url,
                                         LoadSubScriptOptions& options,
--- a/js/xpconnect/src/XPCComponents.cpp
+++ b/js/xpconnect/src/XPCComponents.cpp
@@ -2879,16 +2879,18 @@ nsXPCComponents_Utils::GetCrossProcessWr
 
 NS_IMETHODIMP
 nsXPCComponents_Utils::PermitCPOWsInScope(HandleValue obj)
 {
     if (!obj.isObject())
         return NS_ERROR_INVALID_ARG;
 
     JSObject* scopeObj = js::UncheckedUnwrap(&obj.toObject());
+    MOZ_DIAGNOSTIC_ASSERT(!mozJSComponentLoader::Get()->IsLoaderGlobal(scopeObj),
+                          "Don't call Cu.PermitCPOWsInScope() in a JSM that shares its global");
     CompartmentPrivate::Get(scopeObj)->allowCPOWs = true;
     return NS_OK;
 }
 
 NS_IMETHODIMP
 nsXPCComponents_Utils::RecomputeWrappers(HandleValue vobj, JSContext* cx)
 {
     // Determine the compartment of the given object, if any.
@@ -2908,29 +2910,34 @@ nsXPCComponents_Utils::RecomputeWrappers
 }
 
 NS_IMETHODIMP
 nsXPCComponents_Utils::SetWantXrays(HandleValue vscope, JSContext* cx)
 {
     if (!vscope.isObject())
         return NS_ERROR_INVALID_ARG;
     JSObject* scopeObj = js::UncheckedUnwrap(&vscope.toObject());
+    MOZ_DIAGNOSTIC_ASSERT(!mozJSComponentLoader::Get()->IsLoaderGlobal(scopeObj),
+                          "Don't call Cu.setWantXrays() in a JSM that shares its global");
     JSCompartment* compartment = js::GetObjectCompartment(scopeObj);
     CompartmentPrivate::Get(scopeObj)->wantXrays = true;
     bool ok = js::RecomputeWrappers(cx, js::SingleCompartment(compartment),
                                     js::AllCompartments());
     NS_ENSURE_TRUE(ok, NS_ERROR_FAILURE);
     return NS_OK;
 }
 
 NS_IMETHODIMP
 nsXPCComponents_Utils::ForcePermissiveCOWs(JSContext* cx)
 {
     xpc::CrashIfNotInAutomation();
-    CompartmentPrivate::Get(CurrentGlobalOrNull(cx))->forcePermissiveCOWs = true;
+    JSObject* currentGlobal = CurrentGlobalOrNull(cx);
+    MOZ_DIAGNOSTIC_ASSERT(!mozJSComponentLoader::Get()->IsLoaderGlobal(currentGlobal),
+                          "Don't call Cu.forcePermissiveCOWs() in a JSM that shares its global");
+    CompartmentPrivate::Get(currentGlobal)->forcePermissiveCOWs = true;
     return NS_OK;
 }
 
 NS_IMETHODIMP
 nsXPCComponents_Utils::ForcePrivilegedComponentsForScope(HandleValue vscope,
                                                          JSContext* cx)
 {
     if (!vscope.isObject())
--- a/js/xpconnect/src/XPCWrappedNativeScope.cpp
+++ b/js/xpconnect/src/XPCWrappedNativeScope.cpp
@@ -10,16 +10,17 @@
 #include "XPCWrapper.h"
 #include "nsContentUtils.h"
 #include "nsCycleCollectionNoteRootCallback.h"
 #include "ExpandedPrincipal.h"
 #include "mozilla/MemoryReporting.h"
 #include "mozilla/Preferences.h"
 #include "nsIAddonInterposition.h"
 #include "nsIXULRuntime.h"
+#include "mozJSComponentLoader.h"
 
 #include "mozilla/dom/BindingUtils.h"
 
 using namespace mozilla;
 using namespace xpc;
 using namespace JS;
 
 /***************************************************************************/
@@ -161,16 +162,18 @@ XPCWrappedNativeScope::XPCWrappedNativeS
             UpdateInterpositionWhitelist(cx, mInterposition);
           }
         }
     }
 
     if (addonId) {
         // We forbid CPOWs unless they're specifically allowed.
         priv->allowCPOWs = gAllowCPOWAddonSet ? gAllowCPOWAddonSet->has(addonId) : false;
+        MOZ_ASSERT(!mozJSComponentLoader::Get()->IsLoaderGlobal(aGlobal),
+                   "Don't load addons into the shared JSM global");
     }
 }
 
 // static
 bool
 XPCWrappedNativeScope::IsDyingScope(XPCWrappedNativeScope* scope)
 {
     for (XPCWrappedNativeScope* cur = gDyingScopes; cur; cur = cur->mNext) {
--- a/layout/base/PresShell.cpp
+++ b/layout/base/PresShell.cpp
@@ -651,78 +651,54 @@ nsIPresShell::GetVerifyReflowEnable()
 }
 
 void
 nsIPresShell::SetVerifyReflowEnable(bool aEnabled)
 {
   gVerifyReflowEnabled = aEnabled;
 }
 
-/* virtual */ void
-nsIPresShell::AddAutoWeakFrameExternal(AutoWeakFrame* aWeakFrame)
-{
-  AddAutoWeakFrameInternal(aWeakFrame);
-}
-
-void
-nsIPresShell::AddAutoWeakFrameInternal(AutoWeakFrame* aWeakFrame)
+void
+nsIPresShell::AddAutoWeakFrame(AutoWeakFrame* aWeakFrame)
 {
   if (aWeakFrame->GetFrame()) {
     aWeakFrame->GetFrame()->AddStateBits(NS_FRAME_EXTERNAL_REFERENCE);
   }
   aWeakFrame->SetPreviousWeakFrame(mAutoWeakFrames);
   mAutoWeakFrames = aWeakFrame;
 }
 
-/* virtual */ void
-nsIPresShell::AddWeakFrameExternal(WeakFrame* aWeakFrame)
-{
-  AddWeakFrameInternal(aWeakFrame);
-}
-
-void
-nsIPresShell::AddWeakFrameInternal(WeakFrame* aWeakFrame)
+void
+nsIPresShell::AddWeakFrame(WeakFrame* aWeakFrame)
 {
   if (aWeakFrame->GetFrame()) {
     aWeakFrame->GetFrame()->AddStateBits(NS_FRAME_EXTERNAL_REFERENCE);
   }
   MOZ_ASSERT(!mWeakFrames.GetEntry(aWeakFrame));
   mWeakFrames.PutEntry(aWeakFrame);
 }
 
-/* virtual */ void
-nsIPresShell::RemoveAutoWeakFrameExternal(AutoWeakFrame* aWeakFrame)
-{
-  RemoveAutoWeakFrameInternal(aWeakFrame);
-}
-
-void
-nsIPresShell::RemoveAutoWeakFrameInternal(AutoWeakFrame* aWeakFrame)
+void
+nsIPresShell::RemoveAutoWeakFrame(AutoWeakFrame* aWeakFrame)
 {
   if (mAutoWeakFrames == aWeakFrame) {
     mAutoWeakFrames = aWeakFrame->GetPreviousWeakFrame();
     return;
   }
   AutoWeakFrame* nextWeak = mAutoWeakFrames;
   while (nextWeak && nextWeak->GetPreviousWeakFrame() != aWeakFrame) {
     nextWeak = nextWeak->GetPreviousWeakFrame();
   }
   if (nextWeak) {
     nextWeak->SetPreviousWeakFrame(aWeakFrame->GetPreviousWeakFrame());
   }
 }
 
-/* virtual */ void
-nsIPresShell::RemoveWeakFrameExternal(WeakFrame* aWeakFrame)
-{
-  RemoveWeakFrameInternal(aWeakFrame);
-}
-
-void
-nsIPresShell::RemoveWeakFrameInternal(WeakFrame* aWeakFrame)
+void
+nsIPresShell::RemoveWeakFrame(WeakFrame* aWeakFrame)
 {
   MOZ_ASSERT(mWeakFrames.GetEntry(aWeakFrame));
   mWeakFrames.RemoveEntry(aWeakFrame);
 }
 
 already_AddRefed<nsFrameSelection>
 nsIPresShell::FrameSelection()
 {
@@ -2471,22 +2447,16 @@ PresShell::CheckVisibilityContent(nsICon
   *aRetval = false;
   DoCheckVisibility(mPresContext, aNode, aStartOffset, aEndOffset, aRetval);
   return NS_OK;
 }
 
 //end implementations nsISelectionController
 
 nsIFrame*
-nsIPresShell::GetRootFrameExternal() const
-{
-  return mFrameConstructor->GetRootFrame();
-}
-
-nsIFrame*
 nsIPresShell::GetRootScrollFrame() const
 {
   nsIFrame* rootFrame = mFrameConstructor->GetRootFrame();
   // Ensure root frame is a viewport frame
   if (!rootFrame || !rootFrame->IsViewportFrame())
     return nullptr;
   nsIFrame* theFrame = rootFrame->PrincipalChildList().FirstChild();
   if (!theFrame || !theFrame->IsScrollFrame())
@@ -2501,22 +2471,16 @@ nsIPresShell::GetRootScrollFrameAsScroll
   if (!frame)
     return nullptr;
   nsIScrollableFrame* scrollableFrame = do_QueryFrame(frame);
   NS_ASSERTION(scrollableFrame,
                "All scroll frames must implement nsIScrollableFrame");
   return scrollableFrame;
 }
 
-nsIScrollableFrame*
-nsIPresShell::GetRootScrollFrameAsScrollableExternal() const
-{
-  return GetRootScrollFrameAsScrollable();
-}
-
 nsIPageSequenceFrame*
 PresShell::GetPageSequenceFrame() const
 {
   nsIFrame* frame = mFrameConstructor->GetPageSequenceFrame();
   return do_QueryFrame(frame);
 }
 
 nsCanvasFrame*
@@ -9819,48 +9783,34 @@ PresShell::Observe(nsISupports* aSubject
     return NS_OK;
   }
 
   NS_WARNING("unrecognized topic in PresShell::Observe");
   return NS_ERROR_FAILURE;
 }
 
 bool
-nsIPresShell::AddRefreshObserverInternal(nsARefreshObserver* aObserver,
-                                         FlushType aFlushType)
+nsIPresShell::AddRefreshObserver(nsARefreshObserver* aObserver,
+                                 FlushType aFlushType)
 {
   nsPresContext* presContext = GetPresContext();
   return presContext &&
       presContext->RefreshDriver()->AddRefreshObserver(aObserver, aFlushType);
 }
 
-/* virtual */ bool
-nsIPresShell::AddRefreshObserverExternal(nsARefreshObserver* aObserver,
-                                         FlushType aFlushType)
-{
-  return AddRefreshObserverInternal(aObserver, aFlushType);
-}
-
 bool
-nsIPresShell::RemoveRefreshObserverInternal(nsARefreshObserver* aObserver,
-                                            FlushType aFlushType)
+nsIPresShell::RemoveRefreshObserver(nsARefreshObserver* aObserver,
+                                    FlushType aFlushType)
 {
   nsPresContext* presContext = GetPresContext();
   return presContext &&
       presContext->RefreshDriver()->RemoveRefreshObserver(aObserver, aFlushType);
 }
 
 /* virtual */ bool
-nsIPresShell::RemoveRefreshObserverExternal(nsARefreshObserver* aObserver,
-                                            FlushType aFlushType)
-{
-  return RemoveRefreshObserverInternal(aObserver, aFlushType);
-}
-
-/* virtual */ bool
 nsIPresShell::AddPostRefreshObserver(nsAPostRefreshObserver* aObserver)
 {
   nsPresContext* presContext = GetPresContext();
   if (!presContext) {
     return false;
   }
   presContext->RefreshDriver()->AddPostRefreshObserver(aObserver);
   return true;
--- a/layout/base/nsIPresShell.h
+++ b/layout/base/nsIPresShell.h
@@ -270,30 +270,27 @@ public:
    * Set the document accessible for this pres shell.
    */
   void SetDocAccessible(mozilla::a11y::DocAccessible* aDocAccessible)
   {
     mDocAccessible = aDocAccessible;
   }
 #endif
 
-#ifdef MOZILLA_INTERNAL_API
   mozilla::StyleSetHandle StyleSet() const { return mStyleSet; }
 
   nsCSSFrameConstructor* FrameConstructor() const { return mFrameConstructor; }
 
   nsFrameManager* FrameManager() const {
     // reinterpret_cast is valid since nsFrameManager does not add
     // any members over nsFrameManagerBase.
     return reinterpret_cast<nsFrameManager*>
                            (const_cast<nsIPresShell*>(this)->mFrameManager);
   }
 
-#endif
-
   /* Enable/disable author style level. Disabling author style disables the entire
    * author level of the cascade, including the HTML preshint level.
    */
   // XXX these could easily be inlined, but there is a circular #include
   // problem with nsStyleSet.
   void SetAuthorStyleDisabled(bool aDisabled);
   bool GetAuthorStyleDisabled() const;
 
@@ -386,41 +383,30 @@ public:
   /**
    * Called when document load completes.
    */
   virtual void LoadComplete() = 0;
 
   /**
    * This calls through to the frame manager to get the root frame.
    */
-  virtual nsIFrame* GetRootFrameExternal() const;
   nsIFrame* GetRootFrame() const {
-#ifdef MOZILLA_INTERNAL_API
     return mFrameManager->GetRootFrame();
-#else
-    return GetRootFrameExternal();
-#endif
   }
 
   /*
    * Get root scroll frame from FrameManager()->GetRootFrame().
    */
   nsIFrame* GetRootScrollFrame() const;
 
   /*
    * The same as GetRootScrollFrame, but returns an nsIScrollableFrame
    */
   nsIScrollableFrame* GetRootScrollFrameAsScrollable() const;
 
-  /*
-   * The same as GetRootScrollFrame, but returns an nsIScrollableFrame.
-   * Can be called by code not linked into gklayout.
-   */
-  virtual nsIScrollableFrame* GetRootScrollFrameAsScrollableExternal() const;
-
   /**
    * Get the current focused content or DOM selection that should be the
    * target for scrolling.
    */
   already_AddRefed<nsIContent> GetContentForScrolling() const;
 
   /**
    * Get the DOM selection that should be the target for scrolling, if there
@@ -1156,59 +1142,21 @@ public:
    * frames.
    */
   virtual already_AddRefed<mozilla::gfx::SourceSurface>
   RenderSelection(nsISelection* aSelection,
                   const mozilla::LayoutDeviceIntPoint aPoint,
                   mozilla::LayoutDeviceIntRect* aScreenRect,
                   uint32_t aFlags) = 0;
 
-  void AddAutoWeakFrameInternal(AutoWeakFrame* aWeakFrame);
-  virtual void AddAutoWeakFrameExternal(AutoWeakFrame* aWeakFrame);
-  void AddWeakFrameInternal(WeakFrame* aWeakFrame);
-  virtual void AddWeakFrameExternal(WeakFrame* aWeakFrame);
-
-  void AddAutoWeakFrame(AutoWeakFrame* aWeakFrame)
-  {
-#ifdef MOZILLA_INTERNAL_API
-    AddAutoWeakFrameInternal(aWeakFrame);
-#else
-    AddAutoWeakFrameExternal(aWeakFrame);
-#endif
-  }
-  void AddWeakFrame(WeakFrame* aWeakFrame)
-  {
-#ifdef MOZILLA_INTERNAL_API
-    AddWeakFrameInternal(aWeakFrame);
-#else
-    AddWeakFrameExternal(aWeakFrame);
-#endif
-  }
+  void AddAutoWeakFrame(AutoWeakFrame* aWeakFrame);
+  void AddWeakFrame(WeakFrame* aWeakFrame);
 
-  void RemoveAutoWeakFrameInternal(AutoWeakFrame* aWeakFrame);
-  virtual void RemoveAutoWeakFrameExternal(AutoWeakFrame* aWeakFrame);
-  void RemoveWeakFrameInternal(WeakFrame* aWeakFrame);
-  virtual void RemoveWeakFrameExternal(WeakFrame* aWeakFrame);
-
-  void RemoveAutoWeakFrame(AutoWeakFrame* aWeakFrame)
-  {
-#ifdef MOZILLA_INTERNAL_API
-    RemoveAutoWeakFrameInternal(aWeakFrame);
-#else
-    RemoveAutoWeakFrameExternal(aWeakFrame);
-#endif
-  }
-  void RemoveWeakFrame(WeakFrame* aWeakFrame)
-  {
-#ifdef MOZILLA_INTERNAL_API
-    RemoveWeakFrameInternal(aWeakFrame);
-#else
-    RemoveWeakFrameExternal(aWeakFrame);
-#endif
-  }
+  void RemoveAutoWeakFrame(AutoWeakFrame* aWeakFrame);
+  void RemoveWeakFrame(WeakFrame* aWeakFrame);
 
 #ifdef DEBUG
   nsIFrame* GetDrawEventTargetFrame() { return mDrawEventTargetFrame; }
 #endif
 
   /**
    * Stop or restart non synthetic test mouse event handling on *all*
    * presShells.
@@ -1673,25 +1621,16 @@ public:
    */
   nsresult HasRuleProcessorUsedByMultipleStyleSets(uint32_t aSheetType,
                                                    bool* aRetVal);
 
   /**
    * Refresh observer management.
    */
 protected:
-  virtual bool AddRefreshObserverExternal(nsARefreshObserver* aObserver,
-                                          mozilla::FlushType aFlushType);
-  bool AddRefreshObserverInternal(nsARefreshObserver* aObserver,
-                                  mozilla::FlushType aFlushType);
-  virtual bool RemoveRefreshObserverExternal(nsARefreshObserver* aObserver,
-                                             mozilla::FlushType aFlushType);
-  bool RemoveRefreshObserverInternal(nsARefreshObserver* aObserver,
-                                     mozilla::FlushType aFlushType);
-
   void DoObserveStyleFlushes();
   void DoObserveLayoutFlushes();
 
   /**
    * Do computations necessary to determine if font size inflation is enabled.
    * This value is cached after computation, as the computation is somewhat
    * expensive.
    */
@@ -1719,32 +1658,19 @@ protected:
 #ifdef DEBUG
     MOZ_ASSERT(mAllocatedPointers.Contains(aPtr));
     mAllocatedPointers.RemoveEntry(aPtr);
 #endif
   }
 
 public:
   bool AddRefreshObserver(nsARefreshObserver* aObserver,
-                          mozilla::FlushType aFlushType) {
-#ifdef MOZILLA_INTERNAL_API
-    return AddRefreshObserverInternal(aObserver, aFlushType);
-#else
-    return AddRefreshObserverExternal(aObserver, aFlushType);
-#endif
-  }
-
+                          mozilla::FlushType aFlushType);
   bool RemoveRefreshObserver(nsARefreshObserver* aObserver,
-                             mozilla::FlushType aFlushType) {
-#ifdef MOZILLA_INTERNAL_API
-    return RemoveRefreshObserverInternal(aObserver, aFlushType);
-#else
-    return RemoveRefreshObserverExternal(aObserver, aFlushType);
-#endif
-  }
+                             mozilla::FlushType aFlushType);
 
   virtual bool AddPostRefreshObserver(nsAPostRefreshObserver* aObserver);
   virtual bool RemovePostRefreshObserver(nsAPostRefreshObserver* aObserver);
 
   /**
    * Initialize and shut down static variables.
    */
   static void InitializeStatics();
--- a/layout/base/nsPresContext.cpp
+++ b/layout/base/nsPresContext.cpp
@@ -1307,17 +1307,17 @@ nsPresContext::SetSMILAnimations(nsIDocu
         if (aOldMode != imgIContainer::kDontAnimMode)
           controller->Pause(nsSMILTimeContainer::PAUSE_USERPREF);
         break;
     }
   }
 }
 
 void
-nsPresContext::SetImageAnimationModeInternal(uint16_t aMode)
+nsPresContext::SetImageAnimationMode(uint16_t aMode)
 {
   NS_ASSERTION(aMode == imgIContainer::kNormalAnimMode ||
                aMode == imgIContainer::kDontAnimMode ||
                aMode == imgIContainer::kLoopOnceAnimMode, "Wrong Animation Mode is being set!");
 
   // Image animation mode cannot be changed when rendering to a printer.
   if (!IsDynamic())
     return;
@@ -1335,22 +1335,16 @@ nsPresContext::SetImageAnimationModeInte
       }
       SetSMILAnimations(doc, aMode, mImageAnimationMode);
     }
   }
 
   mImageAnimationMode = aMode;
 }
 
-void
-nsPresContext::SetImageAnimationModeExternal(uint16_t aMode)
-{
-  SetImageAnimationModeInternal(aMode);
-}
-
 already_AddRefed<nsIAtom>
 nsPresContext::GetContentLanguage() const
 {
   nsAutoString language;
   Document()->GetContentLanguage(language);
   language.StripWhitespace();
 
   // Content-Language may be a comma-separated list of language codes,
@@ -1601,48 +1595,36 @@ nsPresContext::SetContainer(nsIDocShell*
   }
   UpdateIsChrome();
   if (mContainer) {
     GetDocumentColorPreferences();
   }
 }
 
 nsISupports*
-nsPresContext::GetContainerWeakInternal() const
+nsPresContext::GetContainerWeak() const
 {
   return static_cast<nsIDocShell*>(mContainer);
 }
 
-nsISupports*
-nsPresContext::GetContainerWeakExternal() const
-{
-  return GetContainerWeakInternal();
-}
-
 nsIDocShell*
 nsPresContext::GetDocShell() const
 {
   return mContainer;
 }
 
 /* virtual */ void
 nsPresContext::Detach()
 {
   SetContainer(nullptr);
   SetLinkHandler(nullptr);
 }
 
 bool
-nsPresContext::BidiEnabledExternal() const
-{
-  return BidiEnabledInternal();
-}
-
-bool
-nsPresContext::BidiEnabledInternal() const
+nsPresContext::BidiEnabled() const
 {
   return Document()->GetBidiEnabled();
 }
 
 void
 nsPresContext::SetBidiEnabled() const
 {
   if (mShell) {
--- a/layout/base/nsPresContext.h
+++ b/layout/base/nsPresContext.h
@@ -215,17 +215,16 @@ public:
   nsIDocument* Document() const
   {
       NS_ASSERTION(!mShell || !mShell->GetDocument() ||
                    mShell->GetDocument() == mDocument,
                    "nsPresContext doesn't have the same document as nsPresShell!");
       return mDocument;
   }
 
-#ifdef MOZILLA_INTERNAL_API
   mozilla::StyleSetHandle StyleSet() const { return GetPresShell()->StyleSet(); }
 
   nsFrameManager* FrameManager()
     { return PresShell()->FrameManager(); }
 
   nsCSSFrameConstructor* FrameConstructor()
     { return PresShell()->FrameConstructor(); }
 
@@ -238,17 +237,16 @@ public:
   mozilla::RestyleManager* RestyleManager() {
     MOZ_ASSERT(mRestyleManager);
     return mRestyleManager;
   }
 
   mozilla::CounterStyleManager* CounterStyleManager() const {
     return mCounterStyleManager;
   }
-#endif
 
   /**
    * Rebuilds all style data by throwing out the old rule tree and
    * building a new one, and additionally applying aExtraHint (which
    * must not contain nsChangeHint_ReconstructFrame) to the root frame.
    * For aRestyleHint, see RestyleManager::RebuildAllStyleData.
    * Also rebuild the user font set and counter style manager.
    */
@@ -314,25 +312,17 @@ public:
    * Notify the context that the document's compatibility mode has changed
    */
   void CompatibilityModeChanged();
 
   /**
    * Access the image animation mode for this context
    */
   uint16_t     ImageAnimationMode() const { return mImageAnimationMode; }
-  virtual void SetImageAnimationModeExternal(uint16_t aMode);
-  void SetImageAnimationModeInternal(uint16_t aMode);
-#ifdef MOZILLA_INTERNAL_API
-  void SetImageAnimationMode(uint16_t aMode)
-  { SetImageAnimationModeInternal(aMode); }
-#else
-  void SetImageAnimationMode(uint16_t aMode)
-  { SetImageAnimationModeExternal(aMode); }
-#endif
+  void SetImageAnimationMode(uint16_t aMode);
 
   /**
    * Get medium of presentation
    */
   nsIAtom* Medium() {
     if (!mIsEmulatingMedia)
       return mMedium;
     return mMediaEmulated;
@@ -424,25 +414,17 @@ public:
 
   bool GetUseFocusColors() const { return mUseFocusColors; }
   uint8_t FocusRingWidth() const { return mFocusRingWidth; }
   bool GetFocusRingOnAnything() const { return mFocusRingOnAnything; }
   uint8_t GetFocusRingStyle() const { return mFocusRingStyle; }
 
   void SetContainer(nsIDocShell* aContainer);
 
-  virtual nsISupports* GetContainerWeakExternal() const;
-  nsISupports* GetContainerWeakInternal() const;
-#ifdef MOZILLA_INTERNAL_API
-  nsISupports* GetContainerWeak() const
-  { return GetContainerWeakInternal(); }
-#else
-  nsISupports* GetContainerWeak() const
-  { return GetContainerWeakExternal(); }
-#endif
+  nsISupports* GetContainerWeak() const;
 
   nsIDocShell* GetDocShell() const;
 
   // XXX this are going to be replaced with set/get container
   void SetLinkHandler(nsILinkHandler* aHandler) { mLinkHandler = aHandler; }
   nsILinkHandler* GetLinkHandler() { return mLinkHandler; }
 
   /**
@@ -775,23 +757,17 @@ public:
 
   /**
    *  Check if bidi enabled (set depending on the presence of RTL
    *  characters or when default directionality is RTL).
    *  If enabled, we should apply the Unicode Bidi Algorithm
    *
    *  @lina 07/12/2000
    */
-#ifdef MOZILLA_INTERNAL_API
-  bool BidiEnabled() const { return BidiEnabledInternal(); }
-#else
-  bool BidiEnabled() const { return BidiEnabledExternal(); }
-#endif
-  virtual bool BidiEnabledExternal() const;
-  bool BidiEnabledInternal() const;
+  bool BidiEnabled() const;
 
   /**
    *  Set bidi enabled. This means we should apply the Unicode Bidi Algorithm
    *
    *  @lina 07/12/2000
    */
   void SetBidiEnabled() const;
 
--- a/layout/generic/nsFrameSelection.cpp
+++ b/layout/generic/nsFrameSelection.cpp
@@ -2903,19 +2903,17 @@ nsFrameSelection::DeleteFromDocument()
     res = range->DeleteContents();
     if (NS_FAILED(res))
       return res;
   }
 
   // Collapse to the new location.
   // If we deleted one character, then we move back one element.
   // FIXME  We don't know how to do this past frame boundaries yet.
-  if (isCollapsed)
-    mDomSelections[index]->Collapse(mDomSelections[index]->GetAnchorNode(), mDomSelections[index]->AnchorOffset()-1);
-  else if (mDomSelections[index]->AnchorOffset() > 0)
+  if (mDomSelections[index]->AnchorOffset() > 0)
     mDomSelections[index]->Collapse(mDomSelections[index]->GetAnchorNode(), mDomSelections[index]->AnchorOffset());
 #ifdef DEBUG
   else
     printf("Don't know how to set selection back past frame boundary\n");
 #endif
 
   return NS_OK;
 }
--- a/layout/style/ServoStyleSet.cpp
+++ b/layout/style/ServoStyleSet.cpp
@@ -609,30 +609,27 @@ ServoStyleSet::ResolveStyleLazily(Elemen
 }
 
 already_AddRefed<ServoStyleContext>
 ServoStyleSet::ResolveInheritingAnonymousBoxStyle(nsIAtom* aPseudoTag,
                                                   ServoStyleContext* aParentContext)
 {
   MOZ_ASSERT(nsCSSAnonBoxes::IsAnonBox(aPseudoTag) &&
              !nsCSSAnonBoxes::IsNonInheritingAnonBox(aPseudoTag));
+  MOZ_ASSERT_IF(aParentContext, !StylistNeedsUpdate());
+
+  UpdateStylistIfNeeded();
+
   RefPtr<ServoStyleContext> style = nullptr;
 
   if (aParentContext) {
     style = aParentContext->GetCachedInheritingAnonBoxStyle(aPseudoTag);
   }
 
   if (!style) {
-    // People like to call into here from random attribute notifications (see
-    // bug 1388234, and bug 1389029).
-    //
-    // We may get a wrong cached style if the stylist needs an update, but we'll
-    // have a whole restyle scheduled anyway.
-    UpdateStylistIfNeeded();
-
     style =
       Servo_ComputedValues_GetForAnonymousBox(aParentContext,
                                               aPseudoTag,
                                               mRawSet.get()).Consume();
     MOZ_ASSERT(style);
     if (aParentContext) {
       aParentContext->SetCachedInheritedAnonBoxStyle(aPseudoTag, style);
     }
--- a/layout/style/test/property_database.js
+++ b/layout/style/test/property_database.js
@@ -4168,17 +4168,17 @@ var gCSSProperties = {
     quirks_values: { "5": "5px" },
   },
   "transition": {
     domProp: "transition",
     inherited: false,
     type: CSS_TYPE_TRUE_SHORTHAND,
     subproperties: [ "transition-property", "transition-duration", "transition-timing-function", "transition-delay" ],
     initial_values: [ "all 0s ease 0s", "all", "0s", "0s 0s", "ease" ],
-    other_values: [ "all 0s cubic-bezier(0.25, 0.1, 0.25, 1.0) 0s", "width 1s linear 2s", "width 1s 2s linear", "width linear 1s 2s", "linear width 1s 2s", "linear 1s width 2s", "linear 1s 2s width", "1s width linear 2s", "1s width 2s linear", "1s 2s width linear", "1s linear width 2s", "1s linear 2s width", "1s 2s linear width", "width linear 1s", "width 1s linear", "linear width 1s", "linear 1s width", "1s width linear", "1s linear width", "1s 2s width", "1s width 2s", "width 1s 2s", "1s 2s linear", "1s linear 2s", "linear 1s 2s", "width 1s", "1s width", "linear 1s", "1s linear", "1s 2s", "2s 1s", "width", "linear", "1s", "height", "2s", "ease-in-out", "2s ease-in", "opacity linear", "ease-out 2s", "2s color, 1s width, 500ms height linear, 1s opacity 4s cubic-bezier(0.0, 0.1, 1.0, 1.0)", "1s \\32width linear 2s", "1s -width linear 2s", "1s -\\32width linear 2s", "1s \\32 0width linear 2s", "1s -\\32 0width linear 2s", "1s \\2width linear 2s", "1s -\\2width linear 2s", "2s, 1s width", "1s width, 2s", "2s all, 1s width", "1s width, 2s all", "2s all, 1s width", "2s width, 1s all", "3s --my-color" ],
+    other_values: [ "all 0s cubic-bezier(0.25, 0.1, 0.25, 1.0) 0s", "width 1s linear 2s", "width 1s 2s linear", "width linear 1s 2s", "linear width 1s 2s", "linear 1s width 2s", "linear 1s 2s width", "1s width linear 2s", "1s width 2s linear", "1s 2s width linear", "1s linear width 2s", "1s linear 2s width", "1s 2s linear width", "width linear 1s", "width 1s linear", "linear width 1s", "linear 1s width", "1s width linear", "1s linear width", "1s 2s width", "1s width 2s", "width 1s 2s", "1s 2s linear", "1s linear 2s", "linear 1s 2s", "width 1s", "1s width", "linear 1s", "1s linear", "1s 2s", "2s 1s", "width", "linear", "1s", "height", "2s", "ease-in-out", "2s ease-in", "opacity linear", "ease-out 2s", "2s color, 1s width, 500ms height linear, 1s opacity 4s cubic-bezier(0.0, 0.1, 1.0, 1.0)", "1s \\32width linear 2s", "1s -width linear 2s", "1s -\\32width linear 2s", "1s \\32 0width linear 2s", "1s -\\32 0width linear 2s", "1s \\2width linear 2s", "1s -\\2width linear 2s", "2s, 1s width", "1s width, 2s", "2s all, 1s width", "1s width, 2s all", "2s all, 1s width", "2s width, 1s all", "3s --my-color", "none", "none 2s linear 2s" ],
     invalid_values: [ "1s width, 2s none", "2s none, 1s width", "2s inherit", "inherit 2s", "2s width, 1s inherit", "2s inherit, 1s width", "2s initial", "1s width,,2s color", "1s width, ,2s color", "bounce 1s cubic-bezier(0, rubbish) 2s", "bounce 1s steps(rubbish) 2s" ]
   },
   "transition-delay": {
     domProp: "transitionDelay",
     inherited: false,
     type: CSS_TYPE_LONGHAND,
     initial_values: [ "0s", "0ms" ],
     other_values: [ "1s", "250ms", "-100ms", "-1s", "1s, 250ms, 2.3s"],
--- a/layout/style/test/test_shorthand_property_getters.html
+++ b/layout/style/test/test_shorthand_property_getters.html
@@ -203,16 +203,18 @@ is(e.style.transition, "", "should not h
 e.setAttribute("style", "transition-property: all; transition-duration: 1s, 200ms; transition-timing-function: ease-in, linear; transition-delay: 0s, 1s");
 is(e.style.transition, "", "should not have transition shorthand (lists different lengths)");
 e.setAttribute("style", "transition-property: color, width; transition-duration: 1s, 200ms, 300ms; transition-timing-function: ease-in, linear; transition-delay: 0s, 1s");
 is(e.style.transition, "", "should not have transition shorthand (lists different lengths)");
 e.setAttribute("style", "transition-property: color, width; transition-duration: 1s, 200ms; transition-timing-function: ease-in, linear, ease-out; transition-delay: 0s, 1s");
 is(e.style.transition, "", "should not have transition shorthand (lists different lengths)");
 e.setAttribute("style", "transition-property: color, width; transition-duration: 1s, 200ms; transition-timing-function: ease-in, linear; transition-delay: 0s, 1s, 0s");
 is(e.style.transition, "", "should not have transition shorthand (lists different lengths)");
+e.setAttribute("style", "transition: color, width; transition-delay: 0s");
+is(e.style.transition, "", "should not have transition shorthand (lists different lengths)");
 
 e.setAttribute("style", "animation-name: bounce, roll; animation-duration: 1s, 200ms; animation-timing-function: ease-in, linear; animation-delay: 0s, 1s; animation-direction: normal, reverse; animation-fill-mode: forwards, backwards; animation-iteration-count: infinite, 2; animation-play-state: paused, running;");
 isnot(e.style.animation, "", "should have animation shorthand (lists same length)");
 e.setAttribute("style", "animation-name: bounce, roll, left; animation-duration: 1s, 200ms; animation-timing-function: ease-in, linear; animation-delay: 0s, 1s; animation-direction: normal, reverse; animation-fill-mode: forwards, backwards; animation-iteration-count: infinite, 2; animation-play-state: paused, running;");
 is(e.style.animation, "", "should not have animation shorthand (lists different lengths)");
 e.setAttribute("style", "animation-name: bounce; animation-duration: 1s, 200ms; animation-timing-function: ease-in, linear; animation-delay: 0s, 1s; animation-direction: normal, reverse; animation-fill-mode: forwards, backwards; animation-iteration-count: infinite, 2; animation-play-state: paused, running;");
 is(e.style.animation, "", "should not have animation shorthand (lists different lengths)");
 e.setAttribute("style", "animation-name: bounce, roll; animation-duration: 1s, 200ms, 300ms; animation-timing-function: ease-in, linear; animation-delay: 0s, 1s; animation-direction: normal, reverse; animation-fill-mode: forwards, backwards; animation-iteration-count: infinite, 2; animation-play-state: paused, running;");
--- a/mobile/android/app/src/main/res/layout-large/tabs_layout_item_view.xml
+++ b/mobile/android/app/src/main/res/layout-large/tabs_layout_item_view.xml
@@ -25,31 +25,29 @@
                android:layout_width="0dip"
                android:layout_height="wrap_content"
                android:layout_weight="1.0"
                style="@style/TabLayoutItemTextAppearance"
                android:textSize="14sp"
                android:textColor="@color/tab_item_title"
                android:singleLine="true"
                android:duplicateParentState="true"
-               android:layout_gravity="center_vertical"
                gecko:fadeWidth="15dp"
-               android:minHeight="24dp"
                android:paddingRight="5dp"
                android:paddingEnd="5dp"
                android:paddingLeft="0dp"
                android:paddingStart="0dp"
                android:drawablePadding="6dp"/>
 
         <!-- Use of baselineAlignBottom only supported from API 11+ - if this needs to work on lower API versions
              we'll need to override getBaseLine() and return image height, but we assume this won't happen -->
         <ImageView android:id="@+id/close"
                      style="@style/TabsItemClose"
-                     android:layout_width="24dp"
-                     android:layout_height="24dp"
+                     android:layout_width="wrap_content"
+                     android:layout_height="wrap_content"
                      android:scaleType="center"
                      android:baselineAlignBottom="true"
                      android:background="@android:color/transparent"
                      android:contentDescription="@string/close_tab"
                      android:src="@drawable/tab_item_close_button"
                      android:duplicateParentState="true"/>
 
     </LinearLayout>
--- a/mobile/android/app/src/main/res/layout/preference_topsites_panel_dialog.xml
+++ b/mobile/android/app/src/main/res/layout/preference_topsites_panel_dialog.xml
@@ -23,29 +23,29 @@
     <org.mozilla.gecko.widget.SwitchPreferenceView
         android:id="@+id/preference_pocket"
         android:layout_width="match_parent"
         android:layout_height="wrap_content"
         style="@style/Gecko.SwitchPreferenceView"
         android:paddingBottom="@dimen/dialog_switchpreferenceview_padding"
         android:text="@string/activity_stream_topstories"
         gecko:androidPreferenceKey="pref_activitystream_pocket_enabled"
-        gecko:defaultValue="true"/>
+        gecko:defaultValue="@bool/pref_activitystream_pocket_enabled_default"/>
 
     <org.mozilla.gecko.widget.SwitchPreferenceView
         android:layout_width="match_parent"
         android:layout_height="wrap_content"
         style="@style/Gecko.SwitchPreferenceView"
         android:paddingBottom="@dimen/dialog_switchpreferenceview_padding"
         android:text="@string/pref_dialog_activitystream_recentBookmarks"
         gecko:androidPreferenceKey="pref_activitystream_recentbookmarks_enabled"
-        gecko:defaultValue="true"/>
+        gecko:defaultValue="@bool/pref_activitystream_recentbookmarks_enabled_default"/>
 
     <org.mozilla.gecko.widget.SwitchPreferenceView
         android:layout_width="match_parent"
         android:layout_height="wrap_content"
         style="@style/Gecko.SwitchPreferenceView"
         android:paddingBottom="@dimen/dialog_switchpreferenceview_padding"
         android:text="@string/pref_dialog_activitystream_visited"
         gecko:androidPreferenceKey="pref_activitystream_visited_enabled"
-        gecko:defaultValue="true"/>
+        gecko:defaultValue="@bool/pref_activitystream_visited_enabled_default"/>
 
 </LinearLayout>
\ No newline at end of file
--- a/mobile/android/app/src/main/res/layout/tabs_layout_item_view.xml
+++ b/mobile/android/app/src/main/res/layout/tabs_layout_item_view.xml
@@ -18,17 +18,17 @@
                   android:duplicateParentState="true"
                   android:paddingLeft="@dimen/tab_highlight_stroke_width"
                   android:paddingRight="@dimen/tab_highlight_stroke_width"
                   android:paddingBottom="@dimen/tab_highlight_stroke_width">
 
        <org.mozilla.gecko.widget.FadedSingleColorTextView
                android:id="@+id/title"
                android:layout_width="0dip"
-               android:layout_height="match_parent"
+               android:layout_height="wrap_content"
                android:layout_weight="1.0"
                style="@style/TabLayoutItemTextAppearance"
                android:textSize="14sp"
                android:textColor="@color/tab_item_title"
                android:singleLine="true"
                android:duplicateParentState="true"
                gecko:fadeWidth="15dp"
                android:paddingRight="5dp"
@@ -36,18 +36,18 @@
                android:paddingLeft="0dp"
                android:paddingStart="0dp"
                android:drawablePadding="6dp"/>
 
         <!-- Use of baselineAlignBottom only supported from API 11+ - if this needs to work on lower API versions
              we'll need to override getBaseLine() and return image height, but we assume this won't happen -->
         <ImageView android:id="@+id/close"
                      style="@style/TabsItemClose"
-                     android:layout_width="24dp"
-                     android:layout_height="24dp"
+                     android:layout_width="wrap_content"
+                     android:layout_height="wrap_content"
                      android:scaleType="center"
                      android:baselineAlignBottom="true"
                      android:background="@android:color/transparent"
                      android:contentDescription="@string/close_tab"
                      android:src="@drawable/tab_item_close_button"
                      android:duplicateParentState="true"/>
 
     </LinearLayout>
--- a/mobile/android/app/src/main/res/values/bool.xml
+++ b/mobile/android/app/src/main/res/values/bool.xml
@@ -10,9 +10,12 @@
          with the configuration retrieved by HardwareUtils (e.g. some custom ROMs allow the user to
          choose a phone or tablet version of the UI even though the hardware stays the same). This
          can cause crashes when we branch on the value returned by HardwareUtils.
 
          In order to work around this, we define the resource size in resources with the expectation that
          we branch on that value, rather than HardwareUtils, so our code is consistent with the used resources.
          See bug 1277379 for a initiative to move all of the HardwareUtils code over. -->
     <bool name="is_large_resource">false</bool>
+    <bool name="pref_activitystream_pocket_enabled_default">true</bool>
+    <bool name="pref_activitystream_visited_enabled_default">true</bool>
+    <bool name="pref_activitystream_recentbookmarks_enabled_default">true</bool>
 </resources>
--- a/mobile/android/base/java/org/mozilla/gecko/activitystream/homepanel/HighlightsDividerItemDecoration.java
+++ b/mobile/android/base/java/org/mozilla/gecko/activitystream/homepanel/HighlightsDividerItemDecoration.java
@@ -32,21 +32,27 @@ import android.view.View;
     }
 
     @Override
     public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {
         final int left = parent.getPaddingLeft();
         final int right = parent.getWidth() - parent.getPaddingRight();
 
         final int childCount = parent.getChildCount();
-        for (int i = START_DRAWING_AT_POSITION; i < childCount; i++) {
+        for (int i = 0; i < childCount; i++) {
             final View child = parent.getChildAt(i);
+
+            if (parent.getChildAdapterPosition(child) < START_DRAWING_AT_POSITION) {
+                continue;
+            }
+
             if (child.getVisibility() == View.GONE) {
                 continue;
             }
+
             final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child
                     .getLayoutParams();
             final int top = child.getBottom() + params.bottomMargin;
             final int bottom = top + divider.getIntrinsicHeight();
             divider.setBounds(left, top, right, bottom);
             divider.draw(c);
         }
     }
--- a/mobile/android/base/java/org/mozilla/gecko/activitystream/homepanel/HighlightsLoader.java
+++ b/mobile/android/base/java/org/mozilla/gecko/activitystream/homepanel/HighlightsLoader.java
@@ -1,22 +1,26 @@
 /* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
  * 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/. */
 
 package org.mozilla.gecko.activitystream.homepanel;
 
 import android.content.Context;
+import android.content.SharedPreferences;
+import android.content.res.Resources;
 import android.database.ContentObserver;
 import android.database.Cursor;
 import android.os.SystemClock;
 import android.support.annotation.WorkerThread;
 import android.support.v4.content.AsyncTaskLoader;
 
+import org.mozilla.gecko.GeckoSharedPrefs;
+import org.mozilla.gecko.R;
 import org.mozilla.gecko.Telemetry;
 import org.mozilla.gecko.activitystream.ranking.HighlightsRanking;
 import org.mozilla.gecko.db.BrowserContract;
 import org.mozilla.gecko.db.BrowserDB;
 import org.mozilla.gecko.activitystream.homepanel.model.Highlight;
 import org.mozilla.gecko.util.ThreadUtils;
 
 import java.util.Collections;
@@ -58,17 +62,22 @@ import java.util.List;
         if (candidatesCursor == null) {
             return Collections.emptyList();
         }
 
         try {
             // From now on get notified about content updates and reload data - until loader is reset.
             enableContentUpdates();
 
-            final List<Highlight> highlights = HighlightsRanking.rank(candidatesCursor, highlightsLimit);
+            final SharedPreferences prefs = GeckoSharedPrefs.forProfile(getContext());
+            final Resources res = getContext().getResources();
+            final boolean includeHistory = prefs.getBoolean(ActivityStreamPanel.PREF_VISITED_ENABLED, res.getBoolean(R.bool.pref_activitystream_visited_enabled_default));
+            final boolean includeBookmarks = prefs.getBoolean(ActivityStreamPanel.PREF_BOOKMARKS_ENABLED, res.getBoolean(R.bool.pref_activitystream_recentbookmarks_enabled_default));
+
+            final List<Highlight> highlights = HighlightsRanking.rank(candidatesCursor, highlightsLimit, includeHistory, includeBookmarks);
             forceLoadHighlightMetadata(highlights); // force load now that we have a short list of the data.
 
             addToPerformanceHistogram(startTime);
 
             return highlights;
         } finally {
             candidatesCursor.close();
         }
--- a/mobile/android/base/java/org/mozilla/gecko/activitystream/homepanel/topstories/PocketStoriesLoader.java
+++ b/mobile/android/base/java/org/mozilla/gecko/activitystream/homepanel/topstories/PocketStoriesLoader.java
@@ -60,17 +60,17 @@ public class PocketStoriesLoader extends
     // Pocket API params and defaults
     private static final String GLOBAL_ENDPOINT = "https://getpocket.cdn.mozilla.net/v3/firefox/global-recs";
     private static final String PARAM_APIKEY = "consumer_key";
     private static final String APIKEY = AppConstants.MOZ_POCKET_API_KEY;
     private static final String PARAM_COUNT = "count";
     private static final int DEFAULT_COUNT = 3;
     private static final String PARAM_LOCALE = "locale_lang";
 
-    private static final long REFRESH_INTERVAL_MILLIS = TimeUnit.HOURS.toMillis(3);
+    private static final long REFRESH_INTERVAL_MILLIS = TimeUnit.HOURS.toMillis(1);
 
     private static final int BUFFER_SIZE = 2048;
     private static final int CONNECT_TIMEOUT = (int) TimeUnit.SECONDS.toMillis(15);
     private static final int READ_TIMEOUT = (int) TimeUnit.SECONDS.toMillis(15);
 
     private String localeLang;
     private final SharedPreferences sharedPreferences;
 
--- a/mobile/android/base/java/org/mozilla/gecko/activitystream/ranking/HighlightCandidate.java
+++ b/mobile/android/base/java/org/mozilla/gecko/activitystream/ranking/HighlightCandidate.java
@@ -1,16 +1,17 @@
 /* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
  * 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/. */
 
 package org.mozilla.gecko.activitystream.ranking;
 
 import android.database.Cursor;
+import android.database.sqlite.SQLiteDatabase;
 import android.net.Uri;
 import android.support.annotation.CheckResult;
 import android.support.annotation.IntDef;
 import android.support.annotation.Nullable;
 import android.support.annotation.VisibleForTesting;
 import org.mozilla.gecko.activitystream.homepanel.model.Highlight;
 
 import java.lang.annotation.Retention;
@@ -63,21 +64,28 @@ import java.lang.annotation.RetentionPol
             return values[featureName];
         }
 
         /* package-private */ void put(final @FeatureName int featureName, final double value) {
             values[featureName] = value;
         }
     }
 
+    /**
+     * The BOOKMARK_ID colmun value is set to -1 for non-bookmarks in
+     * {@link org.mozilla.gecko.db.BrowserProvider#getHighlightCandidates(SQLiteDatabase, String)}
+     */
+    private static final int COLUMN_VALUE_NON_BOOKMARK = -1;
+
     @VisibleForTesting final Features features = new Features();
     private Highlight highlight;
     private @Nullable String imageUrl;
     private String host;
     private double score;
+    private boolean isBookmark;
 
     /**
      * @return the HighlightCandidate, or null if the candidate is invalid.
      */
     @Nullable
     public static HighlightCandidate fromCursor(final Cursor cursor, final HighlightCandidateCursorIndices cursorIndices) {
         final HighlightCandidate candidate = new HighlightCandidate();
 
@@ -112,16 +120,18 @@ import java.lang.annotation.RetentionPol
         // The latter might simply be URIs with invalid characters in them (such as underscore...).
         // See Bug 1363521 and Bug 1378967.
         if (!uri.isHierarchical() || uri.getHost() == null) {
             // Note: we used to throw an exception but sometimes many Exceptions were thrown, potentially
             // impacting performance so we changed it to a boolean return.
             return false;
         }
 
+        candidate.isBookmark = cursor.getDouble(cursorIndices.bookmarkIDColumnIndex) != COLUMN_VALUE_NON_BOOKMARK;
+
         candidate.features.put(
                 FEATURE_AGE_IN_DAYS,
                 (System.currentTimeMillis() - cursor.getDouble(cursorIndices.historyDateLastVisitedColumnIndex))
                         / (1000 * 3600 * 24));
 
         candidate.features.put(
                 FEATURE_VISITS_COUNT,
                 cursor.getDouble(cursorIndices.visitsColumnIndex));
@@ -203,16 +213,20 @@ import java.lang.annotation.RetentionPol
     /* package-private */ String getUrl() {
         return highlight.getUrl();
     }
 
     /* package-private */ String getHost() {
         return host;
     }
 
+    /* package-private */ boolean isBookmark() {
+        return isBookmark;
+    }
+
     /**
      * Gets an estimate of the actual image url that should only be used to compare against other return
      * values of this method. See {@link Highlight#getFastImageURLForComparison()} for more details.
      */
     @Nullable
     /* package-private */ String getFastImageUrlForComparison() {
         return imageUrl;
     }
--- a/mobile/android/base/java/org/mozilla/gecko/activitystream/ranking/HighlightsRanking.java
+++ b/mobile/android/base/java/org/mozilla/gecko/activitystream/ranking/HighlightsRanking.java
@@ -6,27 +6,30 @@
 package org.mozilla.gecko.activitystream.ranking;
 
 import android.database.Cursor;
 import android.net.Uri;
 import android.support.annotation.VisibleForTesting;
 import android.text.TextUtils;
 import android.util.Log;
 import android.util.SparseArray;
+
+import org.mozilla.gecko.SharedPreferencesHelper;
 import org.mozilla.gecko.activitystream.homepanel.model.Highlight;
 import org.mozilla.gecko.util.MapUtils;
 
 import java.util.ArrayList;
 import java.util.Comparator;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
 
+import static android.R.attr.candidatesTextStyleSpans;
 import static android.R.attr.filter;
 import static java.util.Collections.sort;
 import static org.mozilla.gecko.activitystream.ranking.HighlightCandidate.FEATURE_AGE_IN_DAYS;
 import static org.mozilla.gecko.activitystream.ranking.HighlightCandidate.FEATURE_BOOKMARK_AGE_IN_MILLISECONDS;
 import static org.mozilla.gecko.activitystream.ranking.HighlightCandidate.FEATURE_DESCRIPTION_LENGTH;
 import static org.mozilla.gecko.activitystream.ranking.HighlightCandidate.FEATURE_DOMAIN_FREQUENCY;
 import static org.mozilla.gecko.activitystream.ranking.HighlightCandidate.FEATURE_IMAGE_COUNT;
 import static org.mozilla.gecko.activitystream.ranking.HighlightCandidate.FEATURE_IMAGE_SIZE;
@@ -98,19 +101,21 @@ public class HighlightsRanking {
     private static final double BOOKMARK_AGE_DIVIDEND = 3 * 24 * 60 * 60 * 1000;
 
     /**
      * Create a list of highlights based on the candidates provided by the input cursor.
      *
      * THIS METHOD IS CRITICAL FOR HIGHLIGHTS PERFORMANCE AND HAS BEEN OPTIMIZED (bug 1369604):
      * please be careful what you add to it!
      */
-    public static List<Highlight> rank(Cursor cursor, int limit) {
+    public static List<Highlight> rank(Cursor cursor, int limit, boolean includeHistory, boolean includeBookmarks) {
         List<HighlightCandidate> highlights = extractFeatures(cursor);
 
+        filterOutItemsPreffedOff(highlights, includeHistory, includeBookmarks);
+
         normalize(highlights);
 
         scoreEntries(highlights);
 
         filterOutItemsWithNoScore(highlights);
 
         sortDescendingByScore(highlights);
 
@@ -202,16 +207,33 @@ public class HighlightsRanking {
                     return 1;
                 } else {
                     return 0;
                 }
             }
         });
     }
 
+    @VisibleForTesting static void filterOutItemsPreffedOff(List<HighlightCandidate> candidates, final boolean includeHistory, final boolean includeBookmarks) {
+        // Pinned items are not bookmarks, and will be grouped with history.
+        filter(candidates, new Func1<HighlightCandidate, Boolean>() {
+            @Override
+            public Boolean call(HighlightCandidate candidate) {
+                if (includeBookmarks && includeHistory) {
+                    return true;
+                } else if (!includeBookmarks && !includeHistory) {
+                    return false;
+                } else {
+                    // Either B or H are enabled, but not both, so we can filter on bookmark state.
+                    return includeBookmarks == candidate.isBookmark();
+                }
+            }
+        });
+    }
+
     /**
      * Remove all items without or with a negative score.
      */
     @VisibleForTesting static void filterOutItemsWithNoScore(List<HighlightCandidate> candidates) {
         filter(candidates, new Func1<HighlightCandidate, Boolean>() {
             @Override
             public Boolean call(HighlightCandidate candidate) {
                 return candidate.getScore() > 0;
--- a/mobile/android/base/java/org/mozilla/gecko/db/BrowserProvider.java
+++ b/mobile/android/base/java/org/mozilla/gecko/db/BrowserProvider.java
@@ -1267,16 +1267,20 @@ public class BrowserProvider extends Sha
                 DBUtils.qualifyColumn(PageMetadata.TABLE_NAME, PageMetadata.JSON) + " AS " + Highlights.METADATA + ", " +
                 DBUtils.qualifyColumn(Bookmarks.TABLE_NAME, Bookmarks.DATE_CREATED) + ", " +
 
                 DBUtils.qualifyColumn(History.TABLE_NAME, History.TITLE) + " AS " + Highlights.TITLE + ", " +
                 DBUtils.qualifyColumn(Bookmarks.TABLE_NAME, Bookmarks.POSITION) + " AS " + Highlights.POSITION + ", " +
                 DBUtils.qualifyColumn(Bookmarks.TABLE_NAME, Bookmarks.PARENT) + " AS " + Highlights.PARENT + ", " +
                 DBUtils.qualifyColumn(History.TABLE_NAME, History._ID) + " AS " + Highlights.HISTORY_ID + ", " +
 
+                /**
+                 * NB: Highlights filtering depends on BOOKMARK_ID, so changes to this logic should also update
+                 * {@link org.mozilla.gecko.activitystream.ranking.HighlightsRanking#filterOutItemsPreffedOff(List, boolean, boolean)}
+                 */
                 "CASE WHEN " + DBUtils.qualifyColumn(Bookmarks.TABLE_NAME, Bookmarks._ID) + " IS NOT NULL "
                 + "AND " + DBUtils.qualifyColumn(Bookmarks.TABLE_NAME, Bookmarks.PARENT) + " IS NOT " + Bookmarks.FIXED_PINNED_LIST_ID + " "
                 + "THEN " + DBUtils.qualifyColumn(Bookmarks.TABLE_NAME, Bookmarks._ID) + " "
                 + "ELSE -1 "
                 + "END AS " + Highlights.BOOKMARK_ID + ", " +
 
                 "CASE WHEN " + DBUtils.qualifyColumn(Bookmarks.TABLE_NAME, Bookmarks.DATE_CREATED) + " IS NOT NULL "
                     + "THEN " + DBUtils.qualifyColumn(Bookmarks.TABLE_NAME, Bookmarks.DATE_CREATED) + " "
--- a/mobile/android/base/java/org/mozilla/gecko/gcm/GcmTokenClient.java
+++ b/mobile/android/base/java/org/mozilla/gecko/gcm/GcmTokenClient.java
@@ -79,17 +79,23 @@ public class GcmTokenClient {
                 Log.i(LOG_TAG, "Cached GCM token exists.");
             }
             return new Fetched(token, timestamp);
         }
 
         Log.i(LOG_TAG, "Cached GCM token does not exist; requesting new token with sender ID: " + senderID);
 
         final InstanceID instanceID = InstanceID.getInstance(context);
-        token = instanceID.getToken(senderID, GoogleCloudMessaging.INSTANCE_ID_SCOPE, null);
+        try {
+            token = instanceID.getToken(senderID, GoogleCloudMessaging.INSTANCE_ID_SCOPE, null);
+        } catch (SecurityException e) {
+            // Degrade gracefully (see upstream exception handling) if we couldn't get the token.
+            // See Bug 1335110.
+            throw new IOException("Could not get token due to a security exception", e);
+        }
         timestamp = System.currentTimeMillis();
 
         if (debug) {
             Log.i(LOG_TAG, "Got fresh GCM token; caching: " + token);
         } else {
             Log.i(LOG_TAG, "Got fresh GCM token; caching.");
         }
         sharedPrefs
--- a/mobile/android/base/java/org/mozilla/gecko/tabs/TabsLayoutItemView.java
+++ b/mobile/android/base/java/org/mozilla/gecko/tabs/TabsLayoutItemView.java
@@ -2,45 +2,47 @@
  * 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/. */
 
 package org.mozilla.gecko.tabs;
 
 import org.mozilla.gecko.R;
 import org.mozilla.gecko.Tab;
 import org.mozilla.gecko.Tabs;
+import org.mozilla.gecko.widget.HoverDelegateWithReset;
 import org.mozilla.gecko.widget.TabThumbnailWrapper;
 import org.mozilla.gecko.widget.TouchDelegateWithReset;
 import org.mozilla.gecko.widget.themed.ThemedRelativeLayout;
 
 import android.content.Context;
 import android.graphics.Rect;
 import android.graphics.drawable.Drawable;
 import android.support.v4.widget.TextViewCompat;
 import android.support.v7.widget.ViewUtils;
 import android.util.AttributeSet;
 import android.util.TypedValue;
+import android.view.MotionEvent;
 import android.view.View;
-import android.view.ViewTreeObserver;
 import android.widget.Checkable;
 import android.widget.ImageView;
 import android.widget.LinearLayout;
 import android.widget.TextView;
 
 public class TabsLayoutItemView extends LinearLayout
                                 implements Checkable {
     private static final String LOGTAG = "Gecko" + TabsLayoutItemView.class.getSimpleName();
     private static final int[] STATE_CHECKED = { android.R.attr.state_checked };
     private boolean mChecked;
 
     private int mTabId;
     private TextView mTitle;
     private TabsPanelThumbnailView mThumbnail;
     private ImageView mCloseButton;
     private TabThumbnailWrapper mThumbnailWrapper;
+    private HoverDelegateWithReset mHoverDelegate;
 
     public TabsLayoutItemView(Context context, AttributeSet attrs) {
         super(context, attrs);
     }
 
     @Override
     public int[] onCreateDrawableState(int extraSpace) {
         final int[] drawableState = super.onCreateDrawableState(extraSpace + 1);
@@ -96,44 +98,59 @@ public class TabsLayoutItemView extends 
         mThumbnail = (TabsPanelThumbnailView) findViewById(R.id.thumbnail);
         mCloseButton = (ImageView) findViewById(R.id.close);
         mThumbnailWrapper = (TabThumbnailWrapper) findViewById(R.id.wrapper);
 
         growCloseButtonHitArea();
     }
 
     private void growCloseButtonHitArea() {
-        getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
+        addOnLayoutChangeListener(new OnLayoutChangeListener() {
             @Override
-            public boolean onPreDraw() {
-                getViewTreeObserver().removeOnPreDrawListener(this);
-
+            public void onLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft, int oldTop, int oldRight, int oldBottom) {
                 // Ideally we want the close button hit area to be 40x40dp but we are constrained by the height of the parent, so
                 // we make it as tall as the parent view and 40dp across.
-                final int targetHitArea = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 40, getResources().getDisplayMetrics());;
+                final int targetHitArea = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 40, getResources().getDisplayMetrics());
 
                 final Rect hitRect = getHitRectRelatively(targetHitArea);
 
                 setTouchDelegate(new TouchDelegateWithReset(hitRect, mCloseButton));
-
-                return true;
+                setHoverDelegate(new HoverDelegateWithReset(hitRect, mCloseButton));
             }
         });
     }
 
     private Rect getHitRectRelatively(int targetHitArea) {
         final boolean isRtl = ViewUtils.isLayoutRtl(this);
         final Rect hitRect = new Rect();
         hitRect.top = 0;
         hitRect.right = isRtl ? targetHitArea : getWidth();
         hitRect.left = isRtl ? 0 : getWidth() - targetHitArea;
         hitRect.bottom = targetHitArea;
         return hitRect;
     }
 
+    /**
+     * Sets the HoverDelegate for this View.
+     */
+    public void setHoverDelegate(HoverDelegateWithReset delegate) {
+        mHoverDelegate = delegate;
+    }
+
+    @Override
+    public boolean onHoverEvent(MotionEvent event) {
+        if (mHoverDelegate != null) {
+            if (mHoverDelegate.onHoverEvent(event)) {
+                return true;
+            }
+        }
+
+        return super.onHoverEvent(event);
+    }
+
     protected void assignValues(Tab tab)  {
         if (tab == null) {
             return;
         }
 
         mTabId = tab.getId();
 
         setChecked(Tabs.getInstance().isSelectedTab(tab));
copy from mobile/android/base/java/org/mozilla/gecko/widget/TouchDelegateWithReset.java
copy to mobile/android/base/java/org/mozilla/gecko/widget/HoverDelegateWithReset.java
--- a/mobile/android/base/java/org/mozilla/gecko/widget/TouchDelegateWithReset.java
+++ b/mobile/android/base/java/org/mozilla/gecko/widget/HoverDelegateWithReset.java
@@ -1,42 +1,39 @@
 /* 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/. */
 
 package org.mozilla.gecko.widget;
 
 import android.graphics.Rect;
 import android.view.MotionEvent;
-import android.view.TouchDelegate;
 import android.view.View;
 import android.view.ViewConfiguration;
 
 /**
- * This is a copy of TouchDelegate from
- * https://github.com/android/platform_frameworks_base/blob/4b1a8f46d6ec55796bf77fd8921a5a242a219278/core/java/android/view/TouchDelegate.java
- * with a fix to reset mDelegateTargeted on each new gesture - the sole substantive change is a new
- * else leg in the ACTION_DOWN case of onTouchEvent marked by "START|END BUG FIX" comments.
+ * This is an implementation of a delegate class for hover events along the lines of TouchDelegate,
+ * based on our fixed copy in TouchDelegateWithReset.java.
  */
 
 /**
- * Helper class to handle situations where you want a view to have a larger touch area than its
- * actual view bounds. The view whose touch area is changed is called the delegate view. This
- * class should be used by an ancestor of the delegate. To use a TouchDelegate, first create an
+ * Helper class to handle situations where you want a view to have a larger hover area than its
+ * actual view bounds. The view whose hover area is changed is called the delegate view. This
+ * class should be used by an ancestor of the delegate. To use a HoverDelegate, first create an
  * instance that specifies the bounds that should be mapped to the delegate and the delegate
  * view itself.
  * <p>
- * The ancestor should then forward all of its touch events received in its
- * {@link android.view.View#onTouchEvent(MotionEvent)} to {@link #onTouchEvent(MotionEvent)}.
+ * The ancestor should then forward all of its hover events received in its
+ * {@link android.view.View#onHoverEvent(MotionEvent)} to {@link #onHoverEvent(MotionEvent)}.
  * </p>
  */
-public class TouchDelegateWithReset extends TouchDelegate {
+public class HoverDelegateWithReset {
 
     /**
-     * View that should receive forwarded touch events
+     * View that should receive forwarded hover events
      */
     private View mDelegateView;
 
     /**
      * Bounds in local coordinates of the containing view that should be mapped to the delegate
      * view. This rect is used for initial hit testing.
      */
     private Rect mBounds;
@@ -56,57 +53,54 @@ public class TouchDelegateWithReset exte
 
     /**
      * Constructor
      *
      * @param bounds Bounds in local coordinates of the containing view that should be mapped to
      *        the delegate view
      * @param delegateView The view that should receive motion events
      */
-    public TouchDelegateWithReset(Rect bounds, View delegateView) {
-        super(bounds, delegateView);
-
+    public HoverDelegateWithReset(Rect bounds, View delegateView) {
         mBounds = bounds;
 
         mSlop = ViewConfiguration.get(delegateView.getContext()).getScaledTouchSlop();
         mSlopBounds = new Rect(bounds);
         mSlopBounds.inset(-mSlop, -mSlop);
         mDelegateView = delegateView;
     }
 
     /**
-     * Will forward touch events to the delegate view if the event is within the bounds
+     * Will forward hover events to the delegate view if the event is within the bounds
      * specified in the constructor.
      *
-     * @param event The touch event to forward
+     * @param event The hover event to forward
      * @return True if the event was forwarded to the delegate, false otherwise.
      */
-    @Override
-    public boolean onTouchEvent(MotionEvent event) {
+    public boolean onHoverEvent(MotionEvent event) {
         int x = (int)event.getX();
         int y = (int)event.getY();
         boolean sendToDelegate = false;
         boolean hit = true;
         boolean handled = false;
 
         switch (event.getAction()) {
-        case MotionEvent.ACTION_DOWN:
+        case MotionEvent.ACTION_HOVER_ENTER:
             Rect bounds = mBounds;
 
             if (bounds.contains(x, y)) {
                 mDelegateTargeted = true;
                 sendToDelegate = true;
             } /* START BUG FIX */
             else {
                 mDelegateTargeted = false;
             }
             /* END BUG FIX */
             break;
-        case MotionEvent.ACTION_UP:
-        case MotionEvent.ACTION_MOVE:
+        case MotionEvent.ACTION_HOVER_EXIT:
+        case MotionEvent.ACTION_HOVER_MOVE:
             sendToDelegate = mDelegateTargeted;
             if (sendToDelegate) {
                 Rect slopBounds = mSlopBounds;
                 if (!slopBounds.contains(x, y)) {
                     hit = false;
                 }
             }
             break;
@@ -122,13 +116,13 @@ public class TouchDelegateWithReset exte
                 // Offset event coordinates to be inside the target view
                 event.setLocation(delegateView.getWidth() / 2, delegateView.getHeight() / 2);
             } else {
                 // Offset event coordinates to be outside the target view (in case it does
                 // something like tracking pressed state)
                 int slop = mSlop;
                 event.setLocation(-(slop * 2), -(slop * 2));
             }
-            handled = delegateView.dispatchTouchEvent(event);
+            handled = delegateView.dispatchGenericMotionEvent(event);
         }
         return handled;
     }
 }
\ No newline at end of file
--- a/mobile/android/base/moz.build
+++ b/mobile/android/base/moz.build
@@ -970,16 +970,17 @@ gbjar.sources += ['java/org/mozilla/geck
     'widget/FadedTextView.java',
     'widget/FaviconView.java',
     'widget/FilledCardView.java',
     'widget/FlowLayout.java',
     'widget/GeckoActionProvider.java',
     'widget/GeckoPopupMenu.java',
     'widget/GridSpacingDecoration.java',
     'widget/HistoryDividerItemDecoration.java',
+    'widget/HoverDelegateWithReset.java',
     'widget/IconTabWidget.java',
     'widget/LoginDoorHanger.java',
     'widget/RecyclerViewClickSupport.java',
     'widget/ResizablePathDrawable.java',
     'widget/RoundedCornerLayout.java',
     'widget/SiteLogins.java',
     'widget/SplashScreen.java',
     'widget/SquaredImageView.java',
--- a/mobile/android/components/extensions/test/mochitest/test_ext_tabs_executeScript_runAt.html
+++ b/mobile/android/components/extensions/test/mochitest/test_ext_tabs_executeScript_runAt.html
@@ -31,31 +31,36 @@ add_task(function* testExecuteScript() {
   async function background(DEBUG) {
     let tab;
 
     const BASE = "http://mochi.test:8888/tests/mobile/android/components/extensions/test/mochitest/";
     const URL = BASE + "file_iframe_document.sjs";
 
     const MAX_TRIES = 30;
 
+    let onUpdatedPromise = (tabId, url, status) => {
+      return new Promise(resolve => {
+        browser.tabs.onUpdated.addListener(function listener(_, changed, tab) {
+          if (tabId == tab.id && changed.status == status && tab.url == url) {
+            browser.tabs.onUpdated.removeListener(listener);
+            resolve();
+          }
+        });
+      });
+    };
+
     try {
       [tab] = await browser.tabs.query({active: true, currentWindow: true});
 
       let success = false;
       for (let tries = 0; !success && tries < MAX_TRIES; tries++) {
         let url = `${URL}?r=${Math.random()}`;
 
-        let loadingPromise = new Promise(resolve => {
-          browser.tabs.onUpdated.addListener(function listener(tabId, changed, tab_) {
-            if (tabId == tab.id && changed.status == "loading" && tab_.url == url) {
-              browser.tabs.onUpdated.removeListener(listener);
-              resolve();
-            }
-          });
-        });
+        let loadingPromise = onUpdatedPromise(tab.id, url, "loading");
+        let completePromise = onUpdatedPromise(tab.id, url, "complete");
 
         // TODO: Test allFrames and frameId.
 
         await browser.tabs.update({url});
         await loadingPromise;
 
         let states = await Promise.all([
           // Send the executeScript requests in the reverse order that we expect
@@ -76,24 +81,26 @@ add_task(function* testExecuteScript() {
         ].reverse());
 
         browser.test.log(`Got states: ${states}`);
 
         // Make sure that none of our scripts executed earlier than expected,
         // regardless of retries.
         browser.test.assertTrue(states[1] == "interactive" || states[1] == "complete",
                                 `document_end state is valid: ${states[1]}`);
-        browser.test.assertTrue(states[2] == "complete",
+        browser.test.assertTrue(states[2] == "interactive" || states[2] == "complete",
                                 `document_idle state is valid: ${states[2]}`);
 
         // If we have the earliest valid states for each script, we're done.
         // Otherwise, try again.
         success = ((states[0] == "loading" || DEBUG) &&
                    states[1] == "interactive" &&
-                   states[2] == "complete");
+                   (states[2] == "interactive" || states[2] == "complete"));
+
+        await completePromise;
       }
 
       browser.test.assertTrue(success, "Got the earliest expected states at least once");
 
       browser.test.notifyPass("executeScript-runAt");
     } catch (e) {
       browser.test.fail(`Error: ${e} :: ${e.stack}`);
       browser.test.notifyFail("executeScript-runAt");
--- a/modules/libpref/init/all.js
+++ b/modules/libpref/init/all.js
@@ -4800,17 +4800,17 @@ pref("network.tcp.keepalive.idle_time", 
 pref("network.tcp.keepalive.retry_interval", 1); // seconds
 #endif
 // Default maximum probe retransmissions.
 // Linux only; not configurable on Win and Mac; fixed at 10 and 8 respectively.
 #if defined(XP_UNIX) && !defined(XP_MACOSX)
 pref("network.tcp.keepalive.probe_count", 4);
 #endif
 
-pref("network.tcp.tcp_fastopen_enable", true);
+pref("network.tcp.tcp_fastopen_enable", false);
 pref("network.tcp.tcp_fastopen_consecutive_failure_limit", 5);
 
 // Whether to disable acceleration for all widgets.
 pref("layers.acceleration.disabled", false);
 // Preference that when switched at runtime will run a series of benchmarks
 // and output the result to stderr.
 pref("layers.bench.enabled", false);
 
@@ -5238,16 +5238,22 @@ pref("social.toast-notifications.enabled
 pref("dom.idle-observers-api.fuzz_time.disabled", true);
 
 // Minimum delay in milliseconds between network activity notifications (0 means
 // no notifications). The delay is the same for both download and upload, though
 // they are handled separately. This pref is only read once at startup:
 // a restart is required to enable a new value.
 pref("network.activity.blipIntervalMilliseconds", 0);
 
+// If true, reuse the same global for (almost) everything loaded by the component
+// loader (JS components, JSMs, etc). This saves memory, but makes it possible
+// for the scripts to interfere with each other.  A restart is required for this
+// to take effect.
+pref("jsloader.shareGlobal", false);
+
 // When we're asked to take a screenshot, don't wait more than 2000ms for the
 // event loop to become idle before actually taking the screenshot.
 pref("dom.browserElement.maxScreenshotDelayMS", 2000);
 
 // Whether we should show the placeholder when the element is focused but empty.
 pref("dom.placeholder.show_on_focus", true);
 
 // WebVR is enabled by default in beta and release for Windows and for all
--- a/netwerk/test/httpserver/httpd.js
+++ b/netwerk/test/httpserver/httpd.js
@@ -49,17 +49,17 @@ const CC = Components.Constructor;
 const PR_UINT32_MAX = Math.pow(2, 32) - 1;
 
 /** True if debugging output is enabled, false otherwise. */
 var DEBUG = false; // non-const *only* so tweakable in server tests
 
 /** True if debugging output should be timestamped. */
 var DEBUG_TIMESTAMP = false; // non-const so tweakable in server tests
 
-var gGlobalObject = this;
+var gGlobalObject = Cu.getGlobalForObject(this);
 
 /**
  * Asserts that the given condition holds.  If it doesn't, the given message is
  * dumped, a stack trace is printed, and an exception is thrown to attempt to
  * stop execution (which unfortunately must rely upon the exception not being
  * accidentally swallowed by the code that uses it).
  */
 function NS_ASSERT(cond, msg)
--- a/services/sync/modules/engines.js
+++ b/services/sync/modules/engines.js
@@ -1042,16 +1042,17 @@ SyncEngine.prototype = {
     } else {
       this._log.debug("First sync, uploading all items");
       initialChanges = await this.pullAllChanges();
     }
     this._modified.replace(initialChanges);
     // Clear the tracker now. If the sync fails we'll add the ones we failed
     // to upload back.
     this._tracker.clearChangedIDs();
+    this._tracker.resetScore();
 
     this._log.info(this._modified.count() +
                    " outgoing items pre-reconciliation");
 
     // Keep track of what to delete at the end of sync
     this._delete = {};
   },
 
@@ -1724,17 +1725,16 @@ SyncEngine.prototype = {
     // Implement this method to take specific actions against successfully
     // uploaded records and failed records.
   },
 
   // Any cleanup necessary.
   // Save the current snapshot so as to calculate changes at next sync
   async _syncFinish() {
     this._log.trace("Finishing up sync");
-    this._tracker.resetScore();
 
     let doDelete = async (key, val) => {
       let coll = new Collection(this.engineURL, this._recordObj, this.service);
       coll[key] = val;
       await coll.delete();
     };
 
     for (let [key, val] of Object.entries(this._delete)) {
--- a/services/sync/tests/unit/test_engine_changes_during_sync.js
+++ b/services/sync/tests/unit/test_engine_changes_during_sync.js
@@ -1,10 +1,11 @@
 Cu.import("resource://gre/modules/FormHistory.jsm");
 Cu.import("resource://gre/modules/Log.jsm");
+Cu.import("resource://gre/modules/PlacesSyncUtils.jsm");
 Cu.import("resource://services-sync/service.js");
 Cu.import("resource://services-sync/engines/bookmarks.js");
 Cu.import("resource://services-sync/engines/history.js");
 Cu.import("resource://services-sync/engines/forms.js");
 Cu.import("resource://services-sync/engines/passwords.js");
 Cu.import("resource://services-sync/engines/prefs.js");
 Cu.import("resource://testing-common/services/sync/utils.js");
 
@@ -38,29 +39,27 @@ add_task(async function test_history_cha
 
   enableValidationPrefs();
 
   let engine = Service.engineManager.get("history");
   let server = serverForEnginesWithKeys({"foo": "password"}, [engine]);
   await SyncTestingInfrastructure(server);
   let collection = server.user("foo").collection("history");
 
-  // Override `applyIncomingBatch` to insert a record while we're applying
+  // Override `uploadOutgoing` to insert a record while we're applying
   // changes. The tracker should ignore this change.
-  let { applyIncomingBatch } = engine._store;
-  engine._store.applyIncomingBatch = async function(records) {
-    _("Inserting local history visit");
-    engine._store.applyIncomingBatch = applyIncomingBatch;
-    let failed;
+  let uploadOutgoing = engine._uploadOutgoing;
+  engine._uploadOutgoing = async function() {
+    engine._uploadOutgoing = uploadOutgoing;
     try {
-      await addVisit("during_sync");
+      await uploadOutgoing.call(this);
     } finally {
-      failed = await applyIncomingBatch.call(this, records);
+      _("Inserting local history visit");
+      await addVisit("during_sync");
     }
-    return failed;
   };
 
   Svc.Obs.notify("weave:engine:start-tracking");
 
   try {
     let remoteRec = new HistoryRec("history", "UrOOuzE5QM-e");
     remoteRec.histUri = "http://getfirefox.com/";
     remoteRec.title = "Get Firefox!";
@@ -76,49 +75,45 @@ add_task(async function test_history_cha
 
     equal(collection.count(), 1,
       "New local visit should not exist on server after first sync");
 
     await sync_engine_and_validate_telem(engine, true);
     strictEqual(Service.scheduler.globalScore, 0,
       "Should not bump global score during second history sync");
 
-    // ...But we still won't ever upload the visit to the server, since we
-    // cleared the tracker after the first sync.
-    equal(collection.count(), 1,
-      "New local visit won't exist on server after second sync");
+    equal(collection.count(), 2,
+      "New local visit should exist on server after second sync");
   } finally {
-    engine._store.applyIncomingBatch = applyIncomingBatch;
+    engine._uploadOutgoing = uploadOutgoing;
     await cleanup(engine, server);
   }
 });
 
 add_task(async function test_passwords_change_during_sync() {
   _("Ensure that we don't bump the score when applying passwords.");
 
   enableValidationPrefs();
 
   let engine = Service.engineManager.get("passwords");
   let server = serverForEnginesWithKeys({"foo": "password"}, [engine]);
   await SyncTestingInfrastructure(server);
   let collection = server.user("foo").collection("passwords");
 
-  let { applyIncomingBatch } = engine._store;
-  engine._store.applyIncomingBatch = async function(records) {
-    _("Inserting local password");
-    engine._store.applyIncomingBatch = applyIncomingBatch;
-    let failed;
+  let uploadOutgoing = engine._uploadOutgoing;
+  engine._uploadOutgoing = async function() {
+    engine._uploadOutgoing = uploadOutgoing;
     try {
+      await uploadOutgoing.call(this);
+    } finally {
+      _("Inserting local password");
       let login = new LoginInfo("https://example.com", "", null, "username",
         "password", "", "");
       Services.logins.addLogin(login);
-    } finally {
-      failed = await applyIncomingBatch.call(this, records);
     }
-    return failed;
   };
 
   Svc.Obs.notify("weave:engine:start-tracking");
 
   try {
     let remoteRec = new LoginRec("passwords", "{765e3d6e-071d-d640-a83d-81a7eb62d3ed}");
     remoteRec.formSubmitURL = "";
     remoteRec.httpRealm = "";
@@ -135,50 +130,46 @@ add_task(async function test_passwords_c
 
     equal(collection.count(), 1,
       "New local password should not exist on server after first sync");
 
     await sync_engine_and_validate_telem(engine, true);
     strictEqual(Service.scheduler.globalScore, 0,
       "Should not bump global score during second passwords sync");
 
-    // ...But we still won't ever upload the entry to the server, since we
-    // cleared the tracker after the first sync.
-    equal(collection.count(), 1,
-      "New local password won't exist on server after second sync");
+    equal(collection.count(), 2,
+      "New local password should exist on server after second sync");
   } finally {
-    engine._store.applyIncomingBatch = applyIncomingBatch;
+    engine._uploadOutgoing = uploadOutgoing;
     await cleanup(engine, server);
   }
 });
 
 add_task(async function test_prefs_change_during_sync() {
   _("Ensure that we don't bump the score when applying prefs.");
 
   const TEST_PREF = "services.sync.prefs.sync.test.duringSync";
 
   enableValidationPrefs();
 
   let engine = Service.engineManager.get("prefs");
   let server = serverForEnginesWithKeys({"foo": "password"}, [engine]);
   await SyncTestingInfrastructure(server);
   let collection = server.user("foo").collection("prefs");
 
-  let { applyIncomingBatch } = engine._store;
-  engine._store.applyIncomingBatch = async function(records) {
-    _("Updating local pref value");
-    engine._store.applyIncomingBatch = applyIncomingBatch;
-    let failed;
+  let uploadOutgoing = engine._uploadOutgoing;
+  engine._uploadOutgoing = async function() {
+    engine._uploadOutgoing = uploadOutgoing;
     try {
+      await uploadOutgoing.call(this);
+    } finally {
+      _("Updating local pref value");
       // Change the value of a synced pref.
       Services.prefs.setCharPref(TEST_PREF, "hello");
-    } finally {
-      failed = await applyIncomingBatch.call(this, records);
     }
-    return failed;
   };
 
   Svc.Obs.notify("weave:engine:start-tracking");
 
   try {
     // All synced prefs are stored in a single record, so we'll only ever
     // have one record on the server. This test just checks that we don't
     // track or upload prefs changed during the sync.
@@ -199,54 +190,52 @@ add_task(async function test_prefs_chang
       "Should not upload pref value changed during first sync");
 
     await sync_engine_and_validate_telem(engine, true);
     strictEqual(Service.scheduler.globalScore, 0,
       "Should not bump global score during second prefs sync");
     payloads = collection.payloads();
     equal(payloads.length, 1,
       "Should not upload multiple prefs records after second sync");
-    equal(payloads[0].value[TEST_PREF], "world",
-      "Should not upload changed pref value during second sync");
+    equal(payloads[0].value[TEST_PREF], "hello",
+      "Should upload changed pref value during second sync");
   } finally {
-    engine._store.applyIncomingBatch = applyIncomingBatch;
+    engine._uploadOutgoing = uploadOutgoing;
     await cleanup(engine, server);
     Services.prefs.clearUserPref(TEST_PREF);
   }
 });
 
 add_task(async function test_forms_change_during_sync() {
   _("Ensure that we don't bump the score when applying form records.");
 
   enableValidationPrefs();
 
   let engine = Service.engineManager.get("forms");
   let server = serverForEnginesWithKeys({"foo": "password"}, [engine]);
   await SyncTestingInfrastructure(server);
   let collection = server.user("foo").collection("forms");
 
-  let { applyIncomingBatch } = engine._store;
-  engine._store.applyIncomingBatch = async function(records) {
-    _("Inserting local form history entry");
-    engine._store.applyIncomingBatch = applyIncomingBatch;
-    let failed;
+  let uploadOutgoing = engine._uploadOutgoing;
+  engine._uploadOutgoing = async function() {
+    engine._uploadOutgoing = uploadOutgoing;
     try {
+      await uploadOutgoing.call(this);
+    } finally {
+      _("Inserting local form history entry");
       await new Promise(resolve => {
         FormHistory.update([{
           op: "add",
           fieldname: "favoriteDrink",
           value: "cocoa",
         }], {
           handleCompletion: resolve,
         });
       });
-    } finally {
-      failed = await applyIncomingBatch.call(this, records);
     }
-    return failed;
   };
 
   Svc.Obs.notify("weave:engine:start-tracking");
 
   try {
     // Add an existing remote form history entry. We shouldn't bump the score when
     // we apply this record.
     let remoteRec = new FormRec("forms", "Tl9dHgmJSR6FkyxS");
@@ -260,22 +249,20 @@ add_task(async function test_forms_chang
 
     equal(collection.count(), 1,
       "New local form should not exist on server after first sync");
 
     await sync_engine_and_validate_telem(engine, true);
     strictEqual(Service.scheduler.globalScore, 0,
       "Should not bump global score during second forms sync");
 
-    // ...But we still won't ever upload the entry to the server, since we
-    // cleared the tracker after the first sync.
-    equal(collection.count(), 1,
-      "New local form won't exist on server after second sync");
+    equal(collection.count(), 2,
+      "New local form should exist on server after second sync");
   } finally {
-    engine._store.applyIncomingBatch = applyIncomingBatch;
+    engine._uploadOutgoing = uploadOutgoing;
     await cleanup(engine, server);
   }
 });
 
 add_task(async function test_bookmark_change_during_sync() {
   _("Ensure that we track bookmark changes made during a sync.");
 
   enableValidationPrefs();
@@ -296,31 +283,29 @@ add_task(async function test_bookmark_ch
 
   let engine = Service.engineManager.get("bookmarks");
   let server = serverForEnginesWithKeys({"foo": "password"}, [engine]);
   await SyncTestingInfrastructure(server);
   let collection = server.user("foo").collection("bookmarks");
 
   let bmk3; // New child of Folder 1, created locally during sync.
 
-  let { applyIncomingBatch } = engine._store;
-  engine._store.applyIncomingBatch = async function(records) {
-    _("Inserting bookmark into local store");
-    engine._store.applyIncomingBatch = applyIncomingBatch;
-    let failed;
+  let uploadOutgoing = engine._uploadOutgoing;
+  engine._uploadOutgoing = async function() {
+    engine._uploadOutgoing = uploadOutgoing;
     try {
+      await uploadOutgoing.call(this);
+    } finally {
+      _("Inserting bookmark into local store");
       bmk3 = await PlacesUtils.bookmarks.insert({
         parentGuid: folder1.guid,
         url: "https://mozilla.org/",
         title: "Mozilla",
       });
-    } finally {
-      failed = await applyIncomingBatch.call(this, records);
     }
-    return failed;
   };
 
   // New bookmarks that should be uploaded during the first sync.
   let folder1 = await PlacesUtils.bookmarks.insert({
     type: PlacesUtils.bookmarks.TYPE_FOLDER,
     parentGuid: PlacesUtils.bookmarks.toolbarGuid,
     title: "Folder 1",
   });
@@ -343,18 +328,18 @@ add_task(async function test_bookmark_ch
     {
       // An existing record changed on the server that should not trigger
       // another sync when applied.
       let remoteBzBmk = new Bookmark("bookmarks", bzBmk.guid);
       remoteBzBmk.bmkUri      = "https://bugzilla.mozilla.org/";
       remoteBzBmk.description = "New description";
       remoteBzBmk.title       = "Bugzilla";
       remoteBzBmk.tags        = ["new", "tags"];
-      remoteBzBmk.parentName  = "Bookmarks Toolbar";
-      remoteBzBmk.parentid    = "toolbar";
+      remoteBzBmk.parentName  = "Bookmarks Menu";
+      remoteBzBmk.parentid    = "menu";
       collection.insert(bzBmk.guid, encryptPayload(remoteBzBmk.cleartext));
 
       let remoteFolder = new BookmarkFolder("bookmarks", folder2_guid);
       remoteFolder.title      = "Folder 2";
       remoteFolder.children   = [bmk4_guid, tagQuery_guid];
       remoteFolder.parentName = "Bookmarks Menu";
       remoteFolder.parentid   = "menu";
       collection.insert(folder2_guid, encryptPayload(remoteFolder.cleartext));
@@ -391,17 +376,17 @@ add_task(async function test_bookmark_ch
       collection.insert(bmk4_guid, encryptPayload(remoteTaggedBmk.cleartext));
     }
 
     await assertChildGuids(folder1.guid, [tbBmk.guid],
       "Folder should have 1 child before first sync");
 
     let pingsPromise = wait_for_pings(2);
 
-    let changes = await engine.pullNewChanges();
+    let changes = await PlacesSyncUtils.bookmarks.pullChanges();
     deepEqual(Object.keys(changes).sort(), [
       folder1.guid,
       tbBmk.guid,
       "menu",
       "mobile",
       "toolbar",
       "unfiled",
     ].sort(), "Should track bookmark and folder created before first sync");
@@ -424,33 +409,33 @@ add_task(async function test_bookmark_ch
     ok(bmk3, "Should insert bookmark during first sync to simulate change");
     ok(collection.wbo(bmk3.guid),
       "Changed bookmark should be uploaded after follow-up sync");
 
     let bmk2 = await PlacesUtils.bookmarks.fetch({
       guid: bmk2_guid,
     });
     ok(bmk2, "Remote bookmark should be applied during first sync");
-    await assertChildGuids(folder1.guid, [tbBmk.guid, bmk3.guid, bmk2_guid],
+    await assertChildGuids(folder1.guid, [tbBmk.guid, bmk2_guid, bmk3.guid],
       "Folder 1 should have 3 children after first sync");
     await assertChildGuids(folder2_guid, [bmk4_guid, tagQuery_guid],
       "Folder 2 should have 2 children after first sync");
     let taggedURIs = PlacesUtils.tagging.getURIsForTag("taggy");
     equal(taggedURIs.length, 1, "Should have 1 tagged URI");
     equal(taggedURIs[0].spec, "https://example.org/",
       "Synced tagged bookmark should appear in tagged URI list");
 
-    changes = await engine.pullNewChanges();
+    changes = await PlacesSyncUtils.bookmarks.pullChanges();
     deepEqual(changes, {},
       "Should have already uploaded changes in follow-up sync");
 
     // First ping won't include validation data, since we've changed bookmarks
     // and `canValidate` will indicate it can't proceed.
     let engineData = pings.map(p =>
       p.syncs[0].engines.find(e => e.name == "bookmarks")
     );
     ok(!engineData[0].validation, "Should not validate after first sync");
     ok(engineData[1].validation, "Should validate after second sync");
   } finally {
-    engine._store.applyIncomingBatch = applyIncomingBatch;
+    engine._uploadOutgoing = uploadOutgoing;
     await cleanup(engine, server);
   }
 });
--- a/services/sync/tests/unit/test_syncengine_sync.js
+++ b/services/sync/tests/unit/test_syncengine_sync.js
@@ -1534,33 +1534,16 @@ add_task(async function test_uploadOutgo
     }
     ok(!!error);
   } finally {
     await cleanAndGo(engine, server);
   }
 });
 
 
-add_task(async function test_syncFinish_noDelete() {
-  _("SyncEngine._syncFinish resets tracker's score");
-
-  let server = httpd_setup({});
-
-  await SyncTestingInfrastructure(server);
-  let engine = makeRotaryEngine();
-  engine._delete = {}; // Nothing to delete
-  engine._tracker.score = 100;
-
-  // _syncFinish() will reset the engine's score.
-  await engine._syncFinish();
-  do_check_eq(engine.score, 0);
-  server.stop(run_next_test);
-});
-
-
 add_task(async function test_syncFinish_deleteByIds() {
   _("SyncEngine._syncFinish deletes server records slated for deletion (list of record IDs).");
 
   let collection = new ServerCollection();
   collection._wbos.flying = new ServerWBO(
       "flying", encryptPayload({id: "flying",
                                 denomination: "LNER Class A3 4472"}));
   collection._wbos.scotsman = new ServerWBO(
--- a/services/sync/tests/unit/test_telemetry.js
+++ b/services/sync/tests/unit/test_telemetry.js
@@ -176,17 +176,16 @@ add_task(async function test_uploading()
     equal(ping.engines.length, 1);
     equal(ping.engines[0].name, "bookmarks");
     ok(!!ping.engines[0].outgoing);
     greater(ping.engines[0].outgoing[0].sent, 0)
     ok(!ping.engines[0].incoming);
 
     PlacesUtils.bookmarks.setItemTitle(bmk_id, "New Title");
 
-    await store.wipe();
     await engine.resetClient();
 
     ping = await sync_engine_and_validate_telem(engine, false);
     equal(ping.engines.length, 1);
     equal(ping.engines[0].name, "bookmarks");
     equal(ping.engines[0].outgoing.length, 1);
     ok(!!ping.engines[0].incoming);
 
--- a/services/sync/tps/extensions/mozmill/resource/stdlib/httpd.js
+++ b/services/sync/tps/extensions/mozmill/resource/stdlib/httpd.js
@@ -49,17 +49,17 @@ const CC = Components.Constructor;
 const PR_UINT32_MAX = Math.pow(2, 32) - 1;
 
 /** True if debugging output is enabled, false otherwise. */
 var DEBUG = false; // non-const *only* so tweakable in server tests
 
 /** True if debugging output should be timestamped. */
 var DEBUG_TIMESTAMP = false; // non-const so tweakable in server tests
 
-var gGlobalObject = this;
+var gGlobalObject = Cu.getGlobalForObject(this);
 
 /**
  * Asserts that the given condition holds.  If it doesn't, the given message is
  * dumped, a stack trace is printed, and an exception is thrown to attempt to
  * stop execution (which unfortunately must rely upon the exception not being
  * accidentally swallowed by the code that uses it).
  */
 function NS_ASSERT(cond, msg)
--- a/servo/Cargo.lock
+++ b/servo/Cargo.lock
@@ -52,17 +52,17 @@ source = "registry+https://github.com/ru
 
 [[package]]
 name = "antidote"
 version = "1.0.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 
 [[package]]
 name = "app_units"
-version = "0.5.3"
+version = "0.5.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
  "heapsize 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "num-traits 0.1.37 (registry+https://github.com/rust-lang/crates.io-index)",
  "rustc-serialize 0.3.24 (registry+https://github.com/rust-lang/crates.io-index)",
  "serde 1.0.8 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
@@ -1093,17 +1093,17 @@ dependencies = [
 name = "getopts"
 version = "0.2.14"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 
 [[package]]
 name = "gfx"
 version = "0.0.1"
 dependencies = [
- "app_units 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)",
+ "app_units 0.5.5 (registry+https://github.com/rust-lang/crates.io-index)",
  "bitflags 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "byteorder 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "core-foundation 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "core-graphics 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)",
  "core-text 6.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "dwrote 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "euclid 0.15.1 (registry+https://github.com/rust-lang/crates.io-index)",
  "fnv 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -1506,17 +1506,17 @@ source = "registry+https://github.com/ru
 name = "language-tags"
 version = "0.2.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 
 [[package]]
 name = "layout"
 version = "0.0.1"
 dependencies = [
- "app_units 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)",
+ "app_units 0.5.5 (registry+https://github.com/rust-lang/crates.io-index)",
  "atomic_refcell 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "bitflags 0.8.2 (registry+https://github.com/rust-lang/crates.io-index)",
  "canvas_traits 0.0.1",
  "euclid 0.15.1 (registry+https://github.com/rust-lang/crates.io-index)",
  "fnv 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)",
  "gfx 0.0.1",
  "gfx_traits 0.0.1",
  "heapsize 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -1556,17 +1556,17 @@ dependencies = [
  "layout 0.0.1",
  "size_of_test 0.0.1",
 ]
 
 [[package]]
 name = "layout_thread"
 version = "0.0.1"
 dependencies = [
- "app_units 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)",
+ "app_units 0.5.5 (registry+https://github.com/rust-lang/crates.io-index)",
  "atomic_refcell 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "euclid 0.15.1 (registry+https://github.com/rust-lang/crates.io-index)",
  "fnv 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)",
  "gfx 0.0.1",
  "gfx_traits 0.0.1",
  "heapsize 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "html5ever 0.19.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "ipc-channel 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -2530,17 +2530,17 @@ name = "scopeguard"
 version = "0.3.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 
 [[package]]
 name = "script"
 version = "0.0.1"
 dependencies = [
  "angle 0.5.0 (git+https://github.com/servo/angle?branch=servo)",
- "app_units 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)",
+ "app_units 0.5.5 (registry+https://github.com/rust-lang/crates.io-index)",
  "audio-video-metadata 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)",
  "base64 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)",
  "bitflags 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "bluetooth_traits 0.0.1",
  "byteorder 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "canvas_traits 0.0.1",
  "caseless 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)",
  "cmake 0.1.22 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -2609,17 +2609,17 @@ dependencies = [
  "webvr_traits 0.0.1",
  "xml5ever 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
 name = "script_layout_interface"
 version = "0.0.1"
 dependencies = [
- "app_units 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)",
+ "app_units 0.5.5 (registry+https://github.com/rust-lang/crates.io-index)",
  "atomic_refcell 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "canvas_traits 0.0.1",
  "cssparser 0.20.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "euclid 0.15.1 (registry+https://github.com/rust-lang/crates.io-index)",
  "gfx_traits 0.0.1",
  "heapsize 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "heapsize_derive 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)",
  "html5ever 0.19.0 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -2918,17 +2918,17 @@ version = "0.0.1"
 dependencies = [
  "servo_config 0.0.1",
 ]
 
 [[package]]
 name = "servo_geometry"
 version = "0.0.1"
 dependencies = [
- "app_units 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)",
+ "app_units 0.5.5 (registry+https://github.com/rust-lang/crates.io-index)",
  "euclid 0.15.1 (registry+https://github.com/rust-lang/crates.io-index)",
  "heapsize 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
 name = "servo_rand"
 version = "0.0.1"
 dependencies = [
@@ -3074,17 +3074,17 @@ source = "registry+https://github.com/ru
 name = "strsim"
 version = "0.6.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 
 [[package]]
 name = "style"
 version = "0.0.1"
 dependencies = [
- "app_units 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)",
+ "app_units 0.5.5 (registry+https://github.com/rust-lang/crates.io-index)",
  "arraydeque 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)",
  "arrayvec 0.3.23 (registry+https://github.com/rust-lang/crates.io-index)",
  "atomic_refcell 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "bindgen 0.29.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "bit-vec 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)",
  "bitflags 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "byteorder 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "cfg-if 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -3139,17 +3139,17 @@ dependencies = [
  "syn 0.11.11 (registry+https://github.com/rust-lang/crates.io-index)",
  "synstructure 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
 name = "style_tests"
 version = "0.0.1"
 dependencies = [
- "app_units 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)",
+ "app_units 0.5.5 (registry+https://github.com/rust-lang/crates.io-index)",
  "byteorder 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "cssparser 0.20.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "euclid 0.15.1 (registry+https://github.com/rust-lang/crates.io-index)",
  "html5ever 0.19.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "parking_lot 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)",
  "rayon 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)",
  "rustc-serialize 0.3.24 (registry+https://github.com/rust-lang/crates.io-index)",
  "selectors 0.19.0",
@@ -3161,17 +3161,17 @@ dependencies = [
  "style 0.0.1",
  "style_traits 0.0.1",
 ]
 
 [[package]]
 name = "style_traits"
 version = "0.0.1"
 dependencies = [
- "app_units 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)",
+ "app_units 0.5.5 (registry+https://github.com/rust-lang/crates.io-index)",
  "bitflags 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "cssparser 0.20.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "euclid 0.15.1 (registry+https://github.com/rust-lang/crates.io-index)",
  "heapsize 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "heapsize_derive 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)",
  "selectors 0.19.0",
  "serde 1.0.8 (registry+https://github.com/rust-lang/crates.io-index)",
  "servo_atoms 0.0.1",
@@ -3553,17 +3553,17 @@ dependencies = [
  "webdriver 0.22.0 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
 name = "webrender"
 version = "0.50.0"
 source = "git+https://github.com/servo/webrender#01c38a21e6951dcc0241b1b65d6acb837d42ba22"
 dependencies = [
- "app_units 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)",
+ "app_units 0.5.5 (registry+https://github.com/rust-lang/crates.io-index)",
  "bincode 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "bit-set 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "bitflags 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)",
  "byteorder 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "core-graphics 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)",
  "core-text 6.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "dwrote 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "euclid 0.15.1 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -3581,17 +3581,17 @@ dependencies = [
  "webrender_api 0.50.0 (git+https://github.com/servo/webrender)",
 ]
 
 [[package]]
 name = "webrender_api"
 version = "0.50.0"
 source = "git+https://github.com/servo/webrender#01c38a21e6951dcc0241b1b65d6acb837d42ba22"
 dependencies = [
- "app_units 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)",
+ "app_units 0.5.5 (registry+https://github.com/rust-lang/crates.io-index)",
  "bincode 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "byteorder 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "core-foundation 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "core-graphics 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)",
  "dwrote 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "euclid 0.15.1 (registry+https://github.com/rust-lang/crates.io-index)",
  "fxhash 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
  "heapsize 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -3719,17 +3719,17 @@ dependencies = [
 "checksum adler32 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3ff33fe13a08dbce05bcefa2c68eea4844941437e33d6f808240b54d7157b9cd"
 "checksum aho-corasick 0.6.3 (registry+https://github.com/rust-lang/crates.io-index)" = "500909c4f87a9e52355b26626d890833e9e1d53ac566db76c36faa984b889699"
 "checksum alloc-no-stdlib 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b21f6ad9c9957eb5d70c3dee16d31c092b3cab339628f821766b05e6833d72b8"
 "checksum android_glue 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "d8289e9637439939cc92b1995b0972117905be88bc28116c86b64d6e589bcd38"
 "checksum android_injected_glue 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7ec08bc5e100186b5223a24dcfe5655d1488aed9eafeb44fb9a0f67a4f53d0fc"
 "checksum angle 0.5.0 (git+https://github.com/servo/angle?branch=servo)" = "<none>"
 "checksum ansi_term 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "23ac7c30002a5accbf7e8987d0632fa6de155b7c3d39d0067317a391e00a2ef6"
 "checksum antidote 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "34fde25430d87a9388dadbe6e34d7f72a462c8b43ac8d309b42b0a8505d7e2a5"
-"checksum app_units 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)" = "7ff4f3fe57393a150b39b026b6f6f4b9a6c4f49b52d0a4e2d61d08d926358438"
+"checksum app_units 0.5.5 (registry+https://github.com/rust-lang/crates.io-index)" = "7eb1573f29213263e6016754100e0ae875049d3692dfe6764834c2d7957ca94c"
 "checksum arraydeque 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "96e774cadb24c2245225280c6799793f9802b918a58a79615e9490607489a717"
 "checksum arrayvec 0.3.23 (registry+https://github.com/rust-lang/crates.io-index)" = "699e63a93b79d717e8c3b5eb1b28b7780d0d6d9e59a72eb769291c83b0c8dc67"
 "checksum aster 0.41.0 (registry+https://github.com/rust-lang/crates.io-index)" = "4ccfdf7355d9db158df68f976ed030ab0f6578af811f5a7bb6dcf221ec24e0e0"
 "checksum atomic_refcell 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "fb2dcb6e6d35f20276943cc04bb98e538b348d525a04ac79c10021561d202f21"
 "checksum audio-video-metadata 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "3b6ef29ee98ad95a37f34547fd7fb40724772294110ed6ca0445fc2e964c29d1"
 "checksum azure 0.21.0 (git+https://github.com/servo/rust-azure)" = "<none>"
 "checksum backtrace 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "72f9b4182546f4b04ebc4ab7f84948953a118bd6021a1b6a6c909e3e94f6be76"
 "checksum backtrace-sys 0.1.11 (registry+https://github.com/rust-lang/crates.io-index)" = "3a0d842ea781ce92be2bf78a9b38883948542749640b8378b3b2f03d1fd9f1ff"
--- a/servo/components/script/dom/cssmediarule.rs
+++ b/servo/components/script/dom/cssmediarule.rs
@@ -67,23 +67,25 @@ impl CSSMediaRule {
         list.to_css_string().into()
     }
 
     /// https://drafts.csswg.org/css-conditional-3/#the-cssmediarule-interface
     pub fn set_condition_text(&self, text: DOMString) {
         let mut input = ParserInput::new(&text);
         let mut input = Parser::new(&mut input);
         let global = self.global();
-        let win = global.as_window();
-        let url = win.get_url();
-        let quirks_mode = win.Document().quirks_mode();
+        let window = global.as_window();
+        let url = window.get_url();
+        let quirks_mode = window.Document().quirks_mode();
         let context = ParserContext::new_for_cssom(&url, Some(CssRuleType::Media),
                                                    PARSING_MODE_DEFAULT,
                                                    quirks_mode);
-        let new_medialist = parse_media_query_list(&context, &mut input);
+
+        let new_medialist = parse_media_query_list(&context, &mut input,
+                                                   window.css_error_reporter());
         let mut guard = self.cssconditionrule.shared_lock().write();
 
         // Clone an Arc because we can’t borrow `guard` twice at the same time.
 
         // FIXME(SimonSapin): allow access to multiple objects with one write guard?
         // Would need a set of usize pointer addresses or something,
         // the same object is not accessed more than once.
         let mqs = Arc::clone(&self.mediarule.write_with(&mut guard).media_queries);
--- a/servo/components/script/dom/htmllinkelement.rs
+++ b/servo/components/script/dom/htmllinkelement.rs
@@ -284,17 +284,19 @@ impl HTMLLinkElement {
         };
 
         let mut input = ParserInput::new(&mq_str);
         let mut css_parser = CssParser::new(&mut input);
         let doc_url = document.url();
         let context = CssParserContext::new_for_cssom(&doc_url, Some(CssRuleType::Media),
                                                       PARSING_MODE_DEFAULT,
                                                       document.quirks_mode());
-        let media = parse_media_query_list(&context, &mut css_parser);
+        let window = document.window();
+        let media = parse_media_query_list(&context, &mut css_parser,
+                                           window.css_error_reporter());
 
         let im_attribute = element.get_attribute(&ns!(), &local_name!("integrity"));
         let integrity_val = im_attribute.r().map(|a| a.value());
         let integrity_metadata = match integrity_val {
             Some(ref value) => &***value,
             None => "",
         };
 
--- a/servo/components/script/dom/htmlstyleelement.rs
+++ b/servo/components/script/dom/htmlstyleelement.rs
@@ -69,39 +69,42 @@ impl HTMLStyleElement {
                            HTMLStyleElementBinding::Wrap)
     }
 
     pub fn parse_own_css(&self) {
         let node = self.upcast::<Node>();
         let element = self.upcast::<Element>();
         assert!(node.is_in_doc());
 
-        let win = window_from_node(node);
+        let window = window_from_node(node);
         let doc = document_from_node(self);
 
         let mq_attribute = element.get_attribute(&ns!(), &local_name!("media"));
         let mq_str = match mq_attribute {
             Some(a) => String::from(&**a.value()),
             None => String::new(),
         };
 
         let data = node.GetTextContent().expect("Element.textContent must be a string");
-        let url = win.get_url();
+        let url = window.get_url();
         let context = CssParserContext::new_for_cssom(&url,
                                                       Some(CssRuleType::Media),
                                                       PARSING_MODE_DEFAULT,
                                                       doc.quirks_mode());
         let shared_lock = node.owner_doc().style_shared_lock().clone();
         let mut input = ParserInput::new(&mq_str);
-        let mq = Arc::new(shared_lock.wrap(
-                    parse_media_query_list(&context, &mut CssParser::new(&mut input))));
+        let css_error_reporter = window.css_error_reporter();
+        let mq = Arc::new(shared_lock.wrap(parse_media_query_list(&context,
+                                                                  &mut CssParser::new(&mut input),
+                                                                  css_error_reporter)));
         let loader = StylesheetLoader::for_element(self.upcast());
-        let sheet = Stylesheet::from_str(&data, win.get_url(), Origin::Author, mq,
+        let sheet = Stylesheet::from_str(&data, window.get_url(),
+                                         Origin::Author, mq,
                                          shared_lock, Some(&loader),
-                                         win.css_error_reporter(),
+                                         css_error_reporter,
                                          doc.quirks_mode(),
                                          self.line_number as u32);
 
         let sheet = Arc::new(sheet);
 
         // No subresource loads were triggered, just fire the load event now.
         if self.pending_loads.get() == 0 {
             self.upcast::<EventTarget>().fire_event(atom!("load"));
--- a/servo/components/script/dom/medialist.rs
+++ b/servo/components/script/dom/medialist.rs
@@ -69,23 +69,24 @@ impl MediaListMethods for MediaList {
             // Step 1
             *media_queries = StyleMediaList::empty();
             return;
         }
         // Step 3
         let mut input = ParserInput::new(&value);
         let mut parser = Parser::new(&mut input);
         let global = self.global();
-        let win = global.as_window();
-        let url = win.get_url();
-        let quirks_mode = win.Document().quirks_mode();
+        let window = global.as_window();
+        let url = window.get_url();
+        let quirks_mode = window.Document().quirks_mode();
         let context = ParserContext::new_for_cssom(&url, Some(CssRuleType::Media),
                                                    PARSING_MODE_DEFAULT,
                                                    quirks_mode);
-        *media_queries = parse_media_query_list(&context, &mut parser);
+        *media_queries = parse_media_query_list(&context, &mut parser,
+                                                window.css_error_reporter());
     }
 
     // https://drafts.csswg.org/cssom/#dom-medialist-length
     fn Length(&self) -> u32 {
         let guard = self.shared_lock().read();
         self.media_queries.read_with(&guard).media_queries.len() as u32
     }
 
--- a/servo/components/script/dom/performance.rs
+++ b/servo/components/script/dom/performance.rs
@@ -177,16 +177,25 @@ impl Performance {
 
     /// Remove a PerformanceObserver from the list of observers.
     pub fn remove_observer(&self, observer: &DOMPerformanceObserver) {
         let mut observers = self.observers.borrow_mut();
         let index = match observers.iter().position(|o| &(*o.observer) == observer) {
             Some(p) => p,
             None => return,
         };
+
+        if self.pending_notification_observers_task.get() {
+            if let Some(o) = observers.iter().nth(index) {
+                DOMPerformanceObserver::new(&self.global(),
+                                            o.observer.callback(),
+                                            o.observer.entries()).notify();
+            }
+        }
+
         observers.remove(index);
     }
 
     /// Queue a notification for each performance observer interested in
     /// this type of performance entry and queue a low priority task to
     /// notify the observers if no other notification task is already queued.
     ///
     /// Algorithm spec:
--- a/servo/components/script/dom/window.rs
+++ b/servo/components/script/dom/window.rs
@@ -1007,17 +1007,18 @@ impl WindowMethods for Window {
     fn MatchMedia(&self, query: DOMString) -> Root<MediaQueryList> {
         let mut input = ParserInput::new(&query);
         let mut parser = Parser::new(&mut input);
         let url = self.get_url();
         let quirks_mode = self.Document().quirks_mode();
         let context = CssParserContext::new_for_cssom(&url, Some(CssRuleType::Media),
                                                       PARSING_MODE_DEFAULT,
                                                       quirks_mode);
-        let media_query_list = media_queries::parse_media_query_list(&context, &mut parser);
+        let media_query_list = media_queries::parse_media_query_list(&context, &mut parser,
+                                                                     self.css_error_reporter());
         let document = self.Document();
         let mql = MediaQueryList::new(&document, media_query_list);
         self.media_query_lists.push(&*mql);
         mql
     }
 
     #[allow(unrooted_must_root)]
     // https://fetch.spec.whatwg.org/#fetch-method
--- a/servo/components/style/Cargo.toml
+++ b/servo/components/style/Cargo.toml
@@ -24,17 +24,17 @@ servo = ["serde", "heapsize", "heapsize_
 
          # FIXME: Uncomment when https://github.com/servo/servo/pull/16953 has landed:
          #"arrayvec/use_union"
 
          "servo_url"]
 gecko_debug = ["nsstring_vendor/gecko_debug"]
 
 [dependencies]
-app_units = "0.5.3"
+app_units = "0.5.5"
 arrayvec = "0.3.20"
 arraydeque = "0.2.3"
 atomic_refcell = "0.1"
 bitflags = "0.7"
 bit-vec = "0.4.3"
 byteorder = "1.0"
 cfg-if = "0.1.0"
 cssparser = "0.20"
--- a/servo/components/style/animation.rs
+++ b/servo/components/style/animation.rs
@@ -414,27 +414,25 @@ impl PropertyAnimation {
     pub fn has_the_same_end_value_as(&self, other: &Self) -> bool {
         self.property.has_the_same_end_value_as(&other.property)
     }
 }
 
 /// Inserts transitions into the queue of running animations as applicable for
 /// the given style difference. This is called from the layout worker threads.
 /// Returns true if any animations were kicked off and false otherwise.
-//
-// TODO(emilio): Take rid of this mutex splitting SharedLayoutContex into a
-// cloneable part and a non-cloneable part..
 #[cfg(feature = "servo")]
-pub fn start_transitions_if_applicable(new_animations_sender: &Sender<Animation>,
-                                       opaque_node: OpaqueNode,
-                                       old_style: &ComputedValues,
-                                       new_style: &mut Arc<ComputedValues>,
-                                       timer: &Timer,
-                                       possibly_expired_animations: &[PropertyAnimation])
-                                       -> bool {
+pub fn start_transitions_if_applicable(
+    new_animations_sender: &Sender<Animation>,
+    opaque_node: OpaqueNode,
+    old_style: &ComputedValues,
+    new_style: &mut Arc<ComputedValues>,
+    timer: &Timer,
+    possibly_expired_animations: &[PropertyAnimation]
+) -> bool {
     let mut had_animations = false;
     for i in 0..new_style.get_box().transition_property_count() {
         // Create any property animations, if applicable.
         let property_animations = PropertyAnimation::from_transition(i,
                                                                      old_style,
                                                                      Arc::make_mut(new_style));
         for property_animation in property_animations {
             // Set the property to the initial value.
--- a/servo/components/style/bloom.rs
+++ b/servo/components/style/bloom.rs
@@ -83,36 +83,33 @@ struct PushedElement<E: TElement> {
     /// The number of hashes pushed for the element.
     num_hashes: usize,
 }
 
 impl<E: TElement> PushedElement<E> {
     fn new(el: E, num_hashes: usize) -> Self {
         PushedElement {
             element: unsafe { SendElement::new(el) },
-            num_hashes: num_hashes,
+            num_hashes,
         }
     }
 }
 
 fn each_relevant_element_hash<E, F>(element: E, mut f: F)
-    where E: TElement,
-          F: FnMut(u32),
+where
+    E: TElement,
+    F: FnMut(u32),
 {
     f(element.get_local_name().get_hash());
     f(element.get_namespace().get_hash());
 
     if let Some(id) = element.get_id() {
         f(id.get_hash());
     }
 
-    // TODO: case-sensitivity depends on the document type and quirks mode.
-    //
-    // TODO(emilio): It's not clear whether that's relevant here though?
-    // Classes and ids should be normalized already I think.
     element.each_class(|class| {
         f(class.get_hash())
     });
 }
 
 impl<E: TElement> Drop for StyleBloom<E> {
     fn drop(&mut self) {
         // Leave the reusable bloom filter in a zeroed state.
--- a/servo/components/style/error_reporting.rs
+++ b/servo/components/style/error_reporting.rs
@@ -14,17 +14,17 @@ use style_traits::ParseError;
 use stylesheets::UrlExtraData;
 
 /// Errors that can be encountered while parsing CSS.
 pub enum ContextualParseError<'a> {
     /// A property declaration was not recognized.
     UnsupportedPropertyDeclaration(&'a str, ParseError<'a>),
     /// A font face descriptor was not recognized.
     UnsupportedFontFaceDescriptor(&'a str, ParseError<'a>),
-    /// A font feature values descroptor was not recognized.
+    /// A font feature values descriptor was not recognized.
     UnsupportedFontFeatureValuesDescriptor(&'a str, ParseError<'a>),
     /// A keyframe rule was not valid.
     InvalidKeyframeRule(&'a str, ParseError<'a>),
     /// A font feature values rule was not valid.
     InvalidFontFeatureValuesRule(&'a str, ParseError<'a>),
     /// A keyframe property declaration was not recognized.
     UnsupportedKeyframePropertyDeclaration(&'a str, ParseError<'a>),
     /// A rule was invalid for some reason.
@@ -39,17 +39,19 @@ pub enum ContextualParseError<'a> {
     InvalidCounterStyleWithoutSymbols(String),
     /// A counter style rule had less than two symbols.
     InvalidCounterStyleNotEnoughSymbols(String),
     /// A counter style rule did not have additive-symbols.
     InvalidCounterStyleWithoutAdditiveSymbols,
     /// A counter style rule had extends with symbols.
     InvalidCounterStyleExtendsWithSymbols,
     /// A counter style rule had extends with additive-symbols.
-    InvalidCounterStyleExtendsWithAdditiveSymbols
+    InvalidCounterStyleExtendsWithAdditiveSymbols,
+    /// A media rule was invalid for some reason.
+    InvalidMediaRule(&'a str, ParseError<'a>),
 }
 
 impl<'a> fmt::Display for ContextualParseError<'a> {
     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
         fn token_to_str(t: &Token, f: &mut fmt::Formatter) -> fmt::Result {
             match *t {
                 Token::Ident(ref i) => write!(f, "identifier {}", i),
                 Token::AtKeyword(ref kw) => write!(f, "keyword @{}", kw),
@@ -163,16 +165,20 @@ impl<'a> fmt::Display for ContextualPars
                 write!(f, "Invalid @counter-style rule: 'system: additive' without 'additive-symbols'")
             }
             ContextualParseError::InvalidCounterStyleExtendsWithSymbols => {
                 write!(f, "Invalid @counter-style rule: 'system: extends …' with 'symbols'")
             }
             ContextualParseError::InvalidCounterStyleExtendsWithAdditiveSymbols => {
                 write!(f, "Invalid @counter-style rule: 'system: extends …' with 'additive-symbols'")
             }
+            ContextualParseError::InvalidMediaRule(media_rule, ref err) => {
+                write!(f, "Invalid media rule: {}, ", media_rule)?;
+                parse_error_to_str(err, f)
+            }
         }
     }
 }
 
 /// A generic trait for an error reporter.
 pub trait ParseErrorReporter {
     /// Called when the style engine detects an error.
     ///
--- a/servo/components/style/gecko/media_queries.rs
+++ b/servo/components/style/gecko/media_queries.rs
@@ -18,17 +18,16 @@ 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::{nsPresContext, RawGeckoPresContextOwned};
 use gecko_bindings::structs::nsIAtom;
 use media_queries::MediaType;
 use parser::ParserContext;
 use properties::{ComputedValues, StyleBuilder};
 use properties::longhands::font_size;
-use selectors::parser::SelectorParseError;
 use servo_arc::Arc;
 use std::fmt::{self, Write};
 use std::sync::atomic::{AtomicBool, AtomicIsize, Ordering};
 use str::starts_with_ignore_ascii_case;
 use string_cache::Atom;
 use style_traits::{CSSPixel, DevicePixel};
 use style_traits::{ToCss, ParseError, StyleParseError};
 use style_traits::viewport::ViewportConstraints;
@@ -152,21 +151,18 @@ impl Device {
             };
 
             MediaType(CustomIdent(Atom::from(medium_to_use)))
         }
     }
 
     /// Returns the current viewport size in app units.
     pub fn au_viewport_size(&self) -> Size2D<Au> {
-        unsafe {
-            // TODO(emilio): Need to take into account scrollbars.
-            let area = &self.pres_context().mVisibleArea;
-            Size2D::new(Au(area.width), Au(area.height))
-        }
+        let area = &self.pres_context().mVisibleArea;
+        Size2D::new(Au(area.width), Au(area.height))
     }
 
     /// Returns the current viewport size in app units, recording that it's been
     /// used for viewport unit resolution.
     pub fn au_viewport_size_for_viewport_unit_resolution(&self) -> Size2D<Au> {
         self.used_viewport_size.store(true, Ordering::Relaxed);
         self.au_viewport_size()
     }
@@ -464,16 +460,99 @@ unsafe fn find_in_table<F>(mut current_e
         if f(keyword, value) {
             return Some((keyword, value));
         }
 
         current_entry = current_entry.offset(1);
     }
 }
 
+fn parse_feature_value<'i, 't>(feature: &nsMediaFeature,
+                               feature_value_type: nsMediaFeature_ValueType,
+                               context: &ParserContext,
+                               input: &mut Parser<'i, 't>)
+                               -> Result<MediaExpressionValue, ParseError<'i>> {
+    let value = match feature_value_type {
+        nsMediaFeature_ValueType::eLength => {
+           let length = Length::parse_non_negative(context, input)?;
+           // FIXME(canaltinova): See bug 1396057. Gecko doesn't support calc
+           // inside media queries. This check is for temporarily remove it
+           // for parity with gecko. We should remove this check when we want
+           // to support it.
+           if let Length::Calc(_) = length {
+               return Err(StyleParseError::UnspecifiedError.into())
+           }
+           MediaExpressionValue::Length(length)
+        },
+        nsMediaFeature_ValueType::eInteger => {
+           // FIXME(emilio): We should use `Integer::parse` to handle `calc`
+           // properly in integer expressions. Note that calc is still not
+           // supported in media queries per FIXME above.
+           let i = input.expect_integer()?;
+           if i < 0 {
+               return Err(StyleParseError::UnspecifiedError.into())
+           }
+           MediaExpressionValue::Integer(i as u32)
+        }
+        nsMediaFeature_ValueType::eBoolInteger => {
+           let i = input.expect_integer()?;
+           if i < 0 || i > 1 {
+               return Err(StyleParseError::UnspecifiedError.into())
+           }
+           MediaExpressionValue::BoolInteger(i == 1)
+        }
+        nsMediaFeature_ValueType::eFloat => {
+           MediaExpressionValue::Float(input.expect_number()?)
+        }
+        nsMediaFeature_ValueType::eIntRatio => {
+           let a = input.expect_integer()?;
+           if a <= 0 {
+               return Err(StyleParseError::UnspecifiedError.into())
+           }
+
+           input.expect_delim('/')?;
+
+           let b = input.expect_integer()?;
+           if b <= 0 {
+               return Err(StyleParseError::UnspecifiedError.into())
+           }
+           MediaExpressionValue::IntRatio(a as u32, b as u32)
+        }
+        nsMediaFeature_ValueType::eResolution => {
+           MediaExpressionValue::Resolution(Resolution::parse(input)?)
+        }
+        nsMediaFeature_ValueType::eEnumerated => {
+           let keyword = input.expect_ident()?;
+           let keyword = unsafe {
+               bindings::Gecko_LookupCSSKeyword(keyword.as_bytes().as_ptr(),
+               keyword.len() as u32)
+           };
+
+           let first_table_entry: *const nsCSSProps_KTableEntry = unsafe {
+               *feature.mData.mKeywordTable.as_ref()
+           };
+
+           let value =
+               match unsafe { find_in_table(first_table_entry, |kw, _| kw == keyword) } {
+                   Some((_kw, value)) => {
+                       value
+                   }
+                   None => return Err(StyleParseError::UnspecifiedError.into()),
+               };
+
+           MediaExpressionValue::Enumerated(value)
+        }
+        nsMediaFeature_ValueType::eIdent => {
+           MediaExpressionValue::Ident(input.expect_ident()?.as_ref().to_owned())
+        }
+    };
+
+    Ok(value)
+}
+
 impl Expression {
     /// Trivially construct a new expression.
     fn new(feature: &'static nsMediaFeature,
            value: Option<MediaExpressionValue>,
            range: nsMediaExpression_Range) -> Self {
         Expression {
             feature: feature,
             value: value,
@@ -483,23 +562,34 @@ impl Expression {
 
     /// Parse a media expression of the form:
     ///
     /// ```
     /// (media-feature: media-value)
     /// ```
     pub fn parse<'i, 't>(context: &ParserContext, input: &mut Parser<'i, 't>)
                          -> Result<Self, ParseError<'i>> {
-        input.expect_parenthesis_block()?;
+        input.expect_parenthesis_block().map_err(|err|
+            match err {
+                BasicParseError::UnexpectedToken(t) => StyleParseError::ExpectedIdentifier(t),
+                _ => StyleParseError::UnspecifiedError,
+            }
+        )?;
+
         input.parse_nested_block(|input| {
             // FIXME: remove extra indented block when lifetimes are non-lexical
             let feature;
             let range;
             {
-                let ident = input.expect_ident()?;
+                let ident = input.expect_ident().map_err(|err|
+                    match err {
+                        BasicParseError::UnexpectedToken(t) => StyleParseError::ExpectedIdentifier(t),
+                        _ => StyleParseError::UnspecifiedError,
+                    }
+                )?;
 
                 let mut flags = 0;
                 let result = {
                     let mut feature_name = &**ident;
 
                     if unsafe { structs::StylePrefs_sWebkitPrefixedAliasesEnabled } &&
                        starts_with_ignore_ascii_case(feature_name, "-webkit-") {
                         feature_name = &feature_name[8..];
@@ -525,116 +615,49 @@ impl Expression {
                         None => Err(()),
                     }
                 };
 
                 match result {
                     Ok((f, r)) => {
                         feature = f;
                         range = r;
-                    }
-                    Err(()) => return Err(SelectorParseError::UnexpectedIdent(ident.clone()).into()),
+                    },
+                    Err(()) => {
+                        return Err(StyleParseError::MediaQueryExpectedFeatureName(ident.clone()).into())
+                    },
                 }
 
                 if (feature.mReqFlags & !flags) != 0 {
-                    return Err(SelectorParseError::UnexpectedIdent(ident.clone()).into());
+                    return Err(StyleParseError::MediaQueryExpectedFeatureName(ident.clone()).into());
                 }
 
                 if range != nsMediaExpression_Range::eEqual &&
-                    feature.mRangeType != nsMediaFeature_RangeType::eMinMaxAllowed {
-                    return Err(SelectorParseError::UnexpectedIdent(ident.clone()).into());
+                   feature.mRangeType != nsMediaFeature_RangeType::eMinMaxAllowed {
+                    return Err(StyleParseError::MediaQueryExpectedFeatureName(ident.clone()).into());
                 }
             }
 
             // If there's no colon, this is a media query of the form
             // '(<feature>)', that is, there's no value specified.
             //
             // Gecko doesn't allow ranged expressions without a value, so just
             // reject them here too.
             if input.try(|i| i.expect_colon()).is_err() {
                 if range != nsMediaExpression_Range::eEqual {
                     return Err(StyleParseError::RangedExpressionWithNoValue.into())
                 }
                 return Ok(Expression::new(feature, None, range));
             }
 
-            let value = match feature.mValueType {
-                nsMediaFeature_ValueType::eLength => {
-                    let length = Length::parse_non_negative(context, input)?;
-                    // FIXME(canaltinova): See bug 1396057. Gecko doesn't support calc
-                    // inside media queries. This check is for temporarily remove it
-                    // for parity with gecko. We should remove this check when we want
-                    // to support it.
-                    if let Length::Calc(_) = length {
-                        return Err(StyleParseError::UnspecifiedError.into())
-                    }
-                    MediaExpressionValue::Length(length)
-                },
-                nsMediaFeature_ValueType::eInteger => {
-                    // FIXME(emilio): We should use `Integer::parse` to handle `calc`
-                    // properly in integer expressions. Note that calc is still not
-                    // supported in media queries per FIXME above.
-                    let i = input.expect_integer()?;
-                    if i < 0 {
-                        return Err(StyleParseError::UnspecifiedError.into())
-                    }
-                    MediaExpressionValue::Integer(i as u32)
-                }
-                nsMediaFeature_ValueType::eBoolInteger => {
-                    let i = input.expect_integer()?;
-                    if i < 0 || i > 1 {
-                        return Err(StyleParseError::UnspecifiedError.into())
-                    }
-                    MediaExpressionValue::BoolInteger(i == 1)
-                }
-                nsMediaFeature_ValueType::eFloat => {
-                    MediaExpressionValue::Float(input.expect_number()?)
-                }
-                nsMediaFeature_ValueType::eIntRatio => {
-                    let a = input.expect_integer()?;
-                    if a <= 0 {
-                        return Err(StyleParseError::UnspecifiedError.into())
-                    }
-
-                    input.expect_delim('/')?;
-
-                    let b = input.expect_integer()?;
-                    if b <= 0 {
-                        return Err(StyleParseError::UnspecifiedError.into())
-                    }
-                    MediaExpressionValue::IntRatio(a as u32, b as u32)
-                }
-                nsMediaFeature_ValueType::eResolution => {
-                    MediaExpressionValue::Resolution(Resolution::parse(input)?)
-                }
-                nsMediaFeature_ValueType::eEnumerated => {
-                    let keyword = input.expect_ident()?;
-                    let keyword = unsafe {
-                        bindings::Gecko_LookupCSSKeyword(keyword.as_bytes().as_ptr(),
-                                                         keyword.len() as u32)
-                    };
-
-                    let first_table_entry: *const nsCSSProps_KTableEntry = unsafe {
-                        *feature.mData.mKeywordTable.as_ref()
-                    };
-
-                    let value =
-                        match unsafe { find_in_table(first_table_entry, |kw, _| kw == keyword) } {
-                            Some((_kw, value)) => {
-                                value
-                            }
-                            None => return Err(StyleParseError::UnspecifiedError.into()),
-                        };
-
-                    MediaExpressionValue::Enumerated(value)
-                }
-                nsMediaFeature_ValueType::eIdent => {
-                    MediaExpressionValue::Ident(input.expect_ident()?.as_ref().to_owned())
-                }
-            };
+            let value = parse_feature_value(feature,
+                                            feature.mValueType,
+                                            context, input).map_err(|_|
+                StyleParseError::MediaQueryExpectedFeatureValue
+            )?;
 
             Ok(Expression::new(feature, Some(value), range))
         })
     }
 
     /// Returns whether this media query evaluates to true for the given device.
     pub fn matches(&self, device: &Device, quirks_mode: QuirksMode) -> bool {
         let mut css_value = nsCSSValue::null();
--- a/servo/components/style/invalidation/stylesheets.rs
+++ b/servo/components/style/invalidation/stylesheets.rs
@@ -48,18 +48,18 @@ impl InvalidationScope {
                 }
             }
         }
     }
 }
 
 /// 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?
+/// TODO(emilio): We might be able to do the same analysis for media query
+/// changes too (or even selector changes?).
 #[cfg_attr(feature = "servo", derive(HeapSizeOf))]
 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,
 }
 
--- a/servo/components/style/media_queries.rs
+++ b/servo/components/style/media_queries.rs
@@ -3,18 +3,20 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 //! [Media queries][mq].
 //!
 //! [mq]: https://drafts.csswg.org/mediaqueries/
 
 use Atom;
 use context::QuirksMode;
-use cssparser::{Delimiter, Parser, Token, ParserInput};
-use parser::ParserContext;
+use cssparser::{Delimiter, Parser};
+use cssparser::{Token, ParserInput};
+use error_reporting::{ContextualParseError, ParseErrorReporter};
+use parser::{ParserContext, ParserErrorContext};
 use selectors::parser::SelectorParseError;
 use serialize_comma_separated_list;
 use std::fmt;
 use str::string_as_ascii_lowercase;
 use style_traits::{ToCss, ParseError, StyleParseError};
 use values::CustomIdent;
 
 #[cfg(feature = "servo")]
@@ -235,29 +237,42 @@ impl MediaQuery {
 }
 
 /// Parse a media query list from CSS.
 ///
 /// Always returns a media query list. If any invalid media query is found, the
 /// media query list is only filled with the equivalent of "not all", see:
 ///
 /// https://drafts.csswg.org/mediaqueries/#error-handling
-pub fn parse_media_query_list(context: &ParserContext, input: &mut Parser) -> MediaList {
+pub fn parse_media_query_list<R>(
+    context: &ParserContext,
+    input: &mut Parser,
+    error_reporter: &R,
+) -> MediaList
+where
+    R: ParseErrorReporter,
+{
     if input.is_exhausted() {
         return MediaList::empty()
     }
 
     let mut media_queries = vec![];
     loop {
+        let start_position = input.position();
+        let start_location = input.current_source_location();
         match input.parse_until_before(Delimiter::Comma, |i| MediaQuery::parse(context, i)) {
             Ok(mq) => {
                 media_queries.push(mq);
             },
-            Err(..) => {
+            Err(err) => {
                 media_queries.push(MediaQuery::never_matching());
+                let error = ContextualParseError::InvalidMediaRule(
+                    input.slice_from(start_position), err);
+                let error_context = ParserErrorContext { error_reporter };
+                context.log_css_error(&error_context, start_location, error);
             },
         }
 
         match input.next() {
             Ok(&Token::Comma) => {},
             Ok(_) => unreachable!(),
             Err(_) => break,
         }
--- a/servo/components/style/properties/helpers/animated_properties.mako.rs
+++ b/servo/components/style/properties/helpers/animated_properties.mako.rs
@@ -242,31 +242,25 @@ impl TransitionProperty {
             % endif
         % endfor
         false
     }
 
     /// Parse a transition-property value.
     pub fn parse<'i, 't>(input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i>> {
         let ident = input.expect_ident()?;
-        let supported = match_ignore_ascii_case! { &ident,
-            "all" => Ok(Some(TransitionProperty::All)),
+        match_ignore_ascii_case! { &ident,
+            "all" => Ok(TransitionProperty::All),
             % for prop in data.longhands + data.shorthands_except_all():
                 % if prop.transitionable:
-                    "${prop.name}" => Ok(Some(TransitionProperty::${prop.camel_case})),
+                    "${prop.name}" => Ok(TransitionProperty::${prop.camel_case}),
                 % endif
             % endfor
-            "none" => Err(()),
-            _ => Ok(None),
-        };
-
-        match supported {
-            Ok(Some(property)) => Ok(property),
-            Ok(None) => CustomIdent::from_ident(ident, &[]).map(TransitionProperty::Unsupported),
-            Err(()) => Err(SelectorParseError::UnexpectedIdent(ident.clone()).into()),
+            "none" => Err(SelectorParseError::UnexpectedIdent(ident.clone()).into()),
+            _ => CustomIdent::from_ident(ident, &[]).map(TransitionProperty::Unsupported),
         }
     }
 
     /// Return transitionable longhands of this shorthand TransitionProperty, except for "all".
     pub fn longhands(&self) -> &'static [TransitionProperty] {
         % for prop in data.shorthands_except_all():
             % if prop.transitionable:
                 static ${prop.ident.upper()}: &'static [TransitionProperty] = &[
--- a/servo/components/style/properties/shorthand/box.mako.rs
+++ b/servo/components/style/properties/shorthand/box.mako.rs
@@ -87,19 +87,22 @@ macro_rules! try_parse_one {
     use parser::Parse;
     % for prop in "delay duration property timing_function".split():
     use properties::longhands::transition_${prop};
     % endfor
 
     pub fn parse_value<'i, 't>(context: &ParserContext, input: &mut Parser<'i, 't>)
                                -> Result<Longhands, ParseError<'i>> {
         struct SingleTransition {
-            % for prop in "property duration timing_function delay".split():
+            % for prop in "duration timing_function delay".split():
             transition_${prop}: transition_${prop}::SingleSpecifiedValue,
             % endfor
+            // Unlike other properties, transition-property uses an Option<> to
+            // represent 'none' as `None`.
+            transition_property: Option<transition_property::SingleSpecifiedValue>,
         }
 
         fn parse_one_transition<'i, 't>(context: &ParserContext, input: &mut Parser<'i, 't>)
                                         -> Result<SingleTransition,ParseError<'i>> {
             % for prop in "property duration timing_function delay".split():
             let mut ${prop} = None;
             % endfor
 
@@ -107,83 +110,107 @@ macro_rules! try_parse_one {
             loop {
                 parsed += 1;
 
                 try_parse_one!(context, input, duration, transition_duration);
                 try_parse_one!(context, input, timing_function, transition_timing_function);
                 try_parse_one!(context, input, delay, transition_delay);
                 // Must check 'transition-property' after 'transition-timing-function' since
                 // 'transition-property' accepts any keyword.
-                try_parse_one!(input, property, transition_property);
+                if property.is_none() {
+                    if let Ok(value) = input.try(|i| transition_property::SingleSpecifiedValue::parse(i)) {
+                        property = Some(Some(value));
+                        continue;
+                    } else if input.try(|i| i.expect_ident_matching("none")).is_ok() {
+                        // 'none' is not a valid value for <single-transition-property>,
+                        // so it's not acceptable in the function above.
+                        property = Some(None);
+                        continue;
+                    }
+                }
 
                 parsed -= 1;
                 break
             }
 
             if parsed != 0 {
                 Ok(SingleTransition {
-                    % for prop in "property duration timing_function delay".split():
+                    % for prop in "duration timing_function delay".split():
                     transition_${prop}: ${prop}.unwrap_or_else(transition_${prop}::single_value
                                                                                  ::get_initial_specified_value),
                     % endfor
+                    transition_property: property.unwrap_or(
+                        Some(transition_property::single_value::get_initial_specified_value())),
                 })
             } else {
                 Err(StyleParseError::UnspecifiedError.into())
             }
         }
 
         % for prop in "property duration timing_function delay".split():
         let mut ${prop}s = Vec::new();
         % endfor
 
-        if input.try(|input| input.expect_ident_matching("none")).is_err() {
-            let results = input.parse_comma_separated(|i| parse_one_transition(context, i))?;
-            for result in results {
-                % for prop in "property duration timing_function delay".split():
-                ${prop}s.push(result.transition_${prop});
-                % endfor
+        let results = input.parse_comma_separated(|i| parse_one_transition(context, i))?;
+        let multiple_items = results.len() >= 2;
+        for result in results {
+            if let Some(value) = result.transition_property {
+                propertys.push(value);
+            } else if multiple_items {
+                // If there is more than one item, and any of transitions has 'none',
+                // then it's invalid. Othersize, leave propertys to be empty (which
+                // means "transition-property: none");
+                return Err(StyleParseError::UnspecifiedError.into());
             }
-        } else {
-            // `transition: none` is a valid syntax, and we keep transition_property empty because |none| is not
-            // a valid TransitionProperty.
-            // durations, delays, and timing_functions are not allowed as empty, so before we convert them into
-            // longhand properties, we need to put initial values for none transition.
+
             % for prop in "duration timing_function delay".split():
-            ${prop}s.push(transition_${prop}::single_value::get_initial_specified_value());
+            ${prop}s.push(result.transition_${prop});
             % endfor
         }
 
         Ok(expanded! {
             % for prop in "property duration timing_function delay".split():
             transition_${prop}: transition_${prop}::SpecifiedValue(${prop}s),
             % endfor
         })
     }
 
     impl<'a> ToCss for LonghandsToSerialize<'a>  {
         fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write {
-            let len = self.transition_property.0.len();
-            // There should be at least one declared value
-            if len == 0 {
-                return Ok(());
+            let property_len = self.transition_property.0.len();
+
+            // There are two cases that we can do shorthand serialization:
+            // * when all value lists have the same length, or
+            // * when transition-property is none, and other value lists have exactly one item.
+            if property_len == 0 {
+                % for name in "duration delay timing_function".split():
+                    if self.transition_${name}.0.len() != 1 {
+                        return Ok(());
+                    }
+                % endfor
+            } else {
+                % for name in "duration delay timing_function".split():
+                    if self.transition_${name}.0.len() != property_len {
+                        return Ok(());
+                    }
+                % endfor
             }
 
-            // If any value list length is differs then we don't do a shorthand serialization
-            // either.
-            % for name in "property duration delay timing_function".split():
-                if len != self.transition_${name}.0.len() {
-                    return Ok(());
-                }
-            % endfor
+            // Representative length.
+            let len = self.transition_duration.0.len();
 
             for i in 0..len {
                 if i != 0 {
                     dest.write_str(", ")?;
                 }
-                self.transition_property.0[i].to_css(dest)?;
+                if property_len == 0 {
+                    dest.write_str("none")?;
+                } else {
+                    self.transition_property.0[i].to_css(dest)?;
+                }
                 % for name in "duration timing_function delay".split():
                     dest.write_str(" ")?;
                     self.transition_${name}.0[i].to_css(dest)?;
                 % endfor
             }
             Ok(())
         }
     }
--- a/servo/components/style/stylesheets/rule_parser.rs
+++ b/servo/components/style/stylesheets/rule_parser.rs
@@ -173,17 +173,18 @@ impl<'a, 'i, R: ParseErrorReporter> AtRu
                     // "@import must be before any rule but @charset"
                     self.had_hierarchy_error = true;
                     return Err(StyleParseError::UnexpectedImportRule.into())
                 }
 
                 let url_string = input.expect_url_or_string()?.as_ref().to_owned();
                 let specified_url = SpecifiedUrl::parse_from_string(url_string, &self.context)?;
 
-                let media = parse_media_query_list(&self.context, input);
+                let media = parse_media_query_list(&self.context, input,
+                                                   self.error_context.error_reporter);
                 let media = Arc::new(self.shared_lock.wrap(media));
 
                 let prelude = AtRuleNonBlockPrelude::Import(specified_url, media, location);
                 return Ok(AtRuleType::WithoutBlock(prelude));
             },
             "namespace" => {
                 if self.state > State::Namespaces {
                     // "@namespace must be before any rule but @charset and @import"
@@ -349,17 +350,18 @@ impl<'a, 'b, 'i, R: ParseErrorReporter> 
         &mut self,
         name: CowRcStr<'i>,
         input: &mut Parser<'i, 't>
     ) -> Result<AtRuleType<AtRuleNonBlockPrelude, AtRuleBlockPrelude>, ParseError<'i>> {
         let location = get_location_with_offset(input.current_source_location());
 
         match_ignore_ascii_case! { &*name,
             "media" => {
-                let media_queries = parse_media_query_list(self.context, input);
+                let media_queries = parse_media_query_list(self.context, input,
+                                                           self.error_context.error_reporter);
                 let arc = Arc::new(self.shared_lock.wrap(media_queries));
                 Ok(AtRuleType::WithBlock(AtRuleBlockPrelude::Media(arc, location)))
             },
             "supports" => {
                 let cond = SupportsCondition::parse(input)?;
                 Ok(AtRuleType::WithBlock(AtRuleBlockPrelude::Supports(cond, location)))
             },
             "font-face" => {
--- a/servo/components/style/values/specified/length.rs
+++ b/servo/components/style/values/specified/length.rs
@@ -1,17 +1,17 @@
 /* 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/. */
 
 //! [Length values][length].
 //!
 //! [length]: https://drafts.csswg.org/css-values/#lengths
 
-use app_units::{Au, MAX_AU, MIN_AU};
+use app_units::Au;
 use cssparser::{Parser, Token, BasicParseError};
 use euclid::Size2D;
 use font_metrics::FontMetricsQueryResult;
 use parser::{Parse, ParserContext};
 use std::{cmp, fmt, mem};
 use std::ascii::AsciiExt;
 use std::ops::{Add, Mul};
 use style_traits::{ToCss, ParseError, StyleParseError};
@@ -231,26 +231,16 @@ impl CharacterWidth {
         //
         // TODO(pcwalton): Find these from the font.
         let average_advance = reference_font_size.scale_by(0.5);
         let max_advance = reference_font_size;
         average_advance.scale_by(self.0 as CSSFloat - 1.0) + max_advance
     }
 }
 
-/// Helper to convert a floating point length to application units
-fn to_au_round(length: CSSFloat, au_per_unit: CSSFloat) -> Au {
-    Au(
-        ((length * au_per_unit) as f64)
-        .min(MAX_AU.0 as f64)
-        .max(MIN_AU.0 as f64)
-        .round() as i32
-    )
-}
-
 /// Represents an absolute length with its unit
 #[derive(Clone, Copy, Debug, PartialEq)]
 #[cfg_attr(feature = "servo", derive(HeapSizeOf))]
 pub enum AbsoluteLength {
     /// An absolute length in pixels (px)
     Px(CSSFloat),
     /// An absolute length in inches (in)
     In(CSSFloat),
@@ -304,26 +294,30 @@ impl ToComputedValue for AbsoluteLength 
         Au::from(*self)
     }
 
     fn from_computed_value(computed: &Au) -> AbsoluteLength {
         AbsoluteLength::Px(computed.to_f32_px())
     }
 }
 
+fn au_from_f32_round(x: f32) -> Au {
+    Au::from_f64_au((x as f64).round())
+}
+
 impl From<AbsoluteLength> for Au {
     fn from(length: AbsoluteLength) -> Au {
         match length {
-            AbsoluteLength::Px(value) => to_au_round(value, AU_PER_PX),
-            AbsoluteLength::In(value) => to_au_round(value, AU_PER_IN),
-            AbsoluteLength::Cm(value) => to_au_round(value, AU_PER_CM),
-            AbsoluteLength::Mm(value) => to_au_round(value, AU_PER_MM),
-            AbsoluteLength::Q(value) => to_au_round(value, AU_PER_Q),
-            AbsoluteLength::Pt(value) => to_au_round(value, AU_PER_PT),
-            AbsoluteLength::Pc(value) => to_au_round(value, AU_PER_PC),
+            AbsoluteLength::Px(value) => au_from_f32_round((value * AU_PER_PX)),
+            AbsoluteLength::In(value) => au_from_f32_round((value * AU_PER_IN)),
+            AbsoluteLength::Cm(value) => au_from_f32_round((value * AU_PER_CM)),
+            AbsoluteLength::Mm(value) => au_from_f32_round((value * AU_PER_MM)),
+            AbsoluteLength::Q(value) => au_from_f32_round((value * AU_PER_Q)),
+            AbsoluteLength::Pt(value) => au_from_f32_round((value * AU_PER_PT)),
+            AbsoluteLength::Pc(value) => au_from_f32_round((value * AU_PER_PC)),
         }
     }
 }
 
 impl ToCss for AbsoluteLength {
     fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write {
         match *self {
             AbsoluteLength::Px(length) => serialize_dimension(length, "px", dest),
@@ -390,17 +384,17 @@ impl PhysicalLength {
         const MM_PER_INCH: f32 = 25.4;
 
         let physical_inch = unsafe {
             bindings::Gecko_GetAppUnitsPerPhysicalInch(context.device().pres_context())
         };
 
         let inch = self.0 / MM_PER_INCH;
 
-        to_au_round(inch, physical_inch as f32)
+        au_from_f32_round(inch * physical_inch as f32)
     }
 }
 
 #[cfg(feature = "gecko")]
 impl ToCss for PhysicalLength {
     fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write {
         serialize_dimension(self.0, "mozmm", dest)
     }
--- a/servo/components/style_traits/lib.rs
+++ b/servo/components/style_traits/lib.rs
@@ -102,17 +102,23 @@ pub enum StyleParseError<'i> {
     /// Unexpected closing curly bracket in a DVB.
     UnbalancedCloseCurlyBracketInDeclarationValueBlock,
     /// A property declaration parsing error.
     PropertyDeclaration(PropertyDeclarationParseError<'i>),
     /// A property declaration value had input remaining after successfully parsing.
     PropertyDeclarationValueNotExhausted,
     /// An unexpected dimension token was encountered.
     UnexpectedDimension(CowRcStr<'i>),
-    /// A media query using a ranged expression with no value was encountered.
+    /// Expected identifier not found.
+    ExpectedIdentifier(Token<'i>),
+    /// Missing or invalid media feature name.
+    MediaQueryExpectedFeatureName(CowRcStr<'i>),
+    /// Missing or invalid media feature value.
+    MediaQueryExpectedFeatureValue,
+    /// min- or max- properties must have a value.
     RangedExpressionWithNoValue,
     /// A function was encountered that was not expected.
     UnexpectedFunction(CowRcStr<'i>),
     /// @namespace must be before any rule but @charset and @import
     UnexpectedNamespaceRule,
     /// @import must be before any rule but @charset
     UnexpectedImportRule,
     /// Unexpected @charset rule encountered.
--- a/servo/ports/geckolib/error_reporter.rs
+++ b/servo/ports/geckolib/error_reporter.rs
@@ -136,16 +136,24 @@ struct ErrorParams<'a> {
 /// a second parameter if it exists, for use in the prefix for the eventual error message.
 fn extract_error_params<'a>(err: ParseError<'a>) -> Option<ErrorParams<'a>> {
     let (main, prefix) = match err {
         CssParseError::Custom(SelectorParseError::Custom(
             StyleParseError::PropertyDeclaration(
                 PropertyDeclarationParseError::InvalidValue(property, Some(e))))) =>
             (Some(ErrorString::Snippet(property.into())), Some(extract_value_error_param(e))),
 
+        CssParseError::Custom(SelectorParseError::Custom(
+            StyleParseError::MediaQueryExpectedFeatureName(ident))) =>
+            (Some(ErrorString::Ident(ident)), None),
+
+        CssParseError::Custom(SelectorParseError::Custom(
+            StyleParseError::ExpectedIdentifier(token))) =>
+            (Some(ErrorString::UnexpectedToken(token)), None),
+
         CssParseError::Custom(SelectorParseError::UnexpectedTokenInAttributeSelector(t)) |
         CssParseError::Custom(SelectorParseError::BadValueInAttr(t)) |
         CssParseError::Custom(SelectorParseError::ExpectedBarInAttr(t)) |
         CssParseError::Custom(SelectorParseError::NoQualifiedNameInAttributeSelector(t)) |
         CssParseError::Custom(SelectorParseError::InvalidQualNameInAttr(t)) |
         CssParseError::Custom(SelectorParseError::ExplicitNamespaceUnexpectedToken(t)) |
         CssParseError::Custom(SelectorParseError::PseudoElementExpectedIdent(t)) |
         CssParseError::Custom(SelectorParseError::NoIdentForPseudo(t)) |
@@ -184,17 +192,18 @@ impl<'a> ErrorHelpers<'a> for Contextual
             ContextualParseError::UnsupportedFontFaceDescriptor(s, err) |
             ContextualParseError::UnsupportedFontFeatureValuesDescriptor(s, err) |
             ContextualParseError::InvalidKeyframeRule(s, err) |
             ContextualParseError::InvalidFontFeatureValuesRule(s, err) |
             ContextualParseError::UnsupportedKeyframePropertyDeclaration(s, err) |
             ContextualParseError::InvalidRule(s, err) |
             ContextualParseError::UnsupportedRule(s, err) |
             ContextualParseError::UnsupportedViewportDescriptorDeclaration(s, err) |
-            ContextualParseError::UnsupportedCounterStyleDescriptorDeclaration(s, err) =>
+            ContextualParseError::UnsupportedCounterStyleDescriptorDeclaration(s, err) |
+            ContextualParseError::InvalidMediaRule(s, err) =>
                 (s.into(), err),
             ContextualParseError::InvalidCounterStyleWithoutSymbols(s) |
             ContextualParseError::InvalidCounterStyleNotEnoughSymbols(s) =>
                 (s.into(), StyleParseError::UnspecifiedError.into()),
             ContextualParseError::InvalidCounterStyleWithoutAdditiveSymbols |
             ContextualParseError::InvalidCounterStyleExtendsWithSymbols |
             ContextualParseError::InvalidCounterStyleExtendsWithAdditiveSymbols =>
                 ("".into(), StyleParseError::UnspecifiedError.into())
@@ -276,16 +285,40 @@ impl<'a> ErrorHelpers<'a> for Contextual
                     CssParseError::Custom(SelectorParseError::ClassNeedsIdent(_)) =>
                         Some(&b"PEClassSelNotIdent\0"[..]),
                     CssParseError::Custom(SelectorParseError::EmptyNegation) =>
                         Some(&b"PENegationBadArg\0"[..]),
                     _ => None,
                 };
                 return (prefix, b"PEBadSelectorRSIgnored\0", Action::Nothing);
             }
+            ContextualParseError::InvalidMediaRule(_, ref err) => {
+                let err: &[u8] = match *err {
+                    CssParseError::Custom(SelectorParseError::Custom(
+                            StyleParseError::ExpectedIdentifier(..))) => {
+                        b"PEGatherMediaNotIdent\0"
+                    },
+                    CssParseError::Custom(SelectorParseError::Custom(
+                            StyleParseError::MediaQueryExpectedFeatureName(..))) => {
+                        b"PEMQExpectedFeatureName\0"
+                    },
+                    CssParseError::Custom(SelectorParseError::Custom(
+                            StyleParseError::MediaQueryExpectedFeatureValue)) => {
+                        b"PEMQExpectedFeatureValue\0"
+                    },
+                    CssParseError::Custom(SelectorParseError::Custom(
+                            StyleParseError::RangedExpressionWithNoValue)) => {
+                        b"PEMQNoMinMaxWithoutValue\0"
+                    },
+                    _ => {
+                        b"PEDeclDropped\0"
+                    },
+                };
+                (err, Action::Nothing)
+            }
             ContextualParseError::UnsupportedRule(..) =>
                 (b"PEDeclDropped\0", Action::Nothing),
             ContextualParseError::UnsupportedViewportDescriptorDeclaration(..) |
             ContextualParseError::UnsupportedCounterStyleDescriptorDeclaration(..) |
             ContextualParseError::InvalidCounterStyleWithoutSymbols(..) |
             ContextualParseError::InvalidCounterStyleNotEnoughSymbols(..) |
             ContextualParseError::InvalidCounterStyleWithoutAdditiveSymbols |
             ContextualParseError::InvalidCounterStyleExtendsWithSymbols |
--- a/servo/ports/geckolib/glue.rs
+++ b/servo/ports/geckolib/glue.rs
@@ -2486,17 +2486,17 @@ pub extern "C" fn Servo_MediaList_SetTex
     let text = unsafe { text.as_ref().unwrap().as_str_unchecked() };
     let mut input = ParserInput::new(&text);
     let mut parser = Parser::new(&mut input);
     let url_data = unsafe { dummy_url_data() };
     let context = ParserContext::new_for_cssom(url_data, Some(CssRuleType::Media),
                                                PARSING_MODE_DEFAULT,
                                                QuirksMode::NoQuirks);
      write_locked_arc(list, |list: &mut MediaList| {
-        *list = parse_media_query_list(&context, &mut parser);
+        *list = parse_media_query_list(&context, &mut parser, &NullReporter);
     })
 }
 
 #[no_mangle]
 pub extern "C" fn Servo_MediaList_GetLength(list: RawServoMediaListBorrowed) -> u32 {
     read_locked_arc(list, |list: &MediaList| list.media_queries.len() as u32)
 }
 
--- a/testing/talos/talos/ffsetup.py
+++ b/testing/talos/talos/ffsetup.py
@@ -89,17 +89,17 @@ class FFSetup(object):
                 value = utils.interpolate(value, webserver=webserver)
                 preferences[name] = value
 
         extensions = self.browser_config['extensions'][:]
         if self.test_config.get('extensions'):
             extensions.append(self.test_config['extensions'])
 
         if self.browser_config['develop'] or \
-           self.browser_config['branch_name'] == 'Try':
+           'try' in str.lower(self.browser_config['branch_name']):
             extensions = [os.path.dirname(i) for i in extensions]
 
         profile = Profile.clone(
             os.path.normpath(self.test_config['profile_path']),
             self.profile_dir,
             restore=False)
 
         profile.set_preferences(preferences)
--- a/testing/talos/talos/run_tests.py
+++ b/testing/talos/talos/run_tests.py
@@ -113,17 +113,17 @@ def run_tests(config, browser_config):
 
     # pass --no-remote to firefox launch, if --develop is specified
     # we do that to allow locally the user to have another running firefox
     # instance
     if browser_config['develop']:
         browser_config['extra_args'] = '--no-remote'
 
     # with addon signing for production talos, we want to develop without it
-    if browser_config['develop'] or browser_config['branch_name'] == 'Try':
+    if browser_config['develop'] or 'try' in str.lower(browser_config['branch_name']):
         browser_config['preferences']['xpinstall.signatures.required'] = False
 
     browser_config['preferences']['extensions.allow-non-mpc-extensions'] = True
 
     # if using firstNonBlankPaint, must turn on pref for it
     if test.get('fnbpaint', False):
         LOG.info("Using firstNonBlankPaint, so turning on pref for it")
         browser_config['preferences']['dom.performance.time_to_non_blank_paint.enabled'] = True
--- a/third_party/rust/app_units/.cargo-checksum.json
+++ b/third_party/rust/app_units/.cargo-checksum.json
@@ -1,1 +1,1 @@
-{"files":{".cargo-ok":"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",".travis.yml":"6b96b2c6bfd7e1acef4b825a2813fc4277859eb9400a16800db8835c25e4087d","Cargo.toml":"1648e6342e3879aaf3add2a9341915ca1650037df830907dc1e2b768aa087036","README.md":"9f048d969f9f8333cdcdb892744cd0816e4f2922c8817fa5e9e07f9472fe1050","src/app_unit.rs":"7e75ca0ad78d26c6538029c7ddae650f7b76c2f7210735e6da6e52920494fdb4","src/lib.rs":"2df7d863c47d8b22f9af66caeafa87e6a206ee713a8aeaa55c5a80a42a92513b"},"package":"7ff4f3fe57393a150b39b026b6f6f4b9a6c4f49b52d0a4e2d61d08d926358438"}
\ No newline at end of file
+{"files":{".cargo-ok":"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",".travis.yml":"6b96b2c6bfd7e1acef4b825a2813fc4277859eb9400a16800db8835c25e4087d","Cargo.toml":"41d47153a6043d3e4599f827888e1ac43c204e52ed5f6998b1e275fcae21a3cc","README.md":"9f048d969f9f8333cdcdb892744cd0816e4f2922c8817fa5e9e07f9472fe1050","src/app_unit.rs":"0f4fde2c0481b6dd021f48c8ef548090e7c577c02c429c41626c2b5e7a006949","src/lib.rs":"2df7d863c47d8b22f9af66caeafa87e6a206ee713a8aeaa55c5a80a42a92513b"},"package":"ed0a4de09a3b8449515e649f3bb84f72ea15fc2d10639beb0776a09b7d308074"}
\ No newline at end of file
--- a/third_party/rust/app_units/Cargo.toml
+++ b/third_party/rust/app_units/Cargo.toml
@@ -7,25 +7,25 @@
 #
 # If you believe there's an error in this file please file an
 # issue against the rust-lang/cargo repository. If you're
 # editing this file be aware that the upstream Cargo.toml
 # will likely look very different (and much more reasonable)
 
 [package]
 name = "app_units"
-version = "0.5.3"
+version = "0.5.6"
 authors = ["The Servo Project Developers"]
 description = "Servo app units type (Au)"
 documentation = "http://doc.servo.org/app_units/"
 license = "MPL-2.0"
 repository = "https://github.com/servo/app_units"
-[dependencies.serde]
-version = "1.0"
+[dependencies.rustc-serialize]
+version = "0.3"
 
 [dependencies.num-traits]
 version = "0.1.32"
 
-[dependencies.rustc-serialize]
-version = "0.3"
-
 [dependencies.heapsize]
 version = ">=0.3, < 0.5"
+
+[dependencies.serde]
+version = "1.0"
--- a/third_party/rust/app_units/src/app_unit.rs
+++ b/third_party/rust/app_units/src/app_unit.rs
@@ -184,27 +184,34 @@ impl Au {
             MIN_AU
         } else {
             self
         }
     }
 
     #[inline]
     fn clamp_self(&mut self) {
-        *self = self.clamp()
+        *self = Au::clamp(*self)
     }
 
     #[inline]
     pub fn scale_by(self, factor: f32) -> Au {
         let new_float = ((self.0 as f64) * factor as f64).round();
-        Au::clamp_from_f64_au(new_float)
+        Au::from_f64_au(new_float)
     }
 
     #[inline]
-    fn clamp_from_f64_au(float: f64) -> Self {
+    /// Scale, but truncate (useful for viewport-relative units)
+    pub fn scale_by_trunc(self, factor: f32) -> Au {
+        let new_float = ((self.0 as f64) * factor as f64).trunc();
+        Au::from_f64_au(new_float)
+    }
+
+    #[inline]
+    pub fn from_f64_au(float: f64) -> Self {
         // We *must* operate in f64. f32 isn't precise enough
         // to handle MAX_AU
         Au(float.min(MAX_AU.0 as f64)
                 .max(MIN_AU.0 as f64)
             as i32)
     }
 
     #[inline]
@@ -242,23 +249,23 @@ impl Au {
     #[inline]
     pub fn to_f64_px(self) -> f64 {
         (self.0 as f64) / (AU_PER_PX as f64)
     }
 
     #[inline]
     pub fn from_f32_px(px: f32) -> Au {
         let float = (px * AU_PER_PX as f32).round();
-        Au::clamp_from_f64_au(float as f64)
+        Au::from_f64_au(float as f64)
     }
 
     #[inline]
     pub fn from_f64_px(px: f64) -> Au {
         let float = (px * AU_PER_PX as f64).round();
-        Au::clamp_from_f64_au(float)
+        Au::from_f64_au(float)
     }
 
     #[inline]
     pub fn abs(self) -> Self {
         Au(self.0.abs())
     }
 }
 
@@ -298,16 +305,17 @@ fn saturate() {
     assert_eq!(-half * -10, MAX_AU);
 }
 
 #[test]
 fn scale() {
     assert_eq!(Au(12).scale_by(1.5), Au(18));
     assert_eq!(Au(12).scale_by(1.7), Au(20));
     assert_eq!(Au(12).scale_by(1.8), Au(22));
+    assert_eq!(Au(12).scale_by_trunc(1.8), Au(21));
 }
 
 #[test]
 fn abs() {
     assert_eq!(Au(-10).abs(), Au(10));
 }
 
 #[test]
--- a/toolkit/components/extensions/ExtensionCommon.jsm
+++ b/toolkit/components/extensions/ExtensionCommon.jsm
@@ -27,17 +27,17 @@ XPCOMUtils.defineLazyModuleGetters(this,
   PrivateBrowsingUtils: "resource://gre/modules/PrivateBrowsingUtils.jsm",
   Schemas: "resource://gre/modules/Schemas.jsm",
 });
 
 XPCOMUtils.defineLazyServiceGetter(this, "styleSheetService",
                                    "@mozilla.org/content/style-sheet-service;1",
                                    "nsIStyleSheetService");
 
-const global = this;
+const global = Cu.getGlobalForObject(this);
 
 Cu.import("resource://gre/modules/ExtensionUtils.jsm");
 
 var {
   DefaultMap,
   DefaultWeakMap,
   EventEmitter,
   ExtensionError,
--- a/toolkit/components/extensions/ExtensionContent.jsm
+++ b/toolkit/components/extensions/ExtensionContent.jsm
@@ -253,17 +253,25 @@ class Script {
   }
 
   async injectInto(window) {
     let context = this.extension.getContext(window);
 
     if (this.runAt === "document_end") {
       await promiseDocumentReady(window.document);
     } else if (this.runAt === "document_idle") {
-      await promiseDocumentLoaded(window.document);
+      let readyThenIdle = promiseDocumentReady(window.document).then(() => {
+        return new Promise(resolve =>
+          window.requestIdleCallback(resolve, {timeout: idleTimeout}));
+      });
+
+      await Promise.race([
+        readyThenIdle,
+        promiseDocumentLoaded(window.document),
+      ]);
     }
 
     return this.inject(context);
   }
 
   /**
    * Tries to inject this script into the given window and sandbox, if
    * there are pending operations for the window's current load state.
@@ -317,22 +325,16 @@ class Script {
     let {document} = context.contentWindow;
     if (this.runAt === "document_start" && document.readyState !== "complete") {
       document.blockParsing(scriptsPromise);
     }
 
     let scripts = await scriptsPromise;
     let result;
 
-    if (this.runAt === "document_idle") {
-      await new Promise(resolve =>
-          context.contentWindow.requestIdleCallback(resolve,
-                                                    {timeout: idleTimeout}));
-    }
-
     // The evaluations below may throw, in which case the promise will be
     // automatically rejected.
     TelemetryStopwatch.start(CONTENT_SCRIPT_INJECTION_HISTOGRAM, context);
     try {
       for (let script of scripts) {
         result = script.executeInGlobal(context.cloneScope);
       }
 
--- a/toolkit/components/extensions/ext-contextualIdentities.js
+++ b/toolkit/components/extensions/ext-contextualIdentities.js
@@ -71,16 +71,22 @@ const convertIdentity = identity => {
     color: identity.color,
     colorCode: getContainerColor(identity.color),
     cookieStoreId: getCookieStoreIdForContainer(identity.userContextId),
   };
 
   return result;
 };
 
+const checkAPIEnabled = () => {
+  if (!containersEnabled) {
+    throw new ExtensionError("Contextual identities are currently disabled");
+  }
+};
+
 const convertIdentityFromObserver = wrappedIdentity => {
   let identity = wrappedIdentity.wrappedJSObject;
   let iconUrl, colorCode;
   try {
     iconUrl = getContainerIcon(identity.icon);
     colorCode = getContainerColor(identity.color);
   } catch (e) {
     return null;
@@ -122,28 +128,28 @@ this.contextualIdentities = class extend
       ExtensionPreferencesManager.setSetting(extension, CONTAINERS_ENABLED_SETTING_NAME, true);
     }
   }
 
   getAPI(context) {
     let self = {
       contextualIdentities: {
         async get(cookieStoreId) {
+          checkAPIEnabled();
           let containerId = getContainerForCookieStoreId(cookieStoreId);
           if (!containerId) {
-            return Promise.reject({
-              message: `Invalid contextual identitiy: ${cookieStoreId}`,
-            });
+            throw new ExtensionError(`Invalid contextual identitiy: ${cookieStoreId}`);
           }
 
           let identity = ContextualIdentityService.getPublicIdentityFromId(containerId);
           return convertIdentity(identity);
         },
 
         async query(details) {
+          checkAPIEnabled();
           let identities = [];
           ContextualIdentityService.getPublicIdentities().forEach(identity => {
             if (details.name &&
                 ContextualIdentityService.getUserContextLabel(identity.userContextId) != details.name) {
               return;
             }
 
             identities.push(convertIdentity(identity));
@@ -158,29 +164,26 @@ this.contextualIdentities = class extend
           getContainerColor(details.color);
 
           let identity = ContextualIdentityService.create(details.name,
                                                           details.icon,
                                                           details.color);
           return convertIdentity(identity);
         },
 
-        update(cookieStoreId, details) {
+        async update(cookieStoreId, details) {
+          checkAPIEnabled();
           let containerId = getContainerForCookieStoreId(cookieStoreId);
           if (!containerId) {
-            return Promise.reject({
-              message: `Invalid contextual identitiy: ${cookieStoreId}`,
-            });
+            throw new ExtensionError(`Invalid contextual identitiy: ${cookieStoreId}`);
           }
 
           let identity = ContextualIdentityService.getPublicIdentityFromId(containerId);
           if (!identity) {
-            return Promise.reject({
-              message: `Invalid contextual identitiy: ${cookieStoreId}`,
-            });
+            throw new ExtensionError(`Invalid contextual identitiy: ${cookieStoreId}`);
           }
 
           if (details.name !== null) {
             identity.name = details.name;
           }
 
           if (details.color !== null) {
             identity.color = details.color;
@@ -188,46 +191,39 @@ this.contextualIdentities = class extend
 
           if (details.icon !== null) {
             identity.icon = details.icon;
           }
 
           if (!ContextualIdentityService.update(identity.userContextId,
                                                 identity.name, identity.icon,
                                                 identity.color)) {
-            return Promise.reject({
-              message: `Contextual identitiy failed to update: ${cookieStoreId}`,
-            });
+            throw new ExtensionError(`Contextual identitiy failed to update: ${cookieStoreId}`);
           }
 
           return convertIdentity(identity);
         },
 
         async remove(cookieStoreId) {
+          checkAPIEnabled();
           let containerId = getContainerForCookieStoreId(cookieStoreId);
           if (!containerId) {
-            return Promise.reject({
-              message: `Invalid contextual identitiy: ${cookieStoreId}`,
-            });
+            throw new ExtensionError(`Invalid contextual identitiy: ${cookieStoreId}`);
           }
 
           let identity = ContextualIdentityService.getPublicIdentityFromId(containerId);
           if (!identity) {
-            return Promise.reject({
-              message: `Invalid contextual identitiy: ${cookieStoreId}`,
-            });
+            throw new ExtensionError(`Invalid contextual identitiy: ${cookieStoreId}`);
           }
 
           // We have to create the identity object before removing it.
           let convertedIdentity = convertIdentity(identity);
 
           if (!ContextualIdentityService.remove(identity.userContextId)) {
-            return Promise.reject({
-              message: `Contextual identitiy failed to remove: ${cookieStoreId}`,
-            });
+            throw new ExtensionError(`Contextual identitiy failed to remove: ${cookieStoreId}`);
           }
 
           return convertedIdentity;
         },
 
         onCreated: new EventManager(context, "contextualIdentities.onCreated", fire => {
           let observer = (subject, topic) => {
             let convertedIdentity = convertIdentityFromObserver(subject);
--- a/toolkit/components/extensions/test/mochitest/test_ext_notifications.html
+++ b/toolkit/components/extensions/test/mochitest/test_ext_notifications.html
@@ -41,53 +41,59 @@ add_task(async function test_notificatio
   await extension.startup();
   let x = await extension.awaitMessage("running");
   is(x, "0", "got correct id from notifications.create");
   await extension.awaitFinish();
   await extension.unload();
 });
 
 add_task(async function test_notification_events() {
-  async function background() {
+  function background() {
     let opts = {
       type: "basic",
       title: "Testing Notification",
       message: "Carry on",
     };
 
+    let createdId = "98";
+
     // Test an ignored listener.
     browser.notifications.onButtonClicked.addListener(function() {});
 
     // We cannot test onClicked listener without a mock
     // but we can attempt to add a listener.
     browser.notifications.onClicked.addListener(function() {});
 
-    // Test onClosed listener.
-    browser.notifications.onClosed.addListener(id => {
-      browser.test.sendMessage("closed", id);
-      browser.test.notifyPass("background test passed");
+    browser.notifications.onShown.addListener(async function listener(id) {
+      browser.notifications.onShown.removeListener(listener);
+      browser.test.assertEq(createdId, id, "onShown received the expected id.");
+      let newId = await browser.notifications.create(id, opts);
+      browser.test.assertEq(createdId, newId, "create returned the expected id.");
+      browser.test.sendMessage("created");
     });
 
-    await browser.notifications.create("5", opts);
-    let id = await browser.notifications.create("5", opts);
-    browser.test.sendMessage("running", id);
+    // Test onClosed listener.
+    browser.notifications.onClosed.addListener(function listener(id) {
+      browser.notifications.onClosed.removeListener(listener);
+      browser.test.assertEq(createdId, id, "onClosed received the expected id.");
+      browser.test.sendMessage("closed");
+    });
+
+    browser.notifications.create(createdId, opts);
   }
 
   let extension = ExtensionTestUtils.loadExtension({
     manifest: {
       permissions: ["notifications"],
     },
     background,
   });
   await extension.startup();
-  let x = await extension.awaitMessage("closed");
-  is(x, "5", "got correct id from onClosed listener");
-  x = await extension.awaitMessage("running");
-  is(x, "5", "got correct id from notifications.create");
-  await extension.awaitFinish();
+  await extension.awaitMessage("closed");
+  await extension.awaitMessage("created");
   await extension.unload();
 });
 
 add_task(async function test_notification_clear() {
   function background() {
     let opts = {
       type: "basic",
       title: "Testing Notification",
--- a/toolkit/components/extensions/test/xpcshell/test_ext_contextual_identities.js
+++ b/toolkit/components/extensions/test/xpcshell/test_ext_contextual_identities.js
@@ -1,14 +1,26 @@
 "use strict";
 
 do_get_profile();
 
 XPCOMUtils.defineLazyModuleGetter(this, "AddonManager",
                                   "resource://modules/AddonManager.jsm");
+
+function waitForPrefChange(pref) {
+  return new Promise(resolve => {
+    function observeChange() {
+      Services.prefs.removeObserver(pref, observeChange);
+      resolve();
+    }
+
+    Services.prefs.addObserver(pref, observeChange);
+  });
+}
+
 add_task(async function startup() {
   await ExtensionTestUtils.startAddonManager();
 });
 
 add_task(async function test_contextualIdentities_without_permissions() {
   function background() {
     browser.test.assertTrue(!browser.contextualIdentities,
                             "contextualIdentities API is not available when the contextualIdentities permission is not required");
@@ -122,16 +134,40 @@ add_task(async function test_contextualI
     ci = await browser.contextualIdentities.get("firefox-container-1");
     browser.test.assertTrue(!!ci, "We have an identity");
     browser.test.assertTrue("name" in ci, "We have an identity.name");
     browser.test.assertTrue("color" in ci, "We have an identity.color");
     browser.test.assertTrue("icon" in ci, "We have an identity.icon");
     browser.test.assertEq("Personal", ci.name, "identity.name is correct");
     browser.test.assertEq("firefox-container-1", ci.cookieStoreId, "identity.cookieStoreId is correct");
 
+    function listenForMessage(messageName, stateChangeBool) {
+      return new Promise((resolve) => {
+        browser.test.onMessage.addListener(function listener(msg) {
+          browser.test.log(`Got message from background: ${msg}`);
+          if (msg === messageName + "-response") {
+            browser.test.onMessage.removeListener(listener);
+            resolve();
+          }
+        });
+        browser.test.log(`Sending message to background: ${messageName} ${stateChangeBool}`);
+        browser.test.sendMessage(messageName, stateChangeBool);
+      });
+    }
+
+    await listenForMessage("containers-state-change", false);
+
+    browser.test.assertRejects(
+      browser.contextualIdentities.query({}),
+      "Contextual identities are currently disabled",
+      "Throws when containers are disabled"
+    );
+
+    await listenForMessage("containers-state-change", true);
+
     let cis = await browser.contextualIdentities.query({});
     browser.test.assertEq(4, cis.length, "by default we should have 4 containers");
 
     cis = await browser.contextualIdentities.query({name: "Personal"});
     browser.test.assertEq(1, cis.length, "by default we should have 1 container called Personal");
 
     cis = await browser.contextualIdentities.query({name: "foobar"});
     browser.test.assertEq(0, cis.length, "by default we should have 0 container called foobar");
@@ -185,34 +221,47 @@ add_task(async function test_contextualI
     browser.test.assertEq("blue", ci.color, "identity.color is correct");
     browser.test.assertEq("cart", ci.icon, "identity.icon is correct");
 
     cis = await browser.contextualIdentities.query({});
     browser.test.assertEq(4, cis.length, "we are back to 4 identities");
 
     browser.test.notifyPass("contextualIdentities");
   }
+
   function makeExtension(id) {
     return ExtensionTestUtils.loadExtension({
       useAddonManager: "temporary",
       background,
       manifest: {
         applications: {
           gecko: {id},
         },
         permissions: ["contextualIdentities"],
       },
     });
   }
 
   let extension = makeExtension("containers-test@mozilla.org");
+
+  extension.onMessage("containers-state-change", (stateBool) => {
+    Components.utils.reportError(`Got message "containers-state-change", ${stateBool}`);
+    Services.prefs.setBoolPref(CONTAINERS_PREF, stateBool);
+    Components.utils.reportError("Changed pref");
+    extension.sendMessage("containers-state-change-response");
+  });
+
   await extension.startup();
   await extension.awaitFinish("contextualIdentities");
   equal(Services.prefs.getBoolPref(CONTAINERS_PREF), true, "Pref should now be enabled, whatever it's initial state");
+  const prefChange = waitForPrefChange(CONTAINERS_PREF);
   await extension.unload();
+  if (initial === false) {
+    await prefChange;
+  }
   equal(Services.prefs.getBoolPref(CONTAINERS_PREF), initial, "Pref should now be initial state");
 
   Services.prefs.clearUserPref(CONTAINERS_PREF);
 });
 
 add_task(async function test_contextualIdentity_extensions_enable_containers() {
   const CONTAINERS_PREF = "privacy.userContext.enabled";
   const initial = Services.prefs.getBoolPref(CONTAINERS_PREF);
@@ -230,27 +279,16 @@ add_task(async function test_contextualI
         applications: {
           gecko: {id},
         },
         permissions: ["contextualIdentities"],
       },
     });
   }
 
-  function waitForPrefChange(pref) {
-    return new Promise(resolve => {
-      function observeChange() {
-        Services.prefs.removeObserver(pref, observeChange);
-        resolve();
-      }
-
-      Services.prefs.addObserver(pref, observeChange);
-    });
-  }
-
   let extension = makeExtension("containers-test@mozilla.org");
   await extension.startup();
   await extension.awaitFinish("contextualIdentities");
   equal(Services.prefs.getBoolPref(CONTAINERS_PREF), true, "Pref should now be enabled, whatever it's initial state");
   const prefChange = waitForPrefChange(CONTAINERS_PREF);
   await extension.unload();
   // If pref was false we should wait for the pref to change back here.
   if (initial === false) {
--- a/toolkit/components/extensions/test/xpcshell/test_ext_unknown_permissions.js
+++ b/toolkit/components/extensions/test/xpcshell/test_ext_unknown_permissions.js
@@ -14,17 +14,17 @@ add_task(async function test_unknown_per
         "https://example.com/",
       ],
     },
   });
 
   let {messages} = await promiseConsoleOutput(
     () => extension.startup());
 
-  const {WebExtensionPolicy} = Cu.import("resource://gre/modules/Extension.jsm", {});
+  const {WebExtensionPolicy} = Cu.getGlobalForObject(Cu.import("resource://gre/modules/Extension.jsm", {}));
 
   let policy = WebExtensionPolicy.getByID(extension.id);
   Assert.deepEqual(Array.from(policy.permissions).sort(), ["activeTab", "http://*/*"]);
 
   Assert.deepEqual(extension.extension.manifest.optional_permissions, ["https://example.com/"]);
 
   ok(messages.some(message => /Error processing permissions\.1: Value "fooUnknownPermission" must/.test(message)),
      'Got expected error for "fooUnknownPermission"');
--- a/toolkit/components/places/Bookmarks.jsm
+++ b/toolkit/components/places/Bookmarks.jsm
@@ -90,16 +90,17 @@ async function promiseTagsFolderId() {
     "SELECT id FROM moz_bookmarks WHERE guid = :guid",
     { guid: Bookmarks.tagsGuid }
   );
   return gTagsFolderId = rows[0].getResultByName("id");
 }
 
 const MATCH_ANYWHERE_UNMODIFIED = Ci.mozIPlacesAutoComplete.MATCH_ANYWHERE_UNMODIFIED;
 const BEHAVIOR_BOOKMARK = Ci.mozIPlacesAutoComplete.BEHAVIOR_BOOKMARK;
+const SQLITE_MAX_VARIABLE_NUMBER = 999;
 
 var Bookmarks = Object.freeze({
   /**
    * Item's type constants.
    * These should stay consistent with nsINavBookmarksService.idl
    */
   TYPE_BOOKMARK: 1,
   TYPE_FOLDER: 2,
@@ -1433,16 +1434,25 @@ function insertBookmarkTree(items, sourc
                                     dateAdded, lastModified, guid,
                                     syncChangeCounter, syncStatus)
          VALUES (CASE WHEN :url ISNULL THEN NULL ELSE (SELECT id FROM moz_places WHERE url_hash = hash(:url) AND url = :url) END, :type,
          (SELECT id FROM moz_bookmarks WHERE guid = :parentGuid),
          IFNULL(:index, (SELECT count(*) FROM moz_bookmarks WHERE parent = :rootId)),
          NULLIF(:title, ""), :date_added, :last_modified, :guid,
          :syncChangeCounter, :syncStatus)`, items);
 
+      // Remove stale tombstones for new items.
+      for (let chunk of chunkArray(items, SQLITE_MAX_VARIABLE_NUMBER)) {
+        await db.executeCached(
+          `DELETE FROM moz_bookmarks_deleted WHERE guid IN (${
+            new Array(chunk.length).fill("?").join(",")})`,
+          chunk.map(item => item.guid)
+        );
+      }
+
       await setAncestorsLastModified(db, parent.guid, lastAddedForParent,
                                      syncChangeDelta);
     });
 
     // We don't wait for the frecency calculation.
     updateFrecency(db, urls, true).catch(Cu.reportError);
 
     return items;
@@ -2379,8 +2389,19 @@ function adjustSeparatorsSyncCounter(db,
     `,
     {
       delta: syncChangeDelta,
       parent: parentId,
       start_index: startIndex,
       item_type: Bookmarks.TYPE_SEPARATOR
     });
 }
+
+function* chunkArray(array, chunkLength) {
+  if (array.length <= chunkLength) {
+    yield array;
+    return;
+  }
+  let startIndex = 0;
+  while (startIndex < array.length) {
+    yield array.slice(startIndex, startIndex += chunkLength);
+  }
+}
--- a/toolkit/components/places/PlacesSyncUtils.jsm
+++ b/toolkit/components/places/PlacesSyncUtils.jsm
@@ -1942,16 +1942,19 @@ async function fetchQueryItem(db, bookma
     item.query = query;
   }
 
   return item;
 }
 
 function addRowToChangeRecords(row, changeRecords) {
   let syncId = BookmarkSyncUtils.guidToSyncId(row.getResultByName("guid"));
+  if (syncId in changeRecords) {
+    throw new Error(`Duplicate entry for ${syncId} in changeset`);
+  }
   let modifiedAsPRTime = row.getResultByName("modified");
   let modified = modifiedAsPRTime / MICROSECONDS_PER_SECOND;
   if (Number.isNaN(modified) || modified <= 0) {
     BookmarkSyncLog.error("addRowToChangeRecords: Invalid modified date for " +
                           syncId, modifiedAsPRTime);
     modified = 0;
   }
   changeRecords[syncId] = {
@@ -1971,17 +1974,17 @@ function addRowToChangeRecords(row, chan
  *        The Sqlite.jsm connection handle.
  * @return {Promise} resolved once all items have been fetched.
  * @resolves to an object containing records for changed bookmarks, keyed by
  *           the sync ID.
  */
 var pullSyncChanges = async function(db) {
   let changeRecords = {};
 
-  await db.executeCached(`
+  let rows = await db.executeCached(`
     WITH RECURSIVE
     syncedItems(id, guid, modified, syncChangeCounter, syncStatus) AS (
       SELECT b.id, b.guid, b.lastModified, b.syncChangeCounter, b.syncStatus
        FROM moz_bookmarks b
        WHERE b.guid IN ('menu________', 'toolbar_____', 'unfiled_____',
                         'mobile______')
       UNION ALL
       SELECT b.id, b.guid, b.lastModified, b.syncChangeCounter, b.syncStatus
@@ -1990,18 +1993,20 @@ var pullSyncChanges = async function(db)
     )
     SELECT guid, modified, syncChangeCounter, syncStatus, 0 AS tombstone
     FROM syncedItems
     WHERE syncChangeCounter >= 1
     UNION ALL
     SELECT guid, dateRemoved AS modified, 1 AS syncChangeCounter,
            :deletedSyncStatus, 1 AS tombstone
     FROM moz_bookmarks_deleted`,
-    { deletedSyncStatus: PlacesUtils.bookmarks.SYNC_STATUS.NORMAL },
-    row => addRowToChangeRecords(row, changeRecords));
+    { deletedSyncStatus: PlacesUtils.bookmarks.SYNC_STATUS.NORMAL });
+  for (let row of rows) {
+    addRowToChangeRecords(row, changeRecords);
+  }
 
   return changeRecords;
 };
 
 var touchSyncBookmark = async function(db, bookmarkItem) {
   if (BookmarkSyncLog.level <= Log.Level.Trace) {
     BookmarkSyncLog.trace(
       `touch: Reviving item "${bookmarkItem.guid}" and marking parent ` +
--- a/toolkit/components/places/tests/unit/test_sync_utils.js
+++ b/toolkit/components/places/tests/unit/test_sync_utils.js
@@ -2225,17 +2225,17 @@ add_task(async function test_pullChanges
       PlacesUtils.bookmarks.SYNC_STATUS.NORMAL,
       PlacesUtils.bookmarks.SYNC_STATUS.NORMAL,
       PlacesUtils.bookmarks.SYNC_STATUS.NORMAL,
       PlacesUtils.bookmarks.SYNC_STATUS.UNKNOWN,
       PlacesUtils.bookmarks.SYNC_STATUS.UNKNOWN,
     ], "Pulling items restored from JSON backup should not mark them as syncing");
 
     let tombstones = await PlacesTestUtils.fetchSyncTombstones();
-    ok(tombstones.map(({ guid }) => guid), [syncedFolder.guid],
+    deepEqual(tombstones.map(({ guid }) => guid), [syncedFolder.guid],
       "Tombstones should exist after restoring from JSON backup");
 
     await PlacesSyncUtils.bookmarks.markChangesAsSyncing(changes);
     let normalFields = await PlacesTestUtils.fetchBookmarkSyncFields(
       PlacesUtils.bookmarks.menuGuid, PlacesUtils.bookmarks.toolbarGuid,
       PlacesUtils.bookmarks.unfiledGuid, "NnvGl3CRA4hC", "APzP8MupzA8l");
     ok(normalFields.every(field =>
       field.syncStatus == PlacesUtils.bookmarks.SYNC_STATUS.NORMAL
@@ -2904,8 +2904,83 @@ add_task(async function test_ensureMobil
   do_print("We shouldn't track the query or the left pane root");
 
   let changes = await PlacesSyncUtils.bookmarks.pullChanges();
   ok(!(queryGuid in changes), "Should not track mobile query");
 
   await PlacesUtils.bookmarks.eraseEverything();
   await PlacesSyncUtils.bookmarks.reset();
 });
+
+add_task(async function test_remove_stale_tombstones() {
+  do_print("Insert and delete synced bookmark");
+  {
+    await PlacesUtils.bookmarks.insert({
+      guid: "bookmarkAAAA",
+      parentGuid: PlacesUtils.bookmarks.toolbarGuid,
+      url: "http://example.com/a",
+      title: "A",
+      source: PlacesUtils.bookmarks.SOURCES.SYNC,
+    });
+    await PlacesUtils.bookmarks.remove("bookmarkAAAA");
+    let tombstones = await PlacesTestUtils.fetchSyncTombstones();
+    deepEqual(tombstones.map(({ guid }) => guid), ["bookmarkAAAA"],
+      "Should store tombstone for deleted synced bookmark");
+  }
+
+  do_print("Reinsert deleted bookmark");
+  {
+    // Different parent, URL, and title, but same GUID.
+    await PlacesUtils.bookmarks.insert({
+      guid: "bookmarkAAAA",
+      parentGuid: PlacesUtils.bookmarks.unfiledGuid,
+      url: "http://example.com/a-restored",
+      title: "A (Restored)",
+    });
+
+    let tombstones = await PlacesTestUtils.fetchSyncTombstones();
+    deepEqual(tombstones, [],
+      "Should remove tombstone for reinserted bookmark");
+  }
+
+  do_print("Insert tree and erase everything");
+  {
+    await PlacesUtils.bookmarks.insertTree({
+      guid: PlacesUtils.bookmarks.menuGuid,
+      source: PlacesUtils.bookmarks.SOURCES.SYNC,
+      children: [{
+        guid: "bookmarkBBBB",
+        url: "http://example.com/b",
+        title: "B",
+      }, {
+        guid: "bookmarkCCCC",
+        url: "http://example.com/c",
+        title: "C",
+      }],
+    });
+    await PlacesUtils.bookmarks.eraseEverything();
+    let tombstones = await PlacesTestUtils.fetchSyncTombstones();
+    deepEqual(tombstones.map(({ guid }) => guid).sort(), ["bookmarkBBBB",
+      "bookmarkCCCC"], "Should store tombstones after erasing everything");
+  }
+
+  do_print("Reinsert tree");
+  {
+    await PlacesUtils.bookmarks.insertTree({
+      guid: PlacesUtils.bookmarks.mobileGuid,
+      children: [{
+        guid: "bookmarkBBBB",
+        url: "http://example.com/b",
+        title: "B",
+      }, {
+        guid: "bookmarkCCCC",
+        url: "http://example.com/c",
+        title: "C",
+      }],
+    });
+    let tombstones = await PlacesTestUtils.fetchSyncTombstones();
+    deepEqual(tombstones.map(({ guid }) => guid).sort(), [],
+      "Should remove tombstones after reinserting tree");
+  }
+
+  await PlacesUtils.bookmarks.eraseEverything();
+  await PlacesSyncUtils.bookmarks.reset();
+});
--- a/toolkit/components/url-classifier/tests/unit/test_backoff.js
+++ b/toolkit/components/url-classifier/tests/unit/test_backoff.js
@@ -1,14 +1,17 @@
 // Some unittests (e.g., paste into JS shell)
 var jslib = Cc["@mozilla.org/url-classifier/jslib;1"].
             getService().wrappedJSObject;
-var _Datenow = jslib.Date.now;
+
+var jslibDate = Cu.getGlobalForObject(jslib).Date;
+
+var _Datenow = jslibDate.now;
 function setNow(time) {
-  jslib.Date.now = function() {
+  jslibDate.now = function() {
     return time;
   }
 }
 
 function run_test() {
   // 3 errors, 1ms retry period, max 3 requests per ten milliseconds,
   // 5ms backoff interval, 19ms max delay
   var rb = new jslib.RequestBackoff(3, 1, 3, 10, 5, 19, 0);
@@ -80,10 +83,10 @@ function run_test() {
   rb.noteRequest();
   setNow(202);
   do_check_true(rb.canMakeRequest());
   rb.noteRequest();
   do_check_false(rb.canMakeRequest());
   setNow(211);
   do_check_true(rb.canMakeRequest());
 
-  jslib.Date.now = _Datenow;
+  jslibDate.now = _Datenow;
 }
--- a/toolkit/library/gtest/rust/Cargo.lock
+++ b/toolkit/library/gtest/rust/Cargo.lock
@@ -17,17 +17,17 @@ dependencies = [
 
 [[package]]
 name = "ansi_term"
 version = "0.9.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 
 [[package]]
 name = "app_units"
-version = "0.5.3"
+version = "0.5.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
  "heapsize 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "num-traits 0.1.37 (registry+https://github.com/rust-lang/crates.io-index)",
  "rustc-serialize 0.3.22 (registry+https://github.com/rust-lang/crates.io-index)",
  "serde 1.0.8 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
@@ -1205,17 +1205,17 @@ source = "registry+https://github.com/ru
 name = "strsim"
 version = "0.6.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 
 [[package]]
 name = "style"
 version = "0.0.1"
 dependencies = [
- "app_units 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)",
+ "app_units 0.5.6 (registry+https://github.com/rust-lang/crates.io-index)",
  "arraydeque 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)",
  "arrayvec 0.3.23 (registry+https://github.com/rust-lang/crates.io-index)",
  "atomic_refcell 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "bindgen 0.29.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "bit-vec 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)",
  "bitflags 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "byteorder 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "cfg-if 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -1262,17 +1262,17 @@ dependencies = [
  "syn 0.11.11 (registry+https://github.com/rust-lang/crates.io-index)",
  "synstructure 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
 name = "style_traits"
 version = "0.0.1"
 dependencies = [
- "app_units 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)",
+ "app_units 0.5.6 (registry+https://github.com/rust-lang/crates.io-index)",
  "bitflags 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "cssparser 0.20.1 (registry+https://github.com/rust-lang/crates.io-index)",
  "euclid 0.15.1 (registry+https://github.com/rust-lang/crates.io-index)",
  "selectors 0.19.0",
 ]
 
 [[package]]
 name = "syn"
@@ -1471,17 +1471,17 @@ dependencies = [
  "same-file 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)",
  "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
 name = "webrender"
 version = "0.50.0"
 dependencies = [
- "app_units 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)",
+ "app_units 0.5.6 (registry+https://github.com/rust-lang/crates.io-index)",
  "bincode 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "bit-set 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "bitflags 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)",
  "byteorder 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "core-graphics 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)",
  "core-text 6.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "dwrote 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "euclid 0.15.1 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -1498,34 +1498,34 @@ dependencies = [
  "time 0.1.36 (registry+https://github.com/rust-lang/crates.io-index)",
  "webrender_api 0.50.0",
 ]
 
 [[package]]
 name = "webrender_api"
 version = "0.50.0"
 dependencies = [
- "app_units 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)",
+ "app_units 0.5.6 (registry+https://github.com/rust-lang/crates.io-index)",
  "bincode 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "byteorder 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "core-foundation 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "core-graphics 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)",
  "dwrote 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "euclid 0.15.1 (registry+https://github.com/rust-lang/crates.io-index)",
  "fxhash 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
  "heapsize 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "serde 1.0.8 (registry+https://github.com/rust-lang/crates.io-index)",
  "time 0.1.36 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
 name = "webrender_bindings"
 version = "0.1.0"
 dependencies = [
- "app_units 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)",
+ "app_units 0.5.6 (registry+https://github.com/rust-lang/crates.io-index)",
  "bincode 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "euclid 0.15.1 (registry+https://github.com/rust-lang/crates.io-index)",
  "gleam 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)",
  "rayon 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)",
  "thread_profiler 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
  "webrender 0.50.0",
  "webrender_api 0.50.0",
 ]
@@ -1555,17 +1555,17 @@ version = "0.3.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
  "bitflags 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [metadata]
 "checksum aho-corasick 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)" = "0638fd549427caa90c499814196d1b9e3725eb4d15d7339d6de073a680ed0ca2"
 "checksum ansi_term 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "23ac7c30002a5accbf7e8987d0632fa6de155b7c3d39d0067317a391e00a2ef6"
-"checksum app_units 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)" = "7ff4f3fe57393a150b39b026b6f6f4b9a6c4f49b52d0a4e2d61d08d926358438"
+"checksum app_units 0.5.6 (registry+https://github.com/rust-lang/crates.io-index)" = "ed0a4de09a3b8449515e649f3bb84f72ea15fc2d10639beb0776a09b7d308074"
 "checksum arraydeque 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "96e774cadb24c2245225280c6799793f9802b918a58a79615e9490607489a717"
 "checksum arrayvec 0.3.23 (registry+https://github.com/rust-lang/crates.io-index)" = "699e63a93b79d717e8c3b5eb1b28b7780d0d6d9e59a72eb769291c83b0c8dc67"
 "checksum aster 0.41.0 (registry+https://github.com/rust-lang/crates.io-index)" = "4ccfdf7355d9db158df68f976ed030ab0f6578af811f5a7bb6dcf221ec24e0e0"
 "checksum atomic_refcell 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "fb2dcb6e6d35f20276943cc04bb98e538b348d525a04ac79c10021561d202f21"
 "checksum atty 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "d912da0db7fa85514874458ca3651fe2cddace8d0b0505571dbdcd41ab490159"
 "checksum backtrace 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "72f9b4182546f4b04ebc4ab7f84948953a118bd6021a1b6a6c909e3e94f6be76"
 "checksum backtrace-sys 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)" = "d192fd129132fbc97497c1f2ec2c2c5174e376b95f535199ef4fe0a293d33842"
 "checksum binary-space-partition 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "88ceb0d16c4fd0e42876e298d7d3ce3780dd9ebdcbe4199816a32c77e08597ff"
--- a/toolkit/library/rust/Cargo.lock
+++ b/toolkit/library/rust/Cargo.lock
@@ -15,17 +15,17 @@ dependencies = [
 
 [[package]]
 name = "ansi_term"
 version = "0.9.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 
 [[package]]
 name = "app_units"
-version = "0.5.3"
+version = "0.5.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
  "heapsize 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "num-traits 0.1.37 (registry+https://github.com/rust-lang/crates.io-index)",
  "rustc-serialize 0.3.22 (registry+https://github.com/rust-lang/crates.io-index)",
  "serde 1.0.8 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
@@ -1192,17 +1192,17 @@ source = "registry+https://github.com/ru
 name = "strsim"
 version = "0.6.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 
 [[package]]
 name = "style"
 version = "0.0.1"
 dependencies = [
- "app_units 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)",
+ "app_units 0.5.6 (registry+https://github.com/rust-lang/crates.io-index)",
  "arraydeque 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)",
  "arrayvec 0.3.23 (registry+https://github.com/rust-lang/crates.io-index)",
  "atomic_refcell 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "bindgen 0.29.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "bit-vec 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)",
  "bitflags 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "byteorder 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "cfg-if 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -1249,17 +1249,17 @@ dependencies = [
  "syn 0.11.11 (registry+https://github.com/rust-lang/crates.io-index)",
  "synstructure 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
 name = "style_traits"
 version = "0.0.1"
 dependencies = [
- "app_units 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)",
+ "app_units 0.5.6 (registry+https://github.com/rust-lang/crates.io-index)",
  "bitflags 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "cssparser 0.20.1 (registry+https://github.com/rust-lang/crates.io-index)",
  "euclid 0.15.1 (registry+https://github.com/rust-lang/crates.io-index)",
  "selectors 0.19.0",
 ]
 
 [[package]]
 name = "syn"
@@ -1458,17 +1458,17 @@ dependencies = [
  "same-file 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)",
  "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
 name = "webrender"
 version = "0.50.0"
 dependencies = [
- "app_units 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)",
+ "app_units 0.5.6 (registry+https://github.com/rust-lang/crates.io-index)",
  "bincode 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "bit-set 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "bitflags 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)",
  "byteorder 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "core-graphics 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)",
  "core-text 6.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "dwrote 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "euclid 0.15.1 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -1485,34 +1485,34 @@ dependencies = [
  "time 0.1.36 (registry+https://github.com/rust-lang/crates.io-index)",
  "webrender_api 0.50.0",
 ]
 
 [[package]]
 name = "webrender_api"
 version = "0.50.0"
 dependencies = [
- "app_units 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)",
+ "app_units 0.5.6 (registry+https://github.com/rust-lang/crates.io-index)",
  "bincode 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "byteorder 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "core-foundation 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "core-graphics 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)",
  "dwrote 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "euclid 0.15.1 (registry+https://github.com/rust-lang/crates.io-index)",
  "fxhash 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
  "heapsize 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "serde 1.0.8 (registry+https://github.com/rust-lang/crates.io-index)",
  "time 0.1.36 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
 name = "webrender_bindings"
 version = "0.1.0"
 dependencies = [
- "app_units 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)",
+ "app_units 0.5.6 (registry+https://github.com/rust-lang/crates.io-index)",
  "bincode 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "euclid 0.15.1 (registry+https://github.com/rust-lang/crates.io-index)",
  "gleam 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)",
  "rayon 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)",
  "thread_profiler 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
  "webrender 0.50.0",
  "webrender_api 0.50.0",
 ]
@@ -1542,17 +1542,17 @@ version = "0.3.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
  "bitflags 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [metadata]
 "checksum aho-corasick 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)" = "0638fd549427caa90c499814196d1b9e3725eb4d15d7339d6de073a680ed0ca2"
 "checksum ansi_term 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "23ac7c30002a5accbf7e8987d0632fa6de155b7c3d39d0067317a391e00a2ef6"
-"checksum app_units 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)" = "7ff4f3fe57393a150b39b026b6f6f4b9a6c4f49b52d0a4e2d61d08d926358438"
+"checksum app_units 0.5.6 (registry+https://github.com/rust-lang/crates.io-index)" = "ed0a4de09a3b8449515e649f3bb84f72ea15fc2d10639beb0776a09b7d308074"
 "checksum arraydeque 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "96e774cadb24c2245225280c6799793f9802b918a58a79615e9490607489a717"
 "checksum arrayvec 0.3.23 (registry+https://github.com/rust-lang/crates.io-index)" = "699e63a93b79d717e8c3b5eb1b28b7780d0d6d9e59a72eb769291c83b0c8dc67"
 "checksum aster 0.41.0 (registry+https://github.com/rust-lang/crates.io-index)" = "4ccfdf7355d9db158df68f976ed030ab0f6578af811f5a7bb6dcf221ec24e0e0"
 "checksum atomic_refcell 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "fb2dcb6e6d35f20276943cc04bb98e538b348d525a04ac79c10021561d202f21"
 "checksum atty 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "d912da0db7fa85514874458ca3651fe2cddace8d0b0505571dbdcd41ab490159"
 "checksum backtrace 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "72f9b4182546f4b04ebc4ab7f84948953a118bd6021a1b6a6c909e3e94f6be76"
 "checksum backtrace-sys 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)" = "d192fd129132fbc97497c1f2ec2c2c5174e376b95f535199ef4fe0a293d33842"
 "checksum binary-space-partition 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "88ceb0d16c4fd0e42876e298d7d3ce3780dd9ebdcbe4199816a32c77e08597ff"
--- a/toolkit/modules/NewTabUtils.jsm
+++ b/toolkit/modules/NewTabUtils.jsm
@@ -921,50 +921,55 @@ var ActivityStreamProvider = {
   },
 
   /**
    * Computes favicon data for each url in a set of links
    *
    * @param {Array} links
    *          an array containing objects without favicon data or mimeTypes yet
    *
-   * @returns {Promise} Returns a promise with the array of links with favicon data,
-   *                    mimeType, and byte array length
+   * @returns {Promise} Returns a promise with the array of links with the largest
+   *                    favicon available (as a byte array), mimeType, byte array
+   *                    length, and favicon size (width)
    */
   async _addFavicons(aLinks) {
     if (aLinks.length) {
       // Each link in the array needs a favicon for it's page - so we fire off
       // a promise for each link to compute the favicon data and attach it back
       // to the original link object. We must wait until all favicons for
       // the array of links are computed before returning
       await Promise.all(aLinks.map(link => new Promise(resolve => {
         return PlacesUtils.favicons.getFaviconDataForPage(
             Services.io.newURI(link.url),
-            (iconuri, len, data, mime) => {
+            (iconuri, len, data, mime, size) => {
               // Due to the asynchronous behaviour of inserting a favicon into
               // moz_favicons, the data may not be available to us just yet,
               // since we listen on a history entry being inserted. As a result,
               // we don't want to throw if the icon uri is not here yet, we
               // just want to resolve on an empty favicon. Activity Stream
               // knows how to handle null favicons
               if (!iconuri) {
                 link.favicon = null;
                 link.mimeType = null;
+                link.faviconSize = null;
               } else {
                 link.favicon = data;
                 link.mimeType = mime;
                 link.faviconLength = len;
+                link.faviconSize = size;
               }
               return resolve(link);
-            });
+            },
+            0); // preferredWidth: get the biggest width available
         }).catch(() => {
           // If something goes wrong - that's ok - just return a null favicon
           // without rejecting the entire Promise.all
           link.favicon = null;
           link.mimeType = null;
+          link.faviconSize = null;
           return link;
         })
       ));
     }
     return aLinks;
   },
 
   /**
--- a/toolkit/modules/tests/xpcshell/test_NewTabUtils.js
+++ b/toolkit/modules/tests/xpcshell/test_NewTabUtils.js
@@ -338,18 +338,20 @@ add_task(async function addFavicons() {
   await setUpActivityStreamTest();
   let provider = NewTabUtils.activityStreamProvider;
 
   // start by passing in a bad uri and check that we get a null favicon back
   let links = [{url: "mozilla.com"}];
   await provider._addFavicons(links);
   Assert.equal(links[0].favicon, null, "Got a null favicon because we passed in a bad url");
   Assert.equal(links[0].mimeType, null, "Got a null mime type because we passed in a bad url");
+  Assert.equal(links[0].faviconSize, null, "Got a null favicon size because we passed in a bad url");
 
   // now fix the url and try again - this time we get good favicon data back
+  // a 1x1 favicon as a data URI of mime type image/png
   links[0].url = "https://mozilla.com";
   let base64URL = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAA" +
     "AAAA6fptVAAAACklEQVQI12NgAAAAAgAB4iG8MwAAAABJRU5ErkJggg==";
 
   let visit = [
     {uri: links[0].url, visitDate: timeDaysAgo(0), transition: PlacesUtils.history.TRANSITION_TYPED}
   ];
   await PlacesTestUtils.addVisits(visit);
@@ -357,16 +359,17 @@ add_task(async function addFavicons() {
   let faviconData = new Map();
   faviconData.set("https://mozilla.com", base64URL);
   await PlacesTestUtils.addFavicons(faviconData);
 
   await provider._addFavicons(links);
   Assert.equal(links[0].mimeType, "image/png", "Got the right mime type before deleting it");
   Assert.equal(links[0].faviconLength, links[0].favicon.length, "Got the right length for the byte array");
   Assert.equal(provider._faviconBytesToDataURI(links)[0].favicon, base64URL, "Got the right favicon");
+  Assert.equal(links[0].faviconSize, 1, "Got the right favicon size (width and height of favicon)");
 });
 
 add_task(async function getHighlights() {
   const addMetadata = url => PlacesUtils.history.update({
     description: "desc",
     previewImageURL: "https://image/",
     url
   });