merge autoland to mozilla-central a=merge
authorIris Hsiao <ihsiao@mozilla.com>
Mon, 26 Sep 2016 18:04:27 +0800
changeset 315126 bc9d199d895c33f1b4984afe61ec464a43a2a61a
parent 315125 29beaebdfaccbdaeb4c1ee5a43a9795ab015ef49 (current diff)
parent 315107 7e9deb4dce679a457656cf298cec1f2929786a0a (diff)
child 315141 dca71ebb955d5bbfaa9c7fca0539f9aaf9e12b5d
push id32563
push userihsiao@mozilla.com
push dateMon, 26 Sep 2016 11:18:33 +0000
treeherderautoland@eb840c87b5fd [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone52.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
merge autoland to mozilla-central a=merge
--- a/browser/app/profile/firefox.js
+++ b/browser/app/profile/firefox.js
@@ -1356,17 +1356,21 @@ pref("media.gmp.trial-create.enabled", t
 // plugins list, Firefox will download the GMP/CDM if enabled, and our
 // UI to re-enable EME prompts the user to re-enable EME if it's disabled
 // and script requests EME. If *.visible is false, we won't show the UI
 // to enable the CDM if its disabled; it's as if the keysystem is completely
 // unsupported.
 
 #ifdef MOZ_ADOBE_EME
 pref("media.gmp-eme-adobe.visible", true);
-pref("media.gmp-eme-adobe.enabled", true);
+// When Adobe EME is enabled in the build system, we don't actually enable
+// the plugin by default, so that it doesn't download and install by default.
+// When Adobe EME is first used, Firefox will prompt the user to enable it,
+// and then download the CDM.
+pref("media.gmp-eme-adobe.enabled", false);
 #endif
 
 #ifdef MOZ_WIDEVINE_EME
 pref("media.gmp-widevinecdm.visible", true);
 pref("media.gmp-widevinecdm.enabled", true);
 #endif
 
 // Play with different values of the decay time and get telemetry,
--- a/browser/base/content/browser-fxaccounts.js
+++ b/browser/base/content/browser-fxaccounts.js
@@ -340,23 +340,16 @@ var gFxAccounts = {
     PanelUI.hide();
   },
 
   openPreferences: function () {
     openPreferences("paneSync", { urlParams: { entrypoint: "menupanel" } });
   },
 
   openAccountsPage: function (action, urlParams={}) {
-    // An entrypoint param is used for server-side metrics.  If the current tab
-    // is UITour, assume that it initiated the call to this method and override
-    // the entrypoint accordingly.
-    if (UITour.tourBrowsersByWindow.get(window) &&
-        UITour.tourBrowsersByWindow.get(window).has(gBrowser.selectedBrowser)) {
-      urlParams.entrypoint = "uitour";
-    }
     let params = new URLSearchParams();
     if (action) {
       params.set("action", action);
     }
     for (let name in urlParams) {
       if (urlParams[name] !== undefined) {
         params.set(name, urlParams[name]);
       }
--- a/browser/base/content/browser-syncui.js
+++ b/browser/base/content/browser-syncui.js
@@ -285,21 +285,16 @@ var gSyncUI = {
    *          "pair"  -- pair a device first
    *          "reset" -- reset sync
    * @param entryPoint
    *        Indicates the entrypoint from where this method was called.
    */
 
   openSetup: function SUI_openSetup(wizardType, entryPoint = "syncbutton") {
     if (this.weaveService.fxAccountsEnabled) {
-      // If the user is also in an uitour, set the entrypoint to `uitour`
-      if (UITour.tourBrowsersByWindow.get(window) &&
-          UITour.tourBrowsersByWindow.get(window).has(gBrowser.selectedBrowser)) {
-        entryPoint = "uitour";
-      }
       this.openPrefs(entryPoint);
     } else {
       let win = Services.wm.getMostRecentWindow("Weave:AccountSetup");
       if (win)
         win.focus();
       else {
         window.openDialog("chrome://browser/content/sync/setup.xul",
                           "weaveSetup", "centerscreen,chrome,resizable=no",
--- a/browser/components/customizableui/test/browser_967000_button_sync.js
+++ b/browser/components/customizableui/test/browser_967000_button_sync.js
@@ -114,19 +114,16 @@ function* asyncCleanup() {
   gBrowser.addTab(initialLocation);
   gBrowser.removeTab(newTab);
   UITour.tourBrowsersByWindow.delete(window);
 }
 
 // When Sync is not setup.
 add_task(() => openPrefsFromMenuPanel("PanelUI-remotetabs-setupsync", "synced-tabs"));
 add_task(asyncCleanup);
-// Test that uitour is in progress, the entrypoint is `uitour` and not `menupanel`
-add_task(() => openPrefsFromMenuPanel("PanelUI-remotetabs-setupsync", "uitour"));
-add_task(asyncCleanup);
 
 // When Sync is configured in a "needs reauthentication" state.
 add_task(function* () {
   // configure our broadcasters so we are in the right state.
   document.getElementById("sync-reauth-state").hidden = false;
   document.getElementById("sync-setup-state").hidden = true;
   document.getElementById("sync-syncnow-state").hidden = true;
   yield openPrefsFromMenuPanel("PanelUI-remotetabs-reauthsync", "synced-tabs")
--- a/browser/components/extensions/moz.build
+++ b/browser/components/extensions/moz.build
@@ -8,9 +8,10 @@ JAR_MANIFESTS += ['jar.mn']
 
 EXTRA_COMPONENTS += [
     'extensions-browser.manifest',
 ]
 
 DIRS += ['schemas']
 
 BROWSER_CHROME_MANIFESTS += ['test/browser/browser.ini']
+MOCHITEST_MANIFESTS += ['test/mochitest/mochitest.ini']
 XPCSHELL_TESTS_MANIFESTS += ['test/xpcshell/xpcshell.ini']
--- a/browser/components/extensions/schemas/commands.json
+++ b/browser/components/extensions/schemas/commands.json
@@ -77,16 +77,17 @@
           }
         }
       }
     ]
   },
   {
     "namespace": "commands",
     "description": "Use the commands API to add keyboard shortcuts that trigger actions in your extension, for example, an action to open the browser action or send a command to the xtension.",
+    "permissions": ["manifest:commands"],
     "types": [
       {
         "id": "Command",
         "type": "object",
         "properties": {
           "name":        {
             "type": "string",
             "optional": true,
new file mode 100644
--- /dev/null
+++ b/browser/components/extensions/test/mochitest/mochitest.ini
@@ -0,0 +1,6 @@
+[DEFAULT]
+support-files =
+  ../../../../../toolkit/components/extensions/test/mochitest/test_ext_all_apis.js
+tags = webextensions
+
+[test_ext_all_apis.html]
new file mode 100644
--- /dev/null
+++ b/browser/components/extensions/test/mochitest/test_ext_all_apis.html
@@ -0,0 +1,75 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+  <title>WebExtension test</title>
+  <meta charset="utf-8">
+  <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <script type="text/javascript" src="/tests/SimpleTest/SpawnTask.js"></script>
+  <script type="text/javascript" src="/tests/SimpleTest/ExtensionTestUtils.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css">
+</head>
+<body>
+<script>
+"use strict";
+/* exported expectedContentApisTargetSpecific, expectedBackgroundApisTargetSpecific */
+let expectedContentApisTargetSpecific = [
+];
+
+let expectedBackgroundApisTargetSpecific = [
+  "tabs.MutedInfoReason",
+  "tabs.TAB_ID_NONE",
+  "tabs.TabStatus",
+  "tabs.WindowType",
+  "tabs.ZoomSettingsMode",
+  "tabs.ZoomSettingsScope",
+  "tabs.connect",
+  "tabs.create",
+  "tabs.detectLanguage",
+  "tabs.duplicate",
+  "tabs.executeScript",
+  "tabs.get",
+  "tabs.getCurrent",
+  "tabs.getZoom",
+  "tabs.getZoomSettings",
+  "tabs.highlight",
+  "tabs.insertCSS",
+  "tabs.move",
+  "tabs.onActivated",
+  "tabs.onAttached",
+  "tabs.onCreated",
+  "tabs.onDetached",
+  "tabs.onHighlighted",
+  "tabs.onMoved",
+  "tabs.onRemoved",
+  "tabs.onReplaced",
+  "tabs.onUpdated",
+  "tabs.onZoomChange",
+  "tabs.query",
+  "tabs.reload",
+  "tabs.remove",
+  "tabs.removeCSS",
+  "tabs.sendMessage",
+  "tabs.setZoom",
+  "tabs.setZoomSettings",
+  "tabs.update",
+  "windows.CreateType",
+  "windows.WINDOW_ID_CURRENT",
+  "windows.WINDOW_ID_NONE",
+  "windows.WindowState",
+  "windows.WindowType",
+  "windows.create",
+  "windows.get",
+  "windows.getAll",
+  "windows.getCurrent",
+  "windows.getLastFocused",
+  "windows.onCreated",
+  "windows.onFocusChanged",
+  "windows.onRemoved",
+  "windows.remove",
+  "windows.update",
+];
+</script>
+<script src="test_ext_all_apis.js"></script>
+
+</body>
+</html>
--- a/browser/modules/ContentCrashHandlers.jsm
+++ b/browser/modules/ContentCrashHandlers.jsm
@@ -348,16 +348,20 @@ this.TabCrashHandler = {
  */
 this.UnsubmittedCrashHandler = {
   get prefs() {
     delete this.prefs;
     return this.prefs =
       Services.prefs.getBranch("browser.crashReports.unsubmittedCheck.");
   },
 
+  get enabled() {
+    return this.prefs.getBoolPref("enabled");
+  },
+
   // showingNotification is set to true once a notification
   // is successfully shown, and then set back to false if
   // the notification is dismissed by an action by the user.
   showingNotification: false,
   // suppressed is true if we've determined that we've shown
   // the notification too many times across too many days without
   // user interaction, so we're suppressing the notification for
   // some number of days. See the documentation for
@@ -366,19 +370,22 @@ this.UnsubmittedCrashHandler = {
 
   init() {
     if (this.initialized) {
       return;
     }
 
     this.initialized = true;
 
-    let shouldCheck = this.prefs.getBoolPref("enabled");
-
-    if (shouldCheck) {
+    // UnsubmittedCrashHandler can be initialized but still be disabled.
+    // This is intentional, as this makes simulating UnsubmittedCrashHandler's
+    // reactions to browser startup and shutdown easier in test automation.
+    //
+    // UnsubmittedCrashHandler, when initialized but not enabled, is inert.
+    if (this.enabled) {
       if (this.prefs.prefHasUserValue("suppressUntilDate")) {
         if (this.prefs.getCharPref("suppressUntilDate") > this.dateString()) {
           // We'll be suppressing any notifications until after suppressedDate,
           // so there's no need to do anything more.
           this.suppressed = true;
           return;
         }
 
@@ -395,16 +402,20 @@ this.UnsubmittedCrashHandler = {
 
   uninit() {
     if (!this.initialized) {
       return;
     }
 
     this.initialized = false;
 
+    if (!this.enabled) {
+      return;
+    }
+
     if (this.suppressed) {
       this.suppressed = false;
       // No need to do any more clean-up, since we were suppressed.
       return;
     }
 
     if (this.showingNotification) {
       this.prefs.setBoolPref("shutdownWhileShowing", true);
--- a/browser/modules/test/browser_UnsubmittedCrashHandler.js
+++ b/browser/modules/test/browser_UnsubmittedCrashHandler.js
@@ -193,21 +193,30 @@ add_task(function* setup() {
     notification.close();
   }
 
   let env = Cc["@mozilla.org/process/environment;1"]
               .getService(Components.interfaces.nsIEnvironment);
   let oldServerURL = env.get("MOZ_CRASHREPORTER_URL");
   env.set("MOZ_CRASHREPORTER_URL", SERVER_URL);
 
+  // nsBrowserGlue starts up UnsubmittedCrashHandler automatically
+  // so at this point, it is initialized. It's possible that it
+  // was initialized, but is preffed off, so it's inert, so we
+  // shut it down, make sure it's preffed on, and then restart it.
+  // Note that making the component initialize even when it's
+  // disabled is an intentional choice, as this allows for easier
+  // simulation of startup and shutdown.
+  UnsubmittedCrashHandler.uninit();
   yield SpecialPowers.pushPrefEnv({
     set: [
       ["browser.crashReports.unsubmittedCheck.enabled", true],
     ],
   });
+  UnsubmittedCrashHandler.init();
 
   registerCleanupFunction(function() {
     gNotificationBox = null;
     clearPendingCrashReports();
     env.set("MOZ_CRASHREPORTER_URL", oldServerURL);
   });
 });
 
--- a/build.gradle
+++ b/build.gradle
@@ -28,17 +28,17 @@ buildscript {
         }
         // For android-sdk-manager SNAPSHOT releases.
         maven {
             url "file://${gradle.mozconfig.topsrcdir}/mobile/android/gradle/m2repo"
         }
     }
 
     dependencies {
-        classpath 'com.android.tools.build:gradle:2.1.0'
+        classpath 'com.android.tools.build:gradle:2.1.3'
         classpath('com.stanfy.spoon:spoon-gradle-plugin:1.0.4') {
             // Without these, we get errors linting.
             exclude module: 'guava'
         }
         // Provided in tree.
         classpath 'com.jakewharton.sdkmanager:gradle-plugin:1.5.0-SNAPSHOT'
     }
 }
--- a/devtools/client/inspector/inspector-panel.js
+++ b/devtools/client/inspector/inspector-panel.js
@@ -419,16 +419,26 @@ InspectorPanel.prototype = {
       this._InspectorTabPanel =
         this.React.createFactory(this.browserRequire(
         "devtools/client/inspector/components/inspector-tab-panel"));
     }
     return this._InspectorTabPanel;
   },
 
   /**
+   * Check if the inspector should use the landscape mode.
+   *
+   * @return {Boolean} true if the inspector should be in landscape mode.
+   */
+  useLandscapeMode: function () {
+    let { clientWidth } = this.panelDoc.getElementById("inspector-splitter-box");
+    return clientWidth > PORTRAIT_MODE_WIDTH;
+  },
+
+  /**
    * Build Splitter located between the main and side area of
    * the Inspector panel.
    */
   setupSplitter: function () {
     let SplitBox = this.React.createFactory(this.browserRequire(
       "devtools/client/shared/components/splitter/split-box"));
 
     this.panelWin.addEventListener("resize", this.onPanelWindowResize, true);
@@ -440,17 +450,18 @@ InspectorPanel.prototype = {
       minSize: MIN_SIDEBAR_SIZE,
       splitterSize: 1,
       endPanelControl: true,
       startPanel: this.InspectorTabPanel({
         id: "inspector-main-content"
       }),
       endPanel: this.InspectorTabPanel({
         id: "inspector-sidebar-container"
-      })
+      }),
+      vert: this.useLandscapeMode(),
     });
 
     this._splitter = this.ReactDOM.render(splitter,
       this.panelDoc.getElementById("inspector-splitter-box"));
 
     // Persist splitter state in preferences.
     this.sidebar.on("show", this.onSidebarShown);
     this.sidebar.on("hide", this.onSidebarHidden);
@@ -468,19 +479,18 @@ InspectorPanel.prototype = {
     this.sidebar.off("destroy", this.onSidebarHidden);
   },
 
   /**
    * If Toolbox width is less than 600 px, the splitter changes its mode
    * to `horizontal` to support portrait view.
    */
   onPanelWindowResize: function () {
-    let box = this.panelDoc.getElementById("inspector-splitter-box");
     this._splitter.setState({
-      vert: (box.clientWidth > PORTRAIT_MODE_WIDTH)
+      vert: this.useLandscapeMode(),
     });
   },
 
   onSidebarShown: function () {
     let width;
     let height;
 
     // Initialize splitter size from preferences.
--- a/devtools/client/inspector/test/browser.ini
+++ b/devtools/client/inspector/test/browser.ini
@@ -129,16 +129,17 @@ subsuite = clipboard
 [browser_inspector_navigation.js]
 [browser_inspector_pane-toggle-01.js]
 [browser_inspector_pane-toggle-02.js]
 [browser_inspector_pane-toggle-03.js]
 [browser_inspector_pane-toggle-05.js]
 skip-if = os == "mac" # Full keyboard navigation on OSX only works if Full Keyboard Access setting is set to All Control in System Keyboard
 [browser_inspector_picker-stop-on-destroy.js]
 [browser_inspector_picker-stop-on-tool-change.js]
+[browser_inspector_portrait_mode.js]
 [browser_inspector_pseudoclass-lock.js]
 [browser_inspector_pseudoclass-menu.js]
 [browser_inspector_reload-01.js]
 [browser_inspector_reload-02.js]
 [browser_inspector_remove-iframe-during-load.js]
 [browser_inspector_search-01.js]
 [browser_inspector_search-02.js]
 [browser_inspector_search-03.js]
new file mode 100644
--- /dev/null
+++ b/devtools/client/inspector/test/browser_inspector_portrait_mode.js
@@ -0,0 +1,79 @@
+/* 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/. */
+
+"use strict";
+
+// Test that the inspector splitter is properly initialized in horizontal mode if the
+// inspector starts in portrait mode.
+
+add_task(function* () {
+  let { inspector, toolbox } = yield openInspectorForURL(
+    "data:text/html;charset=utf-8,<h1>foo</h1><span>bar</span>", "window");
+
+  let hostWindow = toolbox._host._window;
+  let originalWidth = hostWindow.outerWidth;
+  let originalHeight = hostWindow.outerHeight;
+
+  let splitter = inspector.panelDoc.querySelector(".inspector-sidebar-splitter");
+
+  // If the inspector is not already in landscape mode.
+  if (!splitter.classList.contains("vert")) {
+    info("Resize toolbox window to force inspector to landscape mode");
+    let onClassnameMutation = waitForClassMutation(splitter);
+    hostWindow.resizeTo(800, 500);
+    yield onClassnameMutation;
+
+    ok(splitter.classList.contains("vert"), "Splitter is in vertical mode");
+  }
+
+  info("Resize toolbox window to force inspector to portrait mode");
+  let onClassnameMutation = waitForClassMutation(splitter);
+  hostWindow.resizeTo(500, 500);
+  yield onClassnameMutation;
+
+  ok(splitter.classList.contains("horz"), "Splitter is in horizontal mode");
+
+  info("Close the inspector");
+  let target = TargetFactory.forTab(gBrowser.selectedTab);
+  yield gDevTools.closeToolbox(target);
+
+  info("Reopen inspector");
+  ({ inspector, toolbox } = yield openInspector("window"));
+
+  // Devtools window should still be 500px * 500px, inspector should still be in portrait.
+  splitter = inspector.panelDoc.querySelector(".inspector-sidebar-splitter");
+  ok(splitter.classList.contains("horz"), "Splitter is in horizontal mode");
+
+  info("Restore original window size");
+  toolbox._host._window.resizeTo(originalWidth, originalHeight);
+});
+
+/**
+ * Helper waiting for a class attribute mutation on the provided target. Returns a
+ * promise.
+ *
+ * @param {Node} target
+ *        Node to observe
+ * @return {Promise} promise that will resolve upon receiving a mutation for the class
+ *         attribute on the target.
+ */
+function waitForClassMutation(target) {
+  return new Promise(resolve => {
+    let observer = new MutationObserver((mutations) => {
+      for (let mutation of mutations) {
+        if (mutation.attributeName === "class") {
+          observer.disconnect();
+          resolve();
+          return;
+        }
+      }
+    });
+    observer.observe(target, { attributes: true });
+  });
+}
+
+registerCleanupFunction(function () {
+  // Restore the host type for other tests.
+  Services.prefs.clearUserPref("devtools.toolbox.host");
+});
--- a/devtools/client/shared/test/unit/test_rewriteDeclarations.js
+++ b/devtools/client/shared/test/unit/test_rewriteDeclarations.js
@@ -449,16 +449,35 @@ const TEST_DATA = [
   },
   {
     desc: "disabled create with comment ender in string",
     input: "",
     instruction: {type: "create", name: "content", value: "'*/'", priority: "",
                   index: 0, enabled: false},
     expected: "/*! content: '*\\/'; */"
   },
+
+  {
+    desc: "delete disabled property",
+    input: "\n  a:b;\n  /* color:#f0c; */\n  e:f;",
+    instruction: {type: "remove", name: "color", index: 1},
+    expected: "\n  a:b;\n  e:f;",
+  },
+  {
+    desc: "delete heuristic-disabled property",
+    input: "\n  a:b;\n  /*! c:d; */\n  e:f;",
+    instruction: {type: "remove", name: "c", index: 1},
+    expected: "\n  a:b;\n  e:f;",
+  },
+  {
+    desc: "delete disabled property leaving other disabled property",
+    input: "\n  a:b;\n  /* color:#f0c; background-color: seagreen; */\n  e:f;",
+    instruction: {type: "remove", name: "color", index: 1},
+    expected: "\n  a:b;\n   /* background-color: seagreen; */\n  e:f;",
+  },
 ];
 
 function rewriteDeclarations(inputString, instruction, defaultIndentation) {
   let rewriter = new RuleRewriter(isCssPropertyKnown, null, inputString);
   rewriter.defaultIndentation = defaultIndentation;
 
   switch (instruction.type) {
     case "rename":
--- a/devtools/client/themes/inspector.css
+++ b/devtools/client/themes/inspector.css
@@ -192,16 +192,17 @@ window {
 /* Markup Box */
 
 iframe {
   border: 0;
 }
 
 #markup-box {
   width: 100%;
-  flex: 1 1 auto;
+  flex: 1;
+  min-height: 0;
 }
 
 #markup-box > iframe {
   height: 100%;
   width: 100%;
 }
 
--- a/devtools/client/webconsole/webconsole.js
+++ b/devtools/client/webconsole/webconsole.js
@@ -3293,16 +3293,19 @@ WebConsoleConnectionProxy.prototype = {
       console.error("Web Console getCachedMessages error: invalid state.");
     }
 
     let messages =
       response.messages.concat(...this.webConsoleClient.getNetworkEvents());
     messages.sort((a, b) => a.timeStamp - b.timeStamp);
 
     if (this.webConsoleFrame.NEW_CONSOLE_OUTPUT_ENABLED) {
+      // Filter out CSS page errors.
+      messages = messages.filter(message => !(message._type == "PageError"
+          && Utils.categoryForScriptError(message) === CATEGORY_CSS));
       for (let packet of messages) {
         this.dispatchMessageAdd(packet);
       }
     } else {
       this.webConsoleFrame.displayCachedMessages(messages);
       if (!this._hasNativeConsoleAPI) {
         this.webConsoleFrame.logWarningAboutReplacedAPI();
       }
--- a/devtools/shared/css/parsing-utils.js
+++ b/devtools/shared/css/parsing-utils.js
@@ -481,41 +481,54 @@ function parseDeclarations(isCssProperty
  * @param {StyleRuleFront} rule The style rule to use.  Note that this
  *        is only needed by the |apply| and |getDefaultIndentation| methods;
  *        and in particular for testing it can be |null|.
  * @param {String} inputString The CSS source text to parse and modify.
  * @return {Object} an object that can be used to rewrite the input text.
  */
 function RuleRewriter(isCssPropertyKnown, rule, inputString) {
   this.rule = rule;
-  this.inputString = inputString;
-  // Whether there are any newlines in the input text.
-  this.hasNewLine = /[\r\n]/.test(this.inputString);
+  this.isCssPropertyKnown = isCssPropertyKnown;
+
   // Keep track of which any declarations we had to rewrite while
   // performing the requested action.
   this.changedDeclarations = {};
-  // The declarations.
-  this.declarations = parseDeclarations(isCssPropertyKnown, this.inputString,
-                                        true);
 
-  this.decl = null;
-  this.result = null;
   // If not null, a promise that must be wait upon before |apply| can
   // do its work.
   this.editPromise = null;
 
   // If the |defaultIndentation| property is set, then it is used;
   // otherwise the RuleRewriter will try to compute the default
   // indentation based on the style sheet's text.  This override
   // facility is for testing.
   this.defaultIndentation = null;
+
+  this.startInitialization(inputString);
 }
 
 RuleRewriter.prototype = {
   /**
+   * An internal function to initialize the rewriter with a given
+   * input string.
+   *
+   * @param {String} inputString the input to use
+   */
+  startInitialization: function (inputString) {
+    this.inputString = inputString;
+    // Whether there are any newlines in the input text.
+    this.hasNewLine = /[\r\n]/.test(this.inputString);
+    // The declarations.
+    this.declarations = parseDeclarations(this.isCssPropertyKnown, this.inputString,
+                                          true);
+    this.decl = null;
+    this.result = null;
+  },
+
+  /**
    * An internal function to complete initialization and set some
    * properties for further processing.
    *
    * @param {Number} index The index of the property to modify
    */
   completeInitialization: function (index) {
     if (index < 0) {
       throw new Error("Invalid index " + index + ". Expected positive integer");
@@ -919,16 +932,27 @@ RuleRewriter.prototype = {
   removeProperty: function (index, name) {
     this.completeInitialization(index);
 
     // If asked to remove a property that does not exist, bail out.
     if (!this.decl) {
       return;
     }
 
+    // If the property is disabled, then first enable it, and then
+    // delete it.  We take this approach because we want to remove the
+    // entire comment if possible; but the logic for dealing with
+    // comments is hairy and already implemented in
+    // setPropertyEnabled.
+    if (this.decl.commentOffsets) {
+      this.setPropertyEnabled(index, name, true);
+      this.startInitialization(this.result);
+      this.completeInitialization(index);
+    }
+
     let copyOffset = this.decl.offsets[1];
     // Maybe removing this rule left us with a completely blank
     // line.  In this case, we'll delete the whole thing.  We only
     // bother with this if we're looking at sources that already
     // have a newline somewhere.
     if (this.hasNewLine) {
       let nlOffset = this.skipWhitespaceBackward(this.result,
                                                  this.decl.offsets[0]);
--- a/dom/animation/AnimationUtils.cpp
+++ b/dom/animation/AnimationUtils.cpp
@@ -1,16 +1,17 @@
 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
 /* 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 "AnimationUtils.h"
 
+#include "nsContentUtils.h" // For nsContentUtils::IsCallerChrome
 #include "nsDebug.h"
 #include "nsIAtom.h"
 #include "nsIContent.h"
 #include "nsIDocument.h"
 #include "nsGlobalWindow.h"
 #include "nsString.h"
 #include "xpcpublic.h" // For xpc::NativeGlobal
 #include "mozilla/Preferences.h"
@@ -58,23 +59,23 @@ AnimationUtils::IsOffscreenThrottlingEna
     Preferences::AddBoolVarCache(&sOffscreenThrottlingEnabled,
                                  "dom.animations.offscreen-throttling");
   }
 
   return sOffscreenThrottlingEnabled;
 }
 
 /* static */ bool
-AnimationUtils::IsCoreAPIEnabled()
+AnimationUtils::IsCoreAPIEnabledForCaller()
 {
   static bool sCoreAPIEnabled;
   static bool sPrefCached = false;
 
   if (!sPrefCached) {
     sPrefCached = true;
     Preferences::AddBoolVarCache(&sCoreAPIEnabled,
                                  "dom.animations-api.core.enabled");
   }
 
-  return sCoreAPIEnabled;
+  return sCoreAPIEnabled || nsContentUtils::IsCallerChrome();
 }
 
 } // namespace mozilla
--- a/dom/animation/AnimationUtils.h
+++ b/dom/animation/AnimationUtils.h
@@ -58,17 +58,17 @@ public:
   /**
    * Checks if offscreen animation throttling is enabled.
    */
   static bool
   IsOffscreenThrottlingEnabled();
 
   /**
    * Returns true if the preference to enable the core Web Animations API is
-   * true.
+   * true or the caller is chrome.
    */
   static bool
-  IsCoreAPIEnabled();
+  IsCoreAPIEnabledForCaller();
 };
 
 } // namespace mozilla
 
 #endif
--- a/dom/animation/KeyframeEffect.cpp
+++ b/dom/animation/KeyframeEffect.cpp
@@ -127,17 +127,17 @@ KeyframeEffect::SetTarget(const Nullable
 }
 
 void
 KeyframeEffect::SetIterationComposite(
   const IterationCompositeOperation& aIterationComposite)
 {
   // Ignore iterationComposite if the Web Animations API is not enabled,
   // then the default value 'Replace' will be used.
-  if (!AnimationUtils::IsCoreAPIEnabled()) {
+  if (!AnimationUtils::IsCoreAPIEnabledForCaller()) {
     return;
   }
 
   if (mEffectOptions.mIterationComposite == aIterationComposite) {
     return;
   }
 
   if (mAnimation && mAnimation->IsRelevant()) {
--- a/dom/animation/KeyframeEffectParams.cpp
+++ b/dom/animation/KeyframeEffectParams.cpp
@@ -110,17 +110,17 @@ KeyframeEffectParams::ParseSpacing(const
                                    nsCSSPropertyID& aPacedProperty,
                                    nsAString& aInvalidPacedProperty,
                                    ErrorResult& aRv)
 {
   aInvalidPacedProperty.Truncate();
 
   // Ignore spacing if the core API is not enabled since it is not yet ready to
   // ship.
-  if (!AnimationUtils::IsCoreAPIEnabled()) {
+  if (!AnimationUtils::IsCoreAPIEnabledForCaller()) {
     aSpacingMode = SpacingMode::distribute;
     return;
   }
 
   // Parse spacing.
   // distribute | paced({ident})
   // https://w3c.github.io/web-animations/#dom-keyframeeffectreadonly-spacing
   // 1. distribute spacing.
--- a/dom/animation/KeyframeEffectReadOnly.cpp
+++ b/dom/animation/KeyframeEffectReadOnly.cpp
@@ -507,17 +507,17 @@ KeyframeEffectParamsFromUnion(const Opti
       KeyframeEffectOptionsFromUnion(aOptions);
     KeyframeEffectParams::ParseSpacing(options.mSpacing,
                                        result.mSpacingMode,
                                        result.mPacedProperty,
                                        aInvalidPacedProperty,
                                        aRv);
     // Ignore iterationComposite if the Web Animations API is not enabled,
     // then the default value 'Replace' will be used.
-    if (AnimationUtils::IsCoreAPIEnabled()) {
+    if (AnimationUtils::IsCoreAPIEnabledForCaller()) {
       result.mIterationComposite = options.mIterationComposite;
     }
   }
   return result;
 }
 
 /* static */ Maybe<OwningAnimationTarget>
 KeyframeEffectReadOnly::ConvertTarget(
new file mode 100644
--- /dev/null
+++ b/dom/base/crashtests/1304437.html
@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<html>
+<script>
+window.onload=function(){
+  var e=document.createElement("q");
+  document.documentElement.appendChild(e);
+  e.style="mask-image:url(data:image/gif;base64,R0lGODlhAQABAIABAP///wAAACwAAAAAAQABAAACAkQBADs=),url(data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==)";
+  setTimeout(function(){
+    e.style="mask-image:url(data:image/gif;base64,R0lGODlhAQABAIABAP///wAAACwAAAAAAQABAAACAkQBADs=)";
+  },0);
+};
+</script>
+</html>
--- a/dom/base/crashtests/crashtests.list
+++ b/dom/base/crashtests/crashtests.list
@@ -202,8 +202,9 @@ load 1157995.html
 load 1158412.html
 load 1181619.html
 load structured_clone_container_throws.html
 HTTP(..) load xhr_abortinprogress.html
 load xhr_empty_datauri.html
 load xhr_html_nullresponse.html
 load 1230422.html
 load 1251361.html
+load 1304437.html
--- a/dom/media/MediaDecoder.cpp
+++ b/dom/media/MediaDecoder.cpp
@@ -520,17 +520,16 @@ MediaDecoder::MediaDecoder(MediaDecoderO
   , INIT_MIRROR(mStateMachineIsShutdown, true)
   , INIT_MIRROR(mBuffered, TimeIntervals())
   , INIT_MIRROR(mNextFrameStatus, MediaDecoderOwner::NEXT_FRAME_UNINITIALIZED)
   , INIT_MIRROR(mCurrentPosition, 0)
   , INIT_MIRROR(mStateMachineDuration, NullableTimeUnit())
   , INIT_MIRROR(mPlaybackPosition, 0)
   , INIT_MIRROR(mIsAudioDataAudible, false)
   , INIT_CANONICAL(mVolume, 0.0)
-  , INIT_CANONICAL(mPlaybackRate, 1.0)
   , INIT_CANONICAL(mPreservesPitch, true)
   , INIT_CANONICAL(mEstimatedDuration, NullableTimeUnit())
   , INIT_CANONICAL(mExplicitDuration, Maybe<double>())
   , INIT_CANONICAL(mPlayState, PLAY_STATE_LOADING)
   , INIT_CANONICAL(mNextState, PLAY_STATE_PAUSED)
   , INIT_CANONICAL(mLogicallySeeking, false)
   , INIT_CANONICAL(mSameOriginMedia, false)
   , INIT_CANONICAL(mMediaPrincipalHandle, PRINCIPAL_HANDLE_NONE)
@@ -739,16 +738,19 @@ MediaDecoder::InitializeStateMachine()
 
 void
 MediaDecoder::SetStateMachineParameters()
 {
   MOZ_ASSERT(NS_IsMainThread());
   if (mMinimizePreroll) {
     mDecoderStateMachine->DispatchMinimizePrerollUntilPlaybackStarts();
   }
+  if (mPlaybackRate != 1 && mPlaybackRate != 0) {
+    mDecoderStateMachine->DispatchSetPlaybackRate(mPlaybackRate);
+  }
   mTimedMetadataListener = mDecoderStateMachine->TimedMetadataEvent().Connect(
     AbstractThread::MainThread(), this, &MediaDecoder::OnMetadataUpdate);
   mMetadataLoadedListener = mDecoderStateMachine->MetadataLoadedEvent().Connect(
     AbstractThread::MainThread(), this, &MediaDecoder::MetadataLoaded);
   mFirstFrameLoadedListener = mDecoderStateMachine->FirstFrameLoadedEvent().Connect(
     AbstractThread::MainThread(), this, &MediaDecoder::FirstFrameLoaded);
 
   mOnPlaybackEvent = mDecoderStateMachine->OnPlaybackEvent().Connect(
@@ -1505,25 +1507,32 @@ MediaDecoder::SetLoadInBackground(bool a
 void
 MediaDecoder::SetPlaybackRate(double aPlaybackRate)
 {
   MOZ_ASSERT(NS_IsMainThread());
   mPlaybackRate = aPlaybackRate;
   if (mPlaybackRate == 0.0) {
     mPausedForPlaybackRateNull = true;
     Pause();
-  } else if (mPausedForPlaybackRateNull) {
+    return;
+  }
+
+  if (mPausedForPlaybackRateNull) {
     // Play() uses mPausedForPlaybackRateNull value, so must reset it first
     mPausedForPlaybackRateNull = false;
     // If the playbackRate is no longer null, restart the playback, iff the
     // media was playing.
     if (!mOwner->GetPaused()) {
       Play();
     }
   }
+
+  if (mDecoderStateMachine) {
+    mDecoderStateMachine->DispatchSetPlaybackRate(aPlaybackRate);
+  }
 }
 
 void
 MediaDecoder::SetPreservesPitch(bool aPreservesPitch)
 {
   MOZ_ASSERT(NS_IsMainThread());
   mPreservesPitch = aPreservesPitch;
 }
--- a/dom/media/MediaDecoder.h
+++ b/dom/media/MediaDecoder.h
@@ -759,17 +759,17 @@ protected:
 
   // Used to distinguish whether the audio is producing sound.
   Mirror<bool> mIsAudioDataAudible;
 
   // Volume of playback.  0.0 = muted. 1.0 = full volume.
   Canonical<double> mVolume;
 
   // PlaybackRate and pitch preservation status we should start at.
-  Canonical<double> mPlaybackRate;
+  double mPlaybackRate = 1;
 
   Canonical<bool> mPreservesPitch;
 
   // Media duration according to the demuxer's current estimate.
   // Note that it's quite bizarre for this to live on the main thread - it would
   // make much more sense for this to be owned by the demuxer's task queue. But
   // currently this is only every changed in NotifyDataArrived, which runs on
   // the main thread. That will need to be cleaned up at some point.
@@ -822,19 +822,16 @@ protected:
   // True if the decoder is visible.
   Canonical<bool> mIsVisible;
 
 public:
   AbstractCanonical<media::NullableTimeUnit>* CanonicalDurationOrNull() override;
   AbstractCanonical<double>* CanonicalVolume() {
     return &mVolume;
   }
-  AbstractCanonical<double>* CanonicalPlaybackRate() {
-    return &mPlaybackRate;
-  }
   AbstractCanonical<bool>* CanonicalPreservesPitch() {
     return &mPreservesPitch;
   }
   AbstractCanonical<media::NullableTimeUnit>* CanonicalEstimatedDuration() {
     return &mEstimatedDuration;
   }
   AbstractCanonical<Maybe<double>>* CanonicalExplicitDuration() {
     return &mExplicitDuration;
--- a/dom/media/MediaDecoderStateMachine.cpp
+++ b/dom/media/MediaDecoderStateMachine.cpp
@@ -740,17 +740,16 @@ MediaDecoderStateMachine::MediaDecoderSt
   mAudioOffloading(false),
   INIT_MIRROR(mBuffered, TimeIntervals()),
   INIT_MIRROR(mIsReaderSuspended, true),
   INIT_MIRROR(mEstimatedDuration, NullableTimeUnit()),
   INIT_MIRROR(mExplicitDuration, Maybe<double>()),
   INIT_MIRROR(mPlayState, MediaDecoder::PLAY_STATE_LOADING),
   INIT_MIRROR(mNextPlayState, MediaDecoder::PLAY_STATE_PAUSED),
   INIT_MIRROR(mVolume, 1.0),
-  INIT_MIRROR(mLogicalPlaybackRate, 1.0),
   INIT_MIRROR(mPreservesPitch, true),
   INIT_MIRROR(mSameOriginMedia, false),
   INIT_MIRROR(mMediaPrincipalHandle, PRINCIPAL_HANDLE_NONE),
   INIT_MIRROR(mPlaybackBytesPerSecond, 0.0),
   INIT_MIRROR(mPlaybackRateReliable, true),
   INIT_MIRROR(mDecoderPosition, 0),
   INIT_MIRROR(mMediaSeekable, true),
   INIT_MIRROR(mMediaSeekableOnlyInBufferedRanges, false),
@@ -802,34 +801,32 @@ MediaDecoderStateMachine::Initialization
   // Connect mirrors.
   mBuffered.Connect(mReader->CanonicalBuffered());
   mIsReaderSuspended.Connect(mReader->CanonicalIsSuspended());
   mEstimatedDuration.Connect(aDecoder->CanonicalEstimatedDuration());
   mExplicitDuration.Connect(aDecoder->CanonicalExplicitDuration());
   mPlayState.Connect(aDecoder->CanonicalPlayState());
   mNextPlayState.Connect(aDecoder->CanonicalNextPlayState());
   mVolume.Connect(aDecoder->CanonicalVolume());
-  mLogicalPlaybackRate.Connect(aDecoder->CanonicalPlaybackRate());
   mPreservesPitch.Connect(aDecoder->CanonicalPreservesPitch());
   mSameOriginMedia.Connect(aDecoder->CanonicalSameOriginMedia());
   mMediaPrincipalHandle.Connect(aDecoder->CanonicalMediaPrincipalHandle());
   mPlaybackBytesPerSecond.Connect(aDecoder->CanonicalPlaybackBytesPerSecond());
   mPlaybackRateReliable.Connect(aDecoder->CanonicalPlaybackRateReliable());
   mDecoderPosition.Connect(aDecoder->CanonicalDecoderPosition());
   mMediaSeekable.Connect(aDecoder->CanonicalMediaSeekable());
   mMediaSeekableOnlyInBufferedRanges.Connect(aDecoder->CanonicalMediaSeekableOnlyInBufferedRanges());
 
   // Initialize watchers.
   mWatchManager.Watch(mBuffered, &MediaDecoderStateMachine::BufferedRangeUpdated);
   mWatchManager.Watch(mIsReaderSuspended, &MediaDecoderStateMachine::ReaderSuspendedChanged);
   mWatchManager.Watch(mState, &MediaDecoderStateMachine::UpdateNextFrameStatus);
   mWatchManager.Watch(mAudioCompleted, &MediaDecoderStateMachine::UpdateNextFrameStatus);
   mWatchManager.Watch(mVideoCompleted, &MediaDecoderStateMachine::UpdateNextFrameStatus);
   mWatchManager.Watch(mVolume, &MediaDecoderStateMachine::VolumeChanged);
-  mWatchManager.Watch(mLogicalPlaybackRate, &MediaDecoderStateMachine::LogicalPlaybackRateChanged);
   mWatchManager.Watch(mPreservesPitch, &MediaDecoderStateMachine::PreservesPitchChanged);
   mWatchManager.Watch(mEstimatedDuration, &MediaDecoderStateMachine::RecomputeDuration);
   mWatchManager.Watch(mExplicitDuration, &MediaDecoderStateMachine::RecomputeDuration);
   mWatchManager.Watch(mObservedDuration, &MediaDecoderStateMachine::RecomputeDuration);
   mWatchManager.Watch(mPlayState, &MediaDecoderStateMachine::PlayStateChanged);
 
   if (MediaPrefs::MDSMSuspendBackgroundVideoEnabled()) {
     mIsVisible.Connect(aDecoder->CanonicalIsVisible());
@@ -1489,18 +1486,17 @@ MediaDecoderStateMachine::MaybeStartBuff
 
   // No more data to download. No need to enter buffering.
   if (!mResource->IsExpectingMoreData()) {
     return;
   }
 
   bool shouldBuffer;
   if (mReader->UseBufferingHeuristics()) {
-    shouldBuffer = HasLowDecodedData(EXHAUSTED_DATA_MARGIN_USECS) &&
-                   HasLowBufferedData();
+    shouldBuffer = HasLowDecodedData() && HasLowBufferedData();
   } else {
     MOZ_ASSERT(mReader->IsWaitForDataSupported());
     shouldBuffer = (OutOfDecodedAudio() && mReader->IsWaitingAudioData()) ||
                    (OutOfDecodedVideo() && mReader->IsWaitingVideoData());
   }
   if (shouldBuffer) {
     SetState(DECODER_STATE_BUFFERING);
   }
@@ -1705,17 +1701,16 @@ MediaDecoderStateMachine::Shutdown()
   // Disconnect canonicals and mirrors before shutting down our task queue.
   mBuffered.DisconnectIfConnected();
   mIsReaderSuspended.DisconnectIfConnected();
   mEstimatedDuration.DisconnectIfConnected();
   mExplicitDuration.DisconnectIfConnected();
   mPlayState.DisconnectIfConnected();
   mNextPlayState.DisconnectIfConnected();
   mVolume.DisconnectIfConnected();
-  mLogicalPlaybackRate.DisconnectIfConnected();
   mPreservesPitch.DisconnectIfConnected();
   mSameOriginMedia.DisconnectIfConnected();
   mMediaPrincipalHandle.DisconnectIfConnected();
   mPlaybackBytesPerSecond.DisconnectIfConnected();
   mPlaybackRateReliable.DisconnectIfConnected();
   mDecoderPosition.DisconnectIfConnected();
   mMediaSeekable.DisconnectIfConnected();
   mMediaSeekableOnlyInBufferedRanges.DisconnectIfConnected();
@@ -2313,27 +2308,38 @@ MediaDecoderStateMachine::StartMediaSink
       mMediaSinkVideoPromise.Begin(videoPromise->Then(
         OwnerThread(), __func__, this,
         &MediaDecoderStateMachine::OnMediaSinkVideoComplete,
         &MediaDecoderStateMachine::OnMediaSinkVideoError));
     }
   }
 }
 
-bool MediaDecoderStateMachine::HasLowDecodedData(int64_t aAudioUsecs)
+bool
+MediaDecoderStateMachine::HasLowDecodedAudio()
+{
+  MOZ_ASSERT(OnTaskQueue());
+  return IsAudioDecoding() &&
+         GetDecodedAudioDuration() < EXHAUSTED_DATA_MARGIN_USECS * mPlaybackRate;
+}
+
+bool
+MediaDecoderStateMachine::HasLowDecodedVideo()
+{
+  MOZ_ASSERT(OnTaskQueue());
+  return IsVideoDecoding() &&
+         VideoQueue().GetSize() < LOW_VIDEO_FRAMES * mPlaybackRate;
+}
+
+bool
+MediaDecoderStateMachine::HasLowDecodedData()
 {
   MOZ_ASSERT(OnTaskQueue());
   MOZ_ASSERT(mReader->UseBufferingHeuristics());
-  // We consider ourselves low on decoded data if we're low on audio,
-  // provided we've not decoded to the end of the audio stream, or
-  // if we're low on video frames, provided
-  // we've not decoded to the end of the video stream.
-  return ((IsAudioDecoding() && GetDecodedAudioDuration() < aAudioUsecs) ||
-         (IsVideoDecoding() &&
-          static_cast<uint32_t>(VideoQueue().GetSize()) < LOW_VIDEO_FRAMES));
+  return HasLowDecodedAudio() || HasLowDecodedVideo();
 }
 
 bool MediaDecoderStateMachine::OutOfDecodedAudio()
 {
     MOZ_ASSERT(OnTaskQueue());
     return IsAudioDecoding() && !AudioQueue().IsFinished() &&
            AudioQueue().GetSize() == 0 &&
            !mMediaSink->HasUnplayedFrames(TrackInfo::kAudioTrack);
@@ -2777,26 +2783,22 @@ bool MediaDecoderStateMachine::OnTaskQue
 
 bool MediaDecoderStateMachine::IsStateMachineScheduled() const
 {
   MOZ_ASSERT(OnTaskQueue());
   return mDispatchedStateMachine || mDelayedScheduler.IsScheduled();
 }
 
 void
-MediaDecoderStateMachine::LogicalPlaybackRateChanged()
+MediaDecoderStateMachine::SetPlaybackRate(double aPlaybackRate)
 {
   MOZ_ASSERT(OnTaskQueue());
-
-  if (mLogicalPlaybackRate == 0) {
-    // This case is handled in MediaDecoder by pausing playback.
-    return;
-  }
-
-  mPlaybackRate = mLogicalPlaybackRate;
+  MOZ_ASSERT(aPlaybackRate != 0, "Should be handled by MediaDecoder::Pause()");
+
+  mPlaybackRate = aPlaybackRate;
   mMediaSink->SetPlaybackRate(mPlaybackRate);
 
   if (mIsAudioPrerolling && DonePrerollingAudio()) {
     StopPrerollingAudio();
   }
   if (mIsVideoPrerolling && DonePrerollingVideo()) {
     StopPrerollingVideo();
   }
--- a/dom/media/MediaDecoderStateMachine.h
+++ b/dom/media/MediaDecoderStateMachine.h
@@ -169,16 +169,22 @@ public:
 
   void AddOutputStream(ProcessedMediaStream* aStream, bool aFinishWhenEnded);
   // Remove an output stream added with AddOutputStream.
   void RemoveOutputStream(MediaStream* aStream);
 
   // Seeks to the decoder to aTarget asynchronously.
   RefPtr<MediaDecoder::SeekPromise> InvokeSeek(SeekTarget aTarget);
 
+  void DispatchSetPlaybackRate(double aPlaybackRate)
+  {
+    OwnerThread()->DispatchStateChange(NewRunnableMethod<double>(
+      this, &MediaDecoderStateMachine::SetPlaybackRate, aPlaybackRate));
+  }
+
   // Set/Unset dormant state.
   void DispatchSetDormant(bool aDormant);
 
   RefPtr<ShutdownPromise> BeginShutdown();
 
   // Notifies the state machine that should minimize the number of samples
   // decoded we preroll, until playback starts. The first time playback starts
   // the state machine is free to return to prerolling normally. Note
@@ -362,35 +368,37 @@ protected:
   void Push(MediaData* aSample, MediaData::Type aSampleType);
 
   void OnAudioPopped(const RefPtr<MediaData>& aSample);
   void OnVideoPopped(const RefPtr<MediaData>& aSample);
 
   void AudioAudibleChanged(bool aAudible);
 
   void VolumeChanged();
-  void LogicalPlaybackRateChanged();
+  void SetPlaybackRate(double aPlaybackRate);
   void PreservesPitchChanged();
 
   MediaQueue<MediaData>& AudioQueue() { return mAudioQueue; }
   MediaQueue<MediaData>& VideoQueue() { return mVideoQueue; }
 
   // True if our buffers of decoded audio are not full, and we should
   // decode more.
   bool NeedToDecodeAudio();
 
   // True if our buffers of decoded video are not full, and we should
   // decode more.
   bool NeedToDecodeVideo();
 
-  // Returns true if we've got less than aAudioUsecs microseconds of decoded
-  // and playable data. The decoder monitor must be held.
-  //
+  // True if we are low in decoded audio/video data.
   // May not be invoked when mReader->UseBufferingHeuristics() is false.
-  bool HasLowDecodedData(int64_t aAudioUsecs);
+  bool HasLowDecodedData();
+
+  bool HasLowDecodedAudio();
+
+  bool HasLowDecodedVideo();
 
   bool OutOfDecodedAudio();
 
   bool OutOfDecodedVideo()
   {
     MOZ_ASSERT(OnTaskQueue());
     return IsVideoDecoding() && VideoQueue().GetSize() <= 1;
   }
@@ -881,21 +889,16 @@ private:
 
   // The current play state and next play state, mirrored from the main thread.
   Mirror<MediaDecoder::PlayState> mPlayState;
   Mirror<MediaDecoder::PlayState> mNextPlayState;
 
   // Volume of playback. 0.0 = muted. 1.0 = full volume.
   Mirror<double> mVolume;
 
-  // TODO: The separation between mPlaybackRate and mLogicalPlaybackRate is a
-  // kludge to preserve existing fragile logic while converting this setup to
-  // state-mirroring. Some hero should clean this up.
-  Mirror<double> mLogicalPlaybackRate;
-
   // Pitch preservation for the playback rate.
   Mirror<bool> mPreservesPitch;
 
   // True if the media is same-origin with the element. Data can only be
   // passed to MediaStreams when this is true.
   Mirror<bool> mSameOriginMedia;
 
   // An identifier for the principal of the media. Used to track when
--- a/dom/media/TextTrackList.cpp
+++ b/dom/media/TextTrackList.cpp
@@ -25,18 +25,19 @@ NS_INTERFACE_MAP_END_INHERITING(DOMEvent
 
 TextTrackList::TextTrackList(nsPIDOMWindowInner* aOwnerWindow)
   : DOMEventTargetHelper(aOwnerWindow)
 {
 }
 
 TextTrackList::TextTrackList(nsPIDOMWindowInner* aOwnerWindow,
                              TextTrackManager* aTextTrackManager)
- : DOMEventTargetHelper(aOwnerWindow)
- , mTextTrackManager(aTextTrackManager)
+  : DOMEventTargetHelper(aOwnerWindow)
+  , mPendingTextTrackChange(false)
+  , mTextTrackManager(aTextTrackManager)
 {
 }
 
 TextTrackList::~TextTrackList()
 {
 }
 
 void
@@ -126,50 +127,68 @@ TextTrackList::RemoveTextTrack(TextTrack
 void
 TextTrackList::DidSeek()
 {
   for (uint32_t i = 0; i < mTextTracks.Length(); i++) {
     mTextTracks[i]->SetDirty();
   }
 }
 
-class TrackEventRunner final: public Runnable
+class TrackEventRunner : public Runnable
 {
 public:
   TrackEventRunner(TextTrackList* aList, nsIDOMEvent* aEvent)
     : mList(aList)
     , mEvent(aEvent)
   {}
 
   NS_IMETHOD Run() override
   {
     return mList->DispatchTrackEvent(mEvent);
   }
 
+  RefPtr<TextTrackList> mList;
 private:
-  RefPtr<TextTrackList> mList;
   RefPtr<nsIDOMEvent> mEvent;
 };
 
+class ChangeEventRunner final : public TrackEventRunner
+{
+public:
+  ChangeEventRunner(TextTrackList* aList, nsIDOMEvent* aEvent)
+    : TrackEventRunner(aList, aEvent)
+  {}
+
+  NS_IMETHOD Run() override
+  {
+    mList->mPendingTextTrackChange = false;
+    return TrackEventRunner::Run();
+  }
+};
+
 nsresult
 TextTrackList::DispatchTrackEvent(nsIDOMEvent* aEvent)
 {
   return DispatchTrustedEvent(aEvent);
 }
 
 void
 TextTrackList::CreateAndDispatchChangeEvent()
 {
-  RefPtr<Event> event = NS_NewDOMEvent(this, nullptr, nullptr);
+  MOZ_ASSERT(NS_IsMainThread());
+  if (!mPendingTextTrackChange) {
+    mPendingTextTrackChange = true;
+    RefPtr<Event> event = NS_NewDOMEvent(this, nullptr, nullptr);
 
-  event->InitEvent(NS_LITERAL_STRING("change"), false, false);
-  event->SetTrusted(true);
+    event->InitEvent(NS_LITERAL_STRING("change"), false, false);
+    event->SetTrusted(true);
 
-  nsCOMPtr<nsIRunnable> eventRunner = new TrackEventRunner(this, event);
-  NS_DispatchToMainThread(eventRunner);
+    nsCOMPtr<nsIRunnable> eventRunner = new ChangeEventRunner(this, event);
+    NS_DispatchToMainThread(eventRunner);
+  }
 }
 
 void
 TextTrackList::CreateAndDispatchTrackEventRunner(TextTrack* aTrack,
                                                  const nsAString& aEventName)
 {
   nsCOMPtr<nsIThread> thread;
   nsresult rv = NS_GetMainThread(getter_AddRefs(thread));
--- a/dom/media/TextTrackList.h
+++ b/dom/media/TextTrackList.h
@@ -62,16 +62,18 @@ public:
   nsresult DispatchTrackEvent(nsIDOMEvent* aEvent);
   void CreateAndDispatchChangeEvent();
   void SetCuesInactive();
 
   IMPL_EVENT_HANDLER(change)
   IMPL_EVENT_HANDLER(addtrack)
   IMPL_EVENT_HANDLER(removetrack)
 
+  bool mPendingTextTrackChange;
+
 private:
   ~TextTrackList();
 
   nsTArray< RefPtr<TextTrack> > mTextTracks;
   RefPtr<TextTrackManager> mTextTrackManager;
 
   void CreateAndDispatchTrackEventRunner(TextTrack* aTrack,
                                          const nsAString& aEventName);
--- a/dom/media/test/external/external_media_tests/media_utils/video_puppeteer.py
+++ b/dom/media/test/external/external_media_tests/media_utils/video_puppeteer.py
@@ -56,20 +56,25 @@ class VideoPuppeteer(object):
      cleared. If 0, do not check for stalls.
     :param timeout: The amount of time to wait until the video starts.
     """
 
     _video_var_script = (
         'var video = arguments[0];'
         'var currentTime = video.wrappedJSObject.currentTime;'
         'var duration = video.wrappedJSObject.duration;'
+        'var buffered = video.wrappedJSObject.buffered;'
+        'var bufferedRanges = [];'
+        'for (var i = 0; i < buffered.length; i++) {'
+        'bufferedRanges.push([buffered.start(i), buffered.end(i)]);'
+        '}'
         'var played = video.wrappedJSObject.played;'
-        'var timeRanges = [];'
+        'var playedRanges = [];'
         'for (var i = 0; i < played.length; i++) {'
-        'timeRanges.push([played.start(i), played.end(i)]);'
+        'playedRanges.push([played.start(i), played.end(i)]);'
         '}'
         'var totalFrames = '
         'video.getVideoPlaybackQuality()["totalVideoFrames"];'
         'var droppedFrames = '
         'video.getVideoPlaybackQuality()["droppedVideoFrames"];'
         'var corruptedFrames = '
         'video.getVideoPlaybackQuality()["corruptedVideoFrames"];'
     )
@@ -243,16 +248,21 @@ class VideoPuppeteer(object):
     def _video_state_named_tuple():
         """
         Create a named tuple class that can be used to store state snapshots
         of the wrapped element. The fields in the tuple should be used as
         follows:
 
         current_time: The current time of the wrapped element.
         duration: the duration of the wrapped element.
+        buffered: the buffered ranges of the wrapped element. In its raw form
+        this is as a list where the first element is the length and the second
+        element is a list of 2 item lists, where each two items are a buffered
+        range. Once assigned to the tuple this data should be wrapped in the
+        TimeRanges class.
         played: the played ranges of the wrapped element. In its raw form this
         is as a list where the first element is the length and the second
         element is a list of 2 item lists, where each two items are a played
         range. Once assigned to the tuple this data should be wrapped in the
         TimeRanges class.
         lag: the difference in real world time and wrapped element time.
         Calculated as real world time passed - element time passed.
         totalFrames: number of total frames for the wrapped element
@@ -262,45 +272,51 @@ class VideoPuppeteer(object):
         video_url: the url attribute of the wrapped element.
 
         :return: A 'video_state_info' named tuple class.
         """
         return namedtuple('video_state_info',
                           ['current_time',
                            'duration',
                            'remaining_time',
+                           'buffered',
                            'played',
                            'lag',
                            'total_frames',
                            'dropped_frames',
                            'corrupted_frames',
                            'video_src',
                            'video_url'])
 
     def _create_video_state_info(self, **video_state_info_kwargs):
         """
         Create an instance of the video_state_info named tuple. This function
         expects a dictionary populated with the following keys: current_time,
-        duration, raw_time_ranges, total_frames, dropped_frames, and
+        duration, raw_played_ranges, total_frames, dropped_frames, and
         corrupted_frames.
 
-        Aside from raw_time_ranges, see `_video_state_named_tuple` for more
-        information on the above keys and values. For raw_time_ranges a
+        Aside from raw_played_ranges, see `_video_state_named_tuple` for more
+        information on the above keys and values. For raw_played_ranges a
         list is expected that can be consumed to make a TimeRanges object.
 
         :return: A named tuple 'video_state_info' derived from arguments and
         state information from the puppeteer.
         """
-        raw_time_ranges = video_state_info_kwargs['raw_time_ranges']
-        # Remove raw ranges from dict as it is not used in the final named
+        raw_buffered_ranges = video_state_info_kwargs['raw_buffered_ranges']
+        raw_played_ranges = video_state_info_kwargs['raw_played_ranges']
+        # Remove raw ranges from dict as they are not used in the final named
         # tuple and will provide an unexpected kwarg if kept.
-        del video_state_info_kwargs['raw_time_ranges']
+        del video_state_info_kwargs['raw_buffered_ranges']
+        del video_state_info_kwargs['raw_played_ranges']
+        # Create buffered ranges
+        video_state_info_kwargs['buffered'] = (
+            TimeRanges(raw_buffered_ranges[0], raw_buffered_ranges[1]))
         # Create played ranges
         video_state_info_kwargs['played'] = (
-            TimeRanges(raw_time_ranges[0], raw_time_ranges[1]))
+            TimeRanges(raw_played_ranges[0], raw_played_ranges[1]))
         # Calculate elapsed times
         elapsed_current_time = (video_state_info_kwargs['current_time'] -
                                 self._first_seen_time)
         elapsed_wall_time = clock() - self._first_seen_wall_time
         # Calculate lag
         video_state_info_kwargs['lag'] = (
             elapsed_wall_time - elapsed_current_time)
         # Calculate remaining time
@@ -322,33 +338,35 @@ class VideoPuppeteer(object):
     @property
     def _fetch_state_script(self):
         if not self._fetch_state_script_string:
             self._fetch_state_script_string = (
                 self._video_var_script +
                 'return ['
                 'currentTime,'
                 'duration,'
-                '[played.length, timeRanges],'
+                '[buffered.length, bufferedRanges],'
+                '[played.length, playedRanges],'
                 'totalFrames,'
                 'droppedFrames,'
                 'corruptedFrames];')
         return self._fetch_state_script_string
 
     def _refresh_state(self):
         """
         Refresh the snapshot of the underlying video state. We do this all
         in one so that the state doesn't change in between queries.
 
         We also store information that can be derived from the snapshotted
         information, such as lag. This is stored in the last seen state to
         stress that it's based on the snapshot.
         """
-        keys = ['current_time', 'duration', 'raw_time_ranges', 'total_frames',
-                'dropped_frames', 'corrupted_frames']
+        keys = ['current_time', 'duration', 'raw_buffered_ranges',
+                'raw_played_ranges', 'total_frames', 'dropped_frames',
+                'corrupted_frames']
         values = self._execute_video_script(self._fetch_state_script)
         self._last_seen_video_state = (
             self._create_video_state_info(**dict(zip(keys, values))))
 
     def _measure_progress(self):
         self._refresh_state()
         initial = self._last_seen_video_state.current_time
         sleep(1)
--- a/dom/media/test/external/external_media_tests/media_utils/youtube_puppeteer.py
+++ b/dom/media/test/external/external_media_tests/media_utils/youtube_puppeteer.py
@@ -341,17 +341,18 @@ class YouTubePuppeteer(VideoPuppeteer):
     def _fetch_state_script(self):
         if not self._fetch_state_script_string:
             self._fetch_state_script_string = (
                 self._video_var_script +
                 self._player_var_script +
                 'return ['
                 'currentTime,'
                 'duration,'
-                '[played.length, timeRanges],'
+                '[buffered.length, bufferedRanges],'
+                '[played.length, playedRanges],'
                 'totalFrames,'
                 'droppedFrames,'
                 'corruptedFrames,'
                 'player_duration,'
                 'player_current_time,'
                 'player_playback_quality,'
                 'player_movie_id,'
                 'player_movie_title,'
@@ -366,18 +367,19 @@ class YouTubePuppeteer(VideoPuppeteer):
         Refresh the snapshot of the underlying video and player state. We do
         this allin one so that the state doesn't change in between queries.
 
         We also store information that can be derived from the snapshotted
         information, such as lag. This is stored in the last seen state to
         stress that it's based on the snapshot.
         """
         values = self._execute_yt_script(self._fetch_state_script)
-        video_keys = ['current_time', 'duration', 'raw_time_ranges',
-                      'total_frames', 'dropped_frames', 'corrupted_frames']
+        video_keys = ['current_time', 'duration', 'raw_buffered_ranges',
+                      'raw_played_ranges', 'total_frames', 'dropped_frames',
+                      'corrupted_frames']
         player_keys = ['player_duration', 'player_current_time',
                        'player_playback_quality', 'player_movie_id',
                        'player_movie_title', 'player_url', 'player_state',
                        'player_ad_state', 'player_breaks_count']
         # Get video state
         self._last_seen_video_state = (
             self._create_video_state_info(**dict(
                 zip(video_keys, values[:len(video_keys)]))))
--- a/dom/media/test/test_texttracklist.html
+++ b/dom/media/test/test_texttracklist.html
@@ -23,22 +23,29 @@ isnot(video.textTracks, null, "Video sho
 
 video.addTextTrack("subtitles", "", "");
 
 track = video.textTracks[0];
 video.textTracks.addEventListener("change", changed);
 
 is(track.mode, "hidden", "New TextTrack's mode should be hidden.");
 track.mode = "showing";
+// Bug882674: change the mode again to see if we receive only one
+// change event.
+track.mode = "hidden";
 
+var eventCount = 0;
 function changed(event) {
+  eventCount++;
+  is(eventCount, 1, "change event dispatched multiple times.");
   is(event.target, video.textTracks, "change event's target should be video.textTracks.");
   ok(event instanceof window.Event, "change event should be a simple event.");
   ok(!event.bubbles, "change event should not bubble.");
   ok(event.isTrusted, "change event should be trusted.");
   ok(!event.cancelable, "change event should not be cancelable.");
 
-SimpleTest.finish();
+  // Delay the finish function call for testing the change event count.
+  setTimeout(SimpleTest.finish, 0);
 }
 </script>
 </pre>
 </body>
 </html>
--- a/dom/security/SRICheck.cpp
+++ b/dom/security/SRICheck.cpp
@@ -181,34 +181,36 @@ SRICheck::VerifyIntegrity(const SRIMetad
                           nsIUnicharStreamLoader* aLoader,
                           const nsAString& aString,
                           const nsACString& aSourceFileURI,
                           nsIConsoleReportCollector* aReporter)
 {
   NS_ENSURE_ARG_POINTER(aLoader);
   NS_ENSURE_ARG_POINTER(aReporter);
 
-  NS_ConvertUTF16toUTF8 utf8Hash(aString);
   nsCOMPtr<nsIChannel> channel;
   aLoader->GetChannel(getter_AddRefs(channel));
 
   if (MOZ_LOG_TEST(SRILogHelper::GetSriLog(), mozilla::LogLevel::Debug)) {
     nsAutoCString requestURL;
     nsCOMPtr<nsIURI> originalURI;
     if (channel &&
         NS_SUCCEEDED(channel->GetOriginalURI(getter_AddRefs(originalURI))) &&
         originalURI) {
       originalURI->GetAsciiSpec(requestURL);
     }
     SRILOG(("SRICheck::VerifyIntegrity (unichar stream)"));
   }
 
   SRICheckDataVerifier verifier(aMetadata, aSourceFileURI, aReporter);
   nsresult rv;
-  rv = verifier.Update(utf8Hash.Length(), (uint8_t*)utf8Hash.get());
+  nsDependentCString rawBuffer;
+  rv = aLoader->GetRawBuffer(rawBuffer);
+  NS_ENSURE_SUCCESS(rv, rv);
+  rv = verifier.Update(rawBuffer.Length(), (const uint8_t*)rawBuffer.get());
   NS_ENSURE_SUCCESS(rv, rv);
 
   return verifier.Verify(aMetadata, channel, aSourceFileURI, aReporter);
 }
 
 //////////////////////////////////////////////////////////////
 //
 //////////////////////////////////////////////////////////////
new file mode 100644
--- /dev/null
+++ b/dom/security/test/sri/file_bug_1271796.css
@@ -0,0 +1,2 @@
+/*! Simple test for bug 1271796 */
+p::before { content: "\2014"; }
--- a/dom/security/test/sri/iframe_style_sameorigin.html
+++ b/dom/security/test/sri/iframe_style_sameorigin.html
@@ -67,23 +67,23 @@
     }
     function bad_correctUTF8HashBlocked() {
       ok(false, "We should load UTF8 stylesheets with hashes that match!");
     }
     function good_correctUTF8BOMHashLoaded() {
       ok(true, "A UTF8 stylesheet (with BOM) was correctly loaded when integrity matched");
     }
     function bad_correctUTF8BOMHashBlocked() {
-      todo(false, "We should load UTF8 (with BOM) stylesheets with hashes that match!");
+      ok(false, "We should load UTF8 (with BOM) stylesheets with hashes that match!");
     }
     function good_correctUTF8ishHashLoaded() {
       ok(true, "A UTF8ish stylesheet was correctly loaded when integrity matched");
     }
     function bad_correctUTF8ishHashBlocked() {
-      todo(false, "We should load UTF8ish stylesheets with hashes that match!");
+      ok(false, "We should load UTF8ish stylesheets with hashes that match!");
     }
   </script>
 
   <!-- valid sha256 hash. should trigger onload -->
   <link rel="stylesheet" href="style1.css"
         integrity="sha256-qs8lnkunWoVldk5d5E+652yth4VTSHohlBKQvvgGwa8="
         onerror="bad_correctHashBlocked()"
         onload="good_correctHashLoaded()">
--- a/dom/security/test/sri/mochitest.ini
+++ b/dom/security/test/sri/mochitest.ini
@@ -1,10 +1,11 @@
 [DEFAULT]
 support-files =
+  file_bug_1271796.css
   iframe_require-sri-for_main.html
   iframe_require-sri-for_main.html^headers^
   iframe_script_crossdomain.html
   iframe_script_sameorigin.html
   iframe_sri_disabled.html
   iframe_style_crossdomain.html
   iframe_style_sameorigin.html
   script_crossdomain1.js
@@ -38,8 +39,9 @@ support-files =
 
 [test_script_sameorigin.html]
 [test_script_crossdomain.html]
 [test_sri_disabled.html]
 [test_style_crossdomain.html]
 [test_style_sameorigin.html]
 [test_require-sri-for_csp_directive.html]
 [test_require-sri-for_csp_directive_disabled.html]
+[test_bug_1271796.html]
new file mode 100644
--- /dev/null
+++ b/dom/security/test/sri/test_bug_1271796.html
@@ -0,0 +1,30 @@
+<!DOCTYPE HTML>
+<!-- Any copyright is dedicated to the Public Domain.
+     http://creativecommons.org/publicdomain/zero/1.0/ -->
+<html>
+<head>
+  <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">
+      SimpleTest.waitForExplicitFinish();
+
+      function good_shouldLoadEncodingProblem() {
+        ok(true, "Problematically encoded file correctly loaded.")
+      };
+      function bad_shouldntEncounterBug1271796() {
+        ok(false, "Problematically encoded should load!")
+      }
+      window.onload = function() {
+        SimpleTest.finish();
+      }
+    </script>
+   <link rel="stylesheet" href="file_bug_1271796.css" crossorigin="anonymous"
+         integrity="sha384-8Xl0mTN4S2QZ5xeliG1sd4Ar9o1xMw6JoJy9RNjyHGQDha7GiLxo8l1llwLVgTNG"
+         onload="good_shouldLoadEncodingProblem();"
+         onerror="bad_shouldntEncounterBug1271796();">
+</head>
+<body>
+<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=1271796">Bug 1271796</a><br>
+<p>This text is prepended by emdash if css has loaded</p>
+</body>
+</html>
--- a/gfx/layers/ipc/CompositorBridgeParent.cpp
+++ b/gfx/layers/ipc/CompositorBridgeParent.cpp
@@ -1655,60 +1655,44 @@ CompositorBridgeParent::FlushApzRepaints
 {
   MOZ_ASSERT(mApzcTreeManager);
   uint64_t layersId = aLayerTree->GetId();
   if (layersId == 0) {
     // The request is coming from the parent-process layer tree, so we should
     // use the compositor's root layer tree id.
     layersId = mRootLayerTreeID;
   }
-  mApzcTreeManager->FlushApzRepaints(layersId);
+  APZThreadUtils::RunOnControllerThread(NS_NewRunnableFunction([=] () {
+    mApzcTreeManager->FlushApzRepaints(layersId);
+  }));
 }
 
 void
 CompositorBridgeParent::GetAPZTestData(const LayerTransactionParent* aLayerTree,
                                        APZTestData* aOutData)
 {
   MonitorAutoLock lock(*sIndirectLayerTreesLock);
   *aOutData = sIndirectLayerTrees[mRootLayerTreeID].mApzTestData;
 }
 
-class NotifyAPZConfirmedTargetTask : public Runnable
-{
-public:
-  explicit NotifyAPZConfirmedTargetTask(const RefPtr<APZCTreeManager>& aAPZCTM,
-                                        const uint64_t& aInputBlockId,
-                                        const nsTArray<ScrollableLayerGuid>& aTargets)
-   : mAPZCTM(aAPZCTM),
-     mInputBlockId(aInputBlockId),
-     mTargets(aTargets)
-  {
-  }
-
-  NS_IMETHOD Run() override {
-    mAPZCTM->SetTargetAPZC(mInputBlockId, mTargets);
-    return NS_OK;
-  }
-
-private:
-  RefPtr<APZCTreeManager> mAPZCTM;
-  uint64_t mInputBlockId;
-  nsTArray<ScrollableLayerGuid> mTargets;
-};
-
 void
 CompositorBridgeParent::SetConfirmedTargetAPZC(const LayerTransactionParent* aLayerTree,
                                          const uint64_t& aInputBlockId,
                                          const nsTArray<ScrollableLayerGuid>& aTargets)
 {
   if (!mApzcTreeManager) {
     return;
   }
-  RefPtr<Runnable> task =
-    new NotifyAPZConfirmedTargetTask(mApzcTreeManager, aInputBlockId, aTargets);
+  // Need to specifically bind this since it's overloaded.
+  void (APZCTreeManager::*setTargetApzcFunc)
+        (uint64_t, const nsTArray<ScrollableLayerGuid>&) =
+        &APZCTreeManager::SetTargetAPZC;
+  RefPtr<Runnable> task = NewRunnableMethod
+        <uint64_t, StoreCopyPassByConstLRef<nsTArray<ScrollableLayerGuid>>>
+        (mApzcTreeManager.get(), setTargetApzcFunc, aInputBlockId, aTargets);
   APZThreadUtils::RunOnControllerThread(task.forget());
 
 }
 
 void
 CompositorBridgeParent::InitializeLayerManager(const nsTArray<LayersBackend>& aBackendHints)
 {
   NS_ASSERTION(!mLayerManager, "Already initialised mLayerManager");
--- a/gfx/thebes/gfxContext.h
+++ b/gfx/thebes/gfxContext.h
@@ -587,50 +587,52 @@ private:
  * Sentry helper class for functions with multiple return points that need to
  * back up the current matrix of a context and have it automatically restored
  * before they return.
  */
 class gfxContextMatrixAutoSaveRestore
 {
 public:
     gfxContextMatrixAutoSaveRestore() :
-        mContext(nullptr)
+      mContext(nullptr)
     {
     }
 
     explicit gfxContextMatrixAutoSaveRestore(gfxContext *aContext) :
-        mContext(aContext), mMatrix(aContext->CurrentMatrix())
+      mContext(aContext), mMatrix(aContext->CurrentMatrix())
     {
     }
 
     ~gfxContextMatrixAutoSaveRestore()
     {
-        if (mContext) {
-            mContext->SetMatrix(mMatrix);
-        }
+      if (mContext) {
+        mContext->SetMatrix(mMatrix);
+      }
     }
 
     void SetContext(gfxContext *aContext)
     {
-        NS_ASSERTION(!mContext, "Not going to restore the matrix on some context!");
-        mContext = aContext;
-        mMatrix = aContext->CurrentMatrix();
+      NS_ASSERTION(!mContext,
+                   "Not going to restore the matrix on some context!");
+      mContext = aContext;
+      mMatrix = aContext->CurrentMatrix();
     }
 
     void Restore()
     {
-        if (mContext) {
-            mContext->SetMatrix(mMatrix);
-        }
+      if (mContext) {
+        mContext->SetMatrix(mMatrix);
+        mContext = nullptr;
+      }
     }
 
     const gfxMatrix& Matrix()
     {
-        MOZ_ASSERT(mContext, "mMatrix doesn't contain a useful matrix");
-        return mMatrix;
+      MOZ_ASSERT(mContext, "mMatrix doesn't contain a useful matrix");
+      return mMatrix;
     }
 
     bool HasMatrix() const { return !!mContext; }
 
 private:
     gfxContext *mContext;
     gfxMatrix   mMatrix;
 };
--- a/gradle/wrapper/gradle-wrapper.properties
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -1,7 +1,7 @@
-#Tue Apr 12 09:52:06 CEST 2016
+#Fri Sep 16 15:41:50 PDT 2016
 distributionBase=GRADLE_USER_HOME
 distributionPath=wrapper/dists
 zipStoreBase=GRADLE_USER_HOME
 zipStorePath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-2.10-all.zip
-distributionSha256Sum=496d60c331f8666f99b66d08ff67a880697a7e85a9d9b76ff08814cf97f61a4c
+distributionUrl=https\://services.gradle.org/distributions/gradle-2.14.1-all.zip
+distributionSha256Sum=88a910cdf2e03ebbb5fe90f7ecf534fc9ac22e12112dc9a2fee810c598a76091
--- a/layout/base/RestyleTracker.h
+++ b/layout/base/RestyleTracker.h
@@ -323,17 +323,17 @@ RestyleTracker::AddPendingRestyle(Elemen
                                   mozilla::Maybe<Element*> aRestyleRoot)
 {
   bool hadRestyleLaterSiblings =
     AddPendingRestyleToTable(aElement, aRestyleHint, aMinChangeHint,
                              aRestyleHintData);
 
   // We can only treat this element as a restyle root if we would
   // actually restyle its descendants (so either call
-  // ReResolveStyleContext on it or just reframe it).
+  // ElementRestyler::Restyle on it or just reframe it).
   if ((aRestyleHint & ~eRestyle_LaterSiblings) ||
       (aMinChangeHint & nsChangeHint_ReconstructFrame)) {
     Element* cur =
       aRestyleRoot ? *aRestyleRoot : FindClosestRestyleRoot(aElement);
     if (!cur) {
       mRestyleRoots.AppendElement(aElement);
       cur = aElement;
     }
--- a/layout/base/nsCSSRendering.cpp
+++ b/layout/base/nsCSSRendering.cpp
@@ -3233,17 +3233,18 @@ nsCSSRendering::PaintBackgroundWithSC(co
                               aBorder, aParams.borderArea, aParams.dirtyRect,
                               (aParams.paintFlags & PAINTBG_WILL_PAINT_BORDER),
                               appUnitsPerPixel, &clipState);
           }
           SetupImageLayerClip(clipState, ctx, appUnitsPerPixel, &autoSR);
           clipSet = true;
           if (!clipBorderArea.IsEqualEdges(aParams.borderArea)) {
             // We're drawing the background for the joined continuation boxes
-            // so we need to clip that to the slice that we want for this frame.
+            // so we need to clip that to the slice that we want for this
+            // frame.
             gfxRect clip =
               nsLayoutUtils::RectToGfxRect(aParams.borderArea, appUnitsPerPixel);
             autoSR.EnsureSaved(ctx);
             ctx->NewPath();
             ctx->SnappedRectangle(clip);
             ctx->Clip();
           }
         }
@@ -3254,17 +3255,18 @@ nsCSSRendering::PaintBackgroundWithSC(co
         nsBackgroundLayerState state =
           PrepareImageLayer(&aParams.presCtx, aParams.frame,
                             aParams.paintFlags, paintBorderArea, clipState.mBGClipArea,
                             layer, nullptr);
         result &= state.mImageRenderer.PrepareResult();
         if (!state.mFillArea.IsEmpty()) {
           if (co != CompositionOp::OP_OVER) {
             NS_ASSERTION(ctx->CurrentOp() == CompositionOp::OP_OVER,
-                         "It is assumed the initial op is OP_OVER, when it is restored later");
+                         "It is assumed the initial op is OP_OVER, when it is "
+                         "restored later");
             ctx->SetOp(co);
           }
 
           result &=
             state.mImageRenderer.DrawBackground(&aParams.presCtx,
                                                 aParams.renderingCtx,
                                                 state.mDestArea, state.mFillArea,
                                                 state.mAnchor + paintBorderArea.TopLeft(),
--- a/layout/base/nsDisplayList.cpp
+++ b/layout/base/nsDisplayList.cpp
@@ -6827,26 +6827,38 @@ nsDisplayMask::~nsDisplayMask()
 bool nsDisplayMask::TryMerge(nsDisplayItem* aItem)
 {
   if (aItem->GetType() != TYPE_MASK)
     return false;
 
   // items for the same content element should be merged into a single
   // compositing group
   // aItem->GetUnderlyingFrame() returns non-null because it's nsDisplaySVGEffects
-  if (aItem->Frame()->GetContent() != mFrame->GetContent())
+  if (aItem->Frame()->GetContent() != mFrame->GetContent()) {
+    return false;
+  }
+  if (aItem->GetClip() != GetClip()) {
+    return false;
+  }
+  if (aItem->ScrollClip() != ScrollClip()) {
     return false;
-  if (aItem->GetClip() != GetClip())
+  }
+
+  // Do not merge if mFrame has mask. Continuation frames should apply mask
+  // independently(just like nsDisplayBackgroundImage).
+  const nsStyleSVGReset *style = mFrame->StyleSVGReset();
+  if (style->mMask.HasLayerWithImage()) {
     return false;
-  if (aItem->ScrollClip() != ScrollClip())
-    return false;
+  }
+
   nsDisplayMask* other = static_cast<nsDisplayMask*>(aItem);
   MergeFromTrackingMergedFrames(other);
   mEffectsBounds.UnionRect(mEffectsBounds,
     other->mEffectsBounds + other->mFrame->GetOffsetTo(mFrame));
+
   return true;
 }
 
 already_AddRefed<Layer>
 nsDisplayMask::BuildLayer(nsDisplayListBuilder* aBuilder,
                           LayerManager* aManager,
                           const ContainerLayerParameters& aContainerParameters)
 {
@@ -6881,17 +6893,17 @@ LayerState
 nsDisplayMask::GetLayerState(nsDisplayListBuilder* aBuilder,
                              LayerManager* aManager,
                              const ContainerLayerParameters& aParameters)
 {
   return LAYER_SVG_EFFECTS;
 }
 
 bool nsDisplayMask::ComputeVisibility(nsDisplayListBuilder* aBuilder,
-                                              nsRegion* aVisibleRegion) 
+                                      nsRegion* aVisibleRegion)
 {
   // Our children may be made translucent or arbitrarily deformed so we should
   // not allow them to subtract area from aVisibleRegion.
   nsRegion childrenVisible(mVisibleRect);
   nsRect r = mVisibleRect.Intersect(mList.GetBounds(aBuilder));
   mList.ComputeVisibilityForSublist(aBuilder, &childrenVisible, r);
   return true;
 }
new file mode 100644
--- /dev/null
+++ b/layout/reftests/w3c-css/submitted/masking/mask-image-6-ref.html
@@ -0,0 +1,25 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <meta charset="utf-8">
+    <title>CSS Masking: mask on inline element</title>
+    <link rel="author" title="CJ Ku" href="mailto:cku@mozilla.com">
+    <link rel="author" title="Mozilla" href="https://www.mozilla.org">
+    <style type="text/css">
+      div {
+        width: 100px;
+        height: 100px;
+        font-size: 100px;
+        line-height: 100px;
+      }
+
+      div.mask-by-png {
+        mask-image: url(support/transparent-100x50-blue-100x50.png);
+      }
+    </style>
+  </head>
+  <body>
+    <div class="mask-by-png">A</div>
+    <div class="mask-by-png">B</div>
+  </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/w3c-css/submitted/masking/mask-image-6.html
@@ -0,0 +1,30 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <meta charset="utf-8">
+    <title>CSS Masking: mask on inline element</title>
+    <link rel="author" title="CJ Ku" href="mailto:cku@mozilla.com">
+    <link rel="author" title="Mozilla" href="https://www.mozilla.org">
+    <link rel="help" href="https://www.w3.org/TR/css-masking-1/#the-mask-image">
+    <link rel="match" href="mask-image-6-ref.html">
+    <meta name="assert" content="Test checks whether mask on inline elemnt works correctly or not.">
+    <style type="text/css">
+      div {
+        width: 100px;
+        height: 100px;
+      }
+      span {
+        font-size: 100px;
+        line-height: 100px;
+        mask-image: url(support/transparent-100x50-blue-100x50.png);
+        mask-repeat: repeat;
+      }
+
+    </style>
+  </head>
+  <body>
+    <div>
+      <span>A B</span>
+    </div>
+  </body>
+</html>
--- a/layout/reftests/w3c-css/submitted/masking/reftest.list
+++ b/layout/reftests/w3c-css/submitted/masking/reftest.list
@@ -24,16 +24,17 @@ fuzzy-if(skiaContent||winWidget,1,20000)
 fuzzy-if(skiaContent||winWidget,1,43) == mask-image-3c.html mask-image-3-ref.html
 fuzzy-if(skiaContent||winWidget,1,43) == mask-image-3d.html mask-image-3-ref.html
 == mask-image-3e.html mask-image-3-ref.html
 fuzzy-if(skiaContent,50,50) == mask-image-3f.html mask-image-3-ref.html
 == mask-image-3g.html mask-image-3-ref.html
 == mask-image-4a.html blank.html
 == mask-image-4b.html blank.html
 == mask-image-5.html mask-image-5-ref.html
+== mask-image-6.html mask-image-6-ref.html
 
 # mask-clip test cases
 == mask-clip-1.html mask-clip-1-ref.html
 
 # mask-position test cases
 == mask-position-1a.html mask-position-1-ref.html
 == mask-position-1b.html mask-position-1-ref.html
 == mask-position-1c.html mask-position-1-ref.html
--- a/layout/style/nsStyleContext.h
+++ b/layout/style/nsStyleContext.h
@@ -27,17 +27,17 @@ enum class CSSPseudoElementType : uint8_
  * nsStyleStruct.h) that are cached either on the style context or in
  * the rule tree (see nsRuleNode.h for a description of this caching and
  * how the cached structs are shared).
  *
  * Since the data in |nsIStyleRule|s and |nsRuleNode|s are immutable
  * (with a few exceptions, like system color changes), the data in an
  * nsStyleContext are also immutable (with the additional exception of
  * GetUniqueStyleData).  When style data change,
- * nsFrameManager::ReResolveStyleContext creates a new style context.
+ * ElementRestyler::Restyle creates a new style context.
  *
  * Style contexts are reference counted.  References are generally held
  * by:
  *  1. the |nsIFrame|s that are using the style context and
  *  2. any *child* style contexts (this might be the reverse of
  *     expectation, but it makes sense in this case)
  */
 
--- a/layout/style/nsStyleStruct.cpp
+++ b/layout/style/nsStyleStruct.cpp
@@ -1313,17 +1313,18 @@ nsStyleSVGReset::CalcDifference(const ns
              mFloodColor    != aNewData.mFloodColor    ||
              mLightingColor != aNewData.mLightingColor ||
              mStopOpacity   != aNewData.mStopOpacity   ||
              mFloodOpacity  != aNewData.mFloodOpacity  ||
              mMaskType      != aNewData.mMaskType) {
     hint |= nsChangeHint_RepaintFrame;
   }
 
-  hint |= mMask.CalcDifference(aNewData.mMask, nsChangeHint_RepaintFrame);
+  hint |= mMask.CalcDifference(aNewData.mMask,
+                               nsStyleImageLayers::LayerType::Mask);
 
   return hint;
 }
 
 // nsStyleSVGPaint implementation
 nsStyleSVGPaint::nsStyleSVGPaint(nsStyleSVGPaintType aType)
   : mType(nsStyleSVGPaintType(0))
   , mFallbackColor(NS_RGB(0, 0, 0))
@@ -2422,46 +2423,56 @@ nsStyleImageLayers::nsStyleImageLayers(c
     mMaskModeCount = std::max(mMaskModeCount, count);
     mBlendModeCount = std::max(mBlendModeCount, count);
     mCompositeCount = std::max(mCompositeCount, count);
   }
 }
 
 nsChangeHint
 nsStyleImageLayers::CalcDifference(const nsStyleImageLayers& aNewLayers,
-                                   nsChangeHint aPositionChangeHint) const
+                                   nsStyleImageLayers::LayerType aType) const
 {
+  nsChangeHint positionChangeHint =
+    (aType == nsStyleImageLayers::LayerType::Background)
+    ? nsChangeHint_UpdateBackgroundPosition
+    : nsChangeHint_RepaintFrame;
+
   nsChangeHint hint = nsChangeHint(0);
 
   const nsStyleImageLayers& moreLayers =
     mImageCount > aNewLayers.mImageCount ?
       *this : aNewLayers;
   const nsStyleImageLayers& lessLayers =
     mImageCount > aNewLayers.mImageCount ?
       aNewLayers : *this;
 
   NS_FOR_VISIBLE_IMAGE_LAYERS_BACK_TO_FRONT(i, moreLayers) {
     if (i < lessLayers.mImageCount) {
       nsChangeHint layerDifference =
         moreLayers.mLayers[i].CalcDifference(lessLayers.mLayers[i],
-                                             aPositionChangeHint);
+                                             positionChangeHint);
       hint |= layerDifference;
       if (layerDifference &&
           ((moreLayers.mLayers[i].mImage.GetType() == eStyleImageType_Element) ||
            (lessLayers.mLayers[i].mImage.GetType() == eStyleImageType_Element))) {
         hint |= nsChangeHint_UpdateEffects | nsChangeHint_RepaintFrame;
       }
     } else {
       hint |= nsChangeHint_RepaintFrame;
       if (moreLayers.mLayers[i].mImage.GetType() == eStyleImageType_Element) {
         hint |= nsChangeHint_UpdateEffects | nsChangeHint_RepaintFrame;
       }
     }
   }
 
+  if (aType == nsStyleImageLayers::LayerType::Mask &&
+      mImageCount != aNewLayers.mImageCount) {
+    hint |= nsChangeHint_UpdateEffects;
+  }
+
   if (hint) {
     return hint;
   }
 
   if (mAttachmentCount != aNewLayers.mAttachmentCount ||
       mBlendModeCount != aNewLayers.mBlendModeCount ||
       mClipCount != aNewLayers.mClipCount ||
       mCompositeCount != aNewLayers.mCompositeCount ||
@@ -2724,17 +2735,17 @@ nsStyleImageLayers::Layer::operator==(co
 }
 
 nsChangeHint
 nsStyleImageLayers::Layer::CalcDifference(const nsStyleImageLayers::Layer& aNewLayer,
                                           nsChangeHint aPositionChangeHint) const
 {
   nsChangeHint hint = nsChangeHint(0);
   if (mSourceURI != aNewLayer.mSourceURI) {
-    hint |= nsChangeHint_RepaintFrame;
+    hint |= nsChangeHint_RepaintFrame | nsChangeHint_UpdateEffects;
 
     // If Layer::mSourceURI links to a SVG mask, it has a fragment. Not vice
     // versa. Here are examples of URI contains a fragment, two of them link
     // to a SVG mask:
     //   mask:url(a.svg#maskID); // The fragment of this URI is an ID of a mask
     //                           // element in a.svg.
     //   mask:url(#localMaskID); // The fragment of this URI is an ID of a mask
     //                           // element in local document.
@@ -2752,20 +2763,19 @@ nsStyleImageLayers::Layer::CalcDifferenc
     if (!maybeSVGMask) {
       if (aNewLayer.mSourceURI.IsLocalRef()) {
         maybeSVGMask = true;
       } else if (aNewLayer.mSourceURI.GetSourceURL()) {
         aNewLayer.mSourceURI.GetSourceURL()->GetHasRef(&maybeSVGMask);
       }
     }
 
-    // Return nsChangeHint_UpdateEffects and nsChangeHint_UpdateOverflow if
-    // either URI might link to an SVG mask.
+    // Return nsChangeHint_UpdateOverflow if either URI might link to an SVG
+    // mask.
     if (maybeSVGMask) {
-      hint |= nsChangeHint_UpdateEffects;
       // Mask changes require that we update the PreEffectsBBoxProperty,
       // which is done during overflow computation.
       hint |= nsChangeHint_UpdateOverflow;
     }
   } else if (mAttachment != aNewLayer.mAttachment ||
              mClip != aNewLayer.mClip ||
              mOrigin != aNewLayer.mOrigin ||
              mRepeat != aNewLayer.mRepeat ||
@@ -2822,17 +2832,17 @@ nsChangeHint
 nsStyleBackground::CalcDifference(const nsStyleBackground& aNewData) const
 {
   nsChangeHint hint = nsChangeHint(0);
   if (mBackgroundColor != aNewData.mBackgroundColor) {
     hint |= nsChangeHint_RepaintFrame;
   }
 
   hint |= mImage.CalcDifference(aNewData.mImage,
-                                nsChangeHint_UpdateBackgroundPosition);
+                                nsStyleImageLayers::LayerType::Background);
 
   return hint;
 }
 
 bool
 nsStyleBackground::HasFixedBackground(nsIFrame* aFrame) const
 {
   NS_FOR_VISIBLE_IMAGE_LAYERS_BACK_TO_FRONT(i, mImage) {
@@ -3550,27 +3560,27 @@ nsStyleContent::nsStyleContent(const nsS
   , mResets(aSource.mResets)
 {
   MOZ_COUNT_CTOR(nsStyleContent);
 }
 
 nsChangeHint
 nsStyleContent::CalcDifference(const nsStyleContent& aNewData) const
 {
-  // In ReResolveStyleContext we assume that if there's no existing
+  // In ElementRestyler::Restyle we assume that if there's no existing
   // ::before or ::after and we don't have to restyle children of the
   // node then we can't end up with a ::before or ::after due to the
   // restyle of the node itself.  That's not quite true, but the only
   // exception to the above is when the 'content' property of the node
   // changes and the pseudo-element inherits the changed value.  Since
   // the code here triggers a frame change on the node in that case,
-  // the optimization in ReResolveStyleContext is ok.  But if we ever
+  // the optimization in ElementRestyler::Restyle is ok.  But if we ever
   // change this code to not reconstruct frames on changes to the
   // 'content' property, then we will need to revisit the optimization
-  // in ReResolveStyleContext.
+  // in ElementRestyler::Restyle.
 
   // Unfortunately we need to reframe even if the content lengths are the same;
   // a simple reflow will not pick up different text or different image URLs,
   // since we set all that up in the CSSFrameConstructor
   if (mContents != aNewData.mContents ||
       mIncrements != aNewData.mIncrements ||
       mResets != aNewData.mResets) {
     return nsChangeHint_ReconstructFrame;
--- a/layout/style/nsStyleStruct.h
+++ b/layout/style/nsStyleStruct.h
@@ -813,17 +813,17 @@ struct nsStyleImageLayers {
     }
   }
   void UntrackImages(nsPresContext* aContext) {
     for (uint32_t i = 0; i < mImageCount; ++i)
       mLayers[i].UntrackImages(aContext);
   }
 
   nsChangeHint CalcDifference(const nsStyleImageLayers& aNewLayers,
-                              nsChangeHint aPositionChangeHint) const;
+                              nsStyleImageLayers::LayerType aType) const;
 
   bool HasLayerWithImage() const;
 
   static const nsCSSPropertyID kBackgroundLayerTable[];
   static const nsCSSPropertyID kMaskLayerTable[];
 
   #define NS_FOR_VISIBLE_IMAGE_LAYERS_BACK_TO_FRONT(var_, layers_) \
     for (uint32_t var_ = (layers_).mImageCount; var_-- != 0; )
--- a/layout/style/nsTransitionManager.h
+++ b/layout/style/nsTransitionManager.h
@@ -332,17 +332,17 @@ public:
   NS_DECL_CYCLE_COLLECTION_NATIVE_CLASS(nsTransitionManager)
 
   typedef mozilla::AnimationCollection<mozilla::dom::CSSTransition>
     CSSTransitionCollection;
 
   /**
    * StyleContextChanged
    *
-   * To be called from nsFrameManager::ReResolveStyleContext when the
+   * To be called from RestyleManager::TryStartingTransition when the
    * style of an element has changed, to initiate transitions from
    * that style change.  For style contexts with :before and :after
    * pseudos, aElement is expected to be the generated before/after
    * element.
    *
    * It may modify the new style context (by replacing
    * *aNewStyleContext) to cover up some of the changes for the duration
    * of the restyling of descendants.  If it does, this function will
--- a/layout/svg/nsSVGIntegrationUtils.cpp
+++ b/layout/svg/nsSVGIntegrationUtils.cpp
@@ -175,21 +175,16 @@ GetOffsetToBoundingBox(nsIFrame* aFrame)
 {
   if ((aFrame->GetStateBits() & NS_FRAME_SVG_LAYOUT)) {
     // Do NOT call GetAllInFlowRectsUnion for SVG - it will get the
     // covered region relative to the nsSVGOuterSVGFrame, which is absolutely
     // not what we want. SVG frames are always in user space, so they have
     // no offset adjustment to make.
     return nsPoint();
   }
-  // We could allow aFrame to be any continuation, but since that would require
-  // a GetPrevContinuation() virtual call and conditional returns, and since
-  // all our current consumers always pass in the first continuation, we don't
-  // currently bother.
-  NS_ASSERTION(!aFrame->GetPrevContinuation(), "Not first continuation");
 
   // The GetAllInFlowRectsUnion() call gets the union of the frame border-box
   // rects over all continuations, relative to the origin (top-left of the
   // border box) of its second argument (here, aFrame, the first continuation).
   return -nsLayoutUtils::GetAllInFlowRectsUnion(aFrame, aFrame).TopLeft();
 }
 
 /* static */ nsSize
@@ -429,18 +424,20 @@ ComputeClipExtsInDeviceSpace(gfxContext&
   clippedFrameSurfaceRect.RoundOut();
 
   IntRect result;
   ToRect(clippedFrameSurfaceRect).ToIntRect(&result);
   return mozilla::gfx::Factory::CheckSurfaceSize(result.Size()) ? result
                                                                 : IntRect();
 }
 
+typedef nsSVGIntegrationUtils::PaintFramesParams PaintFramesParams;
+
 static IntRect
-ComputeMaskGeometry(const nsSVGIntegrationUtils::PaintFramesParams& aParams,
+ComputeMaskGeometry(const PaintFramesParams& aParams,
                     const nsStyleSVGReset *svgReset,
                     const nsPoint& aOffsetToUserSpace,
                     const nsTArray<nsSVGMaskFrame *>& aMaskFrames)
 {
   gfxContext& ctx = aParams.ctx;
   nsIFrame* frame = aParams.frame;
 
   // Convert boaderArea and dirtyRect to user space.
@@ -489,17 +486,17 @@ ComputeMaskGeometry(const nsSVGIntegrati
 
   IntRect result = ComputeClipExtsInDeviceSpace(ctx);
   ctx.Restore();
 
   return result;
 }
 
 static DrawResult
-GenerateMaskSurface(const nsSVGIntegrationUtils::PaintFramesParams& aParams,
+GenerateMaskSurface(const PaintFramesParams& aParams,
                     float aOpacity, nsStyleContext* aSC,
                     const nsTArray<nsSVGMaskFrame *>& aMaskFrames,
                     const nsPoint& aOffsetToUserSpace,
                     Matrix& aOutMaskTransform,
                     RefPtr<SourceSurface>& aOutMaskSurface)
 {
   const nsStyleSVGReset *svgReset = aSC->StyleSVGReset();
   MOZ_ASSERT(aMaskFrames.Length() > 0);
@@ -607,32 +604,32 @@ GenerateMaskSurface(const nsSVGIntegrati
     return DrawResult::SUCCESS;
   }
 
   aOutMaskSurface = maskDT->Snapshot();
   return DrawResult::SUCCESS;
 }
 
 static float
-ComputeOpacity(const nsSVGIntegrationUtils::PaintFramesParams& aParams)
+ComputeOpacity(const PaintFramesParams& aParams)
 {
   nsIFrame* frame = aParams.frame;
   float opacity = frame->StyleEffects()->mOpacity;
 
   if (opacity != 1.0f &&
       (nsSVGUtils::CanOptimizeOpacity(frame) || !aParams.handleOpacity)) {
     return 1.0f;
   }
 
   return opacity;
 }
 
 static bool
-ValidateSVGFrame(const nsSVGIntegrationUtils::PaintFramesParams& aParams,
-                 bool aHasSVGLayout, DrawResult* aResult)
+ValidateSVGFrame(const PaintFramesParams& aParams, bool aHasSVGLayout,
+                 DrawResult* aResult)
 {
 #ifdef DEBUG
   NS_ASSERTION(!(aParams.frame->GetStateBits() & NS_FRAME_SVG_LAYOUT) ||
                (NS_SVGDisplayListPaintingEnabled() &&
                 !(aParams.frame->GetStateBits() & NS_FRAME_IS_NONDISPLAY)),
                "Should not use nsSVGIntegrationUtils on this SVG frame");
 #endif
 
@@ -650,69 +647,86 @@ ValidateSVGFrame(const nsSVGIntegrationU
       *aResult = DrawResult::SUCCESS;
       return false;
     }
   }
 
   return true;
 }
 
+/**
+ * Setup transform matrix of a gfx context by a specific frame. Depend on
+ * aClipCtx, this function may clip that context by the visual overflow area
+ * of aFrame.
+ *
+ * @param aFrame is the target frame.
+ * @param aOffsetToBoundingBox returns the offset between the reference frame
+ *        and the bounding box of aFrame.
+ * @oaram aOffsetToUserSpace returns the offset between the reference frame and
+ *        the user space coordinate of aFrame.
+ * @param aClipCtx indicate whether clip aParams.ctx by visual overflow rect of
+ *        aFrame or not.
+ */
 static void
-SetupContextMatrix(const nsSVGIntegrationUtils::PaintFramesParams& aParams,
-                   nsPoint& aOffsetToBoundingBox,
-                   nsPoint& aToUserSpace,
-                   nsPoint& aOffsetToUserSpace)
+SetupContextMatrix(nsIFrame* aFrame, const PaintFramesParams& aParams,
+                   nsPoint& aOffsetToBoundingBox, nsPoint& aOffsetToUserSpace,
+                   bool aClipCtx)
 {
-  nsIFrame* frame = aParams.frame;
-  nsIFrame* firstFrame =
-    nsLayoutUtils::FirstContinuationOrIBSplitSibling(frame);
-
-  nsPoint firstFrameOffset = GetOffsetToBoundingBox(firstFrame);
-  aOffsetToBoundingBox = aParams.builder->ToReferenceFrame(firstFrame) - firstFrameOffset;
-  if (!firstFrame->IsFrameOfType(nsIFrame::eSVG)) {
+  aOffsetToBoundingBox = aParams.builder->ToReferenceFrame(aFrame) -
+                         GetOffsetToBoundingBox(aFrame);
+  if (!aFrame->IsFrameOfType(nsIFrame::eSVG)) {
     /* Snap the offset if the reference frame is not a SVG frame,
      * since other frames will be snapped to pixel when rendering. */
     aOffsetToBoundingBox = nsPoint(
-      frame->PresContext()->RoundAppUnitsToNearestDevPixels(aOffsetToBoundingBox.x),
-      frame->PresContext()->RoundAppUnitsToNearestDevPixels(aOffsetToBoundingBox.y));
+      aFrame->PresContext()->RoundAppUnitsToNearestDevPixels(aOffsetToBoundingBox.x),
+      aFrame->PresContext()->RoundAppUnitsToNearestDevPixels(aOffsetToBoundingBox.y));
   }
 
-  // After applying only "aOffsetToBoundingBox", aCtx would have its origin at
-  // the top left corner of frame's bounding box (over all continuations).
+  // After applying only "aOffsetToBoundingBox", aParams.ctx would have its
+  // origin at the top left corner of frame's bounding box (over all
+  // continuations).
   // However, SVG painting needs the origin to be located at the origin of the
   // SVG frame's "user space", i.e. the space in which, for example, the
   // frame's BBox lives.
   // SVG geometry frames and foreignObject frames apply their own offsets, so
   // their position is relative to their user space. So for these frame types,
   // if we want aCtx to be in user space, we first need to subtract the
   // frame's position so that SVG painting can later add it again and the
   // frame is painted in the right place.
 
-  gfxPoint toUserSpaceGfx = nsSVGUtils::FrameSpaceInCSSPxToUserSpaceOffset(frame);
-  aToUserSpace =
+  gfxPoint toUserSpaceGfx = nsSVGUtils::FrameSpaceInCSSPxToUserSpaceOffset(aFrame);
+  nsPoint toUserSpace =
     nsPoint(nsPresContext::CSSPixelsToAppUnits(float(toUserSpaceGfx.x)),
             nsPresContext::CSSPixelsToAppUnits(float(toUserSpaceGfx.y)));
 
-  aOffsetToUserSpace = aOffsetToBoundingBox - aToUserSpace;
+  aOffsetToUserSpace = aOffsetToBoundingBox - toUserSpace;
 
 #ifdef DEBUG
-  bool hasSVGLayout = (frame->GetStateBits() & NS_FRAME_SVG_LAYOUT);
+  bool hasSVGLayout = (aFrame->GetStateBits() & NS_FRAME_SVG_LAYOUT);
   NS_ASSERTION(hasSVGLayout || aOffsetToBoundingBox == aOffsetToUserSpace,
                "For non-SVG frames there shouldn't be any additional offset");
 #endif
 
   gfxPoint devPixelOffsetToUserSpace =
     nsLayoutUtils::PointToGfxPoint(aOffsetToUserSpace,
-                                   frame->PresContext()->AppUnitsPerDevPixel());
-  aParams.ctx.SetMatrix(aParams.ctx.CurrentMatrix().Translate(devPixelOffsetToUserSpace));
+                                   aFrame->PresContext()->AppUnitsPerDevPixel());
+  gfxContext& context = aParams.ctx;
+  context.SetMatrix(context.CurrentMatrix().Translate(devPixelOffsetToUserSpace));
+
+  if (aClipCtx) {
+    nsRect clipRect =
+      aParams.frame->GetVisualOverflowRectRelativeToSelf() + toUserSpace;
+    context.Clip(NSRectToSnappedRect(clipRect,
+                                  aFrame->PresContext()->AppUnitsPerDevPixel(),
+                                  *context.GetDrawTarget()));
+  }
 }
 
 static already_AddRefed<gfxContext>
-CreateBlendTarget(const nsSVGIntegrationUtils::PaintFramesParams& aParams,
-                  IntPoint& aTargetOffset)
+CreateBlendTarget(const PaintFramesParams& aParams, IntPoint& aTargetOffset)
 {
   MOZ_ASSERT(aParams.frame->StyleEffects()->mMixBlendMode !=
              NS_STYLE_BLEND_NORMAL);
 
   // Create a temporary context to draw to so we can blend it back with
   // another operator.
   IntRect drawRect = ComputeClipExtsInDeviceSpace(aParams.ctx);
 
@@ -726,18 +740,18 @@ CreateBlendTarget(const nsSVGIntegration
   target->SetMatrix(aParams.ctx.CurrentMatrix() *
                     gfxMatrix::Translation(-drawRect.TopLeft()));
   aTargetOffset = drawRect.TopLeft();
 
   return target.forget();
 }
 
 static void
-BlendToTarget(const nsSVGIntegrationUtils::PaintFramesParams& aParams,
-              gfxContext* aTarget, const IntPoint& aTargetOffset)
+BlendToTarget(const PaintFramesParams& aParams, gfxContext* aTarget,
+              const IntPoint& aTargetOffset)
 {
   MOZ_ASSERT(aParams.frame->StyleEffects()->mMixBlendMode !=
              NS_STYLE_BLEND_NORMAL);
 
   RefPtr<DrawTarget> targetDT = aTarget->GetDrawTarget();
   RefPtr<SourceSurface> targetSurf = targetDT->Snapshot();
 
   gfxContext& context = aParams.ctx;
@@ -777,21 +791,16 @@ nsSVGIntegrationUtils::PaintMaskAndClipP
 
   float opacity = ComputeOpacity(aParams);
   if (opacity == 0.0f) {
     return DrawResult::SUCCESS;
   }
 
   gfxContext& context = aParams.ctx;
   gfxContextMatrixAutoSaveRestore matrixAutoSaveRestore(&context);
-  nsPoint offsetToBoundingBox;
-  nsPoint toUserSpace;
-  nsPoint offsetToUserSpace;
-  SetupContextMatrix(aParams, offsetToBoundingBox, toUserSpace,
-                     offsetToUserSpace);
 
   /* Properties are added lazily and may have been removed by a restyle,
      so make sure all applicable ones are set again. */
   nsIFrame* firstFrame =
     nsLayoutUtils::FirstContinuationOrIBSplitSibling(frame);
   nsSVGEffects::EffectProperties effectProperties =
     nsSVGEffects::GetEffectProperties(firstFrame);
 
@@ -825,96 +834,139 @@ nsSVGIntegrationUtils::PaintMaskAndClipP
 #endif
 
   bool shouldGenerateClipMaskLayer = clipPathFrame && !isTrivialClip;
   bool shouldApplyClipPath = clipPathFrame && isTrivialClip;
   bool shouldApplyBasicShape = !clipPathFrame && svgReset->HasClipPath();
   MOZ_ASSERT_IF(shouldGenerateClipMaskLayer,
                 !shouldApplyClipPath && !shouldApplyBasicShape);
 
+  nsPoint offsetToBoundingBox;
+  nsPoint offsetToUserSpace;
+
   // These are used if we require a temporary surface for a custom blend mode.
+  // Clip the source context first, so that we can generate a smaller temporary
+  // surface. (Since we will clip this context in SetupContextMatrix, a pair
+  // of save/restore is needed.)
+  context.Save();
+  SetupContextMatrix(firstFrame, aParams, offsetToBoundingBox,
+                     offsetToUserSpace, true);
   IntPoint targetOffset;
   RefPtr<gfxContext> target =
     (aParams.frame->StyleEffects()->mMixBlendMode == NS_STYLE_BLEND_NORMAL)
       ? RefPtr<gfxContext>(&aParams.ctx).forget()
       : CreateBlendTarget(aParams, targetOffset);
+  context.Restore();
+
   if (!target) {
     return DrawResult::TEMPORARY_ERROR;
   }
 
   bool shouldGenerateMask = (opacity != 1.0f || shouldGenerateClipMaskLayer ||
                              shouldGenerateMaskLayer);
 
   /* Check if we need to do additional operations on this child's
    * rendering, which necessitates rendering into another surface. */
   if (shouldGenerateMask) {
-    context.Save();
-    nsRect clipRect =
-      frame->GetVisualOverflowRectRelativeToSelf() + toUserSpace;
-    context.Clip(NSRectToSnappedRect(clipRect,
-                                  frame->PresContext()->AppUnitsPerDevPixel(),
-                                  *context.GetDrawTarget()));
+    gfxContextMatrixAutoSaveRestore matSR;
+
     Matrix maskTransform;
     RefPtr<SourceSurface> maskSurface;
+
     if (shouldGenerateMaskLayer) {
+      matSR.SetContext(&context);
+
+      // For css-mask, we want to generate a mask for each continuation frame,
+      // so we setup context matrix by the position of the current frame,
+      // instead of the first continuation frame.
+      SetupContextMatrix(frame, aParams, offsetToBoundingBox,
+                         offsetToUserSpace, true);
       result = GenerateMaskSurface(aParams, opacity,
                                   firstFrame->StyleContext(),
                                   maskFrames, offsetToUserSpace,
                                   maskTransform, maskSurface);
-    }
-
-    if (shouldGenerateMaskLayer && !maskSurface) {
-      // Entire surface is clipped out.
-      context.Restore();
-      return result;
+      context.PopClip();
+      if (!maskSurface) {
+        // Entire surface is clipped out.
+        return result;
+      }
     }
 
     if (shouldGenerateClipMaskLayer) {
+      matSR.Restore();
+      matSR.SetContext(&context);
+
+      SetupContextMatrix(firstFrame, aParams, offsetToBoundingBox,
+                         offsetToUserSpace, true);
       Matrix clippedMaskTransform;
       RefPtr<SourceSurface> clipMaskSurface =
         clipPathFrame->GetClipMask(context, frame, cssPxToDevPxMatrix,
                                    &clippedMaskTransform, maskSurface,
                                    maskTransform, &result);
+      context.PopClip();
 
       if (clipMaskSurface) {
         maskSurface = clipMaskSurface;
         maskTransform = clippedMaskTransform;
+      } else {
+        // Either entire surface is clipped out, or gfx buffer allocation
+        // failure in nsSVGClipPathFrame::GetClipMask.
+        return result;
       }
     }
 
+    // opacity != 1.0f.
+    if (!shouldGenerateClipMaskLayer && !shouldGenerateMaskLayer) {
+      MOZ_ASSERT(opacity != 1.0f);
+
+      matSR.SetContext(&context);
+      SetupContextMatrix(firstFrame, aParams, offsetToBoundingBox,
+                         offsetToUserSpace, true);
+    }
+
     target->PushGroupForBlendBack(gfxContentType::COLOR_ALPHA, opacity, maskSurface, maskTransform);
   }
 
   /* If this frame has only a trivial clipPath, set up cairo's clipping now so
    * we can just do normal painting and get it clipped appropriately.
    */
-  if (shouldApplyClipPath) {
+  if (shouldApplyClipPath || shouldApplyBasicShape) {
     context.Save();
-    clipPathFrame->ApplyClipPath(context, frame, cssPxToDevPxMatrix);
-  } else if (shouldApplyBasicShape) {
-    context.Save();
-    nsCSSClipPathInstance::ApplyBasicShapeClip(context, frame);
+    SetupContextMatrix(firstFrame, aParams, offsetToBoundingBox,
+                       offsetToUserSpace, false);
+
+    MOZ_ASSERT(!shouldApplyClipPath || !shouldApplyBasicShape);
+    if (shouldApplyClipPath) {
+      clipPathFrame->ApplyClipPath(context, frame, cssPxToDevPxMatrix);
+    } else {
+      nsCSSClipPathInstance::ApplyBasicShapeClip(context, frame);
+    }
   }
 
   /* Paint the child */
   target->SetMatrix(matrixAutoSaveRestore.Matrix());
   BasicLayerManager* basic = static_cast<BasicLayerManager*>(aParams.layerManager);
   RefPtr<gfxContext> oldCtx = basic->GetTarget();
   basic->SetTarget(target);
   aParams.layerManager->EndTransaction(FrameLayerBuilder::DrawPaintedLayer,
-                                        aParams.builder);
+                                       aParams.builder);
   basic->SetTarget(oldCtx);
 
   if (shouldApplyClipPath || shouldApplyBasicShape) {
     context.Restore();
   }
 
   if (shouldGenerateMask) {
     target->PopGroupAndBlend();
-    context.Restore();
+
+    if (!shouldGenerateClipMaskLayer && !shouldGenerateMaskLayer) {
+      MOZ_ASSERT(opacity != 1.0f);
+      // Pop the clip push by SetupContextMatrix
+      context.PopClip();
+    }
   }
 
   if (aParams.frame->StyleEffects()->mMixBlendMode != NS_STYLE_BLEND_NORMAL) {
     MOZ_ASSERT(target != &aParams.ctx);
     BlendToTarget(aParams, target, targetOffset);
   }
 
   return result;
@@ -935,68 +987,63 @@ nsSVGIntegrationUtils::PaintFilter(const
     return result;
   }
 
   float opacity = ComputeOpacity(aParams);
   if (opacity == 0.0f) {
     return DrawResult::SUCCESS;
   }
 
-  gfxContext& context = aParams.ctx;
-  gfxContextMatrixAutoSaveRestore matrixAutoSaveRestore(&context);
-  nsPoint offsetToBoundingBox;
-  nsPoint toUserSpace;
-  nsPoint offsetToUserSpace;
-  SetupContextMatrix(aParams, offsetToBoundingBox, toUserSpace,
-                     offsetToUserSpace);
-
   /* Properties are added lazily and may have been removed by a restyle,
      so make sure all applicable ones are set again. */
   nsIFrame* firstFrame =
     nsLayoutUtils::FirstContinuationOrIBSplitSibling(frame);
   nsSVGEffects::EffectProperties effectProperties =
     nsSVGEffects::GetEffectProperties(firstFrame);
 
   if (!effectProperties.HasValidFilter()) {
     return DrawResult::NOT_READY;
   }
 
+  gfxContext& context = aParams.ctx;
+  nsPoint offsetToBoundingBox;
+  nsPoint offsetToUserSpace;
+
   // These are used if we require a temporary surface for a custom blend mode.
+  // Clip the source context first, so that we can generate a smaller temporary
+  // surface. (Since we will clip this context in SetupContextMatrix, a pair
+  // of save/restore is needed.)
+  gfxContextAutoSaveRestore autoSR(&context);
+  SetupContextMatrix(firstFrame, aParams, offsetToBoundingBox,
+                     offsetToUserSpace, true);
   IntPoint targetOffset;
   RefPtr<gfxContext> target =
     (aParams.frame->StyleEffects()->mMixBlendMode == NS_STYLE_BLEND_NORMAL)
     ? RefPtr<gfxContext>(&aParams.ctx).forget()
     : CreateBlendTarget(aParams, targetOffset);
   if (!target) {
+    context.Restore();
     return DrawResult::TEMPORARY_ERROR;
   }
 
   if (opacity != 1.0f) {
-    context.Save();
-    nsRect clipRect =
-      frame->GetVisualOverflowRectRelativeToSelf() + toUserSpace;
-    context.Clip(NSRectToSnappedRect(clipRect,
-                                  frame->PresContext()->AppUnitsPerDevPixel(),
-                                  *context.GetDrawTarget()));
-
     target->PushGroupForBlendBack(gfxContentType::COLOR_ALPHA, opacity,
                                   nullptr, Matrix());
   }
 
   /* Paint the child and apply filters */
   RegularFramePaintCallback callback(aParams.builder, aParams.layerManager,
                                      offsetToUserSpace);
   nsRegion dirtyRegion = aParams.dirtyRect - offsetToBoundingBox;
   gfxMatrix tm = nsSVGIntegrationUtils::GetCSSPxToDevPxMatrix(frame);
   nsFilterInstance::PaintFilteredFrame(frame, target->GetDrawTarget(),
                                        tm, &callback, &dirtyRegion);
 
   if (opacity != 1.0f) {
     target->PopGroupAndBlend();
-    context.Restore();
   }
 
   if (aParams.frame->StyleEffects()->mMixBlendMode != NS_STYLE_BLEND_NORMAL) {
     MOZ_ASSERT(target != &aParams.ctx);
     BlendToTarget(aParams, target, targetOffset);
   }
 
   return result;
--- a/media/libstagefright/binding/MP4Metadata.cpp
+++ b/media/libstagefright/binding/MP4Metadata.cpp
@@ -335,19 +335,20 @@ MP4Metadata::Crypto() const
 {
   return mStagefright->Crypto();
 }
 
 bool
 MP4Metadata::ReadTrackIndex(FallibleTArray<Index::Indice>& aDest, mozilla::TrackID aTrackID)
 {
 #ifdef MOZ_RUST_MP4PARSE
-  if (mRust && mPreferRust) {
-    return mRust->ReadTrackIndex(aDest, aTrackID);
+  if (mRust && mPreferRust && mRust->ReadTrackIndex(aDest, aTrackID)) {
+    return true;
   }
+  aDest.Clear();
 #endif
   return mStagefright->ReadTrackIndex(aDest, aTrackID);
 }
 
 static inline bool
 ConvertIndex(FallibleTArray<Index::Indice>& aDest,
              const nsTArray<stagefright::MediaSource::Indice>& aIndex,
              int64_t aMediaTime)
@@ -834,17 +835,17 @@ MP4MetadataRust::ReadTrackIndex(Fallible
     return false;
   }
 
   if (fragmented) {
     return true;
   }
 
   // For non-fragmented mp4.
-  MOZ_ASSERT(false, "Not yet implemented");
+  NS_WARNING("Not yet implemented");
 
   return false;
 }
 
 /*static*/ bool
 MP4MetadataRust::HasCompleteMetadata(Stream* aSource)
 {
   MOZ_ASSERT(false, "Not yet implemented");
--- a/mobile/android/app/build.gradle
+++ b/mobile/android/app/build.gradle
@@ -222,17 +222,17 @@ dependencies {
     // Gradle based builds include LeakCanary.  Gradle based tests include the no-op version.  Mach
     // based builds only include the no-op version of this library.
     compile 'com.squareup.leakcanary:leakcanary-android:1.4-beta1'
     testCompile 'com.squareup.leakcanary:leakcanary-android-no-op:1.4-beta1'
 
     compile project(':thirdparty')
 
     testCompile 'junit:junit:4.12'
-    testCompile 'org.robolectric:robolectric:3.0'
+    testCompile 'org.robolectric:robolectric:3.1.2'
     testCompile 'org.simpleframework:simple-http:6.0.1'
     testCompile 'org.mockito:mockito-core:1.10.19'
 
     // Including the Robotium JAR directly can cause issues with dexing.
     androidTestCompile 'com.jayway.android.robotium:robotium-solo:5.5.4'
 }
 
 // TODO: (bug 1261486): This impl is not robust -
--- a/mobile/android/components/extensions/moz.build
+++ b/mobile/android/components/extensions/moz.build
@@ -7,9 +7,10 @@
 JAR_MANIFESTS += ['jar.mn']
 
 EXTRA_COMPONENTS += [
     'extensions-mobile.manifest',
 ]
 
 DIRS += ['schemas']
 
-MOCHITEST_CHROME_MANIFESTS += ['test/mochitest/chrome.ini']
\ No newline at end of file
+MOCHITEST_MANIFESTS += ['test/mochitest/mochitest.ini']
+MOCHITEST_CHROME_MANIFESTS += ['test/mochitest/chrome.ini']
new file mode 100644
--- /dev/null
+++ b/mobile/android/components/extensions/test/mochitest/mochitest.ini
@@ -0,0 +1,6 @@
+[DEFAULT]
+support-files =
+  ../../../../../../toolkit/components/extensions/test/mochitest/test_ext_all_apis.js
+tags = webextensions
+
+[test_ext_all_apis.html]
new file mode 100644
--- /dev/null
+++ b/mobile/android/components/extensions/test/mochitest/test_ext_all_apis.html
@@ -0,0 +1,23 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+  <title>WebExtension test</title>
+  <meta charset="utf-8">
+  <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <script type="text/javascript" src="/tests/SimpleTest/SpawnTask.js"></script>
+  <script type="text/javascript" src="/tests/SimpleTest/ExtensionTestUtils.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css">
+</head>
+<body>
+<script>
+"use strict";
+/* exported expectedContentApisTargetSpecific, expectedBackgroundApisTargetSpecific */
+let expectedContentApisTargetSpecific = [
+];
+
+let expectedBackgroundApisTargetSpecific = [
+];
+</script>
+<script src="test_ext_all_apis.js"></script>
+</body>
+</html>
--- a/mobile/android/config/tooltool-manifests/android-frontend/releng.manifest
+++ b/mobile/android/config/tooltool-manifests/android-frontend/releng.manifest
@@ -30,26 +30,26 @@
 "filename": "java_home-1.7.0-openjdk-1.7.0.85.x86_64.tar.xz",
 "unpack": true
 },
 {
 "algorithm": "sha512",
 "visibility": "public",
 "filename": "jcentral.tar.xz",
 "unpack": true,
-"digest": "43754910576cf6173f0dcb215ec7988f2a30dbfe32050f53ac7a9088b8b878c3ead80e02afc1dc31e229386116c82e88a403a309e5b5695f0b74f5defb13dec7",
-"size": 42832932
+"digest": "66640e3f77a0f9c0ea52f66c53bee8db3c1a27ea4a11526d15706b9da6a0302cd2d5b088f9addca84f4a962022cba3b76829cb878c90cf9bebb3aab050b4aaa4",
+"size": 47315996
 },
 {
 "algorithm": "sha512",
 "visibility": "public",
 "filename": "gradle-dist.tar.xz",
 "unpack": true,
-"digest": "990edc0e4039dbe5f77790ef59dc0d58faebbb8c82ee497615c7991eec99fe4668d0ab05508c48664b635ff6c0cfd4272db464ae1efaa548e471ab451fe0944f",
-"size": 51955340
+"digest": "36f961f85b0be846cc9e72bfa0dd1f74e7da8ef785717ce4fd102fec977f21f8902c233b28a21c1ce3797eb2759c7a74c5f74e47bd8f13c1eec640f8d7bed4ac",
+"size": 51512016
 },
 {
 "algorithm": "sha512",
 "visibility": "public",
 "filename": "dotgradle.tar.xz",
 "unpack": true,
 "digest": "9f082ccd71ad18991eb71fcad355c6990f50a72a09ab9b79696521485656083a72faf5a8d4714de9c4b901ee2319b6786a51964846bb7075061642a8505501c2",
 "size": 512
--- a/mobile/android/config/tooltool-manifests/android-x86/releng.manifest
+++ b/mobile/android/config/tooltool-manifests/android-x86/releng.manifest
@@ -45,26 +45,26 @@
 "filename": "gcc.tar.xz",
 "unpack": true
 },
 {
 "algorithm": "sha512",
 "visibility": "public",
 "filename": "jcentral.tar.xz",
 "unpack": true,
-"digest": "43754910576cf6173f0dcb215ec7988f2a30dbfe32050f53ac7a9088b8b878c3ead80e02afc1dc31e229386116c82e88a403a309e5b5695f0b74f5defb13dec7",
-"size": 42832932
+"digest": "66640e3f77a0f9c0ea52f66c53bee8db3c1a27ea4a11526d15706b9da6a0302cd2d5b088f9addca84f4a962022cba3b76829cb878c90cf9bebb3aab050b4aaa4",
+"size": 47315996
 },
 {
 "algorithm": "sha512",
 "visibility": "public",
 "filename": "gradle-dist.tar.xz",
 "unpack": true,
-"digest": "990edc0e4039dbe5f77790ef59dc0d58faebbb8c82ee497615c7991eec99fe4668d0ab05508c48664b635ff6c0cfd4272db464ae1efaa548e471ab451fe0944f",
-"size": 51955340
+"digest": "36f961f85b0be846cc9e72bfa0dd1f74e7da8ef785717ce4fd102fec977f21f8902c233b28a21c1ce3797eb2759c7a74c5f74e47bd8f13c1eec640f8d7bed4ac",
+"size": 51512016
 },
 {
 "size": 30899096,
 "visibility": "public",
 "digest": "ac9f5f95d11580d3dbeff87e80a585fe4d324b270dabb91b1165686acab47d99fa6651074ab0be09420239a5d6af38bb2c539506962a7b44e0ed4d080bba2953",
 "algorithm": "sha512",
 "filename": "java_home-1.7.0-openjdk-1.7.0.85.x86_64.tar.xz",
 "unpack": true
--- a/mobile/android/config/tooltool-manifests/android/releng.manifest
+++ b/mobile/android/config/tooltool-manifests/android/releng.manifest
@@ -55,26 +55,26 @@
 "filename": "java_home-1.7.0-openjdk-1.7.0.85.x86_64.tar.xz",
 "unpack": true
 },
 {
 "algorithm": "sha512",
 "visibility": "public",
 "filename": "jcentral.tar.xz",
 "unpack": true,
-"digest": "43754910576cf6173f0dcb215ec7988f2a30dbfe32050f53ac7a9088b8b878c3ead80e02afc1dc31e229386116c82e88a403a309e5b5695f0b74f5defb13dec7",
-"size": 42832932
+"digest": "66640e3f77a0f9c0ea52f66c53bee8db3c1a27ea4a11526d15706b9da6a0302cd2d5b088f9addca84f4a962022cba3b76829cb878c90cf9bebb3aab050b4aaa4",
+"size": 47315996
 },
 {
 "algorithm": "sha512",
 "visibility": "public",
 "filename": "gradle-dist.tar.xz",
 "unpack": true,
-"digest": "990edc0e4039dbe5f77790ef59dc0d58faebbb8c82ee497615c7991eec99fe4668d0ab05508c48664b635ff6c0cfd4272db464ae1efaa548e471ab451fe0944f",
-"size": 51955340
+"digest": "36f961f85b0be846cc9e72bfa0dd1f74e7da8ef785717ce4fd102fec977f21f8902c233b28a21c1ce3797eb2759c7a74c5f74e47bd8f13c1eec640f8d7bed4ac",
+"size": 51512016
 },
 {
 "version": "rustc 1.11.0 (9b21dcd6a 2016-08-15) repack",
 "size": 97552448,
 "digest": "272438c1692a46998dc44f22bd1fe18da1be7af2e7fdcf6c52709366c80c73e30637f0c3864f45c64edf46ce6a905538c14b2313983be973f9f29a2f191ec89b",
 "algorithm": "sha512",
 "filename": "rustc.tar.xz",
 "unpack": true
--- a/mobile/android/tests/background/junit4/src/org/mozilla/gecko/background/testhelpers/TestRunner.java
+++ b/mobile/android/tests/background/junit4/src/org/mozilla/gecko/background/testhelpers/TestRunner.java
@@ -20,17 +20,17 @@
  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
  * THE SOFTWARE.
  */
 
 package org.mozilla.gecko.background.testhelpers;
 
 import org.junit.runners.model.InitializationError;
-import org.robolectric.RobolectricGradleTestRunner;
+import org.robolectric.RobolectricTestRunner;
 import org.robolectric.annotation.Config;
 import org.robolectric.manifest.AndroidManifest;
 import org.robolectric.res.FileFsFile;
 import org.robolectric.res.FsFile;
 import org.robolectric.util.Logger;
 import org.robolectric.util.ReflectionHelpers;
 
 /**
@@ -41,17 +41,17 @@ import org.robolectric.util.ReflectionHe
  * This test runner requires that you set the 'constants' field on the @Config
  * annotation (or the org.robolectric.Config.properties file) for your tests.
  *
  * This is a modified version of
  * https://github.com/robolectric/robolectric/blob/8676da2daa4c140679fb5903696b8191415cec8f/robolectric/src/main/java/org/robolectric/RobolectricGradleTestRunner.java
  * that uses a Gradle `buildConfigField` to find build outputs.
  * See https://github.com/robolectric/robolectric/issues/1648#issuecomment-113731011.
  */
-public class TestRunner extends RobolectricGradleTestRunner {
+public class TestRunner extends RobolectricTestRunner {
     private FsFile buildFolder;
 
     public TestRunner(Class<?> klass) throws InitializationError {
         super(klass);
     }
 
     @Override
     protected AndroidManifest getAppManifest(Config config) {
--- a/mobile/android/tests/background/junit4/src/org/mozilla/gecko/db/BrowserProviderHistoryTest.java
+++ b/mobile/android/tests/background/junit4/src/org/mozilla/gecko/db/BrowserProviderHistoryTest.java
@@ -5,16 +5,17 @@
 package org.mozilla.gecko.db;
 
 import android.content.ContentProviderClient;
 import android.content.ContentValues;
 import android.database.Cursor;
 import android.net.Uri;
 import android.os.RemoteException;
 
+import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mozilla.gecko.background.testhelpers.TestRunner;
 import org.robolectric.shadows.ShadowContentResolver;
 
 import static org.junit.Assert.*;
 
@@ -46,16 +47,23 @@ public class BrowserProviderHistoryTest 
                 ).build();
         expireHistoryAggressiveUri = testUri(BrowserContract.History.CONTENT_OLD_URI).buildUpon()
                 .appendQueryParameter(
                         BrowserContract.PARAM_EXPIRE_PRIORITY,
                         BrowserContract.ExpirePriority.AGGRESSIVE.toString()
                 ).build();
     }
 
+    @After
+    @Override
+    public void tearDown() {
+        thumbnailClient.release();
+        super.tearDown();
+    }
+
     /**
      * Test aggressive expiration on new (recent) history items
      */
     @Test
     public void testHistoryExpirationAggressiveNew() throws Exception {
         final int historyItemsCount = 3000;
         insertHistory(historyItemsCount, System.currentTimeMillis());
 
--- a/mobile/android/tests/background/junit4/src/org/mozilla/gecko/db/BrowserProviderHistoryVisitsTestBase.java
+++ b/mobile/android/tests/background/junit4/src/org/mozilla/gecko/db/BrowserProviderHistoryVisitsTestBase.java
@@ -49,17 +49,17 @@ public class BrowserProviderHistoryVisit
         return baseUri.buildUpon().appendQueryParameter(BrowserContract.PARAM_IS_TEST, "1").build();
     }
 
     /* package-private */  Uri insertHistoryItem(String url, String guid) throws RemoteException {
         return insertHistoryItem(url, guid, System.currentTimeMillis(), null, null);
     }
 
     /* package-private */  Uri insertHistoryItem(String url, String guid, Long lastVisited, Integer visitCount) throws RemoteException {
-        return insertHistoryItem(url, guid, System.currentTimeMillis(), null, null);
+        return insertHistoryItem(url, guid, lastVisited, visitCount, null);
     }
 
     /* package-private */  Uri insertHistoryItem(String url, String guid, Long lastVisited, Integer visitCount, String title) throws RemoteException {
         ContentValues historyItem = new ContentValues();
         historyItem.put(BrowserContract.History.URL, url);
         if (guid != null) {
             historyItem.put(BrowserContract.History.GUID, guid);
         }
--- a/netwerk/base/nsIUnicharStreamLoader.idl
+++ b/netwerk/base/nsIUnicharStreamLoader.idl
@@ -74,9 +74,15 @@ interface nsIUnicharStreamLoader : nsISt
    */
   readonly attribute nsIChannel channel;
 
   /**
    * The charset that onDetermineCharset returned, if that's been
    * called.
    */
   readonly attribute ACString charset;
+
+  /**
+   * Get the raw bytes as seen on the wire prior to character converstion.
+   * Used by Subresource Integrity checker to generate the correct hash.
+   */
+  readonly attribute ACString rawBuffer;
 };
--- a/netwerk/base/nsUnicharStreamLoader.cpp
+++ b/netwerk/base/nsUnicharStreamLoader.cpp
@@ -97,20 +97,29 @@ nsUnicharStreamLoader::OnStopRequest(nsI
     mObserver->OnStreamComplete(this, mContext, aStatus, mBuffer);
   }
 
   mObserver = nullptr;
   mDecoder = nullptr;
   mContext = nullptr;
   mChannel = nullptr;
   mCharset.Truncate();
+  mRawData.Truncate();
+  mRawBuffer.Truncate();
   mBuffer.Truncate();
   return rv;
 }
 
+NS_IMETHODIMP
+nsUnicharStreamLoader::GetRawBuffer(nsACString& aRawBuffer)
+{
+  aRawBuffer = mRawBuffer;
+  return NS_OK;
+}
+
 /* nsIStreamListener implementation */
 NS_IMETHODIMP
 nsUnicharStreamLoader::OnDataAvailable(nsIRequest *aRequest,
                                        nsISupports *aContext,
                                        nsIInputStream *aInputStream,
                                        uint64_t aSourceOffset,
                                        uint32_t aCount)
 {
@@ -215,16 +224,20 @@ nsUnicharStreamLoader::WriteSegmentFun(n
     return rv;
   }
 
   uint32_t capacity = haveRead + dstLen;
   if (!self->mBuffer.SetCapacity(capacity, fallible)) {
     return NS_ERROR_OUT_OF_MEMORY;
   }
 
+  if (!self->mRawBuffer.Append(aSegment, aCount, fallible)) {
+    return NS_ERROR_OUT_OF_MEMORY;
+  }
+
   rv = self->mDecoder->Convert(aSegment,
                                &srcLen,
                                self->mBuffer.BeginWriting() + haveRead,
                                &dstLen);
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
--- a/netwerk/base/nsUnicharStreamLoader.h
+++ b/netwerk/base/nsUnicharStreamLoader.h
@@ -42,15 +42,19 @@ protected:
   nsCOMPtr<nsISupports>                    mContext;
   nsCOMPtr<nsIChannel>                     mChannel;
   nsCString                                mCharset;
 
   // This holds the first up-to-512 bytes of the raw stream.
   // It will be passed to the OnDetermineCharset callback.
   nsCString                                mRawData;
 
+  // Holds complete raw bytes as received so that SRI checks can be
+  // calculated on the raw data prior to character conversion.
+  nsCString                                mRawBuffer;
+
   // This holds the complete contents of the stream so far, after
   // decoding to UTF-16.  It will be passed to the OnStreamComplete
   // callback.
   nsString                                 mBuffer;
 };
 
 #endif // nsUnicharStreamLoader_h__
--- a/python/mozbuild/mozbuild/backend/tup.py
+++ b/python/mozbuild/mozbuild/backend/tup.py
@@ -10,16 +10,19 @@ import mozpack.path as mozpath
 from mozbuild.base import MozbuildObject
 from mozbuild.backend.base import PartialBackend, HybridBackend
 from mozbuild.backend.recursivemake import RecursiveMakeBackend
 from mozbuild.shellutil import quote as shell_quote
 
 from .common import CommonBackend
 from ..frontend.data import (
     ContextDerived,
+    Defines,
+    GeneratedFile,
+    HostDefines,
 )
 from ..util import (
     FileAvoidWrite,
 )
 
 
 class BackendTupfile(object):
     """Represents a generated Tupfile.
@@ -28,41 +31,65 @@ class BackendTupfile(object):
     def __init__(self, srcdir, objdir, environment, topsrcdir, topobjdir):
         self.topsrcdir = topsrcdir
         self.srcdir = srcdir
         self.objdir = objdir
         self.relobjdir = mozpath.relpath(objdir, topobjdir)
         self.environment = environment
         self.name = mozpath.join(objdir, 'Tupfile')
         self.rules_included = False
+        self.shell_exported = False
+        self.defines = []
+        self.host_defines = []
 
         self.fh = FileAvoidWrite(self.name, capture_diff=True)
         self.fh.write('# THIS FILE WAS AUTOMATICALLY GENERATED. DO NOT EDIT.\n')
         self.fh.write('\n')
 
     def write(self, buf):
         self.fh.write(buf)
 
     def include_rules(self):
         if not self.rules_included:
             self.write('include_rules\n')
             self.rules_included = True
 
-    def rule(self, cmd, inputs=None, outputs=None, display=None, extra_outputs=None):
+    def rule(self, cmd, inputs=None, outputs=None, display=None, extra_outputs=None, check_unchanged=False):
         inputs = inputs or []
         outputs = outputs or []
+        display = display or ""
         self.include_rules()
+        flags = ""
+        if check_unchanged:
+            # This flag causes tup to compare the outputs with the previous run
+            # of the command, and skip the rest of the DAG for any that are the
+            # same.
+            flags += "o"
+
+        if display:
+            caret_text = flags + ' ' + display
+        else:
+            caret_text = flags
+
         self.write(': %(inputs)s |> %(display)s%(cmd)s |> %(outputs)s%(extra_outputs)s\n' % {
             'inputs': ' '.join(inputs),
-            'display': '^ %s^ ' % display if display else '',
+            'display': '^%s^ ' % caret_text if caret_text else '',
             'cmd': ' '.join(cmd),
             'outputs': ' '.join(outputs),
             'extra_outputs': ' | ' + ' '.join(extra_outputs) if extra_outputs else '',
         })
 
+    def export_shell(self):
+        if not self.shell_exported:
+            # These are used by mach/mixin/process.py to determine the current
+            # shell.
+            for var in ('SHELL', 'MOZILLABUILD', 'COMSPEC'):
+                self.write('export %s\n' % var)
+            self.shell_exported = True
+
     def close(self):
         return self.fh.close()
 
     @property
     def diff(self):
         return self.fh.diff
 
 
@@ -80,29 +107,77 @@ class TupOnly(CommonBackend, PartialBack
         objdir = mozpath.join(self.environment.topobjdir, relativedir)
         srcdir = mozpath.join(self.environment.topsrcdir, relativedir)
         if objdir not in self._backend_files:
             self._backend_files[objdir] = \
                     BackendTupfile(srcdir, objdir, self.environment,
                                    self.environment.topsrcdir, self.environment.topobjdir)
         return self._backend_files[objdir]
 
+    def _get_backend_file_for(self, obj):
+        return self._get_backend_file(obj.relativedir)
+
+    def _py_action(self, action):
+        cmd = [
+            '$(PYTHON)',
+            '-m',
+            'mozbuild.action.%s' % action,
+        ]
+        return cmd
+
     def consume_object(self, obj):
         """Write out build files necessary to build with tup."""
 
         if not isinstance(obj, ContextDerived):
             return False
 
         consumed = CommonBackend.consume_object(self, obj)
 
         # Even if CommonBackend acknowledged the object, we still need to let
         # the RecursiveMake backend also handle these objects.
         if consumed:
             return False
 
+        backend_file = self._get_backend_file_for(obj)
+
+        if isinstance(obj, GeneratedFile):
+            # TODO: These are directories that don't work in the tup backend
+            # yet, because things they depend on aren't built yet.
+            skip_directories = (
+                'build', # FinalTargetPreprocessedFiles
+                'layout/style/test', # HostSimplePrograms
+                'toolkit/library', # libxul.so
+            )
+            if obj.script and obj.method and obj.relobjdir not in skip_directories:
+                backend_file.export_shell()
+                cmd = self._py_action('file_generate')
+                cmd.extend([
+                    obj.script,
+                    obj.method,
+                    obj.outputs[0],
+                    '%s.pp' % obj.outputs[0], # deps file required
+                ])
+                full_inputs = [f.full_path for f in obj.inputs]
+                cmd.extend(full_inputs)
+
+                outputs = []
+                outputs.extend(obj.outputs)
+                outputs.append('%s.pp' % obj.outputs[0])
+
+                backend_file.rule(
+                    display='python {script}:{method} -> [%o]'.format(script=obj.script, method=obj.method),
+                    cmd=cmd,
+                    inputs=full_inputs,
+                    outputs=outputs,
+                )
+        elif isinstance(obj, Defines):
+            self._process_defines(backend_file, obj)
+        elif isinstance(obj, HostDefines):
+            self._process_defines(backend_file, obj, host=True)
+
         return True
 
     def consume_finished(self):
         CommonBackend.consume_finished(self)
 
         for objdir, backend_file in sorted(self._backend_files.items()):
             with self._write_file(fh=backend_file):
                 pass
@@ -118,85 +193,111 @@ class TupOnly(CommonBackend, PartialBack
             fh.write('ACDEFINES = %s\n' % acdefines_flags)
             fh.write('topsrcdir = $(MOZ_OBJ_ROOT)/%s\n' % (
                 os.path.relpath(self.environment.topsrcdir, self.environment.topobjdir)
             ))
             fh.write('PYTHON = $(MOZ_OBJ_ROOT)/_virtualenv/bin/python -B\n')
             fh.write('PYTHON_PATH = $(PYTHON) $(topsrcdir)/config/pythonpath.py\n')
             fh.write('PLY_INCLUDE = -I$(topsrcdir)/other-licenses/ply\n')
             fh.write('IDL_PARSER_DIR = $(topsrcdir)/xpcom/idl-parser\n')
-            fh.write('IDL_PARSER_CACHE_DIR = $(MOZ_OBJ_ROOT)/xpcom/idl-parser\n')
+            fh.write('IDL_PARSER_CACHE_DIR = $(MOZ_OBJ_ROOT)/xpcom/idl-parser/xpidl\n')
 
         # Run 'tup init' if necessary.
         if not os.path.exists(mozpath.join(self.environment.topsrcdir, ".tup")):
             tup = self.environment.substs.get('TUP', 'tup')
             self._cmd.run_process(cwd=self.environment.topsrcdir, log_name='tup', args=[tup, 'init'])
 
-    def _handle_idl_manager(self, manager):
+    def _process_defines(self, backend_file, obj, host=False):
+        defines = list(obj.get_defines())
+        if defines:
+            if host:
+                backend_file.host_defines = defines
+            else:
+                backend_file.defines = defines
 
-        # TODO: This should come from GENERATED_FILES, and can be removed once
-        # those are implemented.
-        backend_file = self._get_backend_file('xpcom/idl-parser')
-        backend_file.rule(
-            display='python header.py -> [%o]',
-            cmd=[
-                '$(PYTHON_PATH)',
-                '$(PLY_INCLUDE)',
-                '$(topsrcdir)/xpcom/idl-parser/xpidl/header.py',
-            ],
-            outputs=['xpidlyacc.py', 'xpidllex.py'],
-        )
-
+    def _handle_idl_manager(self, manager):
         backend_file = self._get_backend_file('xpcom/xpidl')
-
-        # These are used by mach/mixin/process.py to determine the current
-        # shell.
-        for var in ('SHELL', 'MOZILLABUILD', 'COMSPEC'):
-            backend_file.write('export %s\n' % var)
+        backend_file.export_shell()
 
         for module, data in sorted(manager.modules.iteritems()):
             dest, idls = data
             cmd = [
                 '$(PYTHON_PATH)',
                 '$(PLY_INCLUDE)',
                 '-I$(IDL_PARSER_DIR)',
                 '-I$(IDL_PARSER_CACHE_DIR)',
                 '$(topsrcdir)/python/mozbuild/mozbuild/action/xpidl-process.py',
-                '--cache-dir', '$(MOZ_OBJ_ROOT)/xpcom/idl-parser',
+                '--cache-dir', '$(IDL_PARSER_CACHE_DIR)',
                 '$(DIST)/idl',
                 '$(DIST)/include',
                 '$(MOZ_OBJ_ROOT)/%s/components' % dest,
                 module,
             ]
             cmd.extend(sorted(idls))
 
             outputs = ['$(MOZ_OBJ_ROOT)/%s/components/%s.xpt' % (dest, module)]
             outputs.extend(['$(MOZ_OBJ_ROOT)/dist/include/%s.h' % f for f in sorted(idls)])
             backend_file.rule(
                 inputs=[
-                    '$(MOZ_OBJ_ROOT)/xpcom/idl-parser/xpidllex.py',
-                    '$(MOZ_OBJ_ROOT)/xpcom/idl-parser/xpidlyacc.py',
+                    '$(MOZ_OBJ_ROOT)/xpcom/idl-parser/xpidl/xpidllex.py',
+                    '$(MOZ_OBJ_ROOT)/xpcom/idl-parser/xpidl/xpidlyacc.py',
                 ],
                 display='XPIDL %s' % module,
                 cmd=cmd,
                 outputs=outputs,
             )
 
+    def _preprocess(self, backend_file, input_file):
+        cmd = self._py_action('preprocessor')
+        cmd.extend(backend_file.defines)
+        cmd.extend(['$(ACDEFINES)', '%f', '-o', '%o'])
+
+        backend_file.rule(
+            inputs=[input_file],
+            display='Preprocess %o',
+            cmd=cmd,
+            outputs=[mozpath.basename(input_file)],
+        )
+
     def _handle_ipdl_sources(self, ipdl_dir, sorted_ipdl_sources,
                              unified_ipdl_cppsrcs_mapping):
         # TODO: This isn't implemented yet in the tup backend, but it is called
         # by the CommonBackend.
         pass
 
     def _handle_webidl_build(self, bindings_dir, unified_source_mapping,
                              webidls, expected_build_output_files,
                              global_define_files):
-        # TODO: This isn't implemented yet in the tup backend, but it is called
-        # by the CommonBackend.
-        pass
+        backend_file = self._get_backend_file('dom/bindings')
+        backend_file.export_shell()
+
+        for source in sorted(webidls.all_preprocessed_sources()):
+            self._preprocess(backend_file, source)
+
+        cmd = self._py_action('webidl')
+        cmd.append(mozpath.join(self.environment.topsrcdir, 'dom', 'bindings'))
+
+        # The WebIDLCodegenManager knows all of the .cpp and .h files that will
+        # be created (expected_build_output_files), but there are a few
+        # additional files that are also created by the webidl py_action.
+        outputs = [
+            '_cache/webidlyacc.py',
+            'codegen.json',
+            'codegen.pp',
+            'parser.out',
+        ]
+        outputs.extend(expected_build_output_files)
+
+        backend_file.rule(
+            display='WebIDL code generation',
+            cmd=cmd,
+            inputs=webidls.all_non_static_basenames(),
+            outputs=outputs,
+            check_unchanged=True,
+        )
 
 
 class TupBackend(HybridBackend(TupOnly, RecursiveMakeBackend)):
     def build(self, config, output, jobs, verbose):
         status = config._run_make(directory=self.environment.topobjdir, target='tup',
                                   line_handler=output.on_line, log=False, print_directory=False,
                                   ensure_exit_code=False, num_jobs=jobs, silent=not verbose,
                                   append_env={b'NO_BUILDSTATUS_MESSAGES': b'1'})
--- a/services/sync/tests/unit/test_bookmark_tracker.js
+++ b/services/sync/tests/unit/test_bookmark_tracker.js
@@ -1135,23 +1135,24 @@ add_task(function* test_onItemDeleted_re
     _("Execute the remove folder transaction");
     txn.doTransaction();
     yield verifyTrackedItems(["menu", folder_guid, fx_guid, tb_guid]);
     do_check_eq(tracker.score, SCORE_INCREMENT_XLARGE * 6);
     yield resetTracker();
 
     _("Undo the remove folder transaction");
     txn.undoTransaction();
-    yield verifyTrackedItems(["menu"]);
-    do_check_eq(tracker.score, SCORE_INCREMENT_XLARGE);
-    yield resetTracker();
 
     // At this point, the restored folder has the same ID, but a different GUID.
     let new_folder_guid = yield PlacesUtils.promiseItemGuid(folder_id);
 
+    yield verifyTrackedItems(["menu", new_folder_guid]);
+    do_check_eq(tracker.score, SCORE_INCREMENT_XLARGE * 2);
+    yield resetTracker();
+
     _("Redo the transaction");
     txn.redoTransaction();
     yield verifyTrackedItems(["menu", new_folder_guid]);
     do_check_eq(tracker.score, SCORE_INCREMENT_XLARGE * 2);
   } finally {
     _("Clean up.");
     yield cleanup();
   }
--- a/taskcluster/ci/l10n/kind.yml
+++ b/taskcluster/ci/l10n/kind.yml
@@ -24,16 +24,18 @@ job-defaults:
         max-run-time: 36000
         env:
             # NOTE: this should really be a different "using" since it's using
             # build-l10n.sh instead of build-linux.sh.  Preferably, build-linux.sh
             # and the mozharness run implementation should be modified to support
             # the functionality that l10n needs
 
             JOB_SCRIPT: "taskcluster/scripts/builder/build-l10n.sh"
+    # don't run anywhere by default, but still available via try
+    run-on-projects: []
     when:
         files-changed:
             - browser/locales/all-locales
             - python/compare-locales/**
             - testing/mozharness/configs/single_locale/**
             - testing/mozharness/mozharness/mozilla/l10n/locales.py
             - testing/mozharness/scripts/desktop_l10n.py
             - toolkit/locales/**
--- a/taskcluster/taskgraph/create.py
+++ b/taskcluster/taskgraph/create.py
@@ -18,33 +18,38 @@ from taskgraph.util.time import (
 )
 
 logger = logging.getLogger(__name__)
 
 # the maximum number of parallel createTask calls to make
 CONCURRENCY = 50
 
 
-def create_tasks(taskgraph, label_to_taskid):
-    # TODO: use the taskGroupId of the decision task
-    task_group_id = slugid()
+def create_tasks(taskgraph, label_to_taskid, params):
     taskid_to_label = {t: l for l, t in label_to_taskid.iteritems()}
 
     session = requests.Session()
 
     # Default HTTPAdapter uses 10 connections. Mount custom adapter to increase
     # that limit. Connections are established as needed, so using a large value
     # should not negatively impact performance.
     http_adapter = requests.adapters.HTTPAdapter(pool_connections=CONCURRENCY,
                                                  pool_maxsize=CONCURRENCY)
     session.mount('https://', http_adapter)
     session.mount('http://', http_adapter)
 
     decision_task_id = os.environ.get('TASK_ID')
 
+    # when running as an actual decision task, we use the decision task's
+    # taskId as the taskGroupId.  The process that created the decision task
+    # helpfully placed it in this same taskGroup.  If there is no $TASK_ID,
+    # fall back to a slugid
+    task_group_id = decision_task_id or slugid()
+    scheduler_id = 'gecko-level-{}'.format(params['level'])
+
     with futures.ThreadPoolExecutor(CONCURRENCY) as e:
         fs = {}
 
         # We can't submit a task until its dependencies have been submitted.
         # So our strategy is to walk the graph and submit tasks once all
         # their dependencies have been submitted.
         #
         # Using visit_postorder() here isn't the most efficient: we'll
@@ -57,17 +62,17 @@ def create_tasks(taskgraph, label_to_tas
             attributes = taskgraph.tasks[task_id].attributes
             # if this task has no dependencies, make it depend on this decision
             # task so that it does not start immediately; and so that if this loop
             # fails halfway through, none of the already-created tasks run.
             if decision_task_id and not task_def.get('dependencies'):
                 task_def['dependencies'] = [decision_task_id]
 
             task_def['taskGroupId'] = task_group_id
-            task_def['schedulerId'] = '-'
+            task_def['schedulerId'] = scheduler_id
 
             # Wait for dependencies before submitting this.
             deps_fs = [fs[dep] for dep in task_def.get('dependencies', [])
                        if dep in fs]
             for f in futures.as_completed(deps_fs):
                 f.result()
 
             fs[task_id] = e.submit(_create_task, session, task_id,
--- a/taskcluster/taskgraph/decision.py
+++ b/taskcluster/taskgraph/decision.py
@@ -86,17 +86,17 @@ def taskgraph_decision(options):
     write_artifact('target-tasks.json', tgg.target_task_set.tasks.keys())
 
     # write out the optimized task graph to describe what will actually happen,
     # and the map of labels to taskids
     write_artifact('task-graph.json', tgg.optimized_task_graph.to_json())
     write_artifact('label-to-taskid.json', tgg.label_to_taskid)
 
     # actually create the graph
-    create_tasks(tgg.optimized_task_graph, tgg.label_to_taskid)
+    create_tasks(tgg.optimized_task_graph, tgg.label_to_taskid, parameters)
 
 
 def get_decision_parameters(options):
     """
     Load parameters from the command-line options for 'taskgraph decision'.
     This also applies per-project parameters, based on the given project.
 
     """
--- a/taskcluster/taskgraph/test/test_create.py
+++ b/taskcluster/taskgraph/test/test_create.py
@@ -39,20 +39,21 @@ class TestCreate(unittest.TestCase):
         tasks = {
             'tid-a': TestTask(label='a', task={'payload': 'hello world'}),
             'tid-b': TestTask(label='b', task={'payload': 'hello world'}),
         }
         label_to_taskid = {'a': 'tid-a', 'b': 'tid-b'}
         graph = Graph(nodes={'tid-a', 'tid-b'}, edges={('tid-a', 'tid-b', 'edge')})
         taskgraph = TaskGraph(tasks, graph)
 
-        create.create_tasks(taskgraph, label_to_taskid)
+        create.create_tasks(taskgraph, label_to_taskid, {'level': '4'})
 
         for tid, task in self.created_tasks.iteritems():
             self.assertEqual(task['payload'], 'hello world')
+            self.assertEqual(task['schedulerId'], 'gecko-level-4')
             # make sure the dependencies exist, at least
             for depid in task.get('dependencies', []):
                 if depid is 'decisiontask':
                     # Don't look for decisiontask here
                     continue
                 self.assertIn(depid, self.created_tasks)
 
     def test_create_task_without_dependencies(self):
@@ -60,16 +61,16 @@ class TestCreate(unittest.TestCase):
         os.environ['TASK_ID'] = 'decisiontask'
         tasks = {
             'tid-a': TestTask(label='a', task={'payload': 'hello world'}),
         }
         label_to_taskid = {'a': 'tid-a'}
         graph = Graph(nodes={'tid-a'}, edges=set())
         taskgraph = TaskGraph(tasks, graph)
 
-        create.create_tasks(taskgraph, label_to_taskid)
+        create.create_tasks(taskgraph, label_to_taskid, {'level': '4'})
 
         for tid, task in self.created_tasks.iteritems():
             self.assertEqual(task.get('dependencies'), [os.environ['TASK_ID']])
 
 
 if __name__ == '__main__':
     main()
--- a/testing/docker/android-gradle-build/bin/after.sh
+++ b/testing/docker/android-gradle-build/bin/after.sh
@@ -1,14 +1,14 @@
 #!/bin/bash -vex
 
 set -x -e
 
 : WORKSPACE ${WORKSPACE:=/workspace}
-: GRADLE_VERSION ${GRADLE_VERSION:=2.10}
+: GRADLE_VERSION ${GRADLE_VERSION:=2.14.1}
 
 set -v
 
 # Package everything up.
 pushd ${WORKSPACE}
 # Not yet.  See notes on tooltool below.
 # cp -R /root/.android-sdk android-sdk-linux
 # tar cJf android-sdk-linux.tar.xz android-sdk-linux
--- a/testing/docker/android-gradle-build/dot-config/pip/pip.conf
+++ b/testing/docker/android-gradle-build/dot-config/pip/pip.conf
@@ -1,4 +1,2 @@
 [global]
 disable-pip-version-check = true
-trusted-host = pypi.pub.build.mozilla.org
-
--- a/testing/docker/desktop-build/dot-config/pip/pip.conf
+++ b/testing/docker/desktop-build/dot-config/pip/pip.conf
@@ -1,4 +1,2 @@
 [global]
 disable-pip-version-check = true
-trusted-host = pypi.pub.build.mozilla.org
-
--- a/testing/docker/desktop-test/dot-files/config/pip/pip.conf
+++ b/testing/docker/desktop-test/dot-files/config/pip/pip.conf
@@ -1,4 +1,2 @@
 [global]
 disable-pip-version-check = true
-trusted-host = pypi.pub.build.mozilla.org
-
--- a/testing/docker/desktop1604-test/dot-files/config/pip/pip.conf
+++ b/testing/docker/desktop1604-test/dot-files/config/pip/pip.conf
@@ -1,4 +1,2 @@
 [global]
 disable-pip-version-check = true
-trusted-host = pypi.pub.build.mozilla.org
-
--- a/testing/docker/recipes/run-task
+++ b/testing/docker/recipes/run-task
@@ -32,36 +32,40 @@ import urllib2
 FINGERPRINT_URL = 'http://taskcluster/secrets/v1/secret/project/taskcluster/gecko/hgfingerprint'
 
 
 def print_line(prefix, m):
     now = datetime.datetime.utcnow()
     print(b'[%s %sZ] %s' % (prefix, now.isoformat(), m), end=b'')
 
 
-def run_and_prefix_output(prefix, args):
+def run_and_prefix_output(prefix, args, extra_env=None):
     """Runs a process and prefixes its output with the time.
 
     Returns the process exit code.
     """
     print_line(prefix, b'executing %s\n' % args)
 
+    env = dict(os.environ)
+    env.update(extra_env or {})
+
     # Note: TaskCluster's stdin is a TTY. This attribute is lost
     # when we pass sys.stdin to the invoked process. If we cared
     # to preserve stdin as a TTY, we could make this work. But until
     # someone needs it, don't bother.
     p = subprocess.Popen(args,
                          # Disable buffering because we want to receive output
                          # as it is generated so timestamps in logs are
                          # accurate.
                          bufsize=0,
                          stdout=subprocess.PIPE,
                          stderr=subprocess.STDOUT,
                          stdin=sys.stdin.fileno(),
                          cwd='/',
+                         env=env,
                          # So \r in progress bars are rendered as multiple
                          # lines, preserving progress indicators.
                          universal_newlines=True)
 
     while True:
         data = p.stdout.readline()
         if data == b'':
             break
@@ -116,17 +120,17 @@ def vcs_checkout(args):
         b'/usr/bin/hg',
         b'--config', b'hostsecurity.hg.mozilla.org:fingerprints=%s' % hgmo_fingerprint,
         b'robustcheckout',
         b'--sharebase', b'/home/worker/hg-shared',
         b'--purge',
         b'--upstream', base_repo,
         revision_flag, revision,
         os.environ['GECKO_HEAD_REPOSITORY'], args.vcs_checkout
-    ])
+    ], extra_env={b'PYTHONUNBUFFERED': b'1'})
 
     if res:
         sys.exit(res)
 
     # Update the current revision hash and ensure that it is well formed.
     revision = subprocess.check_output(
         [b'/usr/bin/hg', b'log',
          b'--rev', b'.',
--- a/testing/docker/tester/dot-config/pip/pip.conf
+++ b/testing/docker/tester/dot-config/pip/pip.conf
@@ -1,4 +1,2 @@
 [global]
 disable-pip-version-check = true
-trusted-host = pypi.pub.build.mozilla.org
-
--- a/testing/mozharness/configs/developer_config.py
+++ b/testing/mozharness/configs/developer_config.py
@@ -20,19 +20,16 @@ config = {
     "replace_urls": [
         ("http://pvtbuilds.pvt.build", "https://pvtbuilds"),
     ],
 
     # General local variable overwrite
     "exes": {
         "gittool.py": os.path.join(LOCAL_WORKDIR, "gittool.py"),
     },
-    "env": {
-        "PIP_TRUSTED_HOST": "pypi.pub.build.mozilla.org",
-    },
 
     # Pip
     "find_links": ["http://pypi.pub.build.mozilla.org/pub"],
     "pip_index": False,
 
     # Talos related
     "python_webserver": True,
     "virtualenv_path": '%s/build/venv' % os.getcwd(),
--- a/testing/mozharness/configs/firefox_ui_tests/qa_jenkins.py
+++ b/testing/mozharness/configs/firefox_ui_tests/qa_jenkins.py
@@ -1,19 +1,15 @@
 # Default configuration as used by Mozmill CI (Jenkins)
 
 
 config = {
     # Tests run in mozmill-ci do not use RelEng infra
     'developer_mode': True,
 
-    'env': {
-        'PIP_TRUSTED_HOST': 'pypi.pub.build.mozilla.org',
-    },
-
     # PIP
     'find_links': ['http://pypi.pub.build.mozilla.org/pub'],
     'pip_index': False,
 
     # mozcrash support
     'download_minidump_stackwalk': True,
     'download_symbols': 'ondemand',
     'download_tooltool': True,
--- a/testing/mozharness/configs/firefox_ui_tests/releng_release.py
+++ b/testing/mozharness/configs/firefox_ui_tests/releng_release.py
@@ -8,20 +8,16 @@ import mozharness
 
 external_tools_path = os.path.join(
     os.path.abspath(os.path.dirname(os.path.dirname(mozharness.__file__))),
     'external_tools',
 )
 
 
 config = {
-    'env': {
-        'PIP_TRUSTED_HOST': 'pypi.pub.build.mozilla.org',
-    },
-
     # General local variable overwrite
     'exes': {
         'gittool.py': [
             # Bug 1227079 - Python executable eeded to get it executed on Windows
             sys.executable,
             os.path.join(external_tools_path, 'gittool.py')
         ],
     },
--- a/testing/mozharness/configs/mediatests/jenkins_config.py
+++ b/testing/mozharness/configs/mediatests/jenkins_config.py
@@ -8,20 +8,16 @@ import mozharness
 
 
 external_tools_path = os.path.join(
     os.path.abspath(os.path.dirname(os.path.dirname(mozharness.__file__))),
     'external_tools',
 )
 
 config = {
-    'env': {
-        'PIP_TRUSTED_HOST': 'pypi.pub.build.mozilla.org',
-    },
-
     # PIP
     'find_links': ['http://pypi.pub.build.mozilla.org/pub'],
     'pip_index': False,
 
     # mozcrash support
     'download_minidump_stackwalk': True,
     'download_symbols': 'ondemand',
     'download_tooltool': True,
--- a/testing/mozharness/mozharness/base/python.py
+++ b/testing/mozharness/mozharness/base/python.py
@@ -2,22 +2,24 @@
 # ***** BEGIN LICENSE BLOCK *****
 # 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/.
 # ***** END LICENSE BLOCK *****
 '''Python usage, esp. virtualenv.
 '''
 
+import distutils.version
 import os
 import subprocess
 import sys
 import time
 import json
 import traceback
+import urlparse
 
 import mozharness
 from mozharness.base.script import (
     PostScriptAction,
     PostScriptRun,
     PreScriptAction,
     PreScriptRun,
 )
@@ -44,17 +46,17 @@ def get_tlsv1_post():
                                            block=block,
                                            ssl_version=ssl.PROTOCOL_TLSv1)
     s = requests.Session()
     s.mount('https://', TLSV1Adapter())
     return s.post
 
 # Virtualenv {{{1
 virtualenv_config_options = [
-    [["--venv-path", "--virtualenv-path"], {
+    [["--virtualenv-path"], {
         "action": "store",
         "dest": "virtualenv_path",
         "default": "venv",
         "help": "Specify the path to the virtualenv top level directory"
     }],
     [["--virtualenv"], {
         "action": "store",
         "dest": "virtualenv",
@@ -106,43 +108,44 @@ class VirtualenvMixin(object):
 
         See the documentation for install_module for how the arguments are
         applied.
         """
         self._virtualenv_modules.append((name, url, method, requirements,
                                          optional, two_pass, editable))
 
     def query_virtualenv_path(self):
-        c = self.config
+        """Determine the absolute path to the virtualenv."""
         dirs = self.query_abs_dirs()
-        virtualenv = None
+
         if 'abs_virtualenv_dir' in dirs:
-            virtualenv = dirs['abs_virtualenv_dir']
-        elif c.get('virtualenv_path'):
-            if os.path.isabs(c['virtualenv_path']):
-                virtualenv = c['virtualenv_path']
-            else:
-                virtualenv = os.path.join(dirs['abs_work_dir'],
-                                          c['virtualenv_path'])
-        return virtualenv
+            return dirs['abs_virtualenv_dir']
+
+        p = self.config['virtualenv_path']
+        if not p:
+            self.fatal('virtualenv_path config option not set; '
+                       'this should never happen')
+
+        if os.path.isabs(p):
+            return p
+        else:
+            return os.path.join(dirs['abs_work_dir'], p)
 
     def query_python_path(self, binary="python"):
         """Return the path of a binary inside the virtualenv, if
         c['virtualenv_path'] is set; otherwise return the binary name.
         Otherwise return None
         """
         if binary not in self.python_paths:
             bin_dir = 'bin'
             if self._is_windows():
                 bin_dir = 'Scripts'
             virtualenv_path = self.query_virtualenv_path()
-            if virtualenv_path:
-                self.python_paths[binary] = os.path.abspath(os.path.join(virtualenv_path, bin_dir, binary))
-            else:
-                self.python_paths[binary] = self.query_exe(binary)
+            self.python_paths[binary] = os.path.abspath(os.path.join(virtualenv_path, bin_dir, binary))
+
         return self.python_paths[binary]
 
     def query_python_site_packages_path(self):
         if self.site_packages_path:
             return self.site_packages_path
         python = self.query_python_path()
         self.site_packages_path = self.get_output_from_command(
             [python, '-c',
@@ -252,20 +255,30 @@ class VirtualenvMixin(object):
                 # the 'install' in the executable name hits UAC
                 # - http://answers.microsoft.com/en-us/windows/forum/windows_7-security/uac-message-do-you-want-to-allow-the-following/bea30ad8-9ef8-4897-aab4-841a65f7af71
                 # - https://bugzilla.mozilla.org/show_bug.cgi?id=791840
                 default = [self.query_python_path(), self.query_python_path('easy_install-script.py')]
             command = self.query_exe('easy_install', default=default, return_type="list")
         else:
             self.fatal("install_module() doesn't understand an install_method of %s!" % install_method)
 
-        # Add --find-links pages to look at
+        # Add --find-links pages to look at. Add --trusted-host automatically if
+        # the host isn't secure. This allows modern versions of pip to connect
+        # without requiring an override.
         proxxy = Proxxy(self.config, self.log_obj)
+        trusted_hosts = set()
         for link in proxxy.get_proxies_and_urls(c.get('find_links', [])):
             command.extend(["--find-links", link])
+            parsed = urlparse.urlparse(link)
+            if parsed.scheme != 'https':
+                trusted_hosts.add(parsed.hostname)
+
+        if self.pip_version >= distutils.version.LooseVersion('6.0'):
+            for host in sorted(trusted_hosts):
+                command.extend(['--trusted-host', host])
 
         # module_url can be None if only specifying requirements files
         if module_url:
             if editable:
                 if install_method in (None, 'pip'):
                     command += ['-e']
                 else:
                     self.fatal("editable installs not supported for install_method %s" % install_method)
@@ -344,55 +357,85 @@ class VirtualenvMixin(object):
                 '/path/to/requirements1.txt',
                 '/path/to/requirements2.txt'
             ]
         """
         c = self.config
         dirs = self.query_abs_dirs()
         venv_path = self.query_virtualenv_path()
         self.info("Creating virtualenv %s" % venv_path)
-        virtualenv = c.get('virtualenv', self.query_exe('virtualenv'))
-        if isinstance(virtualenv, str):
-            # allow for [python, virtualenv] in config
-            virtualenv = [virtualenv]
 
-        if not os.path.exists(virtualenv[0]) and not self.which(virtualenv[0]):
-            self.add_summary("The executable '%s' is not found; not creating "
-                             "virtualenv!" % virtualenv[0], level=FATAL)
-            return -1
+        # If running from a source checkout, use the virtualenv that is
+        # vendored since that is deterministic.
+        if self.topsrcdir:
+            virtualenv = [
+                sys.executable,
+                os.path.join(self.topsrcdir, 'python', 'virtualenv', 'virtualenv.py')
+            ]
+            virtualenv_options = c.get('virtualenv_options', [])
+            # Don't create symlinks. If we don't do this, permissions issues may
+            # hinder virtualenv creation or operation. Ideally we should do this
+            # below when using the system virtualenv. However, this is a newer
+            # feature and isn't guaranteed to be supported.
+            virtualenv_options.append('--always-copy')
+
+        # No source checkout. Try to find virtualenv from config options
+        # or search path.
+        else:
+            virtualenv = c.get('virtualenv', self.query_exe('virtualenv'))
+            if isinstance(virtualenv, str):
+                # allow for [python, virtualenv] in config
+                virtualenv = [virtualenv]
 
-        # https://bugs.launchpad.net/virtualenv/+bug/352844/comments/3
-        # https://bugzilla.mozilla.org/show_bug.cgi?id=700415#c50
-        if c.get('virtualenv_python_dll'):
-            # We may someday want to copy a differently-named dll, but
-            # let's not think about that right now =\
-            dll_name = os.path.basename(c['virtualenv_python_dll'])
-            target = self.query_python_path(dll_name)
-            scripts_dir = os.path.dirname(target)
-            self.mkdir_p(scripts_dir)
-            self.copyfile(c['virtualenv_python_dll'], target, error_level=WARNING)
-        else:
-            self.mkdir_p(dirs['abs_work_dir'])
+            if not os.path.exists(virtualenv[0]) and not self.which(virtualenv[0]):
+                self.add_summary("The executable '%s' is not found; not creating "
+                                 "virtualenv!" % virtualenv[0], level=FATAL)
+                return -1
 
-        # make this list configurable?
-        for module in ('distribute', 'pip'):
-            if c.get('%s_url' % module):
-                self.download_file(c['%s_url' % module],
-                                   parent_dir=dirs['abs_work_dir'])
+            # https://bugs.launchpad.net/virtualenv/+bug/352844/comments/3
+            # https://bugzilla.mozilla.org/show_bug.cgi?id=700415#c50
+            if c.get('virtualenv_python_dll'):
+                # We may someday want to copy a differently-named dll, but
+                # let's not think about that right now =\
+                dll_name = os.path.basename(c['virtualenv_python_dll'])
+                target = self.query_python_path(dll_name)
+                scripts_dir = os.path.dirname(target)
+                self.mkdir_p(scripts_dir)
+                self.copyfile(c['virtualenv_python_dll'], target, error_level=WARNING)
+            else:
+                self.mkdir_p(dirs['abs_work_dir'])
 
-        virtualenv_options = c.get('virtualenv_options',
-                                   ['--no-site-packages', '--distribute'])
+            # make this list configurable?
+            for module in ('distribute', 'pip'):
+                if c.get('%s_url' % module):
+                    self.download_file(c['%s_url' % module],
+                                       parent_dir=dirs['abs_work_dir'])
+
+            virtualenv_options = c.get('virtualenv_options',
+                                       ['--no-site-packages', '--distribute'])
 
         if os.path.exists(self.query_python_path()):
             self.info("Virtualenv %s appears to already exist; skipping virtualenv creation." % self.query_python_path())
         else:
             self.run_command(virtualenv + virtualenv_options + [venv_path],
                              cwd=dirs['abs_work_dir'],
                              error_list=VirtualenvErrorList,
                              halt_on_failure=True)
+
+        # Resolve the pip version so we can conditionally do things if we have
+        # a modern pip.
+        pip = self.query_python_path('pip')
+        output = self.get_output_from_command([pip, '--version'],
+                                              halt_on_failure=True)
+        words = output.split()
+        if words[0] != 'pip':
+            self.fatal('pip --version output is weird: %s' % output)
+        pip_version = words[1]
+        self.pip_version = distutils.version.LooseVersion(pip_version)
+
         if not modules:
             modules = c.get('virtualenv_modules', [])
         if not requirements:
             requirements = c.get('virtualenv_requirements', [])
         if not modules and requirements:
             self.install_module(requirements=requirements,
                                 install_method='pip')
         for module in modules:
--- a/testing/mozharness/mozharness/base/script.py
+++ b/testing/mozharness/mozharness/base/script.py
@@ -1363,17 +1363,18 @@ class ScriptMixin(PlatformMixin):
                 if p.timedOut:
                     self.log(
                         'timed out after %s seconds of no output' % output_timeout,
                         level=error_level
                     )
                 returncode = int(p.proc.returncode)
             else:
                 p = subprocess.Popen(command, shell=shell, stdout=subprocess.PIPE,
-                                     cwd=cwd, stderr=subprocess.STDOUT, env=env)
+                                     cwd=cwd, stderr=subprocess.STDOUT, env=env,
+                                     bufsize=0)
                 loop = True
                 while loop:
                     if p.poll() is not None:
                         """Avoid losing the final lines of the log?"""
                         loop = False
                     while True:
                         line = p.stdout.readline()
                         if not line:
@@ -1780,16 +1781,31 @@ class BaseScript(ScriptMixin, LogMixin, 
         rw_config = ConfigClass(config_options=config_options, **kwargs)
         self.config = rw_config.get_read_only_config()
         self.actions = tuple(rw_config.actions)
         self.all_actions = tuple(rw_config.all_actions)
         self.env = None
         self.new_log_obj(default_log_level=default_log_level)
         self.script_obj = self
 
+        # Indicate we're a source checkout if VCS directory is present at the
+        # appropriate place. This code will break if this file is ever moved
+        # to another directory.
+        self.topsrcdir = None
+
+        srcreldir = 'testing/mozharness/mozharness/base'
+        here = os.path.normpath(os.path.dirname(__file__))
+        if here.replace('\\', '/').endswith(srcreldir):
+            topsrcdir = os.path.normpath(os.path.join(here, '..', '..',
+                                                      '..', '..'))
+            hg_dir = os.path.join(topsrcdir, '.hg')
+            git_dir = os.path.join(topsrcdir, '.git')
+            if os.path.isdir(hg_dir) or os.path.isdir(git_dir):
+                self.topsrcdir = topsrcdir
+
         # Set self.config to read-only.
         #
         # We can create intermediate config info programmatically from
         # this in a repeatable way, with logs; this is how we straddle the
         # ideal-but-not-user-friendly static config and the
         # easy-to-write-hard-to-debug writable config.
         #
         # To allow for other, script-specific configurations
--- a/testing/mozharness/mozharness/mozilla/tooltool.py
+++ b/testing/mozharness/mozharness/mozilla/tooltool.py
@@ -42,18 +42,24 @@ class TooltoolMixin(object):
         # take care of auth.  Everywhere else, we'll get auth failures if
         # necessary.
         if os.path.exists(fn):
             return fn
 
     def tooltool_fetch(self, manifest,
                        output_dir=None, privileged=False, cache=None):
         """docstring for tooltool_fetch"""
-
-        if self.config.get("download_tooltool"):
+        # Use vendored tooltool.py if available.
+        if self.topsrcdir:
+            cmd = [
+                sys.executable,
+                os.path.join(self.topsrcdir, 'testing', 'docker', 'recipes',
+                                'tooltool.py')
+            ]
+        elif self.config.get("download_tooltool"):
             cmd = [sys.executable, self._fetch_tooltool_py()]
         else:
             cmd = self.query_exe('tooltool.py', return_type='list')
 
         # get the tooltool servers from configuration
         default_urls = self.config.get('tooltool_servers', TOOLTOOL_SERVERS)
 
         # add slashes (bug 1155630)
new file mode 100644
--- /dev/null
+++ b/toolkit/components/extensions/test/mochitest/test_ext_all_apis.js
@@ -0,0 +1,154 @@
+/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set sts=2 sw=2 et tw=80: */
+"use strict";
+
+// Tests whether not too many APIs are visible by default.
+// This file is used by test_ext_all_apis.html in browser/ and mobile/android/,
+// which may modify the following variables to add or remove expected APIs.
+/* globals expectedContentApisTargetSpecific */
+/* globals expectedBackgroundApisTargetSpecific */
+
+// Generates a list of expectations.
+function generateExpectations(list) {
+  return list.reduce((allApis, path) => {
+    return allApis.concat(`browser.${path}`, `chrome.${path}`);
+  }, []).sort();
+}
+
+let expectedCommonApis = [
+  "extension.getURL",
+  "extension.inIncognitoContext",
+  "extension.lastError",
+  "i18n.detectLanguage",
+  "i18n.getAcceptLanguages",
+  "i18n.getMessage",
+  "i18n.getUILanguage",
+  "runtime.OnInstalledReason",
+  "runtime.OnRestartRequiredReason",
+  "runtime.PlatformArch",
+  "runtime.PlatformOs",
+  "runtime.RequestUpdateCheckStatus",
+  "runtime.getManifest",
+  "runtime.connect",
+  "runtime.getURL",
+  "runtime.id",
+  "runtime.lastError",
+  "runtime.onConnect",
+  "runtime.onMessage",
+  "runtime.sendMessage",
+  // If you want to add a new powerful test API, please see bug 1287233.
+  "test.assertEq",
+  "test.assertFalse",
+  "test.assertTrue",
+  "test.fail",
+  "test.log",
+  "test.notifyFail",
+  "test.notifyPass",
+  "test.onMessage",
+  "test.sendMessage",
+  "test.succeed",
+];
+
+let expectedContentApis = [
+  ...expectedCommonApis,
+  ...expectedContentApisTargetSpecific,
+];
+
+let expectedBackgroundApis = [
+  ...expectedCommonApis,
+  ...expectedBackgroundApisTargetSpecific,
+  "extension.ViewType",
+  "extension.getBackgroundPage",
+  "extension.getViews",
+  "extension.isAllowedFileSchemeAccess",
+  "extension.isAllowedIncognitoAccess",
+  // Note: extensionTypes is not visible in Chrome.
+  "extensionTypes.ImageFormat",
+  "extensionTypes.RunAt",
+  "management.ExtensionDisabledReason",
+  "management.ExtensionInstallType",
+  "management.ExtensionType",
+  "management.getSelf",
+  "management.uninstallSelf",
+  "runtime.getBackgroundPage",
+  "runtime.getBrowserInfo",
+  "runtime.getPlatformInfo",
+  "runtime.onUpdateAvailable",
+  "runtime.openOptionsPage",
+  "runtime.reload",
+  "runtime.setUninstallURL",
+];
+
+function sendAllApis() {
+  function isEvent(key, val) {
+    if (!/^on[A-Z]/.test(key)) {
+      return false;
+    }
+    let eventKeys = [];
+    for (let prop in val) {
+      eventKeys.push(prop);
+    }
+    eventKeys = eventKeys.sort().join();
+    return eventKeys === "addListener,hasListener,removeListener";
+  }
+  function mayRecurse(key, val) {
+    if (Object.keys(val).filter(k => !/^[A-Z\-0-9_]+$/.test(k)).length === 0) {
+      // Don't recurse on constants and empty objects.
+      return false;
+    }
+    return !isEvent(key, val);
+  }
+
+  let results = [];
+  function diveDeeper(path, obj) {
+    for (let key in obj) {
+      let val = obj[key];
+      if (typeof val == "object" && val !== null && mayRecurse(key, val)) {
+        diveDeeper(`${path}.${key}`, val);
+      } else {
+        results.push(`${path}.${key}`);
+      }
+    }
+  }
+  diveDeeper("browser", browser);
+  diveDeeper("chrome", chrome);
+  browser.test.sendMessage("allApis", results.sort());
+}
+
+add_task(function* test_enumerate_content_script_apis() {
+  let extensionData = {
+    manifest: {
+      content_scripts: [{
+        matches: ["http://mochi.test/*/file_sample.html"],
+        js: ["contentscript.js"],
+        run_at: "document_start",
+      }],
+    },
+    files: {
+      "contentscript.js": sendAllApis,
+    },
+  };
+  let extension = ExtensionTestUtils.loadExtension(extensionData);
+  yield extension.startup();
+
+  let win = window.open("file_sample.html");
+  let actualApis = yield extension.awaitMessage("allApis");
+  win.close();
+  let expectedApis = generateExpectations(expectedContentApis);
+  isDeeply(actualApis, expectedApis, "content script APIs");
+
+  yield extension.unload();
+});
+
+add_task(function* test_enumerate_background_script_apis() {
+  let extensionData = {
+    background: sendAllApis,
+  };
+  let extension = ExtensionTestUtils.loadExtension(extensionData);
+  yield extension.startup();
+  let actualApis = yield extension.awaitMessage("allApis");
+  let expectedApis = generateExpectations(expectedBackgroundApis);
+  isDeeply(actualApis, expectedApis, "background script APIs");
+
+  yield extension.unload();
+});
--- a/toolkit/components/places/Helpers.cpp
+++ b/toolkit/components/places/Helpers.cpp
@@ -1,19 +1,19 @@
 /* vim: sw=2 ts=2 et lcs=trail\:.,tab\:>~ :
  * 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 "Helpers.h"
 #include "mozIStorageError.h"
-#include "plbase64.h"
 #include "prio.h"
 #include "nsString.h"
 #include "nsNavHistory.h"
+#include "mozilla/Base64.h"
 #include "mozilla/Services.h"
 
 // The length of guids that are used by history and bookmarks.
 #define GUID_LENGTH 12
 
 namespace mozilla {
 namespace places {
 
@@ -196,40 +196,16 @@ void
 ReverseString(const nsString& aInput, nsString& aReversed)
 {
   aReversed.Truncate(0);
   for (int32_t i = aInput.Length() - 1; i >= 0; i--) {
     aReversed.Append(aInput[i]);
   }
 }
 
-static
-nsresult
-Base64urlEncode(const uint8_t* aBytes,
-                uint32_t aNumBytes,
-                nsCString& _result)
-{
-  // SetLength does not set aside space for null termination.  PL_Base64Encode
-  // will not null terminate, however, nsCStrings must be null terminated.  As a
-  // result, we set the capacity to be one greater than what we need, and the
-  // length to our desired length.
-  uint32_t length = (aNumBytes + 2) / 3 * 4; // +2 due to integer math.
-  NS_ENSURE_TRUE(_result.SetCapacity(length + 1, fallible),
-                 NS_ERROR_OUT_OF_MEMORY);
-  _result.SetLength(length);
-  (void)PL_Base64Encode(reinterpret_cast<const char*>(aBytes), aNumBytes,
-                        _result.BeginWriting());
-
-  // base64url encoding is defined in RFC 4648.  It replaces the last two
-  // alphabet characters of base64 encoding with '-' and '_' respectively.
-  _result.ReplaceChar('+', '-');
-  _result.ReplaceChar('/', '_');
-  return NS_OK;
-}
-
 #ifdef XP_WIN
 } // namespace places
 } // namespace mozilla
 
 // Included here because windows.h conflicts with the use of mozIStorageError
 // above, but make sure that these are not included inside mozilla::places.
 #include <windows.h>
 #include <wincrypt.h>
@@ -279,17 +255,18 @@ GenerateGUID(nsCString& _guid)
   // bytes, we get one character.
   const uint32_t kRequiredBytesLength =
     static_cast<uint32_t>(GUID_LENGTH / 4 * 3);
 
   uint8_t buffer[kRequiredBytesLength];
   nsresult rv = GenerateRandomBytes(kRequiredBytesLength, buffer);
   NS_ENSURE_SUCCESS(rv, rv);
 
-  rv = Base64urlEncode(buffer, kRequiredBytesLength, _guid);
+  rv = Base64URLEncode(kRequiredBytesLength, buffer,
+                       Base64URLEncodePaddingPolicy::Omit, _guid);
   NS_ENSURE_SUCCESS(rv, rv);
 
   NS_ASSERTION(_guid.Length() == GUID_LENGTH, "GUID is not the right size!");
   return NS_OK;
 }
 
 bool
 IsValidGUID(const nsACString& aGUID)
--- a/toolkit/components/places/SQLFunctions.cpp
+++ b/toolkit/components/places/SQLFunctions.cpp
@@ -837,17 +837,25 @@ namespace places {
     MOZ_ASSERT(numArgs == 2);
 
     nsAutoCString table;
     rv = aArgs->GetUTF8String(0, table);
     NS_ENSURE_SUCCESS(rv, rv);
 
     int64_t lastInsertedId = aArgs->AsInt64(1);
 
-    nsNavHistory::StoreLastInsertedId(table, lastInsertedId);
+    MOZ_ASSERT(table.EqualsLiteral("moz_places") ||
+               table.EqualsLiteral("moz_historyvisits") ||
+               table.EqualsLiteral("moz_bookmarks"));
+
+    if (table.EqualsLiteral("moz_bookmarks")) {
+      nsNavBookmarks::StoreLastInsertedId(table, lastInsertedId);
+    } else {
+      nsNavHistory::StoreLastInsertedId(table, lastInsertedId);
+    }
 
     RefPtr<nsVariant> result = new nsVariant();
     rv = result->SetAsInt64(lastInsertedId);
     NS_ENSURE_SUCCESS(rv, rv);
     result.forget(_result);
     return NS_OK;
   }
 
--- a/toolkit/components/places/nsNavBookmarks.cpp
+++ b/toolkit/components/places/nsNavBookmarks.cpp
@@ -143,16 +143,27 @@ NS_IMPL_ISUPPORTS(nsNavBookmarks
 , nsINavBookmarksService
 , nsINavHistoryObserver
 , nsIAnnotationObserver
 , nsIObserver
 , nsISupportsWeakReference
 )
 
 
+Atomic<int64_t> nsNavBookmarks::sLastInsertedItemId(0);
+
+
+void // static
+nsNavBookmarks::StoreLastInsertedId(const nsACString& aTable,
+                                    const int64_t aLastInsertedId) {
+  MOZ_ASSERT(aTable.EqualsLiteral("moz_bookmarks"));
+  sLastInsertedItemId = aLastInsertedId;
+}
+
+
 nsresult
 nsNavBookmarks::Init()
 {
   mDB = Database::GetDatabase();
   NS_ENSURE_STATE(mDB);
 
   nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
   if (os) {
@@ -341,17 +352,17 @@ nsNavBookmarks::InsertBookmarkInDB(int64
   MOZ_ASSERT(aPlaceId && (aPlaceId == -1 || aPlaceId > 0));
 
   nsCOMPtr<mozIStorageStatement> stmt = mDB->GetStatement(
     "INSERT INTO moz_bookmarks "
       "(id, fk, type, parent, position, title, "
        "dateAdded, lastModified, guid) "
     "VALUES (:item_id, :page_id, :item_type, :parent, :item_index, "
             ":item_title, :date_added, :last_modified, "
-            "IFNULL(:item_guid, GENERATE_GUID()))"
+            ":item_guid)"
   );
   NS_ENSURE_STATE(stmt);
   mozStorageStatementScoper scoper(stmt);
 
   nsresult rv;
   if (*_itemId != -1)
     rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("item_id"), *_itemId);
   else
@@ -390,44 +401,32 @@ nsNavBookmarks::InsertBookmarkInDB(int64
   }
   NS_ENSURE_SUCCESS(rv, rv);
 
   // Could use IsEmpty because our callers check for GUID validity,
   // but it doesn't hurt.
   if (_guid.Length() == 12) {
     MOZ_ASSERT(IsValidGUID(_guid));
     rv = stmt->BindUTF8StringByName(NS_LITERAL_CSTRING("item_guid"), _guid);
+    NS_ENSURE_SUCCESS(rv, rv);
   }
   else {
-    rv = stmt->BindNullByName(NS_LITERAL_CSTRING("item_guid"));
+    nsAutoCString guid;
+    rv = GenerateGUID(guid);
+    NS_ENSURE_SUCCESS(rv, rv);
+    rv = stmt->BindUTF8StringByName(NS_LITERAL_CSTRING("item_guid"), guid);
+    NS_ENSURE_SUCCESS(rv, rv);
+    _guid.Assign(guid);
   }
-  NS_ENSURE_SUCCESS(rv, rv);
 
   rv = stmt->Execute();
   NS_ENSURE_SUCCESS(rv, rv);
 
   if (*_itemId == -1) {
-    // Get the newly inserted item id and GUID.
-    nsCOMPtr<mozIStorageStatement> lastInsertIdStmt = mDB->GetStatement(
-      "SELECT id, guid "
-      "FROM moz_bookmarks "
-      "ORDER BY ROWID DESC "
-      "LIMIT 1"
-    );
-    NS_ENSURE_STATE(lastInsertIdStmt);
-    mozStorageStatementScoper lastInsertIdScoper(lastInsertIdStmt);
-
-    bool hasResult;
-    rv = lastInsertIdStmt->ExecuteStep(&hasResult);
-    NS_ENSURE_SUCCESS(rv, rv);
-    NS_ENSURE_TRUE(hasResult, NS_ERROR_UNEXPECTED);
-    rv = lastInsertIdStmt->GetInt64(0, _itemId);
-    NS_ENSURE_SUCCESS(rv, rv);
-    rv = lastInsertIdStmt->GetUTF8String(1, _guid);
-    NS_ENSURE_SUCCESS(rv, rv);
+    *_itemId = sLastInsertedItemId;
   }
 
   if (aParentId > 0) {
     // Update last modified date of the ancestors.
     // TODO (bug 408991): Doing this for all ancestors would be slow without a
     //                    nested tree, so for now update only the parent.
     rv = SetItemDateInternal(LAST_MODIFIED, aParentId, aDateAdded);
     NS_ENSURE_SUCCESS(rv, rv);
--- a/toolkit/components/places/nsNavBookmarks.h
+++ b/toolkit/components/places/nsNavBookmarks.h
@@ -211,16 +211,20 @@ public:
   nsresult GetDescendantFolders(int64_t aFolderId,
                                 nsTArray<int64_t>& aDescendantFoldersArray);
 
   static const int32_t kGetChildrenIndex_Guid;
   static const int32_t kGetChildrenIndex_Position;
   static const int32_t kGetChildrenIndex_Type;
   static const int32_t kGetChildrenIndex_PlaceID;
 
+  static mozilla::Atomic<int64_t> sLastInsertedItemId;
+  static void StoreLastInsertedId(const nsACString& aTable,
+                                  const int64_t aLastInsertedId);
+
 private:
   static nsNavBookmarks* gBookmarksService;
 
   ~nsNavBookmarks();
 
   /**
    * Checks whether or not aFolderId points to a live bookmark.
    *
--- a/toolkit/components/places/nsPlacesTriggers.h
+++ b/toolkit/components/places/nsPlacesTriggers.h
@@ -205,16 +205,17 @@
     "WHERE id = OLD.fk;" \
   "END" \
 )
 
 #define CREATE_BOOKMARKS_FOREIGNCOUNT_AFTERINSERT_TRIGGER NS_LITERAL_CSTRING( \
   "CREATE TEMP TRIGGER moz_bookmarks_foreign_count_afterinsert_trigger " \
   "AFTER INSERT ON moz_bookmarks FOR EACH ROW " \
   "BEGIN " \
+    "SELECT store_last_inserted_id('moz_bookmarks', NEW.id); " \
     "UPDATE moz_places " \
     "SET foreign_count = foreign_count + 1 " \
     "WHERE id = NEW.fk;" \
   "END" \
 )
 
 #define CREATE_BOOKMARKS_FOREIGNCOUNT_AFTERUPDATE_TRIGGER NS_LITERAL_CSTRING( \
   "CREATE TEMP TRIGGER moz_bookmarks_foreign_count_afterupdate_trigger " \
new file mode 100644
--- /dev/null
+++ b/toolkit/components/places/tests/bookmarks/test_removeFolderTransaction_reinsert.js
@@ -0,0 +1,70 @@
+/**
+ * This test ensures that reinserting a folder within a transaction gives it
+ * a different GUID, and passes the GUID to the observers.
+ */
+
+add_task(function* test_removeFolderTransaction_reinsert() {
+  let folder = yield PlacesUtils.bookmarks.insert({
+    type: PlacesUtils.bookmarks.TYPE_FOLDER,
+    parentGuid: PlacesUtils.bookmarks.menuGuid,
+    title: "Test folder",
+  });
+  let folderId = yield PlacesUtils.promiseItemId(folder.guid);
+  let fx = yield PlacesUtils.bookmarks.insert({
+    parentGuid: folder.guid,
+    title: "Get Firefox!",
+    url: "http://getfirefox.com",
+  });
+  let fxId = yield PlacesUtils.promiseItemId(fx.guid);
+  let tb = yield PlacesUtils.bookmarks.insert({
+    parentGuid: folder.guid,
+    title: "Get Thunderbird!",
+    url: "http://getthunderbird.com",
+  });
+  let tbId = yield PlacesUtils.promiseItemId(tb.guid);
+
+  let notifications = [];
+  function checkNotifications(expected, message) {
+    deepEqual(notifications, expected, message);
+    notifications.length = 0;
+  }
+
+  let observer = {
+    onItemAdded(itemId, parentId, index, type, uri, title, dateAdded, guid,
+                parentGuid) {
+      notifications.push(["onItemAdded", itemId, parentId, guid, parentGuid]);
+    },
+    onItemRemoved(itemId, parentId, index, type, uri, guid, parentGuid) {
+      notifications.push(["onItemRemoved", itemId, parentId, guid, parentGuid]);
+    },
+  };
+  PlacesUtils.bookmarks.addObserver(observer, false);
+  PlacesUtils.registerShutdownFunction(function() {
+    PlacesUtils.bookmarks.removeObserver(observer);
+  });
+
+  let transaction = PlacesUtils.bookmarks.getRemoveFolderTransaction(folderId);
+  deepEqual(notifications, [], "We haven't executed the transaction yet");
+
+  transaction.doTransaction();
+  checkNotifications([
+    ["onItemRemoved", tbId, folderId, tb.guid, folder.guid],
+    ["onItemRemoved", fxId, folderId, fx.guid, folder.guid],
+    ["onItemRemoved", folderId, PlacesUtils.bookmarksMenuFolderId, folder.guid,
+      PlacesUtils.bookmarks.menuGuid],
+  ], "Executing transaction should remove folder and its descendants");
+
+  transaction.undoTransaction();
+  // At this point, the restored folder has the same ID, but a different GUID.
+  let newFolderGuid = yield PlacesUtils.promiseItemGuid(folderId);
+  checkNotifications([
+    ["onItemAdded", folderId, PlacesUtils.bookmarksMenuFolderId, newFolderGuid,
+      PlacesUtils.bookmarks.menuGuid],
+  ], "Undo should reinsert folder with same ID and different GUID");
+
+  transaction.redoTransaction();
+  checkNotifications([
+    ["onItemRemoved", folderId, PlacesUtils.bookmarksMenuFolderId,
+      newFolderGuid, PlacesUtils.bookmarks.menuGuid],
+  ], "Redo should forward new GUID to observer");
+});
--- a/toolkit/components/places/tests/bookmarks/xpcshell.ini
+++ b/toolkit/components/places/tests/bookmarks/xpcshell.ini
@@ -40,10 +40,11 @@ skip-if = toolkit == 'android' || toolki
 [test_bookmarks_reorder.js]
 [test_bookmarks_search.js]
 [test_bookmarks_update.js]
 [test_changeBookmarkURI.js]
 [test_getBookmarkedURIFor.js]
 [test_keywords.js]
 [test_nsINavBookmarkObserver.js]
 [test_protectRoots.js]
+[test_removeFolderTransaction_reinsert.js]
 [test_removeItem.js]
 [test_savedsearches.js]
--- a/toolkit/modules/FinderHighlighter.jsm
+++ b/toolkit/modules/FinderHighlighter.jsm
@@ -15,16 +15,17 @@ Cu.import("resource://gre/modules/XPCOMU
 XPCOMUtils.defineLazyModuleGetter(this, "Color", "resource://gre/modules/Color.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "Rect", "resource://gre/modules/Geometry.jsm");
 XPCOMUtils.defineLazyGetter(this, "kDebug", () => {
   const kDebugPref = "findbar.modalHighlight.debug";
   return Services.prefs.getPrefType(kDebugPref) && Services.prefs.getBoolPref(kDebugPref);
 });
 
 const kContentChangeThresholdPx = 5;
+const kBrightTextSampleSize = 5;
 const kModalHighlightRepaintFreqMs = 100;
 const kHighlightAllPref = "findbar.highlightAll";
 const kModalHighlightPref = "findbar.modalHighlight";
 const kFontPropsCSS = ["color", "font-family", "font-kerning", "font-size",
   "font-size-adjust", "font-stretch", "font-variant", "font-weight", "line-height",
   "letter-spacing", "text-emphasis", "text-orientation", "text-transform", "word-spacing"];
 const kFontPropsCamelCase = kFontPropsCSS.map(prop => {
   let parts = prop.split("-");
@@ -52,37 +53,27 @@ const kModalStyles = {
   ],
   outlineNodeDebug: [ ["z-index", 2147483647] ],
   outlineText: [
     ["margin", "0 !important"],
     ["padding", "0 !important"],
     ["vertical-align", "top !important"]
   ],
   maskNode: [
-    ["background", "#000"],
-    ["opacity", ".35"],
+    ["background", "rgba(0,0,0,.35)"],
     ["pointer-events", "none"],
     ["position", "absolute"],
     ["z-index", 1]
   ],
   maskNodeDebug: [
     ["z-index", 2147483646],
     ["top", 0],
     ["left", 0]
   ],
-  maskNodeBrightText: [ ["background", "#fff"] ],
-  maskRect: [
-    ["background", "#fff"],
-    ["border-radius", "3px"],
-    ["margin", "-1px 0 0 -1px !important"],
-    ["padding", "0 1px 2px 1px !important"],
-    ["position", "absolute"],
-    ["white-space", "nowrap"]
-  ],
-  maskRectBrightText: [ ["background", "#000"] ]
+  maskNodeBrightText: [ ["background", "rgba(255,255,255,.35)"] ]
 };
 const kModalOutlineAnim = {
   "keyframes": [
     { transform: "scaleX(1) scaleY(1)" },
     { transform: "scaleX(1.5) scaleY(1.5)", offset: .5, easing: "ease-in" },
     { transform: "scaleX(1) scaleY(1)" }
   ],
   duration: 50,
@@ -111,16 +102,19 @@ function mockAnonymousContentNode(domNod
     },
     remove() {
       try {
         domNode.parentNode.removeChild(domNode);
       } catch (ex) {}
     },
     setAnimationForElement(id, keyframes, duration) {
       return (domNode.querySelector("#" + id) || domNode).animate(keyframes, duration);
+    },
+    setCutoutRectsForElement(id, rects) {
+      // no-op for now.
     }
   };
 }
 
 let gWindows = new Map();
 
 /**
  * FinderHighlighter class that is used by Finder.jsm to take care of the
@@ -359,17 +353,16 @@ FinderHighlighter.prototype = {
     if (dict.modalHighlightOutline) {
       dict.modalHighlightOutline.setAttributeForElement(kModalOutlineId, "style",
         dict.modalHighlightOutline.getAttributeForElement(kModalOutlineId, "style") +
         "; opacity: 0");
     }
 
     this._removeHighlightAllMask(window);
     this._removeModalHighlightListeners(window);
-    delete dict.brightText;
 
     dict.visible = false;
   },
 
   /**
    * Called by the Finder after a find result comes in; update the position and
    * content of the outline to the newly found occurrence.
    * To make sure that the outline covers the found range completely, all the
@@ -415,30 +408,25 @@ FinderHighlighter.prototype = {
       dict.currentFoundRange = foundRange;
 
       let textContent = this._getRangeContentArray(foundRange);
       if (!textContent.length) {
         this.hide(window);
         return;
       }
 
-      let fontStyle = this._getRangeFontStyle(foundRange);
-      if (typeof dict.brightText == "undefined") {
-        dict.brightText = this._isColorBright(fontStyle.color);
-      }
-
       if (data.findAgain)
         dict.updateAllRanges = true;
 
       if (!dict.visible)
         this.show(window);
       else
         this._maybeCreateModalHighlightNodes(window);
 
-      this._updateRangeOutline(dict, textContent, fontStyle);
+      this._updateRangeOutline(dict, textContent);
     }
 
     let outlineNode = dict.modalHighlightOutline;
     if (outlineNode) {
       if (dict.animation)
         dict.animation.finish();
       dict.animation = outlineNode.setAnimationForElement(kModalOutlineId,
         Cu.cloneInto(kModalOutlineAnim.keyframes, window), kModalOutlineAnim.duration);
@@ -464,16 +452,17 @@ FinderHighlighter.prototype = {
     }
 
     let dict = this.getForWindow(window.top);
     if (dict.animation)
       dict.animation.finish();
     dict.dynamicRangesSet.clear();
     dict.frames.clear();
     dict.modalHighlightRectsMap.clear();
+    dict.brightText = null;
   },
 
   /**
    * When the current page is refreshed or navigated away from, the CanvasFrame
    * contents is not valid anymore, i.e. all anonymous content is destroyed.
    * We need to clear the references we keep, which'll make sure we redraw
    * everything when the user starts to find in page again.
    */
@@ -567,17 +556,17 @@ FinderHighlighter.prototype = {
    *
    * @param  {nsIDOMWindow} window          Window to read the boundary rect from
    * @param  {Boolean}      [includeScroll] Whether to ignore the scroll offset,
    *                                        which is useful for comparing DOMRects.
    *                                        Optional, defaults to `true`
    * @return {Rect}
    */
   _getRootBounds(window, includeScroll = true) {
-    let dwu = this._getDWU(window);
+    let dwu = this._getDWU(window.top);
     let cssPageRect = Rect.fromRect(dwu.getRootBounds());
     let scrollX = {};
     let scrollY = {};
     if (includeScroll) {
       dwu.getScrollXY(false, scrollX, scrollY);
       cssPageRect.translate(scrollX.value, scrollY.value);
     }
 
@@ -714,43 +703,89 @@ FinderHighlighter.prototype = {
     cssColor = cssColor.match(kRGBRE);
     if (!cssColor || !cssColor.length)
       return false;
     cssColor.shift();
     return new Color(...cssColor).isBright;
   },
 
   /**
+   * Detects if the overall text color in the page can be described as bright.
+   * This is done according to the following algorithm:
+   *  1. With the entire set of ranges that we have found thusfar;
+   *  2. Get an odd-numbered `sampleSize`, with a maximum of `kBrightTextSampleSize`
+   *     ranges,
+   *  3. Slice the set of ranges into `sampleSize` number of equal parts,
+   *  4. Grab the first range for each slice and inspect the brightness of the
+   *     color of its text content.
+   *  5. When the majority of ranges are counted as contain bright colored text,
+   *     the page is considered to contain bright text overall.
+   *
+   * @param {Object} dict Dictionary of properties belonging to the
+   *                      currently active window. The page text color property
+   *                      will be recorded in `dict.brightText` as `true` or `false`.
+   */
+  _detectBrightText(dict) {
+    let sampleSize = Math.min(dict.modalHighlightRectsMap.size, kBrightTextSampleSize);
+    let ranges = [...dict.modalHighlightRectsMap.keys()];
+    let rangesCount = ranges.length;
+    // Make sure the sample size is an odd number.
+    if (sampleSize % 2 == 0) {
+      // Make the currently found range weigh heavier.
+      if (dict.currentFoundRange) {
+        ranges.push(dict.currentFoundRange);
+        ++sampleSize;
+        ++rangesCount;
+      } else {
+        --sampleSize;
+      }
+    }
+    let brightCount = 0;
+    for (let i = 0; i < sampleSize; ++i) {
+      let range = ranges[Math.floor((rangesCount / sampleSize) * i)];
+      let fontStyle = this._getRangeFontStyle(range);
+      if (this._isColorBright(fontStyle.color))
+        ++brightCount;
+    }
+
+    dict.brightText = (brightCount >= Math.ceil(sampleSize / 2));
+  },
+
+  /**
    * Checks if a range is inside a DOM node that's positioned in a way that it
    * doesn't scroll along when the document is scrolled and/ or zoomed. This
-   * is the case for 'fixed' and 'sticky' positioned elements and elements inside
-   * (i)frames.
+   * is the case for 'fixed' and 'sticky' positioned elements, elements inside
+   * (i)frames and elements that have their overflow styles set to 'auto' or
+   * 'scroll'.
    *
    * @param  {nsIDOMRange} range Range that be enclosed in a dynamic container
    * @return {Boolean}
    */
   _isInDynamicContainer(range) {
-    const kFixed = new Set(["fixed", "sticky"]);
+    const kFixed = new Set(["fixed", "sticky", "scroll", "auto"]);
     let node = range.startContainer;
     while (node.nodeType != 1)
       node = node.parentNode;
     let document = node.ownerDocument;
     let window = document.defaultView;
     let dict = this.getForWindow(window.top);
 
     // Check if we're in a frameset (including iframes).
     if (window != window.top) {
       if (!dict.frames.has(window))
         dict.frames.set(window, null);
       return true;
     }
 
     do {
-      if (kFixed.has(window.getComputedStyle(node, null).position))
+      let style = window.getComputedStyle(node, null);
+      if (kFixed.has(style.position) || kFixed.has(style.overflow) ||
+          kFixed.has(style.overflowX) || kFixed.has(style.overflowY)) {
         return true;
+      }
       node = node.parentNode;
     } while (node && node != document.documentElement)
 
     return false;
   },
 
   /**
    * Read and store the rectangles that encompass the entire region of a range
@@ -819,21 +854,21 @@ FinderHighlighter.prototype = {
   /**
    * Re-read the rectangles of the ranges that we keep track of separately,
    * because they're enclosed by a position: fixed container DOM node or (i)frame.
    *
    * @param {Object} dict Dictionary of properties belonging to the currently
    *                      active window
    */
   _updateDynamicRangesRects(dict) {
-    for (let range of dict.dynamicRangesSet)
-      this._updateRangeRects(range, false, dict);
     // Reset the frame bounds cache.
     for (let frame of dict.frames.keys())
       dict.frames.set(frame, null);
+    for (let range of dict.dynamicRangesSet)
+      this._updateRangeRects(range, false, dict);
   },
 
   /**
    * Update the content, position and style of the yellow current found range
    * outline that floats atop the mask with the dimmed background.
    * Rebuild it, if necessary, This will deactivate the animation between
    * occurrences.
    *
@@ -1001,59 +1036,57 @@ FinderHighlighter.prototype = {
    * the ranges that were found.
    *
    * @param {nsIDOMWindow} window Window to draw in.
    * @param {Boolean} [paintContent]
    */
   _repaintHighlightAllMask(window, paintContent = true) {
     window = window.top;
     let dict = this.getForWindow(window);
-    let document = window.document;
 
     const kMaskId = kModalIdPrefix + "-findbar-modalHighlight-outlineMask";
-    let maskNode = document.createElementNS(kNSHTML, "div");
+    if (!dict.modalHighlightAllMask) {
+      let document = window.document;
+      let maskNode = document.createElementNS(kNSHTML, "div");
+      maskNode.setAttribute("id", kMaskId);
+      dict.modalHighlightAllMask = kDebug ?
+        mockAnonymousContentNode((document.body || document.documentElement).appendChild(maskNode)) :
+        document.insertAnonymousContent(maskNode);
+    }
 
     // Make sure the dimmed mask node takes the full width and height that's available.
     let {width, height} = dict.lastWindowDimensions = this._getWindowDimensions(window);
-    maskNode.setAttribute("id", kMaskId);
-    maskNode.setAttribute("style", this._getStyleString(kModalStyles.maskNode,
+    if (typeof dict.brightText != "boolean" || dict.updateAllRanges)
+      this._detectBrightText(dict);
+    let maskStyle = this._getStyleString(kModalStyles.maskNode,
       [ ["width", width + "px"], ["height", height + "px"] ],
       dict.brightText ? kModalStyles.maskNodeBrightText : [],
-      kDebug ? kModalStyles.maskNodeDebug : []));
+      kDebug ? kModalStyles.maskNodeDebug : []);
+    dict.modalHighlightAllMask.setAttributeForElement(kMaskId, "style", maskStyle);
     if (dict.brightText)
-      maskNode.setAttribute("brighttext", "true");
+      dict.modalHighlightAllMask.setAttributeForElement(kMaskId, "brighttext", "true");
 
+    let allRects = [];
     if (paintContent || dict.modalHighlightAllMask) {
       this._updateRangeOutline(dict);
       this._updateDynamicRangesRects(dict);
-      // Create a DOM node for each rectangle representing the ranges we found.
-      let maskContent = [];
-      const rectStyle = this._getStyleString(kModalStyles.maskRect,
-        dict.brightText ? kModalStyles.maskRectBrightText : []);
+
+      let DOMRect = window.DOMRect;
       for (let [range, rects] of dict.modalHighlightRectsMap) {
         if (dict.updateAllRanges)
           rects = this._updateRangeRects(range);
         if (this._checkOverlap(dict.currentFoundRange, range))
           continue;
-        for (let rect of rects) {
-          maskContent.push(`<div xmlns="${kNSHTML}" style="${rectStyle}; top: ${rect.y}px;
-            left: ${rect.x}px; height: ${rect.height}px; width: ${rect.width}px;"></div>`);
-        }
+        for (let rect of rects)
+          allRects.push(new DOMRect(rect.x, rect.y, rect.width, rect.height));
       }
       dict.updateAllRanges = false;
-      maskNode.innerHTML = maskContent.join("");
     }
 
-    // Always remove the current mask and insert it a-fresh, because we're not
-    // free to alter DOM nodes inside the CanvasFrame.
-    this._removeHighlightAllMask(window);
-
-    dict.modalHighlightAllMask = kDebug ?
-      mockAnonymousContentNode((document.body || document.documentElement).appendChild(maskNode)) :
-      document.insertAnonymousContent(maskNode);
+    dict.modalHighlightAllMask.setCutoutRectsForElement(kMaskId, allRects);
   },
 
   /**
    * Safely remove the mask AnoymousContent node from the CanvasFrame.
    *
    * @param {nsIDOMWindow} window
    */
   _removeHighlightAllMask(window) {
@@ -1093,17 +1126,17 @@ FinderHighlighter.prototype = {
    */
   _scheduleRepaintOfMask(window, { contentChanged, scrollOnly, updateAllRanges } =
                                  { contentChanged: false, scrollOnly: false, updateAllRanges: false }) {
     if (!this._modal)
       return;
 
     window = window.top;
     let dict = this.getForWindow(window);
-    let repaintDynamicRanges = (scrollOnly && !!dict.dynamicRangesSet.size);
+    let repaintDynamicRanges = ((scrollOnly || contentChanged) && !!dict.dynamicRangesSet.size);
 
     // When we request to repaint unconditionally, we mean to call
     // `_repaintHighlightAllMask()` right after the timeout.
     if (!dict.unconditionalRepaintRequested)
       dict.unconditionalRepaintRequested = !contentChanged || repaintDynamicRanges;
     // Some events, like a resize, call for recalculation of all the rects of all ranges.
     if (!dict.updateAllRanges)
       dict.updateAllRanges = updateAllRanges;
--- a/toolkit/modules/tests/browser/browser_FinderHighlighter.js
+++ b/toolkit/modules/tests/browser/browser_FinderHighlighter.js
@@ -64,16 +64,17 @@ function promiseTestHighlighterOutput(br
 
     return new Promise((resolve, reject) => {
       let stubbed = {};
       let callCounts = {
         insertCalls: [],
         removeCalls: []
       };
       let lastMaskNode, lastOutlineNode;
+      let rects = [];
 
       // Amount of milliseconds to wait after the last time one of our stubs
       // was called.
       const kTimeoutMs = 1000;
       // The initial timeout may wait for a while for results to come in.
       let timeout = setTimeout(() => finish(false, "Timeout"), kTimeoutMs * 5);
 
       function finish(ok = true, message = "finished with error") {
@@ -98,28 +99,56 @@ function promiseTestHighlighterOutput(br
           `Max. remove calls should match for '${word}'.`);
 
         // We reached the amount of calls we expected, so now we can check
         // the amount of rects.
         if (!lastMaskNode && expectedResult.rectCount !== 0) {
           Assert.ok(false, `No mask node found, but expected ${expectedResult.rectCount} rects.`);
         }
 
-        if (lastMaskNode) {
-          Assert.equal(lastMaskNode.getElementsByTagName("div").length,
-            expectedResult.rectCount, `Amount of inserted rects should match for '${word}'.`);
-        }
+        Assert.equal(rects.length, expectedResult.rectCount,
+          `Amount of inserted rects should match for '${word}'.`);
 
         // Allow more specific assertions to be tested in `extraTest`.
         extraTest = eval(extraTest);
-        extraTest(lastMaskNode, lastOutlineNode);
+        extraTest(lastMaskNode, lastOutlineNode, rects);
 
         resolve();
       }
 
+      function stubAnonymousContentNode(domNode, anonNode) {
+        let originals = [anonNode.setTextContentForElement,
+          anonNode.setAttributeForElement, anonNode.removeAttributeForElement,
+          anonNode.setCutoutRectsForElement];
+        anonNode.setTextContentForElement = (id, text) => {
+          try {
+            (domNode.querySelector("#" + id) || domNode).textContent = text;
+          } catch (ex) {}
+          return originals[0].call(anonNode, id, text);
+        };
+        anonNode.setAttributeForElement = (id, attrName, attrValue) => {
+          try {
+            (domNode.querySelector("#" + id) || domNode).setAttribute(attrName, attrValue);
+          } catch (ex) {}
+          return originals[1].call(anonNode, id, attrName, attrValue);
+        };
+        anonNode.removeAttributeForElement = (id, attrName) => {
+          try {
+            let node = domNode.querySelector("#" + id) || domNode;
+            if (node.hasAttribute(attrName))
+              node.removeAttribute(attrName);
+          } catch (ex) {}
+          return originals[2].call(anonNode, id, attrName);
+        };
+        anonNode.setCutoutRectsForElement = (id, cutoutRects) => {
+          rects = cutoutRects;
+          return originals[3].call(anonNode, id, cutoutRects);
+        };
+      }
+
       // Create a function that will stub the original version and collects
       // the arguments so we can check the results later.
       function stub(which) {
         stubbed[which] = content.document[which + "AnonymousContent"];
         let prop = which + "Calls";
         return function(node) {
           callCounts[prop].push(node);
           if (which == "insert") {
@@ -127,17 +156,20 @@ function promiseTestHighlighterOutput(br
               lastMaskNode = node;
             else
               lastOutlineNode = node;
           }
           clearTimeout(timeout);
           timeout = setTimeout(() => {
             finish();
           }, kTimeoutMs);
-          return stubbed[which].call(content.document, node);
+          let res = stubbed[which].call(content.document, node);
+          if (which == "insert")
+            stubAnonymousContentNode(node, res);
+          return res;
         };
       }
       content.document.insertAnonymousContent = stub("insert");
       content.document.removeAnonymousContent = stub("remove");
     });
   });
 }
 
@@ -149,41 +181,41 @@ add_task(function* setup() {
 });
 
 // Test the results of modal highlighting, which is on by default.
 add_task(function* testModalResults() {
   let tests = new Map([
     ["Roland", {
       rectCount: 1,
       insertCalls: [2, 4],
-      removeCalls: [1, 2]
+      removeCalls: [0, 1]
     }],
     ["their law might propagate their kind", {
       rectCount: 0,
-      insertCalls: [31, 32],
-      removeCalls: [31, 32],
-      extraTest: function(maskNode, outlineNode) {
-        Assert.equal(outlineNode.getElementsByTagName("div").length, 3,
+      insertCalls: [28, 31],
+      removeCalls: [28, 30],
+      extraTest: function(maskNode, outlineNode, rects) {
+        Assert.equal(outlineNode.getElementsByTagName("div").length, 2,
           "There should be multiple rects drawn");
       }
     }],
     ["ro", {
       rectCount: 40,
       insertCalls: [1, 4],
       removeCalls: [1, 3]
     }],
     ["new", {
       rectCount: 1,
       insertCalls: [1, 4],
-      removeCalls: [1, 3]
+      removeCalls: [0, 2]
     }],
     ["o", {
       rectCount: 491,
-      insertCalls: [3, 7],
-      removeCalls: [3, 6]
+      insertCalls: [1, 4],
+      removeCalls: [0, 2]
     }]
   ]);
   let url = kFixtureBaseURL + "file_FinderSample.html";
   yield BrowserTestUtils.withNewTab(url, function* (browser) {
     let findbar = gBrowser.getFindBar();
 
     for (let [word, expectedResult] of tests) {
       yield promiseOpenFindbar(findbar);
@@ -209,17 +241,17 @@ add_task(function* testModalSwitching() 
 
     yield promiseOpenFindbar(findbar);
     Assert.ok(!findbar.hidden, "Findbar should be open now.");
 
     let word = "Roland";
     let expectedResult = {
       rectCount: 1,
       insertCalls: [2, 4],
-      removeCalls: [1, 2]
+      removeCalls: [0, 1]
     };
     let promise = promiseTestHighlighterOutput(browser, word, expectedResult);
     yield promiseEnterStringIntoFindField(findbar, word);
     yield promise;
 
     yield SpecialPowers.pushPrefEnv({ "set": [[ kPrefModalHighlight, false ]] });
 
     expectedResult = {
@@ -244,18 +276,18 @@ add_task(function* testDarkPageDetection
   yield BrowserTestUtils.withNewTab(url, function* (browser) {
     let findbar = gBrowser.getFindBar();
 
     yield promiseOpenFindbar(findbar);
 
     let word = "Roland";
     let expectedResult = {
       rectCount: 1,
-      insertCalls: [2, 4],
-      removeCalls: [1, 2]
+      insertCalls: [1, 3],
+      removeCalls: [0, 1]
     };
     let promise = promiseTestHighlighterOutput(browser, word, expectedResult, function(node) {
       Assert.ok(!node.hasAttribute("brighttext"), "White HTML page shouldn't have 'brighttext' set");
     });
     yield promiseEnterStringIntoFindField(findbar, word);
     yield promise;
 
     findbar.close(true);
@@ -265,17 +297,17 @@ add_task(function* testDarkPageDetection
     let findbar = gBrowser.getFindBar();
 
     yield promiseOpenFindbar(findbar);
 
     let word = "Roland";
     let expectedResult = {
       rectCount: 1,
       insertCalls: [2, 4],
-      removeCalls: [1, 2]
+      removeCalls: [0, 1]
     };
 
     yield ContentTask.spawn(browser, null, function* () {
       let dwu = content.QueryInterface(Ci.nsIInterfaceRequestor)
         .getInterface(Ci.nsIDOMWindowUtils);
       let uri = "data:text/css;charset=utf-8," + encodeURIComponent(`
         body {
           background: maroon radial-gradient(circle, #a01010 0%, #800000 80%) center center / cover no-repeat;
@@ -302,17 +334,17 @@ add_task(function* testHighlightAllToggl
     let findbar = gBrowser.getFindBar();
 
     yield promiseOpenFindbar(findbar);
 
     let word = "Roland";
     let expectedResult = {
       rectCount: 1,
       insertCalls: [2, 4],
-      removeCalls: [1, 2]
+      removeCalls: [0, 1]
     };
     let promise = promiseTestHighlighterOutput(browser, word, expectedResult);
     yield promiseEnterStringIntoFindField(findbar, word);
     yield promise;
 
     // We now know we have multiple rectangles highlighted, so it's a good time
     // to flip the pref.
     expectedResult = {
@@ -322,18 +354,18 @@ add_task(function* testHighlightAllToggl
     };
     promise = promiseTestHighlighterOutput(browser, word, expectedResult);
     yield SpecialPowers.pushPrefEnv({ "set": [[ kHighlightAllPref, false ]] });
     yield promise;
 
     // For posterity, let's switch back.
     expectedResult = {
       rectCount: 2,
-      insertCalls: [2, 4],
-      removeCalls: [1, 2]
+      insertCalls: [1, 3],
+      removeCalls: [0, 1]
     };
     promise = promiseTestHighlighterOutput(browser, word, expectedResult);
     yield SpecialPowers.pushPrefEnv({ "set": [[ kHighlightAllPref, true ]] });
     yield promise;
   });
 });
 
 add_task(function* testXMLDocument() {
@@ -346,17 +378,17 @@ add_task(function* testXMLDocument() {
     let findbar = gBrowser.getFindBar();
 
     yield promiseOpenFindbar(findbar);
 
     let word = "Example";
     let expectedResult = {
       rectCount: 0,
       insertCalls: [1, 4],
-      removeCalls: [1, 2]
+      removeCalls: [0, 1]
     };
     let promise = promiseTestHighlighterOutput(browser, word, expectedResult);
     yield promiseEnterStringIntoFindField(findbar, word);
     yield promise;
 
     findbar.close(true);
   });
 });
@@ -368,17 +400,17 @@ add_task(function* testHideOnLocationCha
   let findbar = gBrowser.getFindBar();
 
   yield promiseOpenFindbar(findbar);
 
   let word = "Roland";
   let expectedResult = {
     rectCount: 1,
     insertCalls: [2, 4],
-    removeCalls: [1, 2]
+    removeCalls: [0, 1]
   };
   let promise = promiseTestHighlighterOutput(browser, word, expectedResult);
   yield promiseEnterStringIntoFindField(findbar, word);
   yield promise;
 
   // Now we try to navigate away! (Using the same page)
   promise = promiseTestHighlighterOutput(browser, word, {
     rectCount: 0,