Merge m-c to fx-team. a=merge
authorRyan VanderMeulen <ryanvm@gmail.com>
Thu, 26 May 2016 11:23:44 -0400
changeset 338098 5fdfce654062b471415a82f1a2c07200fc463dcf
parent 338097 9cfc40ea2dbd0e258ec58028d85f4b358162e64a (current diff)
parent 338093 b0096c5c727749ad3e79cbdf20d2e96bd179c213 (diff)
child 338099 1af204777b4ddb868882d972663a13cbb324ea0b
push id6249
push userjlund@mozilla.com
push dateMon, 01 Aug 2016 13:59:36 +0000
treeherdermozilla-beta@bad9d4f5bf7e [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone49.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 m-c to fx-team. a=merge
gfx/layers/apz/test/mochitest/test_basic_pan.html
--- a/Makefile.in
+++ b/Makefile.in
@@ -304,22 +304,22 @@ ifdef MOZ_CRASHREPORTER
 	  $(foreach dir,$(SYM_STORE_SOURCE_DIRS),-s $(dir))               \
 	  $(DUMP_SYMS_BIN)                                                \
 	  $(DIST)/crashreporter-symbols                                   \
 	  $(MAKE_SYM_STORE_PATH) | grep -iv test >                        \
 	  $(DIST)/crashreporter-symbols/$(SYMBOL_INDEX_NAME)
 	echo packing symbols
 	$(NSINSTALL) -D $(DIST)/$(PKG_PATH)
 	cd $(DIST)/crashreporter-symbols && \
-          zip -r9D '../$(PKG_PATH)$(SYMBOL_FULL_ARCHIVE_BASENAME).zip' . -x '*test*' -x '*Test*'
+          zip -r5D '../$(PKG_PATH)$(SYMBOL_FULL_ARCHIVE_BASENAME).zip' . -x '*test*' -x '*Test*'
 	cd $(DIST)/crashreporter-symbols && \
 	grep 'sym' $(SYMBOL_INDEX_NAME) > $(SYMBOL_INDEX_NAME).tmp && \
 	  mv $(SYMBOL_INDEX_NAME).tmp $(SYMBOL_INDEX_NAME)
 	cd $(DIST)/crashreporter-symbols && \
-          zip -r9D '../$(PKG_PATH)$(SYMBOL_ARCHIVE_BASENAME).zip' . -i '*.sym' -i '*.txt'  -x '*test*' -x '*Test*'
+          zip -r5D '../$(PKG_PATH)$(SYMBOL_ARCHIVE_BASENAME).zip' . -i '*.sym' -i '*.txt'  -x '*test*' -x '*Test*'
 endif # MOZ_CRASHREPORTER
 
 uploadsymbols:
 ifdef MOZ_CRASHREPORTER
 ifdef SOCORRO_SYMBOL_UPLOAD_TOKEN_FILE
 	$(PYTHON) -u $(topsrcdir)/toolkit/crashreporter/tools/upload_symbols.py '$(DIST)/$(PKG_PATH)$(SYMBOL_FULL_ARCHIVE_BASENAME).zip'
 endif
 endif
--- a/accessible/base/EventTree.cpp
+++ b/accessible/base/EventTree.cpp
@@ -151,20 +151,17 @@ TreeMutation::Done()
   }
 #endif
 
   for (uint32_t idx = mStartIdx; idx < length; idx++) {
     mParent->mChildren[idx]->mInt.mIndexOfEmbeddedChild = -1;
     mParent->mChildren[idx]->mStateFlags |= Accessible::eGroupInfoDirty;
   }
 
-  if (mStartIdx < mParent->mChildren.Length() - 1) {
-    mParent->mEmbeddedObjCollector = nullptr;
-  }
-
+  mParent->mEmbeddedObjCollector = nullptr;
   mParent->mStateFlags |= mStateFlagsCopy & Accessible::eKidsMutating;
 
 #ifdef DEBUG
   mIsDone = true;
 #endif
 
 #ifdef A11Y_LOG
   if (mEventTree != kNoEventTree && logging::IsEnabled(logging::eEventTree)) {
--- a/accessible/tests/browser/.eslintrc
+++ b/accessible/tests/browser/.eslintrc
@@ -7,16 +7,17 @@
     // Content scripts have global 'content' object
     "content": true,
 
     // Defined in accessible/tests/mochitest/ common.js, name.js, states.js
     "prettyName": true,
     "statesToString": true,
     "eventTypeToString": true,
     "testName": true,
+    "testDescr": true,
     "testStates": true,
     "testAccessibleTree": true,
     "isAccessible": true,
     "getAccessibleDOMNodeID": true,
 
     // Defined for all accessibility browser tests.
     "addAccessibleTask": true,
     "BrowserTestUtils": true,
--- a/accessible/tests/browser/browser.ini
+++ b/accessible/tests/browser/browser.ini
@@ -9,16 +9,17 @@ support-files =
   doc_treeupdate_removal.xhtml
   doc_treeupdate_visibility.html
   doc_treeupdate_whitespace.html
   !/accessible/tests/mochitest/*.js
   !/accessible/tests/mochitest/letters.gif
   !/accessible/tests/mochitest/moz.png
 
 # Caching tests
+[browser_caching_description.js]
 [browser_caching_name.js]
 skip-if = e10s
 
 # Events tests
 [browser_events_caretmove.js]
 [browser_events_hide.js]
 [browser_events_show.js]
 [browser_events_statechange.js]
new file mode 100644
--- /dev/null
+++ b/accessible/tests/browser/browser_caching_description.js
@@ -0,0 +1,164 @@
+/* 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';
+
+/* global EVENT_DESCRIPTION_CHANGE, EVENT_NAME_CHANGE, EVENT_REORDER */
+
+loadScripts({ name: 'name.js', dir: MOCHITESTS_DIR });
+
+/**
+ * Test data has the format of:
+ * {
+ *   desc      {String}   description for better logging
+ *   expected  {String}   expected description value for a given accessible
+ *   attrs     {?Array}   an optional list of attributes to update
+ *   waitFor   {?Array}   an optional list of accessible events to wait for when
+ *                        attributes are updated
+ * }
+ */
+const tests = [{
+  desc: 'No description when there are no @alt, @title and @aria-describedby',
+  expected: ''
+}, {
+  desc: 'Description from @aria-describedby attribute',
+  attrs: [{
+    attr: 'aria-describedby',
+    value: 'description'
+  }],
+  waitFor: [{ eventType: EVENT_DESCRIPTION_CHANGE, id: 'image' }],
+  expected: 'aria description'
+}, {
+  desc: 'No description from @aria-describedby since it is the same as the ' +
+        '@alt attribute which is used as the name',
+  attrs: [{
+    attr: 'alt',
+    value: 'aria description'
+  }],
+  waitFor: [{ eventType: EVENT_REORDER, id: 'body' }],
+  expected: ''
+}, {
+  desc: 'Description from @aria-describedby attribute when @alt and ' +
+        '@aria-describedby are not the same',
+  attrs: [{
+    attr: 'aria-describedby',
+    value: 'description2'
+  }],
+  waitFor: [{ eventType: EVENT_DESCRIPTION_CHANGE, id: 'image' }],
+  expected: 'another description'
+}, {
+  desc: 'Description from @aria-describedby attribute when @title (used for ' +
+        'name) and @aria-describedby are not the same',
+  attrs: [{
+    attr: 'alt'
+  }, {
+    attr: 'title',
+    value: 'title'
+  }],
+  waitFor: [{ eventType: EVENT_REORDER, id: 'body' }],
+  expected: 'another description'
+}, {
+  desc: 'No description from @aria-describedby since it is the same as the ' +
+        '@title attribute which is used as the name',
+  attrs: [{
+    attr: 'title',
+    value: 'another description'
+  }],
+  waitFor: [{ eventType: EVENT_NAME_CHANGE, id: 'image' }],
+  expected: ''
+}, {
+  desc: 'No description with only @title attribute which is used as the name',
+  attrs: [{
+    attr: 'aria-describedby'
+  }],
+  waitFor: [{ eventType: EVENT_DESCRIPTION_CHANGE, id: 'image' }],
+  expected: ''
+}, {
+  desc: 'Description from @title attribute when @alt and @atitle are not the ' +
+        'same',
+  attrs: [{
+    attr: 'alt',
+    value: 'aria description'
+  }],
+  waitFor: [{ eventType: EVENT_REORDER, id: 'body' }],
+  expected: 'another description'
+}, {
+  desc: 'No description from @title since it is the same as the @alt ' +
+        'attribute which is used as the name',
+  attrs: [{
+    attr: 'alt',
+    value: 'another description'
+  }],
+  waitFor: [{ eventType: EVENT_NAME_CHANGE, id: 'image' }],
+  expected: ''
+}, {
+  desc: 'No description from @aria-describedby since it is the same as the ' +
+        '@alt (used for name) and @title attributes',
+  attrs: [{
+    attr: 'aria-describedby',
+    value: 'description2'
+  }],
+  waitFor: [{ eventType: EVENT_DESCRIPTION_CHANGE, id: 'image' }],
+  expected: ''
+}, {
+  desc: 'Description from @aria-describedby attribute when it is different ' +
+        'from @alt (used for name) and @title attributes',
+  attrs: [{
+    attr: 'aria-describedby',
+    value: 'description'
+  }],
+  waitFor: [{ eventType: EVENT_DESCRIPTION_CHANGE, id: 'image' }],
+  expected: 'aria description'
+}, {
+  desc: 'No description from @aria-describedby since it is the same as the ' +
+        '@alt attribute (used for name) but different from title',
+  attrs: [{
+    attr: 'alt',
+    value: 'aria description'
+  }],
+  waitFor: [{ eventType: EVENT_NAME_CHANGE, id: 'image' }],
+  expected: ''
+}, {
+  desc: 'Description from @aria-describedby attribute when @alt (used for ' +
+        'name) and @aria-describedby are not the same but @title and ' +
+        'aria-describedby are',
+  attrs: [{
+    attr: 'aria-describedby',
+    value: 'description2'
+  }],
+  waitFor: [{ eventType: EVENT_DESCRIPTION_CHANGE, id: 'image' }],
+  expected: 'another description'
+}];
+
+/**
+ * Test caching of accessible object description
+ */
+addAccessibleTask(`
+  <p id="description">aria description</p>
+  <p id="description2">another description</p>
+  <img id="image" />`,
+  function*(browser, accDoc) {
+    let imgAcc = findAccessibleChildByID(accDoc, 'image');
+
+    for (let { desc, waitFor, attrs, expected } of tests) {
+      info(desc);
+      let onUpdate;
+      if (waitFor) {
+        onUpdate = waitForMultipleEvents(waitFor);
+      }
+      if (attrs) {
+        for (let { attr, value } of attrs) {
+          yield invokeSetAttribute(browser, 'image', attr, value);
+        }
+      }
+      yield onUpdate;
+      // When attribute change (alt) triggers reorder event, accessible will
+      // become defunct.
+      if (isDefunct(imgAcc)) {
+        imgAcc = findAccessibleChildByID(accDoc, 'image');
+      }
+      testDescr(imgAcc, expected);
+    }
+  }
+);
--- a/accessible/tests/browser/events.js
+++ b/accessible/tests/browser/events.js
@@ -4,26 +4,29 @@
 
 'use strict';
 
 /* global nsIAccessibleEvent, nsIAccessibleDocument,
           nsIAccessibleStateChangeEvent, nsIAccessibleTextChangeEvent */
 
 /* exported EVENT_REORDER, EVENT_SHOW, EVENT_TEXT_INSERTED, EVENT_TEXT_REMOVED,
             EVENT_DOCUMENT_LOAD_COMPLETE, EVENT_HIDE, EVENT_TEXT_CARET_MOVED,
-            EVENT_STATE_CHANGE, waitForEvent, waitForMultipleEvents */
+            EVENT_DESCRIPTION_CHANGE, EVENT_NAME_CHANGE, EVENT_STATE_CHANGE,
+            waitForEvent, waitForMultipleEvents */
 
 const EVENT_DOCUMENT_LOAD_COMPLETE = nsIAccessibleEvent.EVENT_DOCUMENT_LOAD_COMPLETE;
 const EVENT_HIDE = nsIAccessibleEvent.EVENT_HIDE;
 const EVENT_REORDER = nsIAccessibleEvent.EVENT_REORDER;
 const EVENT_SHOW = nsIAccessibleEvent.EVENT_SHOW;
 const EVENT_STATE_CHANGE = nsIAccessibleEvent.EVENT_STATE_CHANGE;
 const EVENT_TEXT_CARET_MOVED = nsIAccessibleEvent.EVENT_TEXT_CARET_MOVED;
 const EVENT_TEXT_INSERTED = nsIAccessibleEvent.EVENT_TEXT_INSERTED;
 const EVENT_TEXT_REMOVED = nsIAccessibleEvent.EVENT_TEXT_REMOVED;
+const EVENT_DESCRIPTION_CHANGE = nsIAccessibleEvent.EVENT_DESCRIPTION_CHANGE;
+const EVENT_NAME_CHANGE = nsIAccessibleEvent.EVENT_NAME_CHANGE;
 
 /**
  * Describe an event in string format.
  * @param  {nsIAccessibleEvent}  event  event to strigify
  */
 function eventToString(event) {
   let type = eventTypeToString(event.eventType);
   let info = `Event type: ${type}`;
--- a/accessible/tests/browser/head.js
+++ b/accessible/tests/browser/head.js
@@ -82,23 +82,29 @@ let Logger = {
 };
 
 /**
  * Check if an accessible object has a defunct test.
  * @param  {nsIAccessible}  accessible object to test defunct state for
  * @return {Boolean}        flag indicating defunct state
  */
 function isDefunct(accessible) {
+  let defunct = false;
   try {
     let extState = {};
     accessible.getState({}, extState);
-    return extState.value & Ci.nsIAccessibleStates.EXT_STATE_DEFUNCT;
+    defunct = extState.value & Ci.nsIAccessibleStates.EXT_STATE_DEFUNCT;
   } catch (x) {
-    return true;
+    defunct = true;
+  } finally {
+    if (defunct) {
+      Logger.log(`Defunct accessible: ${prettyName(accessible)}`);
+    }
   }
+  return defunct;
 }
 
 /**
  * Asynchronously set or remove content element's attribute (in content process
  * if e10s is enabled).
  * @param  {Object}  browser  current "tabbrowser" element
  * @param  {String}  id       content element id
  * @param  {String}  attr     attribute name
--- a/accessible/tests/mochitest/events/a11y.ini
+++ b/accessible/tests/mochitest/events/a11y.ini
@@ -45,18 +45,20 @@ skip-if = os == 'win' || os == 'linux'
 [test_menu.xul]
 [test_mutation.html]
 [test_mutation.xhtml]
 [test_namechange.xul]
 [test_namechange.html]
 [test_scroll.xul]
 [test_scroll_caret.xul]
 [test_selection.html]
-skip-if = buildapp == 'mulet'
+skip-if = buildapp == 'mulet' || os == 'mac'
 [test_selection.xul]
+skip-if = os == 'mac'
 [test_selection_aria.html]
 [test_statechange.html]
 [test_text.html]
 [test_text_alg.html]
 [test_textattrchange.html]
 [test_textselchange.html]
 [test_tree.xul]
 [test_valuechange.html]
+skip-if = os == 'mac'
--- a/accessible/tests/mochitest/hypertext/test_update.html
+++ b/accessible/tests/mochitest/hypertext/test_update.html
@@ -146,27 +146,59 @@
       }
 
       this.getID = function removeFirstChild_getID()
       {
         return "Remove first child and check embedded object indeces";
       }
     }
 
+    function removeLastChild(aContainer)
+    {
+      this.ht = getAccessible(aContainer, [ nsIAccessibleHyperText ]);
+      this.eventSeq = [
+        new invokerChecker(EVENT_REORDER, aContainer)
+      ];
+
+      this.invoke = function removeLastChild_invoke()
+      {
+        is(this.ht.linkCount, 1, "Wrong embedded objects count before removal");
+
+        getNode(aContainer).removeChild(getNode(aContainer).lastElementChild);
+      }
+
+      this.finalCheck = function removeLastChild_finalCheck()
+      {
+        is(this.ht.linkCount, 0, "Wrong embedded objects count after removal");
+
+        var link = null;
+        try {
+          link = this.ht.getLinkAt(0);
+        } catch (e) { }
+        ok(!link, "No embedded object is expected");
+      }
+
+      this.getID = function removeLastChild_getID()
+      {
+        return "Remove last child and try its embedded object";
+      }
+    }
+
     //gA11yEventDumpToConsole = true; // debug stuff
 
     var gQueue = null;
     function doTest()
     {
       gQueue = new eventQueue();
       gQueue.push(new addLinks("p1"));
       gQueue.push(new updateText("p2"));
       gQueue.push(new removeChild("div1","div2",
                                   "hello my good friend", "hello friend"));
       gQueue.push(new removeFirstChild("c4"));
+      gQueue.push(new removeLastChild("c5"));
 
       gQueue.invoke(); // Will call SimpleTest.finish();
     }
 
     SimpleTest.waitForExplicitFinish();
     addA11yLoadEvent(doTest);
   </script>
 </head>
@@ -194,10 +226,11 @@
 
   <p id="p1"></p>
   <p id="p2"><b>hello</b><a>friend</a></p>
   <div id="div1">hello<span id="div2"> my<span id="div3"> good</span></span> friend</span></div>
   <form id="c4">
     <label for="c4_input">label</label>
     <input id="c4_input">
   </form>
+  <div id="c5">TextLeaf<input id="c5_input"></div>
 </body>
 </html>
--- a/devtools/client/canvasdebugger/test/browser.ini
+++ b/devtools/client/canvasdebugger/test/browser.ini
@@ -20,18 +20,21 @@ support-files =
 [browser_canvas-actor-test-02.js]
 [browser_canvas-actor-test-03.js]
 [browser_canvas-actor-test-04.js]
 [browser_canvas-actor-test-05.js]
 [browser_canvas-actor-test-06.js]
 [browser_canvas-actor-test-07.js]
 [browser_canvas-actor-test-08.js]
 [browser_canvas-actor-test-09.js]
+subsuite = gpu
 [browser_canvas-actor-test-10.js]
+subsuite = gpu
 [browser_canvas-actor-test-11.js]
+subsuite = gpu
 [browser_canvas-actor-test-12.js]
 [browser_canvas-frontend-call-highlight.js]
 [browser_canvas-frontend-call-list.js]
 [browser_canvas-frontend-call-search.js]
 [browser_canvas-frontend-call-stack-01.js]
 [browser_canvas-frontend-call-stack-02.js]
 [browser_canvas-frontend-call-stack-03.js]
 [browser_canvas-frontend-clear.js]
@@ -50,8 +53,9 @@ support-files =
 [browser_canvas-frontend-snapshot-select-01.js]
 [browser_canvas-frontend-snapshot-select-02.js]
 [browser_canvas-frontend-stepping.js]
 [browser_canvas-frontend-stop-01.js]
 [browser_canvas-frontend-stop-02.js]
 [browser_canvas-frontend-stop-03.js]
 [browser_profiling-canvas.js]
 [browser_profiling-webgl.js]
+subsuite = gpu
--- a/devtools/client/performance/test/browser_perf-tree-abstract-01.js
+++ b/devtools/client/performance/test/browser_perf-tree-abstract-01.js
@@ -33,17 +33,17 @@ add_task(function* () {
     "The root node's target is a child of the container node.");
 
   is(treeRoot.root, treeRoot,
     "The root node has the correct root.");
   is(treeRoot.parent, null,
     "The root node has the correct parent.");
   is(treeRoot.level, 0,
     "The root node has the correct level.");
-  is(treeRoot.target.marginInlineStart, "0px",
+  is(treeRoot.target.style.marginInlineStart, "0px",
     "The root node's indentation is correct.");
   is(treeRoot.target.textContent, "root",
     "The root node's text contents are correct.");
   is(treeRoot.container, container,
     "The root node's container is correct.");
 
   // Expand the root and test the child items...
 
@@ -72,30 +72,30 @@ add_task(function* () {
     "The 'bar' node's target is a child of the container node.");
 
   is(fooItem.root, treeRoot,
     "The 'foo' node has the correct root.");
   is(fooItem.parent, treeRoot,
     "The 'foo' node has the correct parent.");
   is(fooItem.level, 1,
     "The 'foo' node has the correct level.");
-  is(fooItem.target.marginInlineStart, "10px",
+  is(fooItem.target.style.marginInlineStart, "10px",
     "The 'foo' node's indentation is correct.");
   is(fooItem.target.textContent, "foo",
     "The 'foo' node's text contents are correct.");
   is(fooItem.container, container,
     "The 'foo' node's container is correct.");
 
   is(barItem.root, treeRoot,
     "The 'bar' node has the correct root.");
   is(barItem.parent, treeRoot,
     "The 'bar' node has the correct parent.");
   is(barItem.level, 1,
     "The 'bar' node has the correct level.");
-  is(barItem.target.marginInlineStart, "10px",
+  is(barItem.target.style.marginInlineStart, "10px",
     "The 'bar' node's indentation is correct.");
   is(barItem.target.textContent, "bar",
     "The 'bar' node's text contents are correct.");
   is(barItem.container, container,
     "The 'bar' node's container is correct.");
 
   // Test clicking on the `foo` node...
 
@@ -138,17 +138,17 @@ add_task(function* () {
     "The 'baz' node's target is a child of the container node.");
 
   is(bazItem.root, treeRoot,
     "The 'baz' node has the correct root.");
   is(bazItem.parent, barItem,
     "The 'baz' node has the correct parent.");
   is(bazItem.level, 2,
     "The 'baz' node has the correct level.");
-  is(bazItem.target.marginInlineStart, "20px",
+  is(bazItem.target.style.marginInlineStart, "20px",
     "The 'baz' node's indentation is correct.");
   is(bazItem.target.textContent, "baz",
     "The 'baz' node's text contents are correct.");
   is(bazItem.container, container,
     "The 'baz' node's container is correct.");
 
   container.remove();
 });
--- a/devtools/client/performance/test/helpers/synth-utils.js
+++ b/devtools/client/performance/test/helpers/synth-utils.js
@@ -60,17 +60,17 @@ exports.synthesizeCustomTreeClass = () =
   function MyCustomTreeItem(dataSrc, properties) {
     AbstractTreeItem.call(this, properties);
     this.itemDataSrc = dataSrc;
   }
 
   MyCustomTreeItem.prototype = Heritage.extend(AbstractTreeItem.prototype, {
     _displaySelf: function (document, arrowNode) {
       let node = document.createElement("hbox");
-      node.marginInlineStart = (this.level * 10) + "px";
+      node.style.marginInlineStart = (this.level * 10) + "px";
       node.appendChild(arrowNode);
       node.appendChild(document.createTextNode(this.itemDataSrc.label));
       return node;
     },
 
     _populateSelf: function (children) {
       for (let childDataSrc of this.itemDataSrc.children) {
         children.push(new MyCustomTreeItem(childDataSrc, {
--- a/devtools/client/shared/widgets/AbstractTreeItem.jsm
+++ b/devtools/client/shared/widgets/AbstractTreeItem.jsm
@@ -45,17 +45,17 @@ this.EXPORTED_SYMBOLS = ["AbstractTreeIt
  *   _displaySelf: function(document, arrowNode) {
  *     let node = document.createElement("hbox");
  *     ...
  *     // Append the provided arrow node wherever you want.
  *     node.appendChild(arrowNode);
  *     ...
  *     // Use `this.itemDataSrc` to customize the tree item and
  *     // `this.level` to calculate the indentation.
- *     node.marginInlineStart = (this.level * 10) + "px";
+ *     node.style.marginInlineStart = (this.level * 10) + "px";
  *     node.appendChild(document.createTextNode(this.itemDataSrc.label));
  *     ...
  *     return node;
  *   },
  *   _populateSelf: function(children) {
  *     ...
  *     // Use `this.itemDataSrc` to get the data source for the child items.
  *     let someChildDataSrc = this.itemDataSrc.children[0];
--- a/devtools/client/themes/webconsole.css
+++ b/devtools/client/themes/webconsole.css
@@ -409,17 +409,20 @@ a {
   padding: 0;
   padding-inline-start: 20px;
   margin: 0;
   -moz-appearance: none;
   background-color: transparent;
 }
 
 .jsterm-input-node[focused="true"] {
-  box-shadow: var(--theme-focus-box-shadow-inset-bottom);
+  outline: var(--theme-focus-outline);
+  outline-offset: -1px;
+  transition: none;
+  box-shadow: none;
 }
 
 .jsterm-complete-node {
   color: var(--theme-comment);
 }
 
 .jsterm-input-node {
   /* Always allow scrolling on input - it auto expands in js by setting height,
--- a/dom/base/nsDocument.cpp
+++ b/dom/base/nsDocument.cpp
@@ -12816,16 +12816,23 @@ nsDocument::GetVisibilityState() const
   // 1)  Are we in bfcache (!IsVisible())?  If so, nothing else matters.
   // 2)  Do we have an outer window?  If not, we're hidden.  Note that we don't
   //     want to use GetWindow here because it does weird groveling for windows
   //     in some cases.
   // 3)  Is our outer window background?  If so, we're hidden.
   // Otherwise, we're visible.
   if (!IsVisible() || !mWindow || !mWindow->GetOuterWindow() ||
       mWindow->GetOuterWindow()->IsBackground()) {
+
+    // Check if the document is in prerender state.
+    nsCOMPtr<nsIDocShell> docshell = GetDocShell();
+    if (docshell && docshell->GetIsPrerendered()) {
+      return dom::VisibilityState::Prerender;
+    }
+
     return dom::VisibilityState::Hidden;
   }
 
   return dom::VisibilityState::Visible;
 }
 
 /* virtual */ void
 nsDocument::PostVisibilityUpdateEvent()
--- a/dom/bindings/test/mochitest.ini
+++ b/dom/bindings/test/mochitest.ini
@@ -48,16 +48,17 @@ tags = webrtc
 [test_named_getter_enumerability.html]
 [test_Object.prototype_props.html]
 [test_queryInterface.html]
 [test_returnUnion.html]
 skip-if = debug == false
 [test_usvstring.html]
 skip-if = debug == false
 [test_sequence_wrapping.html]
+subsuite = gpu
 [test_setWithNamedGetterNoNamedSetter.html]
 [test_throwing_method_noDCE.html]
 [test_treat_non_object_as_null.html]
 [test_traceProtos.html]
 [test_sequence_detection.html]
 skip-if = debug == false
 [test_exception_options_from_jsimplemented.html]
 skip-if = debug == false
--- a/dom/canvas/WebGLContext.cpp
+++ b/dom/canvas/WebGLContext.cpp
@@ -892,21 +892,21 @@ WebGLContext::SetDimensions(int32_t sign
     if (disabled) {
         Telemetry::Accumulate(Telemetry::CANVAS_WEBGL_FAILURE_ID,
                               NS_LITERAL_CSTRING("FEATURE_FAILURE_WEBGL_DISABLED"));
         const nsLiteralCString text("WebGL is currently disabled.");
         ThrowEvent_WebGLContextCreationError(text);
         return NS_ERROR_FAILURE;
     }
 
-    bool failIfPerfCaveat = mOptions.failIfMajorPerformanceCaveat;
-    if (gfxPrefs::WebGLDisableFailIfMajorPerformanceCaveat())
-        failIfPerfCaveat = false;
+    if (gfxPrefs::WebGLDisableFailIfMajorPerformanceCaveat()) {
+        mOptions.failIfMajorPerformanceCaveat = false;
+    }
 
-    if (failIfPerfCaveat) {
+    if (mOptions.failIfMajorPerformanceCaveat) {
         nsCOMPtr<nsIGfxInfo> gfxInfo = services::GetGfxInfo();
         if (!HasAcceleratedLayers(gfxInfo)) {
             Telemetry::Accumulate(Telemetry::CANVAS_WEBGL_FAILURE_ID,
                                   NS_LITERAL_CSTRING("FEATURE_FAILURE_WEBGL_PERF_CAVEAT"));
             const nsLiteralCString text("failIfMajorPerformanceCaveat: Compositor is not"
                                         " hardware-accelerated.");
             ThrowEvent_WebGLContextCreationError(text);
             return NS_ERROR_FAILURE;
@@ -923,18 +923,26 @@ WebGLContext::SetDimensions(int32_t sign
     if (!CreateAndInitGL(forceEnabled, &failReason, &failureId)) {
         Telemetry::Accumulate(Telemetry::CANVAS_WEBGL_FAILURE_ID, failureId);
         const nsPrintfCString text("WebGL creation failed: %s",
                                    failReason.BeginReading());
         ThrowEvent_WebGLContextCreationError(text);
         return NS_ERROR_FAILURE;
     }
     MOZ_ASSERT(gl);
+    MOZ_ASSERT_IF(mOptions.alpha, gl->Caps().alpha);
 
-    MOZ_ASSERT_IF(mOptions.alpha, gl->Caps().alpha);
+    if (mOptions.failIfMajorPerformanceCaveat) {
+        if (gl->IsWARP()) {
+            const nsLiteralCString text("failIfMajorPerformanceCaveat: Driver is not"
+                                        " hardware-accelerated.");
+            ThrowEvent_WebGLContextCreationError(text);
+            return NS_ERROR_FAILURE;
+        }
+    }
 
     if (!ResizeBackbuffer(width, height)) {
         Telemetry::Accumulate(Telemetry::CANVAS_WEBGL_FAILURE_ID,
                               NS_LITERAL_CSTRING("FEATURE_FAILURE_WEBGL_RESIZE"));
         const nsLiteralCString text("Initializing WebGL backbuffer failed.");
         ThrowEvent_WebGLContextCreationError(text);
         return NS_ERROR_FAILURE;
     }
--- a/dom/canvas/test/chrome/chrome.ini
+++ b/dom/canvas/test/chrome/chrome.ini
@@ -1,7 +1,8 @@
 [DEFAULT]
 skip-if = buildapp == 'b2g' || os == 'android'
 support-files = nonchrome_webgl_debug_renderer_info.html
 
 [test_webgl_debug_renderer_info.html]
+subsuite = gpu
 [test_drawWindow_widget_layers.html]
 support-files = ../file_drawWindow_source.html ../file_drawWindow_common.js
--- a/dom/canvas/test/crossorigin/mochitest.ini
+++ b/dom/canvas/test/crossorigin/mochitest.ini
@@ -4,9 +4,12 @@ support-files =
   image-allow-credentials.png^headers^
   image-allow-star.png
   image-allow-star.png^headers^
   image.png
   video.sjs
 
 [test_canvas2d_crossorigin.html]
 [test_video_crossorigin.html]
+subsuite = gpu
 [test_webgl_crossorigin_textures.html]
+subsuite = gpu
+
--- a/dom/canvas/test/mochitest.ini
+++ b/dom/canvas/test/mochitest.ini
@@ -200,16 +200,17 @@ disabled = bug 407107
 [test_bug613794.html]
 [test_bug753758.html]
 [test_bug764125.html]
 [test_bug856472.html]
 [test_bug866575.html]
 skip-if = (toolkit == 'gonk' && debug) #bug 1045153
 [test_bug902651.html]
 [test_bug1215072.html]
+subsuite = gpu
 [test_canvas.html]
 skip-if = (toolkit == 'gonk' && debug) || (android_version == '18' && debug) #debug-only crash; bug 933541 #android 4.3 debug bug 1143317
 [test_canvas_focusring.html]
 skip-if = (toolkit == 'gonk' && !debug) || os == 'win' #specialpowers.wrap
 [test_canvas_font_setter.html]
 [test_canvas_path.html]
 [test_hitregion_canvas.html]
 [test_hitregion_event.html]
@@ -251,32 +252,38 @@ tags = imagebitmap
 [test_windingRuleUndefined.html]
 [test_2d.fillText.gradient.html]
 skip-if = (buildapp == 'b2g' && toolkit != 'gonk') # bug 1040965
 [test_2d_composite_canvaspattern_setTransform.html]
 [test_createPattern_broken.html]
 [test_setlinedash.html]
 [test_filter.html]
 [test_offscreencanvas_toblob.html]
+subsuite = gpu
 tags = offscreencanvas
 [test_offscreencanvas_toimagebitmap.html]
+subsuite = gpu
 tags = offscreencanvas
 [test_offscreencanvas_basic_webgl.html]
+subsuite = gpu
 tags = offscreencanvas
 [test_offscreencanvas_dynamic_fallback.html]
+subsuite = gpu
 tags = offscreencanvas
 skip-if = (os == 'mac' && os_version == '10.6')
 [test_offscreencanvas_sharedworker.html]
 tags = offscreencanvas
 skip-if = (os == 'mac' && os_version == '10.6')
 [test_offscreencanvas_serviceworker.html]
+subsuite = gpu
 tags = offscreencanvas
 skip-if = (buildapp == 'b2g' || (os == 'mac' && os_version == '10.6'))
 [test_offscreencanvas_neuter.html]
 tags = offscreencanvas
 [test_offscreencanvas_many.html]
 tags = offscreencanvas
 skip-if = (toolkit == 'android' || toolkit == 'gonk' || toolkit == 'windows' || toolkit == 'gtk2' || toolkit == 'gtk3' || (os == 'mac' && os_version == '10.6'))
 [test_offscreencanvas_sizechange.html]
+subsuite = gpu
 tags = offscreencanvas
 [test_offscreencanvas_subworker.html]
 tags = offscreencanvas
 skip-if = (toolkit == 'android' || toolkit == 'gonk' || toolkit == 'windows' || toolkit == 'gtk2' || toolkit == 'gtk3' || (os == 'mac' && os_version == '10.6'))
--- a/dom/html/test/forms/mochitest.ini
+++ b/dom/html/test/forms/mochitest.ini
@@ -82,17 +82,17 @@ skip-if = buildapp == 'mulet'
 [test_pattern_attribute.html]
 [test_progress_element.html]
 [test_radio_in_label.html]
 [test_radio_radionodelist.html]
 [test_required_attribute.html]
 [test_restore_form_elements.html]
 [test_save_restore_radio_groups.html]
 [test_select_change_event.html]
-skip-if = android_version == '18'
+skip-if = android_version == '18' || os == 'mac'
 [test_select_selectedOptions.html]
 [test_select_validation.html]
 [test_set_range_text.html]
 [test_step_attribute.html]
 [test_stepup_stepdown.html]
 [test_textarea_attributes_reflection.html]
 [test_validation.html]
 skip-if = buildapp == 'b2g' # b2g(374 total, bug 901848, no keygen support) b2g-debug(374 total, bug 901848, no keygen support) b2g-desktop(374 total, bug 901848, no keygen support)
--- a/dom/html/test/mochitest.ini
+++ b/dom/html/test/mochitest.ini
@@ -367,17 +367,17 @@ skip-if = buildapp == 'mulet' # TC: Bug 
 [test_bug612730.html]
 skip-if = buildapp == 'mulet' || buildapp == 'b2g' || toolkit == 'android' # b2g(form control not selected/checked with synthesizeMouse, also fails on Android) b2g-debug(form control not selected/checked with synthesizeMouse, also fails on Android) b2g-desktop(form control not selected/checked with synthesizeMouse, also fails on Android)
 [test_bug613113.html]
 skip-if = buildapp == 'b2g' # b2g(bug 587671, need an invalidformsubmit observer) b2g-debug(bug 587671, need an invalidformsubmit observer) b2g-desktop(bug 587671, need an invalidformsubmit observer)
 [test_bug613722.html]
 [test_bug613979.html]
 [test_bug615595.html]
 [test_bug615833.html]
-skip-if = buildapp == 'mulet' || buildapp == 'b2g' || toolkit == 'android' #TIMED_OUT # b2g(form control not selected/checked with synthesizeMouse, also fails on Android) b2g-debug(form control not selected/checked with synthesizeMouse, also fails on Android) b2g-desktop(form control not selected/checked with synthesizeMouse, also fails on Android)
+skip-if = buildapp == 'mulet' || buildapp == 'b2g' || toolkit == 'android' || os == 'mac' #TIMED_OUT # b2g(form control not selected/checked with synthesizeMouse, also fails on Android) b2g-debug(form control not selected/checked with synthesizeMouse, also fails on Android) b2g-desktop(form control not selected/checked with synthesizeMouse, also fails on Android) osx(bug 1275664)
 [test_bug617528.html]
 [test_bug618948.html]
 skip-if = buildapp == 'b2g' # b2g(bug 587671, need an invalidformsubmit observer) b2g-debug(bug 587671, need an invalidformsubmit observer) b2g-desktop(bug 587671, need an invalidformsubmit observer)
 [test_bug619278.html]
 skip-if = buildapp == 'b2g' # b2g(bug 587671, need an invalidformsubmit observer) b2g-debug(bug 587671, need an invalidformsubmit observer) b2g-desktop(bug 587671, need an invalidformsubmit observer)
 [test_bug622558.html]
 [test_bug622597.html]
 skip-if = buildapp == 'b2g' # b2g(bug 587671, need an invalidformsubmit observer) b2g-debug(bug 587671, need an invalidformsubmit observer) b2g-desktop(bug 587671, need an invalidformsubmit observer)
--- a/dom/imptests/html/mochitest.ini
+++ b/dom/imptests/html/mochitest.ini
@@ -1,19 +1,25 @@
 # THIS FILE IS AUTOGENERATED BY importTestsuite.py - DO NOT EDIT
 [DEFAULT]
 support-files =
   webgl/common.js
 
 [typedarrays/test_constructors.html]
 [webgl/test_bufferSubData.html]
+subsuite = gpu
 skip-if = (toolkit == 'android') || (buildapp == 'b2g') #android(WebGL)
 [webgl/test_compressedTexImage2D.html]
+subsuite = gpu
 skip-if = (toolkit == 'android') || (buildapp == 'b2g') #android(WebGL)
 [webgl/test_compressedTexSubImage2D.html]
+subsuite = gpu
 skip-if = true # Bug 1226336
 #skip-if = (toolkit == 'android') || (buildapp == 'b2g') #android(WebGL)
 [webgl/test_texImage2D.html]
+subsuite = gpu
 skip-if = (toolkit == 'android') || (buildapp == 'b2g') #android(WebGL)
 [webgl/test_texSubImage2D.html]
+subsuite = gpu
 skip-if = (toolkit == 'android') || (buildapp == 'b2g') #android(WebGL)
 [webgl/test_uniformMatrixNfv.html]
+subsuite = gpu
 skip-if = (toolkit == 'android') || (buildapp == 'b2g') #android(WebGL)
--- a/dom/media/MediaDecoderStateMachine.cpp
+++ b/dom/media/MediaDecoderStateMachine.cpp
@@ -207,17 +207,17 @@ static void
 InitSuspendBackgroundPref()
 {
   MOZ_ASSERT(NS_IsMainThread(), "Must be on main thread.");
 
   static bool sSetupPrefCache = false;
   if (!sSetupPrefCache) {
     sSetupPrefCache = true;
     Preferences::AddBoolVarCache(&sSuspendBackgroundVideos,
-                                 "media.suspend-bkgnd-video.enabled", true);
+                                 "media.suspend-bkgnd-video.enabled", false);
   }
 }
 
 MediaDecoderStateMachine::MediaDecoderStateMachine(MediaDecoder* aDecoder,
                                                    MediaDecoderReader* aReader,
                                                    bool aRealTime) :
   mDecoderID(aDecoder),
   mFrameStats(&aDecoder->GetFrameStatistics()),
--- a/dom/media/webrtc/MediaEngineWebRTC.h
+++ b/dom/media/webrtc/MediaEngineWebRTC.h
@@ -427,20 +427,16 @@ public:
     , mAudioInput(aAudioInput)
     , mMonitor("WebRTCMic.Monitor")
     , mThread(aThread)
     , mCapIndex(aIndex)
     , mChannel(-1)
     , mNrAllocations(0)
     , mStarted(false)
     , mSampleFrequency(MediaEngine::DEFAULT_SAMPLE_RATE)
-    , mEchoOn(false), mAgcOn(false), mNoiseOn(false)
-    , mEchoCancel(webrtc::kEcDefault)
-    , mAGC(webrtc::kAgcDefault)
-    , mNoiseSuppress(webrtc::kNsDefault)
     , mPlayoutDelay(0)
     , mNullTransport(nullptr)
     , mInputBufferLen(0) {
     MOZ_ASSERT(aVoiceEnginePtr);
     MOZ_ASSERT(aAudioInput);
     mDeviceName.Assign(NS_ConvertUTF8toUTF16(name));
     mDeviceUUID.Assign(uuid);
     mListener = new mozilla::WebRTCAudioDataListener(this);
@@ -549,20 +545,16 @@ private:
   int mNrAllocations; // Per-channel - When this becomes 0, we shut down HW for the channel
   TrackID mTrackID;
   bool mStarted;
 
   nsString mDeviceName;
   nsCString mDeviceUUID;
 
   uint32_t mSampleFrequency;
-  bool mEchoOn, mAgcOn, mNoiseOn;
-  webrtc::EcModes  mEchoCancel;
-  webrtc::AgcModes mAGC;
-  webrtc::NsModes  mNoiseSuppress;
   int32_t mPlayoutDelay;
 
   NullTransport *mNullTransport;
 
   // For full_duplex packetizer output
   size_t mInputBufferLen;
   UniquePtr<int16_t[]> mInputBuffer;
 };
--- a/dom/media/webrtc/MediaEngineWebRTCAudio.cpp
+++ b/dom/media/webrtc/MediaEngineWebRTCAudio.cpp
@@ -272,62 +272,34 @@ MediaEngineWebRTCMicrophoneSource::Resta
   bool noise_on = c.mMozNoiseSuppression.Get(aPrefs.mNoiseOn);
 
   LOG(("Audio config: aec: %d, agc: %d, noise: %d, delay: %d",
        aec_on ? aPrefs.mAec : -1,
        agc_on ? aPrefs.mAgc : -1,
        noise_on ? aPrefs.mNoise : -1,
        aPrefs.mPlayoutDelay));
 
-  bool update_echo = (mEchoOn != aec_on);
-  bool update_agc = (mAgcOn != agc_on);
-  bool update_noise = (mNoiseOn != noise_on);
-  mEchoOn = aec_on;
-  mAgcOn = agc_on;
-  mNoiseOn = noise_on;
-
   mPlayoutDelay = aPrefs.mPlayoutDelay;
-  if ((webrtc::EcModes) aPrefs.mAec != webrtc::kEcUnchanged) {
-    if (mEchoCancel != (webrtc::EcModes) aPrefs.mAec) {
-      update_echo = true;
-      mEchoCancel = (webrtc::EcModes) aPrefs.mAec;
-    }
-  }
-  if ((webrtc::AgcModes) aPrefs.mAgc != webrtc::kAgcUnchanged) {
-    if (mAGC != (webrtc::AgcModes) aPrefs.mAgc) {
-      update_agc = true;
-      mAGC = (webrtc::AgcModes) aPrefs.mAgc;
-    }
-  }
-  if ((webrtc::NsModes) aPrefs.mNoise != webrtc::kNsUnchanged) {
-    if (mNoiseSuppress != (webrtc::NsModes) aPrefs.mNoise) {
-      update_noise = true;
-      mNoiseSuppress = (webrtc::NsModes) aPrefs.mNoise;
-    }
-  }
 
   if (sChannelsOpen > 0) {
     int error;
 
-    if (update_echo &&
-      0 != (error = mVoEProcessing->SetEcStatus(mEchoOn, (webrtc::EcModes) aPrefs.mAec))) {
+    if (0 != (error = mVoEProcessing->SetEcStatus(aec_on, (webrtc::EcModes) aPrefs.mAec))) {
       LOG(("%s Error setting Echo Status: %d ",__FUNCTION__, error));
       // Overhead of capturing all the time is very low (<0.1% of an audio only call)
-      if (mEchoOn) {
+      if (aec_on) {
         if (0 != (error = mVoEProcessing->SetEcMetricsStatus(true))) {
           LOG(("%s Error setting Echo Metrics: %d ",__FUNCTION__, error));
         }
       }
     }
-    if (update_agc &&
-      0 != (error = mVoEProcessing->SetAgcStatus(mAgcOn, (webrtc::AgcModes) aPrefs.mAgc))) {
+    if (0 != (error = mVoEProcessing->SetAgcStatus(agc_on, (webrtc::AgcModes) aPrefs.mAgc))) {
       LOG(("%s Error setting AGC Status: %d ",__FUNCTION__, error));
     }
-    if (update_noise &&
-      0 != (error = mVoEProcessing->SetNsStatus(mNoiseOn, (webrtc::NsModes) aPrefs.mNoise))) {
+    if (0 != (error = mVoEProcessing->SetNsStatus(noise_on, (webrtc::NsModes) aPrefs.mNoise))) {
       LOG(("%s Error setting NoiseSuppression Status: %d ",__FUNCTION__, error));
     }
   }
   return NS_OK;
 }
 
 nsresult
 MediaEngineWebRTCMicrophoneSource::Deallocate()
--- a/dom/network/TCPSocket.cpp
+++ b/dom/network/TCPSocket.cpp
@@ -402,17 +402,31 @@ TCPSocket::EnsureCopying()
   RefPtr<CopierCallbacks> callbacks = new CopierCallbacks(this);
   return mMultiplexStreamCopier->AsyncCopy(callbacks, nullptr);
 }
 
 void
 TCPSocket::NotifyCopyComplete(nsresult aStatus)
 {
   mAsyncCopierActive = false;
-  mMultiplexStream->RemoveStream(0);
+  
+  uint32_t countRemaining;
+  nsresult rvRemaining = mMultiplexStream->GetCount(&countRemaining);
+  NS_ENSURE_SUCCESS_VOID(rvRemaining);
+
+  while (countRemaining--) {
+    mMultiplexStream->RemoveStream(0);
+  }
+
+  while (!mPendingDataWhileCopierActive.IsEmpty()) {
+      nsCOMPtr<nsIInputStream> stream = mPendingDataWhileCopierActive[0];
+      mMultiplexStream->AppendStream(stream);
+      mPendingDataWhileCopierActive.RemoveElementAt(0);
+  }
+  
   if (mSocketBridgeParent) {
     mozilla::Unused << mSocketBridgeParent->SendUpdateBufferedAmount(BufferedAmount(),
                                                                      mTrackingNumber);
   }
 
   if (NS_FAILED(aStatus)) {
     MaybeReportErrorAndCloseIfOpen(aStatus);
     return;
@@ -900,16 +914,19 @@ TCPSocket::Send(nsIInputStream* aStream,
     mBufferedAmount = newBufferedAmount;
     return !bufferFull;
   }
 
   if (mWaitingForStartTLS) {
     // When we are waiting for starttls, newStream is added to pendingData
     // and will be appended to multiplexStream after tls had been set up.
     mPendingDataAfterStartTLS.AppendElement(aStream);
+  } else if (mAsyncCopierActive) {
+    // While the AsyncCopier is still active..
+    mPendingDataWhileCopierActive.AppendElement(aStream);
   } else {
     mMultiplexStream->AppendStream(aStream);
   }
 
   EnsureCopying();
 
 #ifdef MOZ_WIDGET_GONK
   // Collect transmitted amount for network statistics.
--- a/dom/network/TCPSocket.h
+++ b/dom/network/TCPSocket.h
@@ -234,16 +234,19 @@ private:
   uint32_t mTrackingNumber;
 
   // True if this socket has been upgraded to secure after the initial connection,
   // but the actual upgrade is waiting for an in-progress copy operation to complete.
   bool mWaitingForStartTLS;
   // The buffered data awaiting the TLS upgrade to finish.
   nsTArray<nsCOMPtr<nsIInputStream>> mPendingDataAfterStartTLS;
 
+  // The data to be sent while AsyncCopier is still active.
+  nsTArray<nsCOMPtr<nsIInputStream>> mPendingDataWhileCopierActive;
+
   bool mObserversActive;
 
 #ifdef MOZ_WIDGET_GONK
   // Number of bytes sent.
   uint32_t mTxBytes;
   // Number of bytes received.
   uint32_t mRxBytes;
   // The app that owns this socket.
--- a/dom/webidl/Document.webidl
+++ b/dom/webidl/Document.webidl
@@ -7,17 +7,17 @@
  */
 
 interface WindowProxy;
 interface nsISupports;
 interface URI;
 interface nsIDocShell;
 interface nsILoadGroup;
 
-enum VisibilityState { "hidden", "visible" };
+enum VisibilityState { "hidden", "visible", "prerender" };
 
 /* http://dom.spec.whatwg.org/#interface-document */
 [Constructor]
 interface Document : Node {
   [Throws]
   readonly attribute DOMImplementation implementation;
   [Pure]
   readonly attribute DOMString URL;
--- a/dom/xul/test/chrome.ini
+++ b/dom/xul/test/chrome.ini
@@ -6,16 +6,18 @@ support-files =
   file_bug236853.rdf
   overlay1_bug335375.xul
   overlay2_bug335375.xul
   window_bug583948.xul
   window_bug757137.xul
   1061864.html
   file_bug1271240_local.xul
   file_bug1271240_remote.xul
+  file_bug1069772_local.xul
+  file_bug1069772_remote.xul
 
 [test_bug199692.xul]
 [test_bug233643.xul]
 [test_bug236853.xul]
 [test_bug311681.xul]
 [test_bug335375.xul]
 [test_bug391002.xul]
 [test_bug398289.html]
@@ -32,8 +34,11 @@ support-files =
 [test_bug775972.xul]
 [test_bug1061864_1.xul]
 [test_bug1061864_2.xul]
 [test_bug1070049_throw_from_script.xul]
 [test_import_xul_to_content.xul]
 [test_bug1271240_local.xul]
 [test_bug1271240_remote.xul]
 skip-if = !e10s
+[test_bug1069772_local.xul]
+[test_bug1069772_remote.xul]
+skip-if = !e10s
new file mode 100644
--- /dev/null
+++ b/dom/xul/test/file_bug1069772_local.xul
@@ -0,0 +1,127 @@
+<?xml version="1.0"?>
+<?xml-stylesheet type="text/css" href="chrome://global/skin"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css"
+                 type="text/css"?>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1069772
+-->
+<window title="Mozilla Bug 1069772"
+        xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" onload="run();">
+
+  <!-- test code goes here -->
+  <script type="application/javascript">
+  <![CDATA[
+
+  const {interfaces: Ci, classes: Cc, results: Cr, utils: Cu} = Components;
+  Cu.import("resource://testing-common/ContentTask.jsm");
+  Cu.import("resource://testing-common/BrowserTestUtils.jsm");
+  Cu.import("resource://gre/modules/Services.jsm");
+  ContentTask.setTestScope(window.opener.wrappedJSObject);
+
+  let imports = ['SimpleTest', 'ok', 'is'];
+  for (let name of imports) {
+    window[name] = window.opener.wrappedJSObject[name];
+  }
+
+  /** Test for Bug 1069772 **/
+  function run() {
+    // test the transition:
+    // prerender => visible => hidden => visible
+    // on a non-remote browser
+    createPrerenderedBrowser(false)
+    .then(browser => verifyBrowser(browser, true, false))
+    .then(browser => verifyVisibility(browser, 'prerender'))
+    .then(browser => makePrerenderedBrowserActive(browser))
+    .then(browser => verifyBrowser(browser, false, false))
+    .then(browser => verifyVisibility(browser, 'visible'))
+    .then(browser => hideBrowser(browser))
+    .then(browser => verifyBrowser(browser, false, false))
+    .then(browser => verifyVisibility(browser, 'hidden'))
+    .then(browser => showBrowser(browser))
+    .then(browser => verifyBrowser(browser, false, false))
+    .then(browser => verifyVisibility(browser, 'visible'))
+
+    // finish test
+    .then(() => {
+      window.close();
+      SimpleTest.finish();
+    });
+  }
+
+  function createPrerenderedBrowser(remote) {
+    let browser = document.createElement('browser');
+    browser.setAttribute('type', 'content');
+    browser.setAttribute('prerendered', true);
+    browser.setAttribute('remote', remote);
+    browser.setAttribute('src', 'data:text/html;charset=UTF-8,<html><body>' +
+     '<iframe id="iframe" src="data:text/html;charset=UTF-8,Hello Frame!">' +
+     '</iframe></body></html>');
+
+    // wait for 'load' and 'pageshow'
+    let promises = [];
+    promises.push(BrowserTestUtils.browserLoaded(browser));
+    promises.push(new Promise(resolve =>
+      Services.mm.addMessageListener('test:pageshow', resolve)));
+    Services.mm.loadFrameScript('data:,' +
+      'addEventListener("pageshow", ' +
+      '() => sendAsyncMessage("test:pageshow", null), false);',
+      true);
+
+    document.getElementById('stack').appendChild(browser);
+    return Promise.all(promises).then(() => browser);
+  }
+
+  function verifyBrowser(browser, prerendered, remote) {
+    let docShellOrTabParent = remote ?
+      browser.frameLoader.tabParent : browser.frameLoader.docShell;
+    ok(docShellOrTabParent, 'docShellOrTabParent should not be null');
+    is(docShellOrTabParent.isPrerendered, prerendered,
+      'isPrerendered should be ' + prerendered);
+    return browser;
+  }
+
+  function verifyVisibility(browser, visibility) {
+    return ContentTask.spawn(browser, visibility, (v) => {
+      let iframe = content.document.getElementById('iframe');
+      is(content.document.visibilityState, v, 'check doc.visibilityState');
+      is(content.document.hidden, v != 'visible', 'check doc.hidden');
+      is(iframe.contentDocument.visibilityState, v, 'check iframe doc.visibilityState');
+      is(iframe.contentDocument.hidden, v != 'visible', 'check iframe doc.hidden');
+    }).then(() => browser);
+  }
+
+  function makePrerenderedBrowserActive(browser) {
+    let promise = waitForVisibilityChange(browser);
+    browser.setAttribute('prerendered', false);
+    browser.makePrerenderedBrowserActive();
+    return promise.then(() => browser);
+  }
+
+  function hideBrowser(browser) {
+    let promise = waitForVisibilityChange(browser);
+    browser.docShellIsActive = false;
+    return promise.then(() => browser);
+  }
+
+  function showBrowser(browser) {
+    let promise = waitForVisibilityChange(browser);
+    browser.docShellIsActive = true;
+    return promise.then(() => browser);
+  }
+
+  function waitForVisibilityChange(browser) {
+    return ContentTask.spawn(browser, null, () => {
+      return new Promise(resolve => {
+        let iframe = content.document.getElementById('iframe');
+        iframe.contentDocument.addEventListener('visibilitychange', function listener() {
+          iframe.contentDocument.removeEventListener('visibilitychange', listener);
+          resolve();
+        });
+      });
+    });
+  }
+
+  ]]>
+  </script>
+  <stack id="stack" flex="1" />
+</window>
new file mode 100644
--- /dev/null
+++ b/dom/xul/test/file_bug1069772_remote.xul
@@ -0,0 +1,127 @@
+<?xml version="1.0"?>
+<?xml-stylesheet type="text/css" href="chrome://global/skin"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css"
+                 type="text/css"?>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1069772
+-->
+<window title="Mozilla Bug 1069772"
+        xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" onload="run();">
+
+  <!-- test code goes here -->
+  <script type="application/javascript">
+  <![CDATA[
+
+  const {interfaces: Ci, classes: Cc, results: Cr, utils: Cu} = Components;
+  Cu.import("resource://testing-common/ContentTask.jsm");
+  Cu.import("resource://testing-common/BrowserTestUtils.jsm");
+  Cu.import("resource://gre/modules/Services.jsm");
+  ContentTask.setTestScope(window.opener.wrappedJSObject);
+
+  let imports = ['SimpleTest', 'ok', 'is'];
+  for (let name of imports) {
+    window[name] = window.opener.wrappedJSObject[name];
+  }
+
+  /** Test for Bug 1069772 **/
+  function run() {
+    // test the transition:
+    // prerender => visible => hidden => visible
+    // on a remote browser
+    createPrerenderedBrowser(true)
+    .then(browser => verifyBrowser(browser, true, true))
+    .then(browser => verifyVisibility(browser, 'prerender'))
+    .then(browser => makePrerenderedBrowserActive(browser))
+    .then(browser => verifyBrowser(browser, false, true))
+    .then(browser => verifyVisibility(browser, 'visible'))
+    .then(browser => hideBrowser(browser))
+    .then(browser => verifyBrowser(browser, false, true))
+    .then(browser => verifyVisibility(browser, 'hidden'))
+    .then(browser => showBrowser(browser))
+    .then(browser => verifyBrowser(browser, false, true))
+    .then(browser => verifyVisibility(browser, 'visible'))
+
+    // finish test
+    .then(() => {
+      window.close();
+      SimpleTest.finish();
+    });
+  }
+
+  function createPrerenderedBrowser(remote) {
+    let browser = document.createElement('browser');
+    browser.setAttribute('type', 'content');
+    browser.setAttribute('prerendered', true);
+    browser.setAttribute('remote', remote);
+    browser.setAttribute('src', 'data:text/html;charset=UTF-8,<html><body>' +
+     '<iframe id="iframe" src="data:text/html;charset=UTF-8,Hello Frame!">' +
+     '</iframe></body></html>');
+
+    // wait for 'load' and 'pageshow'
+    let promises = [];
+    promises.push(BrowserTestUtils.browserLoaded(browser));
+    promises.push(new Promise(resolve =>
+      Services.mm.addMessageListener('test:pageshow', resolve)));
+    Services.mm.loadFrameScript('data:,' +
+      'addEventListener("pageshow", ' +
+      '() => sendAsyncMessage("test:pageshow", null), false);',
+      true);
+
+    document.getElementById('stack').appendChild(browser);
+    return Promise.all(promises).then(() => browser);
+  }
+
+  function verifyBrowser(browser, prerendered, remote) {
+    let docShellOrTabParent = remote ?
+      browser.frameLoader.tabParent : browser.frameLoader.docShell;
+    ok(docShellOrTabParent, 'docShellOrTabParent should not be null');
+    is(docShellOrTabParent.isPrerendered, prerendered,
+      'isPrerendered should be ' + prerendered);
+    return browser;
+  }
+
+  function verifyVisibility(browser, visibility) {
+    return ContentTask.spawn(browser, visibility, (v) => {
+      let iframe = content.document.getElementById('iframe');
+      is(content.document.visibilityState, v, 'check doc.visibilityState');
+      is(content.document.hidden, v != 'visible', 'check doc.hidden');
+      is(iframe.contentDocument.visibilityState, v, 'check iframe doc.visibilityState');
+      is(iframe.contentDocument.hidden, v != 'visible', 'check iframe doc.hidden');
+    }).then(() => browser);
+  }
+
+  function makePrerenderedBrowserActive(browser) {
+    let promise = waitForVisibilityChange(browser);
+    browser.setAttribute('prerendered', false);
+    browser.makePrerenderedBrowserActive();
+    return promise.then(() => browser);
+  }
+
+  function hideBrowser(browser) {
+    let promise = waitForVisibilityChange(browser);
+    browser.docShellIsActive = false;
+    return promise.then(() => browser);
+  }
+
+  function showBrowser(browser) {
+    let promise = waitForVisibilityChange(browser);
+    browser.docShellIsActive = true;
+    return promise.then(() => browser);
+  }
+
+  function waitForVisibilityChange(browser) {
+    return ContentTask.spawn(browser, null, () => {
+      return new Promise(resolve => {
+        let iframe = content.document.getElementById('iframe');
+        iframe.contentDocument.addEventListener('visibilitychange', function listener() {
+          iframe.contentDocument.removeEventListener('visibilitychange', listener);
+          resolve();
+        });
+      });
+    });
+  }
+
+  ]]>
+  </script>
+  <stack id="stack" flex="1" />
+</window>
new file mode 100644
--- /dev/null
+++ b/dom/xul/test/test_bug1069772_local.xul
@@ -0,0 +1,32 @@
+<?xml version="1.0"?>
+<?xml-stylesheet type="text/css" href="chrome://global/skin"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css"
+                 type="text/css"?>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1069772
+-->
+<window title="Mozilla Bug 1069772"
+        xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+        onload="loadTest();">
+  <script type="application/javascript"
+          src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/>
+
+  <!-- test results are displayed in the html:body -->
+  <body xmlns="http://www.w3.org/1999/xhtml">
+  <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=1069772"
+     target="_blank">Mozilla Bug 1069772</a>
+  </body>
+
+  <!-- test code goes here -->
+  <script type="application/javascript">
+  <![CDATA[
+
+  /** Test for Bug 1069772 **/
+  SimpleTest.waitForExplicitFinish();
+  function loadTest() {
+    window.open("file_bug1069772_local.xul", "", "width=360,height=240,chrome");
+  }
+
+  ]]>
+  </script>
+</window>
new file mode 100644
--- /dev/null
+++ b/dom/xul/test/test_bug1069772_remote.xul
@@ -0,0 +1,32 @@
+<?xml version="1.0"?>
+<?xml-stylesheet type="text/css" href="chrome://global/skin"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css"
+                 type="text/css"?>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1069772
+-->
+<window title="Mozilla Bug 1069772"
+        xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+        onload="loadTest();">
+  <script type="application/javascript"
+          src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/>
+
+  <!-- test results are displayed in the html:body -->
+  <body xmlns="http://www.w3.org/1999/xhtml">
+  <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=1069772"
+     target="_blank">Mozilla Bug 1069772</a>
+  </body>
+
+  <!-- test code goes here -->
+  <script type="application/javascript">
+  <![CDATA[
+
+  /** Test for Bug 1069772 **/
+  SimpleTest.waitForExplicitFinish();
+  function loadTest() {
+    window.open("file_bug1069772_remote.xul", "", "width=360,height=240,chrome");
+  }
+
+  ]]>
+  </script>
+</window>
--- a/extensions/spellcheck/src/mozInlineSpellWordUtil.cpp
+++ b/extensions/spellcheck/src/mozInlineSpellWordUtil.cpp
@@ -778,17 +778,17 @@ mozInlineSpellWordUtil::FindRealWordCont
       return index - 1;
   }
 
   // We allow ourselves to return the end of this word even if we're
   // doing HINT_START. This will only happen if there is no word which this
   // point is the start of. I'm not 100% sure this is OK...
   const RealWord& word = mRealWords[index];
   int32_t offset = aSoftTextOffset - word.mSoftTextOffset;
-  if (offset >= 0 && offset <= word.mLength)
+  if (offset >= 0 && offset <= static_cast<int32_t>(word.mLength))
     return index;
 
   if (aSearchForward) {
     if (mRealWords[0].mSoftTextOffset > aSoftTextOffset) {
       // All words have mSoftTextOffset > aSoftTextOffset
       return 0;
     }
     // 'index' is the last word such that mSoftTextOffset <= aSoftTextOffset.
--- a/extensions/spellcheck/src/mozInlineSpellWordUtil.h
+++ b/extensions/spellcheck/src/mozInlineSpellWordUtil.h
@@ -120,23 +120,24 @@ private:
       : mNodeOffset(aNodeOffset), mSoftTextOffset(aSoftTextOffset),
         mLength(aLength) {}
   };
   nsTArray<DOMTextMapping> mSoftTextDOMMapping;
   
   // A list of the "real words" in mSoftText, ordered by mSoftTextOffset
   struct RealWord {
     int32_t      mSoftTextOffset;
-    int32_t      mLength : 31;
-    int32_t mCheckableWord : 1;
+    uint32_t      mLength : 31;
+    uint32_t mCheckableWord : 1;
     
-    RealWord(int32_t aOffset, int32_t aLength, bool aCheckable)
+    RealWord(int32_t aOffset, uint32_t aLength, bool aCheckable)
       : mSoftTextOffset(aOffset), mLength(aLength), mCheckableWord(aCheckable)
     {
       static_assert(sizeof(RealWord) == 8, "RealWord should be limited to 8 bytes");
+      MOZ_ASSERT(aLength < INT32_MAX, "Word length is too large to fit in the bitfield");
     }
 
     int32_t EndOffset() const { return mSoftTextOffset + mLength; }
   };
   nsTArray<RealWord> mRealWords;
   int32_t            mNextWordIndex;
 
   bool mSoftTextValid;
--- a/gfx/layers/apz/test/mochitest/apz_test_utils.js
+++ b/gfx/layers/apz/test/mochitest/apz_test_utils.js
@@ -184,18 +184,18 @@ function runSubtestsSeriallyInFreshWindo
         // entire test which is more deterministic.
         SpecialPowers.getDOMWindowUtils(window).respectDisplayPortSuppression(test.dp_suppression);
       }
 
       function spawnTest(aFile) {
         w = window.open('', "_blank");
         w.subtestDone = advanceSubtestExecution;
         w.SimpleTest = SimpleTest;
-        w.is = is;
-        w.ok = ok;
+        w.is = function(a, b, msg) { return is(a, b, aFile + " | " + msg); };
+        w.ok = function(cond, name, diag) { return ok(cond, aFile + " | " + name, diag); };
         w.location = location.href.substring(0, location.href.lastIndexOf('/') + 1) + aFile;
         return w;
       }
 
       if (test.prefs) {
         // Got some prefs for this subtest, push them
         SpecialPowers.pushPrefEnv({"set": test.prefs}, function() {
           w = spawnTest(test.file);
@@ -203,8 +203,67 @@ function runSubtestsSeriallyInFreshWindo
       } else {
         w = spawnTest(test.file);
       }
     }
 
     advanceSubtestExecution();
   });
 }
+
+function pushPrefs(prefs) {
+  return SpecialPowers.pushPrefEnv({'set': prefs});
+}
+
+function waitUntilApzStable() {
+  return new Promise(function(resolve, reject) {
+    SimpleTest.waitForFocus(function() {
+      waitForAllPaints(function() {
+        flushApzRepaints(resolve);
+      });
+    }, window);
+  });
+}
+
+function isApzEnabled() {
+  var enabled = SpecialPowers.getDOMWindowUtils(window).asyncPanZoomEnabled;
+  if (!enabled) {
+    // All tests are required to have at least one assertion. Since APZ is
+    // disabled, and the main test is presumably not going to run, we stick in
+    // a dummy assertion here to keep the test passing.
+    SimpleTest.ok(true, "APZ is not enabled; this test will be skipped");
+  }
+  return enabled;
+}
+
+// Despite what this function name says, this does not *directly* run the
+// provided continuation testFunction. Instead, it returns a function that
+// can be used to run the continuation. The extra level of indirection allows
+// it to be more easily added to a promise chain, like so:
+//   waitUntilApzStable().then(runContinuation(myTest));
+//
+// If you want to run the continuation directly, outside of a promise chain,
+// you can invoke the return value of this function, like so:
+//   runContinuation(myTest)();
+function runContinuation(testFunction) {
+  // We need to wrap this in an extra function, so that the call site can
+  // be more readable without running the promise too early. In other words,
+  // if we didn't have this extra function, the promise would start running
+  // during construction of the promise chain, concurrently with the first
+  // promise in the chain.
+  return function() {
+    return new Promise(function(resolve, reject) {
+      var testContinuation = null;
+
+      function driveTest() {
+        if (!testContinuation) {
+          testContinuation = testFunction(driveTest);
+        }
+        var ret = testContinuation.next();
+        if (ret.done) {
+          resolve();
+        }
+      }
+
+      driveTest();
+    });
+  };
+}
--- a/gfx/layers/apz/test/mochitest/helper_basic_pan.html
+++ b/gfx/layers/apz/test/mochitest/helper_basic_pan.html
@@ -18,25 +18,21 @@ function scrollPage() {
   SpecialPowers.Services.obs.addObserver(transformEnd, "APZ:TransformEnd", false);
 
   const TOUCH_SLOP = 1;
   synthesizeNativeDrag(document.body, 10, 100, 0, -(50 + TOUCH_SLOP));
   dump("Finished native drag, waiting for transform-end observer...\n");
 }
 
 function checkScroll() {
-  window.opener.is(window.scrollY, 50, "check that the window scrolled");
-  window.opener.testDone();
+  is(window.scrollY, 50, "check that the window scrolled");
+  subtestDone();
 }
 
-window.onload = function() {
-  waitForAllPaints(function() {
-    flushApzRepaints(scrollPage);
-  });
-}
+waitUntilApzStable().then(scrollPage);
 
   </script>
 </head>
 <body>
  <div style="height: 5000px; background-color: lightgreen;">
   This div makes the page scrollable.
  </div>
 </body>
--- a/gfx/layers/apz/test/mochitest/helper_bug1271432.html
+++ b/gfx/layers/apz/test/mochitest/helper_bug1271432.html
@@ -1,58 +1,46 @@
 <head>
   <title>Ensure that the hit region doesn't get unexpectedly expanded</title>
   <script type="application/javascript" src="apz_test_native_event_utils.js"></script>
   <script type="application/javascript" src="apz_test_utils.js"></script>
   <script type="application/javascript" src="/tests/SimpleTest/paint_listener.js"></script>
 <script type="application/javascript">
-function* runTest() {
+function* test(testDriver) {
   var scroller = document.getElementById('scroller');
   var scrollerPos = scroller.scrollTop;
   var dx = 100, dy = 50;
 
   is(window.scrollY, 0, "Initial page scroll position should be 0");
   is(scrollerPos, 0, "Initial scroller position should be 0");
 
-  yield synthesizeNativeMouseMoveAndWaitForMoveEvent(scroller, dx, dy, driveTest);
-  yield synthesizeNativeWheelAndWaitForScrollEvent(scroller, dx, dy, 0, -10, driveTest);
+  yield synthesizeNativeMouseMoveAndWaitForMoveEvent(scroller, dx, dy, testDriver);
+  yield synthesizeNativeWheelAndWaitForScrollEvent(scroller, dx, dy, 0, -10, testDriver);
 
   is(window.scrollY, 0, "Page scroll position should still be 0");
   ok(scroller.scrollTop > scrollerPos, "Scroller should have scrolled");
 
   // wait for it to layerize fully and then try again
   yield waitForAllPaints(function() {
-    flushApzRepaints(driveTest);
+    flushApzRepaints(testDriver);
   });
   scrollerPos = scroller.scrollTop;
 
   // The mouse is already at the right position. If we call scrollWheelOver it
   // hangs on windows waiting for the mouse-move, so instead we just synthesize
   // the wheel directly.
-  yield synthesizeNativeWheelAndWaitForScrollEvent(scroller, dx, dy, 0, -10, driveTest);
+  yield synthesizeNativeWheelAndWaitForScrollEvent(scroller, dx, dy, 0, -10, testDriver);
   is(window.scrollY, 0, "Page scroll position should still be 0 after layerization");
   ok(scroller.scrollTop > scrollerPos, "Scroller should have continued scrolling");
 }
 
-var gTestContinuation = null;
-function driveTest() {
-  if (!gTestContinuation) {
-    gTestContinuation = runTest();
-  }
-  var ret = gTestContinuation.next();
-  if (ret.done) {
-    subtestDone();
-  }
-}
+waitUntilApzStable()
+.then(runContinuation(test))
+.then(subtestDone);
 
-window.onload = function() {
-  waitForAllPaints(function() {
-    flushApzRepaints(driveTest);
-  });
-}
 </script>
 <style>
 a#with_after_content {
     background-color: #F16725;
     opacity: 0.8;
     display: inline-block;
     margin-top: 40px;
     margin-left: 40px;
--- a/gfx/layers/apz/test/mochitest/helper_click.html
+++ b/gfx/layers/apz/test/mochitest/helper_click.html
@@ -17,20 +17,16 @@ function clickButton() {
   });
 }
 
 function clicked(e) {
   is(e.target, document.getElementById('b'), "Clicked on button, yay! (at " + e.clientX + "," + e.clientY + ")");
   subtestDone();
 }
 
-window.onload = function() {
-  waitForAllPaints(function() {
-    flushApzRepaints(clickButton);
-  });
-}
+waitUntilApzStable().then(clickButton);
 
   </script>
 </head>
 <body>
  <button id="b" style="width: 10px; height: 10px"></button>
 </body>
 </html>
--- a/gfx/layers/apz/test/mochitest/helper_div_pan.html
+++ b/gfx/layers/apz/test/mochitest/helper_div_pan.html
@@ -19,25 +19,21 @@ function scrollOuter() {
 
   const TOUCH_SLOP = 1;
   synthesizeNativeDrag(document.getElementById('outer'), 10, 100, 0, -(50 + TOUCH_SLOP));
   dump("Finished native drag, waiting for transform-end observer...\n");
 }
 
 function checkScroll() {
   var outerScroll = document.getElementById('outer').scrollTop;
-  window.opener.is(outerScroll, 50, "check that the div scrolled");
-  window.opener.testDone();
+  is(outerScroll, 50, "check that the div scrolled");
+  subtestDone();
 }
 
-window.onload = function() {
-  waitForAllPaints(function() {
-    flushApzRepaints(scrollOuter);
-  });
-}
+waitUntilApzStable().then(scrollOuter);
 
   </script>
 </head>
 <body>
  <div id="outer" style="height: 250px; border: solid 1px black; overflow:scroll">
   <div style="height: 5000px; background-color: lightblue">
     This div makes the |outer| div scrollable.
   </div>
--- a/gfx/layers/apz/test/mochitest/helper_drag_click.html
+++ b/gfx/layers/apz/test/mochitest/helper_drag_click.html
@@ -4,50 +4,38 @@
   <meta charset="utf-8">
   <meta name="viewport" content="width=device-width; initial-scale=1.0">
   <title>Sanity mouse-drag click test</title>
   <script type="application/javascript" src="apz_test_native_event_utils.js"></script>
   <script type="application/javascript" src="apz_test_utils.js"></script>
   <script type="application/javascript" src="/tests/SimpleTest/paint_listener.js"></script>
   <script type="application/javascript">
 
-function* runTest() {
+function* test(testDriver) {
   document.addEventListener('click', clicked, false);
 
   // mouse down, move it around, and release it near where it went down. this
   // should generate a click at the release point
-  yield synthesizeNativeMouseEvent(document.getElementById('b'), 5, 5, nativeMouseDownEventMsg(), driveTest);
-  yield synthesizeNativeMouseEvent(document.getElementById('b'), 100, 100, nativeMouseMoveEventMsg(), driveTest);
-  yield synthesizeNativeMouseEvent(document.getElementById('b'), 10, 10, nativeMouseMoveEventMsg(), driveTest);
-  yield synthesizeNativeMouseEvent(document.getElementById('b'), 8, 8, nativeMouseUpEventMsg(), driveTest);
+  yield synthesizeNativeMouseEvent(document.getElementById('b'), 5, 5, nativeMouseDownEventMsg(), testDriver);
+  yield synthesizeNativeMouseEvent(document.getElementById('b'), 100, 100, nativeMouseMoveEventMsg(), testDriver);
+  yield synthesizeNativeMouseEvent(document.getElementById('b'), 10, 10, nativeMouseMoveEventMsg(), testDriver);
+  yield synthesizeNativeMouseEvent(document.getElementById('b'), 8, 8, nativeMouseUpEventMsg(), testDriver);
   dump("Finished synthesizing click with a drag in the middle\n");
 }
 
 function clicked(e) {
   // The mouse down at (5, 5) should not have generated a click, but the up
   // at (8, 8) should have.
   is(e.target, document.getElementById('b'), "Clicked on button, yay! (at " + e.clientX + "," + e.clientY + ")");
   is(e.clientX, 8 + Math.floor(document.getElementById('b').getBoundingClientRect().left), 'x-coord of click event looks sane');
   is(e.clientY, 8 + Math.floor(document.getElementById('b').getBoundingClientRect().top), 'y-coord of click event looks sane');
   subtestDone();
 }
 
-var gTestContinuation = null;
-
-function driveTest() {
-  if (!gTestContinuation) {
-    gTestContinuation = runTest();
-  }
-  gTestContinuation.next();
-}
-
-window.onload = function() {
-  waitForAllPaints(function() {
-    flushApzRepaints(driveTest);
-  });
-}
+waitUntilApzStable()
+.then(runContinuation(test));
 
   </script>
 </head>
 <body>
  <button id="b" style="width: 10px; height: 10px"></button>
 </body>
 </html>
--- a/gfx/layers/apz/test/mochitest/helper_iframe_pan.html
+++ b/gfx/layers/apz/test/mochitest/helper_iframe_pan.html
@@ -20,25 +20,21 @@ function scrollOuter() {
 
   const TOUCH_SLOP = 1;
   synthesizeNativeDrag(outer.contentDocument.body, 10, 100, 0, -(50 + TOUCH_SLOP));
   dump("Finished native drag, waiting for transform-end observer...\n");
 }
 
 function checkScroll() {
   var outerScroll = document.getElementById('outer').contentWindow.scrollY;
-  window.opener.is(outerScroll, 50, "check that the iframe scrolled");
-  window.opener.testDone();
+  is(outerScroll, 50, "check that the iframe scrolled");
+  subtestDone();
 }
 
-window.onload = function() {
-  waitForAllPaints(function() {
-    flushApzRepaints(scrollOuter);
-  });
-}
+waitUntilApzStable().then(scrollOuter);
 
   </script>
 </head>
 <body>
  <iframe id="outer" style="height: 250px; border: solid 1px black" src="data:text/html,<body style='height:5000px'>"></iframe>
  <div style="height: 5000px; background-color: lightgreen;">
   This div makes the top-level page scrollable.
  </div>
--- a/gfx/layers/apz/test/mochitest/helper_long_tap.html
+++ b/gfx/layers/apz/test/mochitest/helper_long_tap.html
@@ -37,28 +37,22 @@ function recordEvent(e) {
       flushApzRepaints(function() {
         dump("Done APZ flush, ending test...\n");
         subtestDone(); // closing the window should dismiss the context menu dialog
       });
     });
   }
 }
 
-function registerListeners() {
-  window.addEventListener('touchstart', recordEvent, { passive: true, capture: true });
-  window.addEventListener('touchend', recordEvent, { passive: true, capture: true });
-  window.addEventListener('touchcancel', recordEvent, true);
-  window.addEventListener('contextmenu', recordEvent, true);
-}
+window.addEventListener('touchstart', recordEvent, { passive: true, capture: true });
+window.addEventListener('touchend', recordEvent, { passive: true, capture: true });
+window.addEventListener('touchcancel', recordEvent, true);
+window.addEventListener('contextmenu', recordEvent, true);
 
-window.onload = function() {
-  registerListeners();
-  waitForAllPaints(function() {
-    flushApzRepaints(longPressLink);
-  });
-}
+waitUntilApzStable()
+.then(longPressLink);
 
   </script>
 </head>
 <body>
  <a id="b" href="#">Link to nowhere</a>
 </body>
 </html>
--- a/gfx/layers/apz/test/mochitest/helper_scroll_on_position_fixed.html
+++ b/gfx/layers/apz/test/mochitest/helper_scroll_on_position_fixed.html
@@ -2,86 +2,74 @@
   <meta name="viewport" content="width=device-width; initial-scale=1.0">
   <title>Wheel-scrolling over position:fixed and position:sticky elements, in the top-level document as well as iframes</title>
   <script type="application/javascript" src="apz_test_native_event_utils.js"></script>
   <script type="application/javascript" src="apz_test_utils.js"></script>
   <script type="application/javascript" src="/tests/SimpleTest/paint_listener.js"></script>
   <script type="application/javascript">
 
 // Scroll the mouse wheel at (dx, dy) relative to |element|.
-function scrollWheelOver(element, dx, dy) {
+function scrollWheelOver(element, dx, dy, testDriver) {
   // Move the mouse to the desired wheel location.
   // Not doing so can result in the wheel events from two consecutive
   // scrollWheelOver() calls on different elements being incorrectly considered
   // as part of the same wheel transaction.
   // We also wait for the mouse move event to be processed before sending the
   // wheel event, otherwise there is a chance they might get reordered, and
   // we have the transaction problem again.
   return synthesizeNativeMouseMoveAndWaitForMoveEvent(element, dx, dy, function() {
-    synthesizeNativeWheelAndWaitForScrollEvent(element, dx, dy, 0, -10, driveTest);
+    synthesizeNativeWheelAndWaitForScrollEvent(element, dx, dy, 0, -10, testDriver);
   });
 }
 
-function* runTest() {
+function* test(testDriver) {
   var iframeWin = document.getElementById('iframe').contentWindow;
 
   // scroll over the middle of the iframe's position:sticky element, check
   // that it scrolls the iframe
   var scrollPos = iframeWin.scrollY;
-  yield scrollWheelOver(iframeWin.document.body, 50, 150);
+  yield scrollWheelOver(iframeWin.document.body, 50, 150, testDriver);
   ok(iframeWin.scrollY > scrollPos, "iframe scrolled after wheeling over the position:sticky element");
 
   // same, but using the iframe's position:fixed element
   scrollPos = iframeWin.scrollY;
-  yield scrollWheelOver(iframeWin.document.body, 250, 150);
+  yield scrollWheelOver(iframeWin.document.body, 250, 150, testDriver);
   ok(iframeWin.scrollY > scrollPos, "iframe scrolled after wheeling over the position:fixed element");
 
   // same, but scrolling the scrollable frame *inside* the position:fixed item
   var fpos = document.getElementById('fpos_scrollable');
   scrollPos = fpos.scrollTop;
-  yield scrollWheelOver(fpos, 50, 150);
+  yield scrollWheelOver(fpos, 50, 150, testDriver);
   ok(fpos.scrollTop > scrollPos, "scrollable item inside fixed-pos element scrolled");
   // wait for it to layerize fully and then try again
   yield waitForAllPaints(function() {
-    flushApzRepaints(driveTest);
+    flushApzRepaints(testDriver);
   });
   scrollPos = fpos.scrollTop;
   // The mouse is already at the right position. If we call scrollWheelOver it
   // hangs on windows waiting for the mouse-move, so instead we just synthesize
   // the wheel directly.
-  yield synthesizeNativeWheelAndWaitForScrollEvent(fpos, 50, 150, 0, -10, driveTest);
+  yield synthesizeNativeWheelAndWaitForScrollEvent(fpos, 50, 150, 0, -10, testDriver);
   ok(fpos.scrollTop > scrollPos, "scrollable item inside fixed-pos element scrolled after layerization");
 
   // same, but using the top-level window's position:sticky element
   scrollPos = window.scrollY;
-  yield scrollWheelOver(document.body, 50, 150);
+  yield scrollWheelOver(document.body, 50, 150, testDriver);
   ok(window.scrollY > scrollPos, "top-level document scrolled after wheeling over the position:sticky element");
 
   // same, but using the top-level window's position:fixed element
   scrollPos = window.scrollY;
-  yield scrollWheelOver(document.body, 250, 150);
+  yield scrollWheelOver(document.body, 250, 150, testDriver);
   ok(window.scrollY > scrollPos, "top-level document scrolled after wheeling over the position:fixed element");
 }
 
-var gTestContinuation = null;
-function driveTest() {
-  if (!gTestContinuation) {
-    gTestContinuation = runTest();
-  }
-  var ret = gTestContinuation.next();
-  if (ret.done) {
-    subtestDone();
-  }
-}
+waitUntilApzStable()
+.then(runContinuation(test))
+.then(subtestDone);
 
-window.onload = function() {
-  waitForAllPaints(function() {
-    flushApzRepaints(driveTest);
-  });
-}
   </script>
 </head>
 <body style="height:5000px; margin:0">
   <div style="position:sticky; width: 100px; height: 300px; top: 0; background-color:red">sticky</div>
   <div style="position:fixed; width: 100px; height: 300px; top: 0; left: 200px; background-color: green">fixed</div>
   <iframe id='iframe' width="300" height="400" src="data:text/html,<body style='height:5000px; margin:0'><div style='position:sticky; width:100px; height:300px; top: 0; background-color:red'>sticky</div><div style='position:fixed; right:0; top: 0; width:100px; height:300px; background-color:green'>fixed</div>"></iframe>
 
   <div id="fpos_scrollable" style="position:fixed; width: 100px; height: 300px; top: 0; left: 400px; background-color: red; overflow:scroll">
--- a/gfx/layers/apz/test/mochitest/helper_scrollto_tap.html
+++ b/gfx/layers/apz/test/mochitest/helper_scrollto_tap.html
@@ -51,20 +51,17 @@ function clickButton() {
   });
 }
 
 function clicked(e) {
   is(e.target, document.getElementById('b'), "Clicked on button, yay! (at " + e.clientX + "," + e.clientY + ")");
   subtestDone();
 }
 
-window.onload = function() {
-  waitForAllPaints(function() {
-    flushApzRepaints(startTest);
-  });
-}
+waitUntilApzStable().then(startTest);
+
   </script>
 </head>
 <body style="height: 5000px">
  <div style="height: 50px">spacer</div>
  <button id="b" style="width: 10px; height: 10px"></button>
 </body>
 </html>
--- a/gfx/layers/apz/test/mochitest/helper_tap.html
+++ b/gfx/layers/apz/test/mochitest/helper_tap.html
@@ -23,20 +23,16 @@ function clickButton() {
   });
 }
 
 function clicked(e) {
   is(e.target, document.getElementById('b'), "Clicked on button, yay! (at " + e.clientX + "," + e.clientY + ")");
   subtestDone();
 }
 
-window.onload = function() {
-  waitForAllPaints(function() {
-    flushApzRepaints(clickButton);
-  });
-}
+waitUntilApzStable().then(clickButton);
 
   </script>
 </head>
 <body>
  <button id="b" style="width: 10px; height: 10px"></button>
 </body>
 </html>
--- a/gfx/layers/apz/test/mochitest/helper_tap_passive.html
+++ b/gfx/layers/apz/test/mochitest/helper_tap_passive.html
@@ -43,26 +43,20 @@ function recordEvent(e) {
   e.preventDefault();
 
   synthesizeNativeTouch(document.getElementById('b'), 5, 5, SpecialPowers.DOMWindowUtils.TOUCH_REMOVE, function() {
     dump("Finished synthesizing touch-end to clear state; finishing test...\n");
     subtestDone();
   });
 }
 
-function registerListeners() {
-  window.addEventListener('touchstart', recordEvent, { passive: true, capture: true });
-  window.addEventListener('contextmenu', recordEvent, true);
-}
+window.addEventListener('touchstart', recordEvent, { passive: true, capture: true });
+window.addEventListener('contextmenu', recordEvent, true);
 
-window.onload = function() {
-  registerListeners();
-  waitForAllPaints(function() {
-    flushApzRepaints(longPressLink);
-  });
-}
+waitUntilApzStable()
+.then(longPressLink);
 
   </script>
 </head>
 <body>
  <a id="b" href="#">Link to nowhere</a>
 </body>
 </html>
--- a/gfx/layers/apz/test/mochitest/mochitest.ini
+++ b/gfx/layers/apz/test/mochitest/mochitest.ini
@@ -24,20 +24,16 @@ tags = apz
 [test_wheel_scroll.html]
 skip-if = (os == 'android') || (os == 'b2g') || (buildapp == 'mulet') # wheel events not supported on mobile; see bug 1164274 for mulet
 [test_wheel_transactions.html]
 skip-if = (os == 'android') || (os == 'b2g') || (buildapp == 'mulet') # wheel events not supported on mobile; see bug 1164274 for mulet
 [test_bug1151667.html]
 skip-if = (os == 'android') || (os == 'b2g') # wheel events not supported on mobile
 [test_layerization.html]
 skip-if = (os == 'android') || (os == 'b2g') # uses wheel events which are not supported on mobile
-[test_basic_pan.html]
-# Windows touch injection doesn't work in automation, but this test can be run locally on a windows touch device.
-# On OS X we don't support touch events at all.
-skip-if = (toolkit == 'windows') || (toolkit == 'cocoa')
 [test_scroll_inactive_flattened_frame.html]
 skip-if = (os == 'android') || (os == 'b2g') || (buildapp == 'mulet') # wheel events not supported on mobile; see bug 1164274 for mulet
 [test_scroll_inactive_bug1190112.html]
 skip-if = (os == 'android') || (os == 'b2g') || (buildapp == 'mulet') # wheel events not supported on mobile; see bug 1164274 for mulet
 [test_scroll_subframe_scrollbar.html]
 skip-if = (os == 'android') || (os == 'b2g') || (buildapp == 'mulet') # wheel events not supported on mobile; see bug 1164274 for mulet
 [test_frame_reconstruction.html]
 [test_group_touchevents.html]
deleted file mode 100644
--- a/gfx/layers/apz/test/mochitest/test_basic_pan.html
+++ /dev/null
@@ -1,76 +0,0 @@
-<!DOCTYPE HTML>
-<html>
-<head>
-  <meta charset="utf-8">
-  <title>Sanity panning test</title>
-  <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
-  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
-  <script type="application/javascript">
-
-SimpleTest.waitForExplicitFinish();
-
-// this page just serially loads each one of the following test helper pages in
-// a new window and waits for it to call testDone()
-var tests = [
-  'helper_basic_pan.html',
-  'helper_div_pan.html',
-  'helper_iframe_pan.html',
-];
-
-var testIndex = -1;
-var w = null;
-
-function testDone() {
-  if (w) {
-    w.close();
-  }
-  testIndex++;
-  if (testIndex < tests.length) {
-    w = window.open(tests[testIndex], "_blank");
-  } else {
-    SimpleTest.finish();
-  }
-}
-
-window.onload = function() {
-  if (!SpecialPowers.getDOMWindowUtils(window).asyncPanZoomEnabled) {
-    ok(true, "APZ is not enabled, this test is not relevant, sorry!\n");
-    SimpleTest.finish();
-    return;
-  }
-  if (!window.TouchEvent) {
-    ok(true, "Touch events are not supported on this platform, sorry!\n");
-    SimpleTest.finish();
-    return;
-  }
-  SpecialPowers.pushPrefEnv(
-    { "set":
-      [
-        // Dropping the touch slop to 0 makes the tests easier to write because
-        // we can just do a one-pixel drag to get over the pan threshold rather
-        // than having to hard-code some larger value.
-        ["apz.touch_start_tolerance", "0.0"],
-        // The touchstart from the drag can turn into a long-tap if the touch-move
-        // events get held up. Try to prevent that by making long-taps require
-        // a 10 second hold. Note that we also cannot enable chaos mode on this
-        // test for this reason, since chaos mode can cause the long-press timer
-        // to fire sooner than the pref dictates.
-        ["ui.click_hold_context_menus.delay", 10000],
-        // The subtests in this test do touch-drags to pan the page, but we don't
-        // want those pans to turn into fling animations, so we increase the
-        // fling-stop threshold velocity to absurdly high.
-        ["apz.fling_stopped_threshold", "10000"],
-        // The helper_div_pan's div gets a displayport on scroll, but if the
-        // test takes too long the displayport can expire before the new scroll
-        // position is synced back to the main thread. So we disable displayport
-        // expiry for these tests.
-        ["apz.displayport_expiry_ms", 0],
-      ]
-    }, testDone);
-};
-
-  </script>
-</head>
-<body>
-</body>
-</html>
--- a/gfx/layers/apz/test/mochitest/test_bug1151663.html
+++ b/gfx/layers/apz/test/mochitest/test_bug1151663.html
@@ -2,35 +2,33 @@
 <html>
 <!--
 https://bugzilla.mozilla.org/show_bug.cgi?id=1151663
 -->
 <head>
   <meta charset="utf-8">
   <title>Test for Bug 1151663</title>
   <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <script type="application/javascript" src="apz_test_utils.js"></script>
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
   <script type="application/javascript">
 
-    SimpleTest.waitForExplicitFinish();
+    if (isApzEnabled()) {
+      SimpleTest.waitForExplicitFinish();
 
-    // Run the actual test in its own window, because it requires that the
-    // root APZC be scrollable. Mochitest pages themselves often run
-    // inside an iframe which means we have no control over the root APZC.
-    var w = null;
-    window.onload = function() {
-      if (!SpecialPowers.getDOMWindowUtils(window).asyncPanZoomEnabled) {
-        ok(true, "APZ is not enabled, this test is not relevant, sorry!\n");
-        SimpleTest.finish();
-        return;
-      }
-      SpecialPowers.pushPrefEnv({"set": [["apz.test.logging_enabled", true]]}, function() {
-        w = window.open("helper_bug1151663.html", "_blank");
-      });
-    };
+      // Run the actual test in its own window, because it requires that the
+      // root APZC be scrollable. Mochitest pages themselves often run
+      // inside an iframe which means we have no control over the root APZC.
+      var w = null;
+      window.onload = function() {
+        pushPrefs([["apz.test.logging_enabled", true]]).then(function() {
+          w = window.open("helper_bug1151663.html", "_blank");
+        });
+      };
+    }
 
     function finishTest() {
       w.close();
       SimpleTest.finish();
     };
 
   </script>
 </head>
--- a/gfx/layers/apz/test/mochitest/test_bug1151667.html
+++ b/gfx/layers/apz/test/mochitest/test_bug1151667.html
@@ -52,18 +52,14 @@ function startTest() {
 function continueTest() {
   var subframe = document.getElementById('subframe');
   is(subframe.scrollTop > 0, true, "We should have scrolled the subframe down");
   is(document.documentElement.scrollTop, 0, "We should not have scrolled the page");
   SimpleTest.finish();
 }
 
 SimpleTest.waitForExplicitFinish();
-SimpleTest.waitForFocus(function() {
-  waitForAllPaints(function() {
-    flushApzRepaints(startTest);
-  })
-}, window);
+waitUntilApzStable().then(startTest);
 
 </script>
 </pre>
 </body>
 </html>
--- a/gfx/layers/apz/test/mochitest/test_bug982141.html
+++ b/gfx/layers/apz/test/mochitest/test_bug982141.html
@@ -2,35 +2,33 @@
 <html>
 <!--
 https://bugzilla.mozilla.org/show_bug.cgi?id=982141
 -->
 <head>
   <meta charset="utf-8">
   <title>Test for Bug 982141</title>
   <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <script type="application/javascript" src="apz_test_utils.js"></script>
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
   <script type="application/javascript">
 
-    SimpleTest.waitForExplicitFinish();
+    if (isApzEnabled()) {
+      SimpleTest.waitForExplicitFinish();
 
-    // Run the actual test in its own window, because it requires that the
-    // root APZC not be scrollable. Mochitest pages themselves often run
-    // inside an iframe which means we have no control over the root APZC.
-    var w = null;
-    window.onload = function() {
-      if (!SpecialPowers.getDOMWindowUtils(window).asyncPanZoomEnabled) {
-        ok(true, "APZ is not enabled, this test is not relevant, sorry!\n");
-        SimpleTest.finish();
-        return;
-      }
-      SpecialPowers.pushPrefEnv({"set": [["apz.test.logging_enabled", true]]}, function() {
-        w = window.open("helper_bug982141.html", "_blank");
-      });
-    };
+      // Run the actual test in its own window, because it requires that the
+      // root APZC not be scrollable. Mochitest pages themselves often run
+      // inside an iframe which means we have no control over the root APZC.
+      var w = null;
+      window.onload = function() {
+        pushPrefs([["apz.test.logging_enabled", true]]).then(function() {
+          w = window.open("helper_bug982141.html", "_blank");
+        });
+      };
+    }
 
     function finishTest() {
       w.close();
       SimpleTest.finish();
     };
 
   </script>
 </head>
--- a/gfx/layers/apz/test/mochitest/test_frame_reconstruction.html
+++ b/gfx/layers/apz/test/mochitest/test_frame_reconstruction.html
@@ -149,33 +149,33 @@
     <li>Some text</li>
    </ol>
   </div>
  </div>
 </div>
 
 <pre id="test">
 <script type="application/javascript;version=1.7">
-function* runTest() {
+function* test(testDriver) {
   var elm = document.getElementsByClassName('inner')[0];
   elm.scrollTop = 0;
-  yield flushApzRepaints(driveTest);
+  yield flushApzRepaints(testDriver);
 
   // Take over control of the refresh driver and compositor
   var utils = SpecialPowers.DOMWindowUtils;
   utils.advanceTimeAndRefresh(0);
 
   // Kick off an APZ smooth-scroll to 0,200
   elm.scrollTo(0, 200);
-  yield waitForAllPaints(function() { setTimeout(driveTest, 0); });
+  yield waitForAllPaints(function() { setTimeout(testDriver, 0); });
 
   // Let's do a couple of frames of the animation, and make sure it's going
   utils.advanceTimeAndRefresh(16);
   utils.advanceTimeAndRefresh(16);
-  yield flushApzRepaints(driveTest);
+  yield flushApzRepaints(testDriver);
   ok(elm.scrollTop > 0, "APZ animation in progress", "scrollTop is now " + elm.scrollTop);
   ok(elm.scrollTop < 200, "APZ animation not yet completed", "scrollTop is now " + elm.scrollTop);
 
   var frameReconstructionTriggered = 0;
   // Register the listener that triggers the frame reconstruction
   elm.onscroll = function() {
     // Do the reconstruction
     elm.parentNode.classList.add('contentBefore');
@@ -185,47 +185,34 @@ function* runTest() {
       elm.parentNode.classList.remove('contentBefore');
     }, 0);
   }
 
   // and do a few more frames of the animation, this should trigger the listener
   // and the frame reconstruction
   utils.advanceTimeAndRefresh(16);
   utils.advanceTimeAndRefresh(16);
-  yield flushApzRepaints(driveTest);
+  yield flushApzRepaints(testDriver);
   ok(elm.scrollTop < 200, "APZ animation not yet completed", "scrollTop is now " + elm.scrollTop);
   ok(frameReconstructionTriggered > 0, "Frame reconstruction triggered", "reconstruction triggered " + frameReconstructionTriggered + " times");
 
   // and now run to completion
   for (var i = 0; i < 100; i++) {
     utils.advanceTimeAndRefresh(16);
   }
   utils.restoreNormalRefresh();
-  yield waitForAllPaints(function() { setTimeout(driveTest, 0); });
-  yield flushApzRepaints(driveTest);
+  yield waitForAllPaints(function() { setTimeout(testDriver, 0); });
+  yield flushApzRepaints(testDriver);
 
   is(elm.scrollTop, 200, "Element should have scrolled by 200px");
 }
 
-var gTestContinuation = null;
-function driveTest() {
-  if (!gTestContinuation) {
-    gTestContinuation = runTest();
-  }
-  var ret = gTestContinuation.next();
-  if (ret.done) {
-    SimpleTest.finish();
-  }
-}
-
-SimpleTest.waitForExplicitFinish();
-var apzEnabled = SpecialPowers.getDOMWindowUtils(window).asyncPanZoomEnabled;
-if (!apzEnabled) {
-  ok(true, "APZ not enabled, skipping test");
-  SimpleTest.finish();
-} else {
+if (isApzEnabled()) {
+  SimpleTest.waitForExplicitFinish();
   SimpleTest.expectAssertions(0, 1); // this test triggers an assertion, see bug 1247050
-  SimpleTest.waitForFocus(driveTest, window);
+  waitUntilApzStable()
+  .then(runContinuation(test))
+  .then(SimpleTest.finish);
 }
 
 </script>
 </body>
 </html>
--- a/gfx/layers/apz/test/mochitest/test_group_mouseevents.html
+++ b/gfx/layers/apz/test/mochitest/test_group_mouseevents.html
@@ -10,24 +10,21 @@
 
 var subtests = [
   // Sanity test to synthesize a mouse click
   {'file': 'helper_click.html'},
   // Sanity test for click but with some mouse movement between the down and up
   {'file': 'helper_drag_click.html'}
 ];
 
-SimpleTest.waitForExplicitFinish();
-window.onload = function() {
-  if (!SpecialPowers.getDOMWindowUtils(window).asyncPanZoomEnabled) {
-    ok(true, "APZ is not enabled, this test is not relevant, sorry!\n");
-    SimpleTest.finish();
-    return;
-  }
-  runSubtestsSeriallyInFreshWindows(subtests)
-    .then(function() { SimpleTest.finish() });
-};
+if (isApzEnabled()) {
+  SimpleTest.waitForExplicitFinish();
+  window.onload = function() {
+    runSubtestsSeriallyInFreshWindows(subtests)
+    .then(SimpleTest.finish);
+  };
+}
 
   </script>
 </head>
 <body>
 </body>
 </html>
--- a/gfx/layers/apz/test/mochitest/test_group_touchevents.html
+++ b/gfx/layers/apz/test/mochitest/test_group_touchevents.html
@@ -3,42 +3,70 @@
 <head>
   <meta charset="utf-8">
   <title>Various touch tests that spawn in new windows</title>
   <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
   <script type="application/javascript" src="apz_test_utils.js"></script>
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
   <script type="application/javascript">
 
+var basic_pan_prefs = [
+  // Dropping the touch slop to 0 makes the tests easier to write because
+  // we can just do a one-pixel drag to get over the pan threshold rather
+  // than having to hard-code some larger value.
+  ["apz.touch_start_tolerance", "0.0"],
+  // The touchstart from the drag can turn into a long-tap if the touch-move
+  // events get held up. Try to prevent that by making long-taps require
+  // a 10 second hold. Note that we also cannot enable chaos mode on this
+  // test for this reason, since chaos mode can cause the long-press timer
+  // to fire sooner than the pref dictates.
+  ["ui.click_hold_context_menus.delay", 10000],
+  // The subtests in this test do touch-drags to pan the page, but we don't
+  // want those pans to turn into fling animations, so we increase the
+  // fling-stop threshold velocity to absurdly high.
+  ["apz.fling_stopped_threshold", "10000"],
+  // The helper_div_pan's div gets a displayport on scroll, but if the
+  // test takes too long the displayport can expire before the new scroll
+  // position is synced back to the main thread. So we disable displayport
+  // expiry for these tests.
+  ["apz.displayport_expiry_ms", 0],
+];
+
 var subtests = [
+  // Simple tests to exercise basic panning behaviour
+  {'file': 'helper_basic_pan.html', 'prefs': basic_pan_prefs},
+  {'file': 'helper_div_pan.html', 'prefs': basic_pan_prefs},
+  {'file': 'helper_iframe_pan.html', 'prefs': basic_pan_prefs},
+
+  // Simple test to exercise touch-tapping behaviour
   {'file': 'helper_tap.html'},
+
   // For the following two tests, disable displayport suppression to make sure it
   // doesn't interfere with the test by scheduling paints non-deterministically.
   {'file': 'helper_scrollto_tap.html?true', 'prefs': [["apz.paint_skipping.enabled", true]], 'dp_suppression': false},
   {'file': 'helper_scrollto_tap.html?false', 'prefs': [["apz.paint_skipping.enabled", false]], 'dp_suppression': false},
+
   // For the long-tap test, reduce the content response timeout because the touchstart
   // event doesn't get processed (because of the event listener) until this expires.
   // Once we support passive event listeners, we can use that instead and stop mucking
   // with the timeout.
   {'file': 'helper_long_tap.html'},
+
   // For the following test, we want to make sure APZ doesn't wait for a content
   // response that is never going to arrive. To detect this we set the content response
   // timeout to a day, so that the entire test times out and fails if APZ does
   // end up waiting.
   {'file': 'helper_tap_passive.html', 'prefs': [["apz.content_response_timeout", 24 * 60 * 60 * 1000]]},
 ];
 
-SimpleTest.waitForExplicitFinish();
-window.onload = function() {
-  if (!SpecialPowers.getDOMWindowUtils(window).asyncPanZoomEnabled) {
-    ok(true, "APZ is not enabled, this test is not relevant, sorry!\n");
-    SimpleTest.finish();
-    return;
-  }
-  runSubtestsSeriallyInFreshWindows(subtests)
-    .then(function() { SimpleTest.finish() });
-};
+if (isApzEnabled()) {
+  SimpleTest.waitForExplicitFinish();
+  window.onload = function() {
+    runSubtestsSeriallyInFreshWindows(subtests)
+    .then(SimpleTest.finish);
+  };
+}
 
   </script>
 </head>
 <body>
 </body>
 </html>
--- a/gfx/layers/apz/test/mochitest/test_group_wheelevents.html
+++ b/gfx/layers/apz/test/mochitest/test_group_wheelevents.html
@@ -20,24 +20,21 @@ var subtests = [
         ['mousewheel.transaction.timeout', 0]]},
   {'file': 'helper_bug1271432.html', 'prefs': [
         // same prefs as in the previous test, for the same reasons.
         ['general.smoothScroll', false],
         ['mousewheel.transaction.ignoremovedelay', 0],
         ['mousewheel.transaction.timeout', 0]]}
 ];
 
-SimpleTest.waitForExplicitFinish();
-window.onload = function() {
-  if (!SpecialPowers.getDOMWindowUtils(window).asyncPanZoomEnabled) {
-    ok(true, "APZ is not enabled, this test is not relevant, sorry!\n");
-    SimpleTest.finish();
-    return;
-  }
-  runSubtestsSeriallyInFreshWindows(subtests)
-    .then(function() { SimpleTest.finish() });
-};
+if (isApzEnabled()) {
+  SimpleTest.waitForExplicitFinish();
+  window.onload = function() {
+    runSubtestsSeriallyInFreshWindows(subtests)
+    .then(SimpleTest.finish);
+  };
+}
 
   </script>
 </head>
 <body>
 </body>
 </html>
--- a/gfx/layers/apz/test/mochitest/test_layerization.html
+++ b/gfx/layers/apz/test/mochitest/test_layerization.html
@@ -49,36 +49,35 @@ https://bugzilla.mozilla.org/show_bug.cg
      optimization that layerizes the primary async-scrollable frame on page
      load layerizes it rather than its child subframes. -->
   <div id="container-content"></div>
 </div>
 <pre id="test">
 <script type="application/javascript;version=1.7">
 
 // Scroll the mouse wheel over |element|.
-function scrollWheelOver(element, waitForScroll = true) {
+function scrollWheelOver(element, waitForScroll, testDriver) {
   var x = 10;
   var y = 10;
   // Move the mouse to the desired wheel location.
   // Not doing so can result in the wheel events from two consecutive
   // scrollWheelOver() calls on different elements being incorrectly considered
   // as part of the same wheel transaction.
   // We also wait for the mouse move event to be processed before sending the
   // wheel event, otherwise there is a chance they might get reordered, and
   // we have the transaction problem again.
   synthesizeNativeMouseMoveAndWaitForMoveEvent(element, x, y, function() {
     if (waitForScroll) {
-      synthesizeNativeWheelAndWaitForScrollEvent(element, x, y, 0, -10, driveTest);
+      synthesizeNativeWheelAndWaitForScrollEvent(element, x, y, 0, -10, testDriver);
     } else {
-      synthesizeNativeWheelAndWaitForWheelEvent(element, x, y, 0, -10, driveTest);
+      synthesizeNativeWheelAndWaitForWheelEvent(element, x, y, 0, -10, testDriver);
     }
   });
 }
 
-var gTestContinuation = null;
 var utils;
 
 const DISPLAYPORT_EXPIRY = 100;
 
 // Return whether the element with id |elementId| has been layerized.
 // Assumes |elementId| will be present in the content description for the
 // element, and not in the content descriptions of other elements.
 function isLayerized(elementId) {
@@ -92,164 +91,147 @@ function isLayerized(elementId) {
       if (paint[scrollId]["contentDescription"].includes(elementId)) {
         return true;
       }
     }
   }
   return false;
 }
 
-// Helper function to pass to waitForAllPaints rather than passing driveTest
-// directly. If there are no paints pending waitForAllPaints will invoke the
-// callback synchronously, and if we did waitForAllPaints(driveTest) that might
-// cause reentrancy into driveTest which is bad.
-function callDriveTestAsync() {
-  setTimeout(driveTest, 0);
+// This helper function produces another helper function, which, when invoked,
+// invokes the provided testDriver argument in a setTimeout 0. This is really
+// just useful in cases when there are no paints pending, because then
+// waitForAllPaints will invoke its callback synchronously. If we did
+// waitForAllPaints(testDriver) that might cause reentrancy into the testDriver
+// which is bad. This function works around that.
+function asyncWrapper(testDriver) {
+  return function() {
+    setTimeout(testDriver, 0);
+  };
 }
 
-function* runTest() {
+function* test(testDriver) {
   utils = SpecialPowers.getDOMWindowUtils(window);
 
   // Initially, nothing should be layerized.
   ok(!isLayerized('outer1'), "initially 'outer1' should not be layerized");
   ok(!isLayerized('inner1'), "initially 'inner1' should not be layerized");
   ok(!isLayerized('outer2'), "initially 'outer2' should not be layerized");
   ok(!isLayerized('inner2'), "initially 'inner2' should not be layerized");
   ok(!isLayerized('outer3'), "initially 'outer3' should not be layerized");
   ok(!isLayerized('inner3'), "initially 'inner3' should not be layerized");
   ok(!isLayerized('outer4'), "initially 'outer4' should not be layerized");
   ok(!isLayerized('inner4'), "initially 'inner4' should not be layerized");
 
   // Scrolling over outer1 should layerize outer1, but not inner1.
-  yield scrollWheelOver(document.getElementById('outer1'));
+  yield scrollWheelOver(document.getElementById('outer1'), true, testDriver);
   ok(isLayerized('outer1'), "scrolling 'outer1' should cause it to be layerized");
   ok(!isLayerized('inner1'), "scrolling 'outer1' should not cause 'inner1' to be layerized");
 
   // Scrolling over inner2 should layerize both outer2 and inner2.
-  yield scrollWheelOver(document.getElementById('inner2'));
+  yield scrollWheelOver(document.getElementById('inner2'), true, testDriver);
   ok(isLayerized('inner2'), "scrolling 'inner2' should cause it to be layerized");
   ok(isLayerized('outer2'), "scrolling 'inner2' should also cause 'outer2' to be layerized");
 
   // The second half of the test repeats the same checks as the first half,
   // but with an iframe as the outer scrollable frame.
 
   // Scrolling over outer3 should layerize outer3, but not inner3.
-  yield scrollWheelOver(document.getElementById('outer3').contentDocument.documentElement);
+  yield scrollWheelOver(document.getElementById('outer3').contentDocument.documentElement, true, testDriver);
   ok(isLayerized('outer3'), "scrolling 'outer3' should cause it to be layerized");
   ok(!isLayerized('inner3'), "scrolling 'outer3' should not cause 'inner3' to be layerized");
 
   // Scrolling over outer4 should layerize both outer4 and inner4.
-  yield scrollWheelOver(document.getElementById('outer4').contentDocument.getElementById('inner4'));
+  yield scrollWheelOver(document.getElementById('outer4').contentDocument.getElementById('inner4'), true, testDriver);
   ok(isLayerized('inner4'), "scrolling 'inner4' should cause it to be layerized");
   ok(isLayerized('outer4'), "scrolling 'inner4' should also cause 'outer4' to be layerized");
 
   // Now we enable displayport expiry, and verify that things are still
   // layerized as they were before.
-  yield SpecialPowers.pushPrefEnv({"set": [["apz.displayport_expiry_ms", DISPLAYPORT_EXPIRY]]}, driveTest);
+  yield SpecialPowers.pushPrefEnv({"set": [["apz.displayport_expiry_ms", DISPLAYPORT_EXPIRY]]}, testDriver);
   ok(isLayerized('outer1'), "outer1 is still layerized after enabling expiry");
   ok(!isLayerized('inner1'), "inner1 is still not layerized after enabling expiry");
   ok(isLayerized('outer2'), "outer2 is still layerized after enabling expiry");
   ok(isLayerized('inner2'), "inner2 is still layerized after enabling expiry");
   ok(isLayerized('outer3'), "outer3 is still layerized after enabling expiry");
   ok(!isLayerized('inner3'), "inner3 is still not layerized after enabling expiry");
   ok(isLayerized('outer4'), "outer4 is still layerized after enabling expiry");
   ok(isLayerized('inner4'), "inner4 is still layerized after enabling expiry");
 
   // Now we trigger a scroll on some of the things still layerized, so that
   // the displayport expiry gets triggered.
 
   // Expire displayport with scrolling on outer1
-  yield scrollWheelOver(document.getElementById('outer1'));
+  yield scrollWheelOver(document.getElementById('outer1'), true, testDriver);
   yield waitForAllPaints(function() {
-    flushApzRepaints(driveTest);
+    flushApzRepaints(testDriver);
   });
-  yield setTimeout(driveTest, DISPLAYPORT_EXPIRY);
-  yield waitForAllPaints(callDriveTestAsync);
+  yield setTimeout(testDriver, DISPLAYPORT_EXPIRY);
+  yield waitForAllPaints(asyncWrapper(testDriver));
   ok(!isLayerized('outer1'), "outer1 is no longer layerized after displayport expiry");
   ok(!isLayerized('inner1'), "inner1 is still not layerized after displayport expiry");
 
   // Expire displayport with scrolling on inner2
-  yield scrollWheelOver(document.getElementById('inner2'));
+  yield scrollWheelOver(document.getElementById('inner2'), true, testDriver);
   yield waitForAllPaints(function() {
-    flushApzRepaints(driveTest);
+    flushApzRepaints(testDriver);
   });
   // Once the expiry elapses, it will trigger expiry on outer2, so we check
   // both, one at a time.
-  yield setTimeout(driveTest, DISPLAYPORT_EXPIRY);
-  yield waitForAllPaints(callDriveTestAsync);
+  yield setTimeout(testDriver, DISPLAYPORT_EXPIRY);
+  yield waitForAllPaints(asyncWrapper(testDriver));
   ok(!isLayerized('inner2'), "inner2 is no longer layerized after displayport expiry");
-  yield setTimeout(driveTest, DISPLAYPORT_EXPIRY);
-  yield waitForAllPaints(callDriveTestAsync);
+  yield setTimeout(testDriver, DISPLAYPORT_EXPIRY);
+  yield waitForAllPaints(asyncWrapper(testDriver));
   ok(!isLayerized('outer2'), "outer2 got de-layerized with inner2");
 
   // Scroll on inner3. inner3 isn't layerized, and this will cause it to
   // get layerized, but it will also trigger displayport expiration for inner3
   // which will eventually trigger displayport expiration on inner3 and outer3.
   // Note that the displayport expiration might actually happen before the wheel
   // input is processed in the compositor (see bug 1246480 comment 3), and so
   // we make sure not to wait for a scroll event here, since it may never fire.
-  yield scrollWheelOver(document.getElementById('outer3').contentDocument.getElementById('inner3'), false);
+  yield scrollWheelOver(document.getElementById('outer3').contentDocument.getElementById('inner3'), false, testDriver);
   yield waitForAllPaints(function() {
-    flushApzRepaints(driveTest);
+    flushApzRepaints(testDriver);
   });
-  yield setTimeout(driveTest, DISPLAYPORT_EXPIRY);
-  yield waitForAllPaints(callDriveTestAsync);
+  yield setTimeout(testDriver, DISPLAYPORT_EXPIRY);
+  yield waitForAllPaints(asyncWrapper(testDriver));
   ok(!isLayerized('inner3'), "inner3 becomes unlayerized after expiry");
-  yield setTimeout(driveTest, DISPLAYPORT_EXPIRY);
-  yield waitForAllPaints(callDriveTestAsync);
+  yield setTimeout(testDriver, DISPLAYPORT_EXPIRY);
+  yield waitForAllPaints(asyncWrapper(testDriver));
   ok(!isLayerized('outer3'), "outer3 is no longer layerized after inner3 triggered expiry");
 
   // Scroll outer4 and wait for the expiry. It should NOT get expired because
   // inner4 is still layerized
-  yield scrollWheelOver(document.getElementById('outer4').contentDocument.documentElement);
+  yield scrollWheelOver(document.getElementById('outer4').contentDocument.documentElement, true, testDriver);
   yield waitForAllPaints(function() {
-    flushApzRepaints(driveTest);
+    flushApzRepaints(testDriver);
   });
   // Wait for the expiry to elapse
-  yield setTimeout(driveTest, DISPLAYPORT_EXPIRY);
-  yield waitForAllPaints(callDriveTestAsync);
+  yield setTimeout(testDriver, DISPLAYPORT_EXPIRY);
+  yield waitForAllPaints(asyncWrapper(testDriver));
   ok(isLayerized('inner4'), "inner4 is still layerized because it never expired");
   ok(isLayerized('outer4'), "outer4 is still layerized because inner4 is still layerized");
 }
 
-function driveTest() {
-  if (!gTestContinuation) {
-    gTestContinuation = runTest();
-  }
-  var ret = gTestContinuation.next();
-  if (ret.done) {
-    SimpleTest.finish();
-  }
+if (isApzEnabled()) {
+  SimpleTest.waitForExplicitFinish();
+  SimpleTest.requestFlakyTimeout("we are testing code that measures an actual timeout");
+  SimpleTest.expectAssertions(0, 8); // we get a bunch of "ASSERTION: Bounds computation mismatch" sometimes (bug 1232856)
+
+  // Disable smooth scrolling, because it results in long-running scroll
+  // animations that can result in a 'scroll' event triggered by an earlier
+  // wheel event as corresponding to a later wheel event.
+  // Also enable APZ test logging, since we use that data to determine whether
+  // a scroll frame was layerized.
+  pushPrefs([["general.smoothScroll", false],
+             ["apz.displayport_expiry_ms", 0],
+             ["apz.test.logging_enabled", true]])
+  .then(waitUntilApzStable)
+  .then(runContinuation(test))
+  .then(SimpleTest.finish);
 }
 
-function startTest() {
-  // This test requires APZ - if it's not enabled, skip it.
-  var apzEnabled = SpecialPowers.getDOMWindowUtils(window).asyncPanZoomEnabled;
-  if (!apzEnabled) {
-    ok(true, "APZ not enabled, skipping test");
-    SimpleTest.finish();
-    return;
-  }
-
-  waitForAllPaints(function() {
-    flushApzRepaints(driveTest);
-  })
-}
-
-SimpleTest.waitForExplicitFinish();
-SimpleTest.requestFlakyTimeout("we are testing code that measures an actual timeout");
-SimpleTest.expectAssertions(0, 8); // we get a bunch of "ASSERTION: Bounds computation mismatch" sometimes (bug 1232856)
-
-// Disable smooth scrolling, because it results in long-running scroll
-// animations that can result in a 'scroll' event triggered by an earlier
-// wheel event as corresponding to a later wheel event.
-// Also enable APZ test logging, since we use that data to determine whether
-// a scroll frame was layerized.
-SpecialPowers.pushPrefEnv({"set": [["general.smoothScroll", false],
-                                   ["apz.displayport_expiry_ms", 0],
-                                   ["apz.test.logging_enabled", true]]},
-                          function() {
-                            SimpleTest.waitForFocus(startTest, window);
-                          });
 </script>
 </pre>
 </body>
 </html>
--- a/gfx/layers/apz/test/mochitest/test_scroll_inactive_bug1190112.html
+++ b/gfx/layers/apz/test/mochitest/test_scroll_inactive_bug1190112.html
@@ -1,15 +1,16 @@
 <!DOCTYPE HTML>
 <html>
 <head>
   <title>Test scrolling flattened inactive frames</title>
-  <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
   <script type="application/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
   <script type="application/javascript" src="/tests/SimpleTest/paint_listener.js"></script>
+  <script type="application/javascript" src="apz_test_utils.js"></script>
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
 <style>
 p {
   width:200px;
   height:200px;
   border:solid 1px black;
   overflow:auto;
 }
@@ -521,23 +522,20 @@ function doInnerScrollAgain(subframe, ol
   sendWheelAndPaint(subframe, 10, 10, DefaultEvent, function () {
     var newpos = new ScrollTops();
     ok(oldpos.outerScrollTop == newpos.outerScrollTop, "viewport should not have scrolled");
     ok(oldpos.innerScrollTop != newpos.innerScrollTop, "subframe should have scrolled");
     SimpleTest.finish();
   });
 }
 
-window.onload = function() {
-  SpecialPowers.pushPrefEnv({
-    'set': [['general.smoothScroll', false],
-            ['mousewheel.transaction.timeout', 0],
-            ['mousewheel.transaction.ignoremovedelay', 0]]
-  }, function () {
-    SimpleTest.waitForFocus(test);
-  });
-}
-
 SimpleTest.testInChaosMode();
 SimpleTest.waitForExplicitFinish();
+
+pushPrefs([['general.smoothScroll', false],
+           ['mousewheel.transaction.timeout', 0],
+           ['mousewheel.transaction.ignoremovedelay', 0]])
+.then(waitUntilApzStable)
+.then(test);
+
 </script>
 </body>
 </html>
--- a/gfx/layers/apz/test/mochitest/test_scroll_inactive_flattened_frame.html
+++ b/gfx/layers/apz/test/mochitest/test_scroll_inactive_flattened_frame.html
@@ -31,28 +31,19 @@ function test() {
   };
   sendWheelAndPaint(inner, 20, 30, event, function () {
     ok(container.scrollTop == containerScrollTop, "container scrollframe should not have scrolled");
     ok(outer.scrollTop > outerScrollTop, "nested scrollframe should have scrolled");
     SimpleTest.finish();
   });
 }
 
-function startTest() {
-  waitForAllPaints(function() {
-    flushApzRepaints(test);
-  });
-}
-
-window.onload = function() {
-  SpecialPowers.pushPrefEnv({
-    'set': [['general.smoothScroll', false],
-            ['mousewheel.transaction.timeout', 1000000]],
-  }, function () {
-    SimpleTest.waitForFocus(startTest);
-  });
-}
-
 SimpleTest.testInChaosMode();
 SimpleTest.waitForExplicitFinish();
+
+pushPrefs([['general.smoothScroll', false],
+           ['mousewheel.transaction.timeout', 1000000]])
+.then(waitUntilApzStable)
+.then(test);
+
 </script>
 </body>
 </html>
--- a/gfx/layers/apz/test/mochitest/test_scroll_subframe_scrollbar.html
+++ b/gfx/layers/apz/test/mochitest/test_scroll_subframe_scrollbar.html
@@ -99,28 +99,19 @@ function testScrolling(subframe) {
   var oldScrollTop = subframe.scrollTop;
 
   sendWheelAndPaint(subframe, posX, posY, DefaultEvent, function () {
     ok(subframe.scrollTop > oldScrollTop, "subframe should have scrolled");
     SimpleTest.finish();
   });
 }
 
-function startTest() {
-  waitForAllPaints(function() {
-    flushApzRepaints(test);
-  });
-}
+SimpleTest.waitForExplicitFinish();
 
-window.onload = function() {
-  SpecialPowers.pushPrefEnv({
-    'set': [['general.smoothScroll', false],
-            ['mousewheel.transaction.timeout', 0],
-            ['mousewheel.transaction.ignoremovedelay', 0]]
-  }, function () {
-    SimpleTest.waitForFocus(startTest);
-  });
-}
+pushPrefs([['general.smoothScroll', false],
+           ['mousewheel.transaction.timeout', 0],
+           ['mousewheel.transaction.ignoremovedelay', 0]])
+.then(waitUntilApzStable)
+.then(test);
 
-SimpleTest.waitForExplicitFinish();
 </script>
 </body>
 </html>
--- a/gfx/layers/apz/test/mochitest/test_touch_listeners_impacting_wheel.html
+++ b/gfx/layers/apz/test/mochitest/test_touch_listeners_impacting_wheel.html
@@ -84,76 +84,53 @@ function takeSnapshots(e) {
   // Until now, no scroll events will have been dispatched to content. That's
   // because scroll events are dispatched on the main thread, which we've been
   // hogging with the code above. At this point we restore the normal refresh
   // behaviour and let the main thread go back to C++ code, so the scroll events
   // fire and we unwind from the main test continuation.
   SpecialPowers.DOMWindowUtils.restoreNormalRefresh();
 }
 
-function* runTest() {
+function* test(testDriver) {
   var box = document.getElementById('box');
   box.addEventListener('touchstart', function(e) {
     ok(false, "This should never be run");
   }, false);
   box.addEventListener('wheel', takeSnapshots, { capture: false, passive: true });
 
   // Let the event regions propagate to the APZ
   yield waitForAllPaints(function() {
-    flushApzRepaints(driveTest);
+    flushApzRepaints(testDriver);
   });
 
   // Take over control of the refresh driver and compositor
   var utils = SpecialPowers.DOMWindowUtils;
   utils.advanceTimeAndRefresh(0);
 
   // Trigger an APZ scroll using a wheel event. If APZ is waiting for a
   // content response, it will wait for takeSnapshots to finish running before
   // it starts scrolling, which will cause the checks in takeSnapshots to fail.
-  yield synthesizeNativeMouseMoveAndWaitForMoveEvent(box, 10, 10, driveTest);
-  yield synthesizeNativeWheelAndWaitForScrollEvent(box, 10, 10, 0, -50, driveTest);
-}
-
-var gTestContinuation = null;
-function driveTest() {
-  if (!gTestContinuation) {
-    gTestContinuation = runTest();
-  }
-  var ret = gTestContinuation.next();
-  if (ret.done) {
-    SimpleTest.finish();
-  }
+  yield synthesizeNativeMouseMoveAndWaitForMoveEvent(box, 10, 10, testDriver);
+  yield synthesizeNativeWheelAndWaitForScrollEvent(box, 10, 10, 0, -50, testDriver);
 }
 
-function startTest() {
-  // This test requires APZ - if it's not enabled, skip it.
-  var apzEnabled = SpecialPowers.getDOMWindowUtils(window).asyncPanZoomEnabled;
-  if (!apzEnabled) {
-    ok(true, "APZ not enabled, skipping test");
-    SimpleTest.finish();
-    return;
-  }
-
-  waitForAllPaints(function() {
-    flushApzRepaints(driveTest);
-  })
+if (isApzEnabled()) {
+  SimpleTest.waitForExplicitFinish();
+  // Disable touch events, so that APZ knows not to wait for touch listeners.
+  // Also explicitly set the content response timeout, so we know how long it
+  // is (see comment in takeSnapshots).
+  // Finally, enable smooth scrolling, so that the wheel-scroll we do as part
+  // of the test triggers an APZ animation rather than doing an instant scroll.
+  // Note that this pref doesn't work for the synthesized wheel events on OS X,
+  // those are hard-coded to be instant scrolls.
+  pushPrefs([["dom.w3c_touch_events.enabled", 0],
+             ["apz.content_response_timeout", kResponseTimeoutMs],
+             ["general.smoothscroll", true]])
+  .then(waitUntilApzStable)
+  .then(runContinuation(test))
+  .then(SimpleTest.finish);
 }
 
-SimpleTest.waitForExplicitFinish();
-
-// Disable touch events, so that APZ knows not to wait for touch listeners.
-// Also explicitly set the content response timeout, so we know how long it
-// is (see comment in takeSnapshots).
-// Finally, enable smooth scrolling, so that the wheel-scroll we do as part
-// of the test triggers an APZ animation rather than doing an instant scroll.
-// Note that this pref doesn't work for the synthesized wheel events on OS X,
-// those are hard-coded to be instant scrolls.
-SpecialPowers.pushPrefEnv({"set": [["dom.w3c_touch_events.enabled", 0],
-                                   ["apz.content_response_timeout", kResponseTimeoutMs],
-                                   ["general.smoothscroll", true]]},
-                          function() {
-                            SimpleTest.waitForFocus(startTest, window);
-                          });
 </script>
 </pre>
 
 </body>
 </html>
--- a/gfx/layers/apz/test/mochitest/test_wheel_scroll.html
+++ b/gfx/layers/apz/test/mochitest/test_wheel_scroll.html
@@ -1,17 +1,19 @@
 <!DOCTYPE HTML>
 <html>
 <!--
 https://bugzilla.mozilla.org/show_bug.cgi?id=1013412
 -->
 <head>
   <title>Test for Bug 1013412</title>
   <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
-  <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+  <script type="application/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+  <script type="application/javascript" src="/tests/SimpleTest/paint_listener.js"></script>
+  <script type="application/javascript" src="apz_test_utils.js"></script>
   <script type="application/javascript" src="apz_test_native_event_utils.js"></script>
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
   <style>
   #content {
     height: 800px;
     overflow: scroll;
   }
 
@@ -70,48 +72,35 @@ var incrementForMode = function (mode) {
 
 document.getElementById("scrollbox").addEventListener("wheel", function (e) {
   rotation += e.deltaY * incrementForMode(e.deltaMode) * 0.2;
   document.getElementById("circle").style.transform = "rotate(" + rotation + "deg)";
   rotationAdjusted = true;
   e.preventDefault();
 });
 
-function* runTest() {
+function* test(testDriver) {
   var content = document.getElementById('content');
   for (i = 0; i < 300; i++) { // enough iterations that we would scroll to the bottom of 'content'
-    yield synthesizeNativeWheelAndWaitForWheelEvent(content, 100, 150, 0, -5, continueTest);
+    yield synthesizeNativeWheelAndWaitForWheelEvent(content, 100, 150, 0, -5, testDriver);
   }
   var scrollbox = document.getElementById('scrollbox');
   is(content.scrollTop > 0, true, "We should have scrolled down somewhat");
   is(content.scrollTop < content.scrollTopMax, true, "We should not have scrolled to the bottom of the scrollframe");
   is(rotationAdjusted, true, "The rotation should have been adjusted");
 }
 
-var gTestContinuation = null;
-function continueTest() {
-  if (!gTestContinuation) {
-    gTestContinuation = runTest();
-  }
-  var ret = gTestContinuation.next();
-  if (ret.done) {
-    SimpleTest.finish();
-  } else {
-    is(ret.value, true, "Wheel event successfully synthesized");
-  }
-}
-
-function startTest() {
-  // If we allow smooth scrolling the "smooth" scrolling may cause the page to
-  // glide past the scrollbox (which is supposed to stop the scrolling) and so
-  // we might end up at the bottom of the page.
-  SpecialPowers.pushPrefEnv({"set": [["general.smoothScroll", false]]}, continueTest);
-}
-
 SimpleTest.testInChaosMode();
 SimpleTest.waitForExplicitFinish();
-SimpleTest.waitForFocus(startTest, window);
+
+// If we allow smooth scrolling the "smooth" scrolling may cause the page to
+// glide past the scrollbox (which is supposed to stop the scrolling) and so
+// we might end up at the bottom of the page.
+pushPrefs([["general.smoothScroll", false]])
+.then(waitUntilApzStable)
+.then(runContinuation(test))
+.then(SimpleTest.finish);
 
 </script>
 </pre>
 
 </body>
 </html>
--- a/gfx/layers/apz/test/mochitest/test_wheel_transactions.html
+++ b/gfx/layers/apz/test/mochitest/test_wheel_transactions.html
@@ -36,119 +36,102 @@ https://bugzilla.mozilla.org/show_bug.cg
 <div id="outer-frame">
     <div id="inner-frame">
         <div id="inner-content"></div>
     </div>
 </div>
 <pre id="test">
 <script type="application/javascript;version=1.7">
 
-function scrollWheelOver(element, deltaY) {
-  synthesizeNativeWheelAndWaitForScrollEvent(element, 10, 10, 0, deltaY, driveTest);
+function scrollWheelOver(element, deltaY, testDriver) {
+  synthesizeNativeWheelAndWaitForScrollEvent(element, 10, 10, 0, deltaY, testDriver);
 }
 
-function* runTest() {
+function* test(testDriver) {
   var outer = document.getElementById('outer-frame');
   var inner = document.getElementById('inner-frame');
   var innerContent = document.getElementById('inner-content');
 
   // Register a wheel event listener that records the target of
   // the last wheel event, so that we can make assertions about it.
   var lastWheelTarget;
   var wheelTargetRecorder = function(e) { lastWheelTarget = e.target; };
   window.addEventListener("wheel", wheelTargetRecorder);
 
   // Scroll |outer| to the bottom.
   while (outer.scrollTop < outer.scrollTopMax) {
-    yield scrollWheelOver(outer, -10);
+    yield scrollWheelOver(outer, -10, testDriver);
   }
 
   // Verify that this has brought |inner| under the wheel.
   is(lastWheelTarget, innerContent, "'inner-content' should have been brought under the wheel");
   window.removeEventListener("wheel", wheelTargetRecorder);
 
   // Immediately after, scroll it back up a bit.
-  yield scrollWheelOver(outer, 10);
+  yield scrollWheelOver(outer, 10, testDriver);
 
   // Check that it was |outer| that scrolled back, and |inner| didn't
   // scroll at all, as all the above scrolls should be in the same
   // transaction.
   ok(outer.scrollTop < outer.scrollTopMax, "'outer' should have scrolled back a bit");
   is(inner.scrollTop, 0, "'inner' should not have scrolled");
 
   // The next part of the test is related to the transaction timeout.
   // Turn it down a bit so waiting for the timeout to elapse doesn't
   // slow down the test harness too much.
   var timeout = 5;
-  yield SpecialPowers.pushPrefEnv({"set": [["mousewheel.transaction.timeout", timeout]]}, driveTest);
+  yield SpecialPowers.pushPrefEnv({"set": [["mousewheel.transaction.timeout", timeout]]}, testDriver);
   SimpleTest.requestFlakyTimeout("we are testing code that measures actual elapsed time between two events");
 
   // Scroll up a bit more. It's still |outer| scrolling because
   // |inner| is still scrolled all the way to the top.
-  yield scrollWheelOver(outer, 10);
+  yield scrollWheelOver(outer, 10, testDriver);
 
   // Wait for the transaction timeout to elapse.
   // timeout * 5 is used to make it less likely that the timeout is less than
   // the system timestamp resolution
-  yield window.setTimeout(driveTest, timeout * 5);
+  yield window.setTimeout(testDriver, timeout * 5);
 
   // Now scroll down. The transaction having timed out, the event
   // should pick up a new target, and that should be |inner|.
-  yield scrollWheelOver(outer, -10);
+  yield scrollWheelOver(outer, -10, testDriver);
   ok(inner.scrollTop > 0, "'inner' should have been scrolled");
 
   // Finally, test scroll handoff after a timeout.
 
   // Continue scrolling |inner| down to the bottom.
   var prevScrollTop = inner.scrollTop;
   while (inner.scrollTop < inner.scrollTopMax) {
-    yield scrollWheelOver(outer, -10);
+    yield scrollWheelOver(outer, -10, testDriver);
     // Avoid a failure getting us into an infinite loop.
     ok(inner.scrollTop > prevScrollTop, "scrolling down should increase scrollTop");
     prevScrollTop = inner.scrollTop;
   }
 
   // Wait for the transaction timeout to elapse.
   // timeout * 5 is used to make it less likely that the timeout is less than
   // the system timestamp resolution
-  yield window.setTimeout(driveTest, timeout * 5);
+  yield window.setTimeout(testDriver, timeout * 5);
 
   // Continued downward scrolling should scroll |outer| to the bottom.
   prevScrollTop = outer.scrollTop;
   while (outer.scrollTop < outer.scrollTopMax) {
-    yield scrollWheelOver(outer, -10);
+    yield scrollWheelOver(outer, -10, testDriver);
     // Avoid a failure getting us into an infinite loop.
     ok(outer.scrollTop > prevScrollTop, "scrolling down should increase scrollTop");
     prevScrollTop = outer.scrollTop;
   }
 }
 
-var gTestContinuation = null;
-function driveTest() {
-  if (!gTestContinuation) {
-    gTestContinuation = runTest();
-  }
-  var ret = gTestContinuation.next();
-  if (ret.done) {
-    SimpleTest.finish();
-  }
-}
-
-function startTest() {
-  waitForAllPaints(function() {
-    flushApzRepaints(driveTest);
-  });
-}
+SimpleTest.waitForExplicitFinish();
 
 // Disable smooth scrolling because it makes the test flaky (we don't have a good
 // way of detecting when the scrolling is finished).
-SpecialPowers.pushPrefEnv({"set": [["general.smoothScroll", false]]},
-                          function() {
-                            SimpleTest.waitForFocus(startTest, window);
-                          });
-
-SimpleTest.waitForExplicitFinish();
+pushPrefs([["general.smoothScroll", false]])
+.then(waitUntilApzStable)
+.then(runContinuation(test))
+.then(SimpleTest.finish);
 
 </script>
 </pre>
 
 </body>
 </html>
--- a/gfx/tests/mochitest/mochitest.ini
+++ b/gfx/tests/mochitest/mochitest.ini
@@ -1,10 +1,11 @@
 [DEFAULT]
 skip-if = buildapp == 'mulet' || buildapp == 'b2g'
 
 [test_acceleration.html]
+subsuite = gpu
 fail-if = (os == "win" && os_version == "5.1" && e10s) # Bug 1253862
 [test_bug509244.html]
 [test_bug513439.html]
 [test_overdraw.html]
 # Disable test until bug 1064136 is fixed
 skip-if = true
--- a/js/src/builtin/RegExp.js
+++ b/js/src/builtin/RegExp.js
@@ -804,17 +804,17 @@ function RegExpExec(R, S, forTest) {
 }
 
 // ES 2017 draft rev 6a13789aa9e7c6de4e96b7d3e24d9e6eba6584ad 21.2.5.2.2.
 function RegExpBuiltinExec(R, S, forTest) {
     // ES6 21.2.5.2.1 step 6.
     // This check is here for RegExpTest.  RegExp_prototype_Exec does same
     // thing already.
     if (!IsRegExpObject(R))
-        return callFunction(CallRegExpMethodIfWrapped, R, R, S, forTest, "RegExpBuiltinExec");
+        return UnwrapAndCallRegExpBuiltinExec(R, S, forTest);
 
     // Steps 1-2 (skipped).
 
     // Step 4.
     var lastIndex = ToLength(R.lastIndex);
 
     // Step 5.
     var flags = UnsafeGetInt32FromReservedSlot(R, REGEXP_FLAGS_SLOT);
@@ -858,16 +858,24 @@ function RegExpBuiltinExec(R, S, forTest
         // Step 15.
         if (globalOrSticky)
             R.lastIndex = result.index + result[0].length;
     }
 
     return result;
 }
 
+function UnwrapAndCallRegExpBuiltinExec(R, S, forTest) {
+    return callFunction(CallRegExpMethodIfWrapped, R, S, forTest, "CallRegExpBuiltinExec");
+}
+
+function CallRegExpBuiltinExec(S, forTest) {
+    return RegExpBuiltinExec(this, S, forTest);
+}
+
 // ES6 21.2.5.13.
 function RegExpTest(string) {
     // Steps 1-2.
     var R = this;
     if (!IsObject(R))
         ThrowTypeError(JSMSG_NOT_NONNULL_OBJECT, R === null ? "null" : typeof R);
 
     // Steps 3-4.
--- a/js/src/jit/IonAnalysis.cpp
+++ b/js/src/jit/IonAnalysis.cpp
@@ -11,16 +11,18 @@
 #include "jit/BaselineJIT.h"
 #include "jit/FlowAliasAnalysis.h"
 #include "jit/Ion.h"
 #include "jit/IonBuilder.h"
 #include "jit/IonOptimizationLevels.h"
 #include "jit/LIR.h"
 #include "jit/Lowering.h"
 #include "jit/MIRGraph.h"
+#include "vm/RegExpObject.h"
+#include "vm/SelfHosting.h"
 
 #include "jsobjinlines.h"
 #include "jsopcodeinlines.h"
 #include "jsscriptinlines.h"
 
 #include "jit/shared/Lowering-shared-inl.h"
 
 using namespace js;
@@ -1822,56 +1824,240 @@ jit::ApplyTypeInformation(MIRGenerator* 
     TypeAnalyzer analyzer(mir, graph);
 
     if (!analyzer.analyze())
         return false;
 
     return true;
 }
 
+// Check if `def` is only the N-th operand of `useDef`.
+static inline size_t
+IsExclusiveNthOperand(MDefinition* useDef, size_t n, MDefinition* def)
+{
+    uint32_t num = useDef->numOperands();
+    if (n >= num || useDef->getOperand(n) != def)
+        return false;
+
+    for (uint32_t i = 0; i < num; i++) {
+        if (i == n)
+            continue;
+        if (useDef->getOperand(i) == def)
+            return false;
+    }
+
+    return true;
+}
+
+static size_t
+IsExclusiveThisArg(MCall* call, MDefinition* def)
+{
+    return IsExclusiveNthOperand(call, MCall::IndexOfThis(), def);
+}
+
+static size_t
+IsExclusiveFirstArg(MCall* call, MDefinition* def)
+{
+    return IsExclusiveNthOperand(call, MCall::IndexOfArgument(0), def);
+}
+
+static bool
+IsRegExpHoistableCall(MCall* call, MDefinition* def)
+{
+    if (call->isConstructing())
+        return false;
+
+    JSAtom* name;
+    JSFunction* fun = call->getSingleTarget();
+    if (fun) {
+        if (!fun->isSelfHostedBuiltin())
+            return false;
+        name = GetSelfHostedFunctionName(fun);
+    } else {
+        MDefinition* funDef = call->getFunction();
+        if (funDef->isDebugCheckSelfHosted())
+            funDef = funDef->toDebugCheckSelfHosted()->input();
+        if (funDef->isTypeBarrier())
+            funDef = funDef->toTypeBarrier()->input();
+
+        if (!funDef->isCallGetIntrinsicValue())
+            return false;
+        name = funDef->toCallGetIntrinsicValue()->name();
+    }
+
+    // Hoistable only if the RegExp is the first argument of RegExpBuiltinExec.
+    CompileRuntime* runtime = GetJitContext()->runtime;
+    if (name == runtime->names().RegExpBuiltinExec ||
+        name == runtime->names().UnwrapAndCallRegExpBuiltinExec ||
+        name == runtime->names().RegExpMatcher ||
+        name == runtime->names().RegExpTester ||
+        name == runtime->names().RegExpSearcher)
+    {
+        return IsExclusiveFirstArg(call, def);
+    }
+
+    if (name == runtime->names().RegExp_prototype_Exec)
+        return IsExclusiveThisArg(call, def);
+
+    return false;
+}
+
+static bool
+CanCompareWithoutToPrimitive(MCompare* compare, MDefinition* def)
+{
+    JSOp op = compare->jsop();
+    // Strict equality comparison won't invoke @@toPrimitive.
+    if (op == JSOP_STRICTEQ || op == JSOP_STRICTNE)
+        return true;
+
+    if (op != JSOP_EQ && op != JSOP_NE) {
+        // Relational comparison always invoke @@toPrimitive.
+        MOZ_ASSERT(op == JSOP_GT || op == JSOP_GE || op == JSOP_LT || op == JSOP_LE);
+        return false;
+    }
+
+    // Loose equality comparison can invoke @@toPrimitive.
+    MDefinition* value;
+    if (compare->lhs() == def) {
+        value = compare->rhs();
+    } else {
+        MOZ_ASSERT(compare->rhs() == def);
+        value = compare->lhs();
+    }
+
+    if (value->mightBeType(MIRType::Boolean) || value->mightBeType(MIRType::String) ||
+        value->mightBeType(MIRType::Int32) ||
+        value->mightBeType(MIRType::Double) || value->mightBeType(MIRType::Float32) ||
+        value->mightBeType(MIRType::Symbol))
+    {
+        return false;
+    }
+
+    return true;
+}
+
+static inline void
+SetNotInWorklist(MDefinitionVector& worklist)
+{
+    for (size_t i = 0; i < worklist.length(); i++)
+        worklist[i]->setNotInWorklist();
+}
+
+static bool
+IsRegExpHoistable(MDefinition* regexp, MDefinitionVector& worklist, bool* hoistable)
+{
+    MOZ_ASSERT(worklist.length() == 0);
+
+    if (!worklist.append(regexp))
+        return false;
+    regexp->setInWorklist();
+
+    for (size_t i = 0; i < worklist.length(); i++) {
+        MDefinition* def = worklist[i];
+        for (MUseIterator use = def->usesBegin(); use != def->usesEnd(); use++) {
+            // Ignore resume points. At this point all uses are listed.
+            // No DCE or GVN or something has happened.
+            if (use->consumer()->isResumePoint())
+                continue;
+
+            MDefinition* useDef = use->consumer()->toDefinition();
+
+            // Step through a few white-listed ops.
+            if (useDef->isPhi() || useDef->isFilterTypeSet() || useDef->isGuardShape()) {
+                if (useDef->isInWorklist())
+                    continue;
+
+                if (!worklist.append(useDef))
+                    return false;
+                useDef->setInWorklist();
+                continue;
+            }
+
+            // Instructions that doesn't invoke unknown code that may modify
+            // RegExp instance or pass it to elsewhere.
+            if (useDef->isRegExpMatcher() || useDef->isRegExpTester() ||
+                useDef->isRegExpSearcher())
+            {
+                if (IsExclusiveNthOperand(useDef, 0, def))
+                    continue;
+            } else if (useDef->isLoadFixedSlot() || useDef->isTypeOf()) {
+                continue;
+            } else if (useDef->isCompare()) {
+                if (CanCompareWithoutToPrimitive(useDef->toCompare(), def))
+                    continue;
+            }
+            // Instructions that modifies `lastIndex` property.
+            else if (useDef->isStoreFixedSlot()) {
+                if (IsExclusiveNthOperand(useDef, 0, def)) {
+                    MStoreFixedSlot* store = useDef->toStoreFixedSlot();
+                    if (store->slot() == RegExpObject::lastIndexSlot())
+                        continue;
+                }
+            } else if (useDef->isSetPropertyCache()) {
+                if (IsExclusiveNthOperand(useDef, 0, def)) {
+                    MSetPropertyCache* setProp = useDef->toSetPropertyCache();
+                    if (setProp->idval()->isConstant()) {
+                        Value propIdVal = setProp->idval()->toConstant()->toJSValue();
+                        if (propIdVal.isString()) {
+                            CompileRuntime* runtime = GetJitContext()->runtime;
+                            if (propIdVal.toString() == runtime->names().lastIndex)
+                                continue;
+                        }
+                    }
+                }
+            }
+            // MCall is safe only for some known safe functions.
+            else if (useDef->isCall()) {
+                if (IsRegExpHoistableCall(useDef->toCall(), def))
+                    continue;
+            }
+
+            // Everything else is unsafe.
+            SetNotInWorklist(worklist);
+            worklist.clear();
+            *hoistable = false;
+
+            return true;
+        }
+    }
+
+    SetNotInWorklist(worklist);
+    worklist.clear();
+    *hoistable = true;
+    return true;
+}
+
 bool
 jit::MakeMRegExpHoistable(MIRGraph& graph)
 {
+    MDefinitionVector worklist(graph.alloc());
+
     for (ReversePostorderIterator block(graph.rpoBegin()); block != graph.rpoEnd(); block++) {
         for (MDefinitionIterator iter(*block); iter; iter++) {
             if (!*iter)
                 MOZ_CRASH("confirm bug 1263794.");
 
             if (!iter->isRegExp())
                 continue;
 
             MRegExp* regexp = iter->toRegExp();
 
-            // Test if MRegExp is hoistable by looking at all uses.
-            bool hoistable = true;
-            for (MUseIterator i = regexp->usesBegin(); i != regexp->usesEnd(); i++) {
-                // Ignore resume points. At this point all uses are listed.
-                // No DCE or GVN or something has happened.
-                if (i->consumer()->isResumePoint())
-                    continue;
-
-                MOZ_ASSERT(i->consumer()->isDefinition());
-
-                // All MRegExp* MIR's don't adjust the regexp.
-                MDefinition* use = i->consumer()->toDefinition();
-                if (use->isRegExpMatcher() || use->isRegExpTester() || use->isRegExpSearcher())
-                    continue;
-
-                hoistable = false;
-                break;
-            }
+            bool hoistable = false;
+            if (!IsRegExpHoistable(regexp, worklist, &hoistable))
+                return false;
 
             if (!hoistable)
                 continue;
 
             // Make MRegExp hoistable
             regexp->setMovable();
 
-            // That would be incorrect for global/sticky, because lastIndex could be wrong.
-            // Therefore setting the lastIndex to 0. That is faster than a not movable regexp.
+            // That would be incorrect for global/sticky, because lastIndex
+            // could be wrong.  Therefore setting the lastIndex to 0. That is
+            // faster than a not movable regexp.
             RegExpObject* source = regexp->source();
             if (source->sticky() || source->global()) {
                 MOZ_ASSERT(regexp->mustClone());
                 MConstant* zero = MConstant::New(graph.alloc(), Int32Value(0));
                 regexp->block()->insertAfter(regexp, zero);
 
                 MStoreFixedSlot* lastIndex =
                     MStoreFixedSlot::New(graph.alloc(), regexp, RegExpObject::lastIndexSlot(), zero);
--- a/js/src/js.msg
+++ b/js/src/js.msg
@@ -291,17 +291,17 @@ MSG_DEF(JSMSG_PAREN_BEFORE_CATCH,      0
 MSG_DEF(JSMSG_PAREN_BEFORE_COND,       0, JSEXN_SYNTAXERR, "missing ( before condition")
 MSG_DEF(JSMSG_PAREN_BEFORE_FORMAL,     0, JSEXN_SYNTAXERR, "missing ( before formal parameters")
 MSG_DEF(JSMSG_PAREN_BEFORE_SWITCH,     0, JSEXN_SYNTAXERR, "missing ( before switch expression")
 MSG_DEF(JSMSG_PAREN_BEFORE_WITH,       0, JSEXN_SYNTAXERR, "missing ( before with-statement object")
 MSG_DEF(JSMSG_PAREN_IN_PAREN,          0, JSEXN_SYNTAXERR, "missing ) in parenthetical")
 MSG_DEF(JSMSG_RC_AFTER_EXPORT_SPEC_LIST, 0, JSEXN_SYNTAXERR, "missing '}' after export specifier list")
 MSG_DEF(JSMSG_RC_AFTER_IMPORT_SPEC_LIST, 0, JSEXN_SYNTAXERR, "missing '}' after module specifier list")
 MSG_DEF(JSMSG_REDECLARED_CATCH_IDENTIFIER, 1, JSEXN_SYNTAXERR, "redeclaration of identifier '{0}' in catch")
-MSG_DEF(JSMSG_REDECLARED_PARAM,        1, JSEXN_TYPEERR, "redeclaration of formal parameter {0}")
+MSG_DEF(JSMSG_REDECLARED_PARAM,        1, JSEXN_SYNTAXERR, "redeclaration of formal parameter {0}")
 MSG_DEF(JSMSG_RESERVED_ID,             1, JSEXN_SYNTAXERR, "{0} is a reserved identifier")
 MSG_DEF(JSMSG_REST_WITH_DEFAULT,       0, JSEXN_SYNTAXERR, "rest parameter may not have a default")
 MSG_DEF(JSMSG_SELFHOSTED_TOP_LEVEL_LEXICAL, 1, JSEXN_SYNTAXERR, "self-hosted code cannot contain top-level {0} declarations")
 MSG_DEF(JSMSG_SELFHOSTED_UNBOUND_NAME, 0, JSEXN_TYPEERR, "self-hosted code may not contain unbound name lookups")
 MSG_DEF(JSMSG_SEMI_AFTER_FOR_COND,     0, JSEXN_SYNTAXERR, "missing ; after for-loop condition")
 MSG_DEF(JSMSG_SEMI_AFTER_FOR_INIT,     0, JSEXN_SYNTAXERR, "missing ; after for-loop initializer")
 MSG_DEF(JSMSG_SEMI_BEFORE_STMNT,       0, JSEXN_SYNTAXERR, "missing ; before statement")
 MSG_DEF(JSMSG_SOURCE_TOO_LONG,         0, JSEXN_RANGEERR, "source is too long")
--- a/js/src/tests/js1_8_1/regress/regress-452498-092.js
+++ b/js/src/tests/js1_8_1/regress/regress-452498-092.js
@@ -17,24 +17,24 @@ test();
 function test()
 {
   enterFunc ('test');
   printBugNumber(BUGNUMBER);
   printStatus (summary);
 
 // ------- Comment #92 From Jesse Ruderman
 
-  expect = 'TypeError: redeclaration of formal parameter e';
+  expect = 'SyntaxError: redeclaration of formal parameter e';
   try
   {
     eval('(function (e) { var e; const e = undefined; });');
   }
   catch(ex)
   {
     actual = ex + '';
   }
-// Without patch: "TypeError: redeclaration of var e"
-// expected new behavior // With patch:    "TypeError: redeclaration of formal parameter e:"
+// Without patch: "SyntaxError: redeclaration of var e"
+// expected new behavior // With patch:    "SyntaxError: redeclaration of formal parameter e:"
 
   reportCompare(expect, actual, summary);
 
   exitFunc ('test');
 }
--- a/js/src/vm/CommonPropertyNames.h
+++ b/js/src/vm/CommonPropertyNames.h
@@ -294,16 +294,22 @@
     macro(values, values, "values") \
     macro(valueOf, valueOf, "valueOf") \
     macro(var, var, "var") \
     macro(variable, variable, "variable") \
     macro(void0, void0, "(void 0)") \
     macro(wasm, wasm, "wasm") \
     macro(watch, watch, "watch") \
     macro(WeakSet_add, WeakSet_add, "WeakSet_add") \
+    macro(RegExp_prototype_Exec, RegExp_prototype_Exec, "RegExp_prototype_Exec") \
+    macro(UnwrapAndCallRegExpBuiltinExec, UnwrapAndCallRegExpBuiltinExec, "UnwrapAndCallRegExpBuiltinExec") \
+    macro(RegExpBuiltinExec, RegExpBuiltinExec, "RegExpBuiltinExec") \
+    macro(RegExpMatcher, RegExpMatcher, "RegExpMatcher") \
+    macro(RegExpSearcher, RegExpSearcher, "RegExpSearcher") \
+    macro(RegExpTester, RegExpTester, "RegExpTester") \
     macro(weekday, weekday, "weekday") \
     macro(writable, writable, "writable") \
     macro(year, year, "year") \
     macro(yield, yield, "yield") \
     macro(raw, raw, "raw") \
     /* Type names must be contiguous and ordered; see js::TypeName. */ \
     macro(undefined, undefined, "undefined") \
     macro(object, object, "object") \
--- a/js/src/vm/SavedStacks.cpp
+++ b/js/src/vm/SavedStacks.cpp
@@ -1356,18 +1356,20 @@ SavedStacks::adoptAsyncStack(JSContext* 
     return true;
 }
 
 SavedFrame*
 SavedStacks::getOrCreateSavedFrame(JSContext* cx, SavedFrame::HandleLookup lookup)
 {
     const SavedFrame::Lookup& lookupInstance = lookup.get();
     DependentAddPtr<SavedFrame::Set> p(cx, frames, lookupInstance);
-    if (p)
+    if (p) {
+        MOZ_ASSERT(*p);
         return *p;
+    }
 
     RootedSavedFrame frame(cx, createFrameFromLookup(cx, lookup));
     if (!frame)
         return nullptr;
 
     if (!p.add(cx, frames, lookupInstance, frame))
         return nullptr;
 
--- a/js/src/vm/SelfHosting.cpp
+++ b/js/src/vm/SelfHosting.cpp
@@ -3064,15 +3064,20 @@ js::SelfHostedFunction(JSContext* cx, Ha
     MOZ_ASSERT(func.isObject());
     MOZ_ASSERT(func.toObject().is<JSFunction>());
     return &func.toObject().as<JSFunction>();
 }
 
 bool
 js::IsSelfHostedFunctionWithName(JSFunction* fun, JSAtom* name)
 {
-    return fun->isSelfHostedBuiltin() &&
-           fun->getExtendedSlot(LAZY_FUNCTION_NAME_SLOT).toString() == name;
+    return fun->isSelfHostedBuiltin() && GetSelfHostedFunctionName(fun) == name;
+}
+
+JSAtom*
+js::GetSelfHostedFunctionName(JSFunction* fun)
+{
+    return &fun->getExtendedSlot(LAZY_FUNCTION_NAME_SLOT).toString()->asAtom();
 }
 
 static_assert(JSString::MAX_LENGTH <= INT32_MAX,
               "StringIteratorNext in builtin/String.js assumes the stored index "
               "into the string is an Int32Value");
--- a/js/src/vm/SelfHosting.h
+++ b/js/src/vm/SelfHosting.h
@@ -18,16 +18,19 @@ namespace js {
 
 /*
  * Check whether the given JSFunction is a self-hosted function whose
  * self-hosted name is the given name.
  */
 bool
 IsSelfHostedFunctionWithName(JSFunction* fun, JSAtom* name);
 
+JSAtom*
+GetSelfHostedFunctionName(JSFunction* fun);
+
 bool
 IsCallSelfHostedNonGenericMethod(NativeImpl impl);
 
 bool
 ReportIncompatibleSelfHostedMethod(JSContext* cx, const CallArgs& args);
 
 /* Get the compile options used when compiling self hosted code. */
 void
--- a/js/xpconnect/src/XPCJSRuntime.cpp
+++ b/js/xpconnect/src/XPCJSRuntime.cpp
@@ -1439,18 +1439,21 @@ XPCJSRuntime::InterruptCallback(JSContex
     if (!chrome && !self->mTimeoutAccumulated) {
       uint32_t delay = uint32_t(self->mSlowScriptActualWait.ToMilliseconds() - (limit * 1000.0));
       Telemetry::Accumulate(Telemetry::SLOW_SCRIPT_NOTIFY_DELAY, delay);
       self->mTimeoutAccumulated = true;
     }
 
     // Show the prompt to the user, and kill if requested.
     nsGlobalWindow::SlowScriptResponse response = win->ShowSlowScriptDialog();
-    if (response == nsGlobalWindow::KillSlowScript)
+    if (response == nsGlobalWindow::KillSlowScript) {
+        if (Preferences::GetBool("dom.global_stop_script", true))
+            xpc::Scriptability::Get(global).Block();
         return false;
+    }
 
     // The user chose to continue the script. Reset the timer, and disable this
     // machinery with a pref of the user opted out of future slow-script dialogs.
     if (response != nsGlobalWindow::ContinueSlowScriptAndKeepNotifying)
         self->mSlowScriptCheckpoint = TimeStamp::NowLoRes();
 
     if (response == nsGlobalWindow::AlwaysContinueSlowScript)
         Preferences::SetInt(prefName, 0);
--- a/layout/forms/nsListControlFrame.cpp
+++ b/layout/forms/nsListControlFrame.cpp
@@ -2103,20 +2103,35 @@ nsListControlFrame::KeyDown(nsIDOMEvent*
   // XXXmats keyboard events, even tabbing, when preventDefault() is called
   // XXXmats in onkeydown. That seems sub-optimal though.
 
   const WidgetKeyboardEvent* keyEvent =
     aKeyEvent->WidgetEventPtr()->AsKeyboardEvent();
   MOZ_ASSERT(keyEvent,
     "DOM event must have WidgetKeyboardEvent for its internal event");
 
+  bool dropDownMenuOnUpDown;
+  bool dropDownMenuOnSpace;
+#ifdef XP_MACOSX
+  dropDownMenuOnUpDown = IsInDropDownMode() && !mComboboxFrame->IsDroppedDown();
+  dropDownMenuOnSpace = !keyEvent->IsAlt() && !keyEvent->IsControl() &&
+    !keyEvent->IsMeta();
+#else
+  dropDownMenuOnUpDown = keyEvent->IsAlt();
+  dropDownMenuOnSpace = IsInDropDownMode() && !mComboboxFrame->IsDroppedDown();
+#endif
+  if ((dropDownMenuOnUpDown &&
+       (keyEvent->mKeyCode == NS_VK_UP || keyEvent->mKeyCode == NS_VK_DOWN)) ||
+      (dropDownMenuOnSpace && keyEvent->mKeyCode == NS_VK_SPACE)) {
+    DropDownToggleKey(aKeyEvent);
+    if (keyEvent->DefaultPrevented()) {
+      return NS_OK;
+    }
+  }
   if (keyEvent->IsAlt()) {
-    if (keyEvent->mKeyCode == NS_VK_UP || keyEvent->mKeyCode == NS_VK_DOWN) {
-      DropDownToggleKey(aKeyEvent);
-    }
     return NS_OK;
   }
 
   // now make sure there are options or we are wasting our time
   RefPtr<dom::HTMLOptionsCollection> options = GetOptions();
   NS_ENSURE_TRUE(options, NS_ERROR_FAILURE);
 
   uint32_t numOptions = options->Length();
--- a/layout/forms/test/mochitest.ini
+++ b/layout/forms/test/mochitest.ini
@@ -5,17 +5,17 @@ support-files =
   bug564115_window.html
 
 [test_bug231389.html]
 [test_bug287446.html]
 skip-if = (buildapp == 'b2g' && toolkit != 'gonk') #bug 947789
 [test_bug345267.html]
 [test_bug346043.html]
 [test_bug348236.html]
-skip-if = buildapp == 'mulet' || buildapp == 'b2g' || toolkit == 'android' || e10s # b2g(select form control popup) b2g-debug(select form control popup) b2g-desktop(select form control popup)
+skip-if = buildapp == 'mulet' || buildapp == 'b2g' || toolkit == 'android' || e10s || os == 'mac' # b2g(select form control popup) b2g-debug(select form control popup) b2g-desktop(select form control popup) mac(select form control popup behavior is different)
 [test_bug353539.html]
 [test_bug365410.html]
 [test_bug378670.html]
 skip-if = toolkit == 'android' #TIMED_OUT
 [test_bug402198.html]
 [test_bug411236.html]
 [test_bug446663.html]
 skip-if = buildapp == 'b2g' || toolkit == 'android' # b2g(needs copy support) b2g-debug(needs copy support) b2g-desktop(needs copy support)
--- a/layout/forms/test/test_bug672810.html
+++ b/layout/forms/test/test_bug672810.html
@@ -86,30 +86,34 @@ SimpleTest.waitForFocus(function() {
     ok(!s[1].selected,s.id + ": CTRL+SHIFT+DOWN deselected 2nd option");
     synthesizeKey(' ', {ctrlKey: true, shiftKey: true});
     is(s.selectedIndex,3, s.id + ": 4th option is still selected");
     synthesizeKey(' ', {ctrlKey: true, shiftKey: true});
     is(s.selectedIndex,3, s.id + ": 4th option is still selected");
     setTimeout(function(){sel[2].focus()},0);
   }, false);
   sel[2].addEventListener('focus', function() {
-    s = sel[2];
-    s.removeEventListener('focus', arguments.callee, false);
-    synthesizeKey('VK_DOWN', {});
-    is(s.selectedIndex,1, s.id + ": initial DOWN selects 2nd option");
-    synthesizeKey('VK_DOWN', {ctrlKey: true});
-    is(s.selectedIndex,2, s.id + ": 3rd option is selected");
-    ok(!s[1].selected,s.id + ": CTRL+DOWN deselected 2nd option");
-    synthesizeKey('VK_DOWN', {ctrlKey: true, shiftKey: true});
-    is(s.selectedIndex,3, s.id + ": 4th option is selected");
-    ok(!s[2].selected,s.id + ": CTRL+SHIFT+DOWN deselected 3rd option");
-    synthesizeKey(' ', {ctrlKey: true, shiftKey: true});
-    is(s.selectedIndex,3, s.id + ": 4th option is still selected");
-    synthesizeKey(' ', {ctrlKey: true, shiftKey: true});
-    is(s.selectedIndex,3, s.id + ": 4th option is still selected");
+    if (navigator.platform.indexOf("Mac") == -1) {
+      s = sel[2];
+      s.removeEventListener('focus', arguments.callee, false);
+      synthesizeKey('VK_DOWN', {});
+      is(s.selectedIndex,1, s.id + ": initial DOWN selects 2nd option");
+      synthesizeKey('VK_DOWN', {ctrlKey: true});
+      is(s.selectedIndex,2, s.id + ": 3rd option is selected");
+      ok(!s[1].selected,s.id + ": CTRL+DOWN deselected 2nd option");
+      synthesizeKey('VK_DOWN', {ctrlKey: true, shiftKey: true});
+      is(s.selectedIndex,3, s.id + ": 4th option is selected");
+      ok(!s[2].selected,s.id + ": CTRL+SHIFT+DOWN deselected 3rd option");
+      synthesizeKey(' ', {ctrlKey: true, shiftKey: true});
+      is(s.selectedIndex,3, s.id + ": 4th option is still selected");
+      synthesizeKey(' ', {ctrlKey: true, shiftKey: true});
+      is(s.selectedIndex,3, s.id + ": 4th option is still selected");
+    } else {
+      todo(false, "Make this test work on OSX");
+    }
     setTimeout(function(){SimpleTest.finish()},0);
   }, false);
   sel[0].focus();
 });
 
 </script>
 </pre>
 </body>
--- a/layout/forms/test/test_bug935876.html
+++ b/layout/forms/test/test_bug935876.html
@@ -266,26 +266,30 @@ function runTests()
   combobox.addEventListener("keydown", onKeydown, false);
   combobox.addEventListener("keypress", onKeypress, false);
   SpecialPowers.addSystemEventListener(combobox, "keydown", onkeydownInSystemEventGroup, false);
 
   combobox.focus();
 
   [ false, true ].forEach(function (consume) {
     doPreventDefault = consume;
-    for (var i = 0; i < combobox.options.length + 1; i++) {
-      reset()
-      synthesizeKey("VK_DOWN", {});
-      check(true, "DownArrow key on combobox #" + i);
-    }
+    if (!kIsMac) {
+      for (var i = 0; i < combobox.options.length + 1; i++) {
+        reset()
+        synthesizeKey("VK_DOWN", {});
+        check(true, "DownArrow key on combobox #" + i);
+      }
 
-    for (var i = 0; i < combobox.options.length + 1; i++) {
-      reset()
-      synthesizeKey("VK_UP", {});
-      check(true, "UpArrow key on combobox #" + i);
+      for (var i = 0; i < combobox.options.length + 1; i++) {
+        reset()
+        synthesizeKey("VK_UP", {});
+        check(true, "UpArrow key on combobox #" + i);
+      }
+    } else {
+      todo(false, "Make this test work on OSX");
     }
 
     for (var i = 0; i < combobox.options.length + 1; i++) {
       reset()
       synthesizeKey("VK_RIGHT", {});
       check(true, "RightArrow key on combobox #" + i);
     }
 
--- a/layout/forms/test/test_bug961363.html
+++ b/layout/forms/test/test_bug961363.html
@@ -44,18 +44,22 @@ https://bugzilla.mozilla.org/show_bug.cg
           var sel = Array.prototype.slice.call(element.options).map(function(o){return o.selected})
           is(""+sel, ""+data.s, "selected options match after CTRL+SPACE (after testing CTRL+key " + key + ") for (id: " + id + ")");
           previousValue = element.value;
           });
       };
       select_test("one", one, false);
       select_test("two", two_1, true);
       select_test("two", two_2, false);
-      select_test("three", three_1, true);
-      select_test("three", three_2, false);
+      if (navigator.platform.indexOf("Mac") == -1) {
+        select_test("three", three_1, true);
+        select_test("three", three_2, false);
+      } else {
+        todo(false, "Make these tests work on OSX");
+      }
       SimpleTest.finish();
     });
   }
 </script>
 </head>
 <body onload="test();">
 <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=961363">Mozilla Bug 961363</a>
 <div>
--- a/layout/forms/test/test_select_prevent_default.html
+++ b/layout/forms/test/test_select_prevent_default.html
@@ -24,16 +24,21 @@ https://bugzilla.mozilla.org/show_bug.cg
     SpecialPowers.pushPrefEnv({"set":[["snav.enabled", false]]}, runTest);
   }
 
   function runTest() {
     document.getElementById("keydown").addEventListener("keydown", preventDefault);
     document.getElementById("keypress").addEventListener("keypress", preventDefault);
 
     SimpleTest.waitForFocus(function() {
+      if (navigator.platform.indexOf("Mac") == 0) {
+        todo(false, "Make this test work on OSX");
+        SimpleTest.finish();
+        return;
+      }
       var testData = [ "one", "two", "three", "four", "keydown", "keypress" ];
 
       // The order of the keys in otherKeys is important for the test to function properly.
       var otherKeys = [ "DOWN", "UP", "RIGHT", "LEFT", "PAGE_DOWN", "PAGE_UP",
                         "END", "HOME" ];
 
       testData.forEach(function(id) {
         var element = document.getElementById(id);
--- a/layout/generic/nsFlexContainerFrame.cpp
+++ b/layout/generic/nsFlexContainerFrame.cpp
@@ -3253,21 +3253,23 @@ FlexboxAxisTracker::InitAxesFromLegacyPr
   // XXXdholbert BEGIN CODE TO SET DEPRECATED MEMBER-VARS
   if (boxOrientIsVertical) {
     mMainAxis = eAxis_TB;
     mCrossAxis = eAxis_LR;
   } else {
     mMainAxis = eAxis_LR;
     mCrossAxis = eAxis_TB;
   }
-  // "direction: rtl" (in a horizontal -webkit-box) reverses the main axis.
+  // "direction: rtl" reverses the writing-mode's inline axis.
+  // So, we need to reverse the corresponding flex axis to match.
   // (Note this we don't toggle "mIsMainAxisReversed" for this condition,
   // because the main axis will still match aWM's inline direction.)
-  if (aWM.IsBidiLTR()) {
-    mMainAxis = GetReverseAxis(mMainAxis);
+  if (!aWM.IsBidiLTR()) {
+    AxisOrientationType& axisToFlip = mIsRowOriented ? mMainAxis : mCrossAxis;
+    axisToFlip = GetReverseAxis(axisToFlip);
   }
   // XXXdholbert END CODE TO SET DEPRECATED MEMBER-VARS
 
   // Legacy flexbox can use "-webkit-box-direction: reverse" to reverse the
   // main axis (so it runs in the reverse direction of the inline axis):
   if (styleXUL->mBoxDirection == NS_STYLE_BOX_DIRECTION_REVERSE) {
     mMainAxis = GetReverseAxis(mMainAxis);
     mIsMainAxisReversed = true;
--- a/layout/generic/nsPluginFrame.cpp
+++ b/layout/generic/nsPluginFrame.cpp
@@ -647,16 +647,18 @@ nsPluginFrame::CallSetWindow(bool aCheck
     scaleFactor = 1.0;
   }
   size_t intScaleFactor = ceil(scaleFactor);
   window->x = intBounds.x / intScaleFactor;
   window->y = intBounds.y / intScaleFactor;
   window->width = intBounds.width / intScaleFactor;
   window->height = intBounds.height / intScaleFactor;
 
+  mInstanceOwner->ResolutionMayHaveChanged();
+
   // This will call pi->SetWindow and take care of window subclassing
   // if needed, see bug 132759. Calling SetWindow can destroy this frame
   // so check for that before doing anything else with this frame's memory.
   if (mInstanceOwner->UseAsyncRendering()) {
     rv = pi->AsyncSetWindow(window);
   }
   else {
     rv = window->CallSetWindow(pi);
--- a/layout/reftests/webkit-box/reftest.list
+++ b/layout/reftests/webkit-box/reftest.list
@@ -16,16 +16,17 @@ fails == webkit-box-anon-flex-items-3.ht
 
 # Tests for "-webkit-box-align" (cross-axis alignment):
 == webkit-box-align-horiz-1a.html webkit-box-align-horiz-1-ref.html
 == webkit-box-align-horiz-1b.html webkit-box-align-horiz-1-ref.html
 == webkit-box-align-vert-1.html webkit-box-align-vert-1-ref.html
 
 # Tests for "-webkit-box-direction":
 == webkit-box-direction-1.html webkit-box-direction-1-ref.html
+== webkit-box-direction-2.html webkit-box-direction-2-ref.html
 
 # Tests for "-webkit-box-flex" (flexibility of items)
 == webkit-box-flex-1.html webkit-box-flex-1-ref.html
 
 # Tests for "-webkit-box-ordinal-group"
 == webkit-box-ordinal-group-1.html webkit-box-ordinal-group-1-ref.html
 # XXXdholbert The following test fails because we accept "0" as a valid value
 # for -webkit-box-ordinal-group (unlike Chrome/Blink), because that's simply
copy from layout/reftests/webkit-box/webkit-box-direction-1-ref.html
copy to layout/reftests/webkit-box/webkit-box-direction-2-ref.html
--- a/layout/reftests/webkit-box/webkit-box-direction-1-ref.html
+++ b/layout/reftests/webkit-box/webkit-box-direction-2-ref.html
@@ -13,19 +13,25 @@
       display: flex;
       border: 1px solid black;
       margin: 5px 20px;
       float: left; /* For testing in "rows" */
       font: 10px serif;
     }
     .box > *:nth-child(1) {
       background: turquoise;
+      margin: 2px 4px 6px 8px;
+      border: 1px solid blue;
+      padding: 4px 3px 2px 1px;
     }
     .box > *:nth-child(2) {
       background: salmon;
+      margin: 9px 3px 7px 5px;
+      border: 2px solid purple;
+      padding: 6px 7px 8px 9px;
     }
 
     .rtl { direction: rtl; }
 
     .horizNormal {
       flex-direction: row;
     }
     .horizReverse {
copy from layout/reftests/webkit-box/webkit-box-direction-1.html
copy to layout/reftests/webkit-box/webkit-box-direction-2.html
--- a/layout/reftests/webkit-box/webkit-box-direction-1.html
+++ b/layout/reftests/webkit-box/webkit-box-direction-2.html
@@ -3,30 +3,37 @@
      Any copyright is dedicated to the Public Domain.
      http://creativecommons.org/publicdomain/zero/1.0/
 -->
 <html>
 <head>
   <title>
     CSS Test: "-webkit-box-direction" property
     in a -webkit-box with default writing-mode
+    and some margin/border/padding on flex items
   </title>
   <style>
     .box {
       display: -webkit-box;
       border: 1px solid black;
       margin: 5px 20px;
       float: left; /* For testing in "rows" */
       font: 10px serif;
     }
     .box > *:nth-child(1) {
       background: turquoise;
+      margin: 2px 4px 6px 8px;
+      border: 1px solid blue;
+      padding: 4px 3px 2px 1px;
     }
     .box > *:nth-child(2) {
       background: salmon;
+      margin: 9px 3px 7px 5px;
+      border: 2px solid purple;
+      padding: 6px 7px 8px 9px;
     }
 
     .rtl { direction: rtl; }
 
     .horizNormal {
       -webkit-box-orient: horizontal;
       -webkit-box-direction: normal;
     }
--- a/layout/tools/reftest/reftestcommandline.py
+++ b/layout/tools/reftest/reftestcommandline.py
@@ -212,16 +212,22 @@ class ReftestArgumentsParser(argparse.Ar
                           dest="specialPowersExtensionPath",
                           help="Path to the special powers extension")
 
         self.add_argument("--suite",
                           choices=["reftest", "crashtest", "jstestbrowser"],
                           default=None,
                           help=argparse.SUPPRESS)
 
+        self.add_argument("--cleanup-crashes",
+                          action = "store_true",
+                          dest = "cleanupCrashes",
+                          default = False,
+                          help = "Delete pending crash reports before running tests.")
+
         self.add_argument("tests",
                           metavar="TEST_PATH",
                           nargs="*",
                           help="Path to test file, manifest file, or directory containing tests")
 
         mozlog.commandline.add_logging_group(self)
 
     def get_ip(self):
--- a/layout/tools/reftest/runreftest.py
+++ b/layout/tools/reftest/runreftest.py
@@ -407,16 +407,19 @@ class RefTest(object):
         # Despite our efforts to clean up servers started by this script, in practice
         # we still see infrequent cases where a process is orphaned and interferes
         # with future tests, typically because the old server is keeping the port in use.
         # Try to avoid those failures by checking for and killing orphan servers before
         # trying to start new ones.
         self.killNamedOrphans('ssltunnel')
         self.killNamedOrphans('xpcshell')
 
+        if options.cleanupCrashes:
+            mozcrash.cleanup_pending_crash_reports()
+
         manifests = self.resolver.resolveManifests(options, tests)
         if options.filter:
             manifests[""] = options.filter
 
         if not getattr(options, 'runTestsInParallel', False):
             return self.runSerialTests(manifests, options, cmdargs)
 
         cpuCount = multiprocessing.cpu_count()
--- a/media/libstagefright/binding/mp4parse/capi.rs
+++ b/media/libstagefright/binding/mp4parse/capi.rs
@@ -113,17 +113,24 @@ pub unsafe extern "C" fn mp4parse_read(c
 
     let mut context: &mut MediaContext = &mut *context;
 
     // Wrap the buffer we've been give in a slice.
     let b = std::slice::from_raw_parts(buffer, size);
     let mut c = Cursor::new(b);
 
     // Parse in a subthread to catch any panics.
-    let task = std::thread::spawn(move || read_mp4(&mut c, &mut context));
+    // We must use the thread::Builder API to avoid spawn itself
+    // panicking if thread creation fails. See bug 1266309.
+    let task = match std::thread::Builder::new()
+        .name("mp4parse_read isolation".to_string())
+        .spawn(move || read_mp4(&mut c, &mut context)) {
+            Ok(task) => task,
+            Err(_) => return MP4PARSE_ASSERT,
+    };
     // The task's JoinHandle will return an error result if the
     // thread panicked, and will wrap the closure's return'd
     // result in an Ok(..) otherwise, meaning we could see
     // Ok(Err(Error::..)) here. So map thread failures back
     // to an mp4parse::Error before converting to a C return value.
     match task.join().unwrap_or(Err(Error::AssertCaught)) {
         Ok(_) => MP4PARSE_OK,
         Err(Error::InvalidData) => MP4PARSE_ERROR_INVALID,
--- a/modules/libpref/init/all.js
+++ b/modules/libpref/init/all.js
@@ -362,17 +362,21 @@ pref("media.gmp.storage.version.expected
 
 // Filter what triggers user notifications.
 // See DecoderDoctorDocumentWatcher::ReportAnalysis for details.
 pref("media.decoder-doctor.notifications-allowed", "MediaWMFNeeded,MediaWidevineNoWMFNoSilverlight");
 // Whether we report partial failures.
 pref("media.decoder-doctor.verbose", false);
 
 // Whether to suspend decoding of videos in background tabs.
+#ifdef NIGHTLY_BUILD
 pref("media.suspend-bkgnd-video.enabled", true);
+#else
+pref("media.suspend-bkgnd-video.enabled", false);
+#endif
 
 #ifdef MOZ_WEBRTC
 pref("media.navigator.enabled", true);
 pref("media.navigator.video.enabled", true);
 pref("media.navigator.load_adapt", true);
 pref("media.navigator.load_adapt.measure_interval",1000);
 pref("media.navigator.load_adapt.avg_seconds",3);
 pref("media.navigator.load_adapt.high_load","0.90");
@@ -2644,16 +2648,19 @@ pref("gestures.enable_single_finger_inpu
 
 pref("editor.resizing.preserve_ratio",       true);
 pref("editor.positioning.offset",            0);
 
 pref("dom.use_watchdog", true);
 pref("dom.max_chrome_script_run_time", 20);
 pref("dom.max_script_run_time", 10);
 
+// Stop all scripts in a compartment when the "stop script" dialog is used.
+pref("dom.global_stop_script", true);
+
 // If true, ArchiveReader will be enabled
 pref("dom.archivereader.enabled", false);
 
 // Hang monitor timeout after which we kill the browser, in seconds
 // (0 is disabled)
 // Disabled on all platforms per bug 705748 until the found issues are
 // resolved.
 pref("hangmonitor.timeout", 0);
--- a/python/mozboot/mozboot/base.py
+++ b/python/mozboot/mozboot/base.py
@@ -70,17 +70,17 @@ We recommend the following tools for ins
     pythonz -- https://github.com/saghul/pythonz
     official installers -- http://www.python.org/
 '''
 
 
 # Upgrade Mercurial older than this.
 # This should match OLDEST_NON_LEGACY_VERSION from
 # tools/mercurial/hgsetup/wizard.py.
-MODERN_MERCURIAL_VERSION = LooseVersion('3.5.2')
+MODERN_MERCURIAL_VERSION = LooseVersion('3.7.3')
 
 # Upgrade Python older than this.
 MODERN_PYTHON_VERSION = LooseVersion('2.7.3')
 
 
 class BaseBootstrapper(object):
     """Base class for system bootstrappers."""
 
@@ -327,16 +327,19 @@ class BaseBootstrapper(object):
                   version)
             return
 
         self._ensure_package_manager_updated()
 
         if installed:
             print('Your version of Mercurial (%s) is not modern enough.' %
                   version)
+            print('(Older versions of Mercurial have known security vulnerabilities. '
+                  'Unless you are running a patched Mercurial version, you may be '
+                  'vulnerable.')
         else:
             print('You do not have Mercurial installed')
 
         if self.upgrade_mercurial(version) is False:
             return
 
         installed, modern, after = self.is_mercurial_modern()
 
--- a/taskcluster/taskgraph/kind/legacy.py
+++ b/taskcluster/taskgraph/kind/legacy.py
@@ -186,16 +186,18 @@ class LegacyKind(base.Kind):
 
             when = task['when']
 
             # If the task defines file patterns and we have a set of changed
             # files to compare against, only run if a file pattern matches one
             # of the changed files.
             file_patterns = when.get('file_patterns', None)
             if file_patterns and changed_files:
+                # Always consider changes to the task definition itself
+                file_patterns.append('testing/taskcluster/{task}'.format(task=task['task']))
                 for pattern in file_patterns:
                     for path in changed_files:
                         if mozpackmatch(path, pattern):
                             logger.debug('scheduling {task} because pattern {pattern} '
                                          'matches {path}'.format(
                                              task=task['task'],
                                              pattern=pattern,
                                              path=path,
--- a/testing/mochitest/mochitest_options.py
+++ b/testing/mochitest/mochitest_options.py
@@ -554,16 +554,23 @@ class MochitestArguments(ArgumentContain
           "help": "Timeout while waiting for the marionette port to open.",
           "suppress": True,
           }],
         [["--marionette-socket-timeout"],
          {"default": None,
           "help": "Timeout while waiting to receive a message from the marionette server.",
           "suppress": True,
           }],
+        [["--cleanup-crashes"],
+         {"action": "store_true",
+          "dest": "cleanupCrashes",
+          "default": False,
+          "help": "Delete pending crash reports before running tests.",
+          "suppress": True,
+          }],
     ]
 
     defaults = {
         # Bug 1065098 - The geckomediaplugin process fails to produce a leak
         # log for some reason.
         'ignoreMissingLeaks': ["geckomediaplugin"],
         'extensionsToExclude': ['specialpowers'],
         # Set server information on the args object
--- a/testing/mochitest/runtests.py
+++ b/testing/mochitest/runtests.py
@@ -78,21 +78,23 @@ except ImportError:
 
 here = os.path.abspath(os.path.dirname(__file__))
 
 
 ########################################
 # Option for MOZ (former NSPR) logging #
 ########################################
 
-# Set the desired log modules you want a log be produced by a try run for, or leave blank to disable the feature.
-# This will be passed to MOZ_LOG_MODULES environment variable. Try run will then put a download link for all log files
+# Set the desired log modules you want a log be produced
+# by a try run for, or leave blank to disable the feature.
+# This will be passed to MOZ_LOG environment variable.
+# Try run will then put a download link for all log files
 # on tbpl.mozilla.org.
 
-MOZ_LOG_MODULES = ""
+MOZ_LOG = ""
 
 #####################
 # Test log handling #
 #####################
 
 # output processing
 
 
@@ -1229,21 +1231,21 @@ toolbar#nav-bar {
                 browserEnv["MOZ_GMP_PATH"] = gmp_path
         except EnvironmentError:
             self.log.error('Could not find path to gmp-fake plugin!')
             return None
 
         if options.fatalAssertions:
             browserEnv["XPCOM_DEBUG_BREAK"] = "stack-and-abort"
 
-        # Produce a mozlog, if setup (see MOZ_LOG_MODULES global at the top of
+        # Produce a mozlog, if setup (see MOZ_LOG global at the top of
         # this script).
-        self.mozLogs = MOZ_LOG_MODULES and "MOZ_UPLOAD_DIR" in os.environ
+        self.mozLogs = MOZ_LOG and "MOZ_UPLOAD_DIR" in os.environ
         if self.mozLogs:
-            browserEnv["MOZ_LOG_MODULES"] = MOZ_LOG_MODULES
+            browserEnv["MOZ_LOG"] = MOZ_LOG
 
         if debugger and not options.slowscript:
             browserEnv["JS_DISABLE_SLOW_SCRIPT_SIGNALS"] = "1"
 
         # For e10s, our tests default to suppressing the "unsafe CPOW usage"
         # warnings that can plague test logs.
         if not options.enableCPOWWarnings:
             browserEnv["DISABLE_UNSAFE_CPOW_WARNINGS"] = "1"
@@ -2201,16 +2203,19 @@ class MochitestDesktop(MochitestBase):
         # Despite our efforts to clean up servers started by this script, in practice
         # we still see infrequent cases where a process is orphaned and interferes
         # with future tests, typically because the old server is keeping the port in use.
         # Try to avoid those failures by checking for and killing orphan servers before
         # trying to start new ones.
         self.killNamedOrphans('ssltunnel')
         self.killNamedOrphans('xpcshell')
 
+        if options.cleanupCrashes:
+            mozcrash.cleanup_pending_crash_reports()
+
         # Until we have all green, this only runs on bc*/dt*/mochitest-chrome
         # jobs, not jetpack*, a11yr (for perf reasons), or plain
 
         testsToRun = self.getTestsToRun(options)
         if not options.runByDir:
             return self.runMochitests(options, testsToRun)
 
         # code for --run-by-dir
--- a/testing/mozbase/mozcrash/mozcrash/mozcrash.py
+++ b/testing/mozbase/mozcrash/mozcrash/mozcrash.py
@@ -2,16 +2,17 @@
 # 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/.
 
 __all__ = [
     'check_for_crashes',
     'check_for_java_exception',
     'kill_and_get_minidump',
     'log_crashes',
+    'cleanup_pending_crash_reports',
 ]
 
 import glob
 import os
 import re
 import shutil
 import signal
 import subprocess
@@ -486,16 +487,45 @@ def kill_and_get_minidump(pid, dump_dire
     if mozinfo.isWin:
         write_minidump(pid, dump_directory, utility_path)
     elif mozinfo.isLinux or mozinfo.isMac:
         os.kill(pid, signal.SIGABRT)
         needs_killing = False
     if needs_killing:
         kill_pid(pid)
 
+def cleanup_pending_crash_reports():
+    """
+    Delete any pending crash reports.
+
+    The presence of pending crash reports may be reported by the browser,
+    affecting test results; it is best to ensure that these are removed
+    before starting any browser tests.
+
+    Firefox stores pending crash reports in "<UAppData>/Crash Reports".
+    If the browser is not running, it cannot provide <UAppData>, so this
+    code tries to anticipate its value.
+
+    See dom/system/OSFileConstants.cpp for platform variations of <UAppData>.
+    """
+    if mozinfo.isWin:
+        location = os.path.expanduser("~\\AppData\\Roaming\\Mozilla\\Firefox\\Crash Reports")
+    elif mozinfo.isMac:
+        location = os.path.expanduser("~/Library/Application Support/firefox/Crash Reports")
+    else:
+        location = os.path.expanduser("~/.mozilla/firefox/Crash Reports")
+    logger = get_logger()
+    if os.path.exists(location):
+        try:
+            mozfile.remove(location)
+            logger.info("Removed pending crash reports at '%s'" % location)
+        except:
+            pass
+
+
 if __name__ == '__main__':
     import argparse
     parser = argparse.ArgumentParser()
     parser.add_argument('--stackwalk-binary', '-b')
     parser.add_argument('--dump-save-path', '-o')
     parser.add_argument('--test-name', '-n')
     parser.add_argument('dump_directory')
     parser.add_argument('symbols_path')
--- a/testing/mozbase/mozrunner/mozrunner/devices/emulator.py
+++ b/testing/mozbase/mozrunner/mozrunner/devices/emulator.py
@@ -12,17 +12,16 @@ import tempfile
 import time
 
 from mozprocess import ProcessHandler
 
 from .base import Device
 from .emulator_battery import EmulatorBattery
 from .emulator_geo import EmulatorGeo
 from .emulator_screen import EmulatorScreen
-from ..utils import uses_marionette
 from ..errors import TimeoutException
 
 class ArchContext(object):
     def __init__(self, arch, context, binary=None):
         kernel = os.path.join(context.homedir, 'prebuilts', 'qemu-kernel', '%s', '%s')
         sysdir = os.path.join(context.homedir, 'out', 'target', 'product', '%s')
         if arch == 'x86':
             self.binary = os.path.join(context.bindir, 'emulator-x86')
@@ -170,47 +169,16 @@ class Emulator(Device):
         if self.proc:
             self.proc.kill()
             self.proc = None
 
         # Remove temporary files
         self.userdata.close()
         shutil.rmtree(self.tmpdir)
 
-    # TODO this function is B2G specific and shouldn't live here
-    @uses_marionette
-    def wait_for_system_message(self, marionette):
-        marionette.set_script_timeout(45000)
-        # Telephony API's won't be available immediately upon emulator
-        # boot; we have to wait for the syste-message-listener-ready
-        # message before we'll be able to use them successfully.  See
-        # bug 792647.
-        print 'waiting for system-message-listener-ready...'
-        try:
-            marionette.execute_async_script("""
-waitFor(
-    function() { marionetteScriptFinished(true); },
-    function() { return isSystemMessageListenerReady(); }
-);
-            """)
-        except:
-            # Look for ScriptTimeoutException this way to avoid a
-            # dependency on the marionette python client.
-            exc_name = sys.exc_info()[0].__name__
-            if exc_name != 'ScriptTimeoutException':
-                raise
-
-            print 'timed out'
-            # We silently ignore the timeout if it occurs, since
-            # isSystemMessageListenerReady() isn't available on
-            # older emulators.  45s *should* be enough of a delay
-            # to allow telephony API's to work.
-            pass
-        print '...done'
-
     def _get_telnet_response(self, command=None):
         output = []
         assert(self.telnet)
         if command is not None:
             self.telnet.write('%s\n' % command)
         while True:
             line = self.telnet.read_until('\n')
             output.append(line.rstrip())
--- a/testing/mozbase/mozrunner/mozrunner/utils.py
+++ b/testing/mozbase/mozrunner/mozrunner/utils.py
@@ -69,37 +69,16 @@ if __name__ == '__main__':
 def _find_marionette_in_args(*args, **kwargs):
     try:
         m = [a for a in args + tuple(kwargs.values()) if hasattr(a, 'session')][0]
     except IndexError:
         print("Can only apply decorator to function using a marionette object")
         raise
     return m
 
-def uses_marionette(func):
-    """Decorator which creates a marionette session and deletes it
-    afterwards if one doesn't already exist.
-    """
-    @wraps(func)
-    def _(*args, **kwargs):
-        m = _find_marionette_in_args(*args, **kwargs)
-        delete_session = False
-        if not m.session:
-            delete_session = True
-            m.start_session()
-
-        m.set_context(m.CONTEXT_CHROME)
-        ret = func(*args, **kwargs)
-
-        if delete_session:
-            m.delete_session()
-
-        return ret
-    return _
-
 
 def _raw_log():
     import logging
     return logging.getLogger(__name__)
 
 
 def test_environment(xrePath, env=None, crashreporter=True, debugger=False,
                      dmdPath=None, lsanPath=None, log=None):
--- a/testing/mozharness/configs/builds/branch_specifics.py
+++ b/testing/mozharness/configs/builds/branch_specifics.py
@@ -291,18 +291,18 @@ config = {
     'gum': {
         'branch_uses_per_checkin_strategy': True,
         'stage_server': 'upload.ffxbld.productdelivery.prod.mozaws.net',
     },
     'mozilla-inbound': {
         'repo_path': 'integration/mozilla-inbound',
         'stage_server': 'upload.ffxbld.productdelivery.prod.mozaws.net',
     },
-    'services-central': {
-        'repo_path': 'services/services-central',
+    'autoland': {
+        'repo_path': 'integration/autoland',
         'stage_server': 'upload.ffxbld.productdelivery.prod.mozaws.net',
     },
     'ux': {
         "graph_server_branch_name": "UX",
         'stage_server': 'upload.ffxbld.productdelivery.prod.mozaws.net',
     },
     # When build promotion goes live the mozconfig changes are probably better
     # expressed once in files like configs/builds/releng_base_windows_32_builds.py
--- a/testing/mozharness/configs/unittests/linux_unittest.py
+++ b/testing/mozharness/configs/unittests/linux_unittest.py
@@ -124,16 +124,17 @@ config = {
                 "--symbols-path=%(symbols_path)s",
                 "--certificate-path=tests/certs",
                 "--setpref=webgl.force-enabled=true",
                 "--quiet",
                 "--log-raw=%(raw_log_file)s",
                 "--log-errorsummary=%(error_summary_file)s",
                 "--use-test-media-devices",
                 "--screenshot-on-fail",
+                "--cleanup-crashes",
             ],
             "run_filename": "runtests.py",
             "testsdir": "mochitest"
         },
         "mozbase": {
             "options": [
                 "-b",
                 "%(binary_path)s"
@@ -154,16 +155,17 @@ config = {
         "reftest": {
             "options": [
                 "--appname=%(binary_path)s",
                 "--utility-path=tests/bin",
                 "--extra-profile-file=tests/bin/plugins",
                 "--symbols-path=%(symbols_path)s",
                 "--log-raw=%(raw_log_file)s",
                 "--log-errorsummary=%(error_summary_file)s",
+                "--cleanup-crashes",
             ],
             "run_filename": "runreftest.py",
             "testsdir": "reftest"
         },
         "xpcshell": {
             "options": [
                 "--symbols-path=%(symbols_path)s",
                 "--test-plugin-path=%(test_plugin_path)s",
--- a/testing/mozharness/configs/unittests/mac_unittest.py
+++ b/testing/mozharness/configs/unittests/mac_unittest.py
@@ -78,16 +78,17 @@ config = {
                 "--utility-path=tests/bin",
                 "--extra-profile-file=tests/bin/plugins",
                 "--symbols-path=%(symbols_path)s",
                 "--certificate-path=tests/certs",
                 "--quiet",
                 "--log-raw=%(raw_log_file)s",
                 "--log-errorsummary=%(error_summary_file)s",
                 "--screenshot-on-fail",
+                "--cleanup-crashes",
             ],
             "run_filename": "runtests.py",
             "testsdir": "mochitest"
         },
         "mozbase": {
             "options": [
                 "-b",
                 "%(binary_path)s"
@@ -106,16 +107,17 @@ config = {
             "testsdir": "mozmill"
         },
         "reftest": {
             "options": [
                 "--appname=%(binary_path)s",
                 "--utility-path=tests/bin",
                 "--extra-profile-file=tests/bin/plugins",
                 "--symbols-path=%(symbols_path)s"
+                "--cleanup-crashes",
             ],
             "run_filename": "runreftest.py",
             "testsdir": "reftest"
         },
         "xpcshell": {
             "options": [
                 "--symbols-path=%(symbols_path)s",
                 "--test-plugin-path=%(test_plugin_path)s",
--- a/testing/mozharness/configs/unittests/win_unittest.py
+++ b/testing/mozharness/configs/unittests/win_unittest.py
@@ -89,16 +89,17 @@ config = {
                 "--utility-path=tests/bin",
                 "--extra-profile-file=tests/bin/plugins",
                 "--symbols-path=%(symbols_path)s",
                 "--certificate-path=tests/certs",
                 "--quiet",
                 "--log-raw=%(raw_log_file)s",
                 "--log-errorsummary=%(error_summary_file)s",
                 "--screenshot-on-fail",
+                "--cleanup-crashes",
             ],
             "run_filename": "runtests.py",
             "testsdir": "mochitest"
         },
         "mozbase": {
             "options": [
                 "-b",
                 "%(binary_path)s"
@@ -119,16 +120,17 @@ config = {
         "reftest": {
             "options": [
                 "--appname=%(binary_path)s",
                 "--utility-path=tests/bin",
                 "--extra-profile-file=tests/bin/plugins",
                 "--symbols-path=%(symbols_path)s",
                 "--log-raw=%(raw_log_file)s",
                 "--log-errorsummary=%(error_summary_file)s",
+                "--cleanup-crashes",
             ],
             "run_filename": "runreftest.py",
             "testsdir": "reftest"
         },
         "xpcshell": {
             "options": [
                 "--symbols-path=%(symbols_path)s",
                 "--test-plugin-path=%(test_plugin_path)s",
--- a/testing/mozharness/mozharness/mozilla/l10n/locales.py
+++ b/testing/mozharness/mozharness/mozilla/l10n/locales.py
@@ -222,17 +222,17 @@ class LocalesMixin(ChunkingMixin):
         if c.get("user_repo_override"):
             hg_l10n_base = hg_l10n_base % {"user_repo_override": c["user_repo_override"]}
         for locale in locales:
             tag = c.get('hg_l10n_tag', 'default')
             if self.l10n_revisions.get(locale):
                 tag = self.l10n_revisions[locale]
             locale_repos.append({
                 'repo': "%s/%s" % (hg_l10n_base, locale),
-                'revision': tag,
+                'branch': tag,
                 'vcs': vcs
             })
         revs = self.vcs_checkout_repos(repo_list=locale_repos,
                                        parent_dir=parent_dir,
                                        tag_override=c.get('tag_override'))
         self.gecko_locale_revisions = revs
 
     def query_l10n_repo(self):
--- a/testing/taskcluster/mach_commands.py
+++ b/testing/taskcluster/mach_commands.py
@@ -306,16 +306,18 @@ class Graph(object):
 
             when = task['when']
 
             # If the task defines file patterns and we have a set of changed
             # files to compare against, only run if a file pattern matches one
             # of the changed files.
             file_patterns = when.get('file_patterns', None)
             if file_patterns and changed_files:
+                # Always consider changes to the task definition itself
+                file_patterns.append('testing/taskcluster/{task}'.format(task=task['task']))
                 for pattern in file_patterns:
                     for path in changed_files:
                         if mozpackmatch(path, pattern):
                             sys.stderr.write('scheduling %s because pattern %s '
                                              'matches %s\n' % (task['task'],
                                                                pattern,
                                                                path))
                             return True
--- a/testing/taskcluster/tasks/branches/base_jobs.yml
+++ b/testing/taskcluster/tasks/branches/base_jobs.yml
@@ -22,16 +22,17 @@ builds:
         task: tasks/builds/dbg_linux32.yml
   linux64:
     platforms:
       - Linux64
     extra-builds:  # see RIDEALONG_BUILDS in `mach taskgraph`
       - sm-plain
       - sm-nonunified
       - sm-arm-sim
+      - sm-arm64-sim
       - sm-compacting
       - sm-rootanalysis
       - sm-package
     types:
       opt:
         task: tasks/builds/opt_linux64.yml
       debug:
         task: tasks/builds/dbg_linux64.yml
--- a/testing/taskcluster/tasks/builds/android_api_15.yml
+++ b/testing/taskcluster/tasks/builds/android_api_15.yml
@@ -36,26 +36,25 @@ task:
           builds/releng_base_android_64_builds.py
           disable_signing.py
           platform_supports_post_upload_to_latest.py
       MOZHARNESS_ACTIONS: "get-secrets build multi-l10n update"
       MH_CUSTOM_BUILD_VARIANT_CFG: api-15
       MH_BRANCH: {{project}}
       MH_BUILD_POOL: taskcluster
 
-    maxRunTime: 36000
-
     command: ["/bin/bash", "bin/build.sh"]
 
   extra:
     treeherderEnv:
       - production
       - staging
     treeherder:
       machine:
         # see https://github.com/mozilla/treeherder/blob/master/ui/js/values.js
         platform: android-4-0-armv7-api15
     # Rather then enforcing particular conventions we require that all build
     # tasks provide the "build" extra field to specify where the build and tests
     # files are located.
     locations:
-      build: 'public/build/target.linux-x86_64.tar.bz2'
-      tests: 'public/build/target.tests.zip'
+      build: 'public/build/target.apk'
+      mozharness: 'public/build/mozharness.zip'
+      test_packages: 'public/build/target.test_packages.json'
--- a/testing/web-platform/meta/cors/response-headers.htm.ini
+++ b/testing/web-platform/meta/cors/response-headers.htm.ini
@@ -1,8 +1,4 @@
 [response-headers.htm]
   type: testharness
   [getResponseHeader: Combined testing of cors response headers]
     expected: FAIL
-
-  [getResponse: don't expose x-nonexposed]
-    expected: FAIL
-
--- a/testing/web-platform/tests/cors/response-headers.htm
+++ b/testing/web-platform/tests/cors/response-headers.htm
@@ -62,17 +62,17 @@ async_test("getResponseHeader: Combined 
     client.onreadystatechange = this.step_func(function()
     {
         if (client.readyState == 1)
         {
             assert_equals(client.getResponseHeader("x-custom-header"), null, 'x-custom-header')
         }
         if (client.readyState > 1)
         {
-            assert_equals(client.getResponseHeader("x-custom-header"), "test", 'x-custom-header')
+            assert_equals(client.getResponseHeader("x-custom-header"), "test, test", 'x-custom-header')
             assert_equals(client.getResponseHeader("x-custom-header-empty"), "", 'x-custom-header-empty')
             assert_equals(client.getResponseHeader("set-cookie"), null)
             assert_equals(client.getResponseHeader("set-cookie2"), null)
             assert_equals(client.getResponseHeader("x-non-existent-header"), null)
             assert_equals(client.getResponseHeader("x-nonexposed"), null)
         }
         if (client.readyState == 4)
         {
@@ -81,17 +81,17 @@ async_test("getResponseHeader: Combined 
     })
     client.send()
 })
 
 test(function() {
     var client = new XMLHttpRequest()
     client.open('GET', CROSSDOMAIN + 'resources/cors-headers.asis', false)
     client.send(null)
-    assert_equals(client.getResponseHeader("x-custom-header"), "test", 'x-custom-header')
+    assert_equals(client.getResponseHeader("x-custom-header"), "test, test", 'x-custom-header')
     assert_equals(client.getResponseHeader("x-nonexposed"), null, 'x-nonexposed')
 }, "getResponse: don't expose x-nonexposed")
 
 test(function() {
     var client = new XMLHttpRequest()
     client.open('GET', CROSSDOMAIN + 'resources/cors-headers.asis', false)
     client.send(null)
 
--- a/toolkit/components/extensions/ext-extension.js
+++ b/toolkit/components/extensions/ext-extension.js
@@ -35,14 +35,14 @@ extensions.registerSchemaAPI("extension"
         return context.incognito;
       },
 
       isAllowedIncognitoAccess() {
         return Promise.resolve(true);
       },
 
       isAllowedFileSchemeAccess() {
-        return Promise.resolve(true);
+        return Promise.resolve(false);
       },
     },
   };
 });
 
--- a/toolkit/components/extensions/test/mochitest/test_ext_extension.html
+++ b/toolkit/components/extensions/test/mochitest/test_ext_extension.html
@@ -31,17 +31,17 @@ add_task(function* test_is_allowed_incog
   yield extension.awaitFinish("isAllowedIncognitoAccess");
   yield extension.unload();
   info("extension unloaded");
 });
 
 add_task(function* test_is_allowed_file_scheme_access() {
   function backgroundScript() {
     browser.extension.isAllowedFileSchemeAccess().then(isAllowedFileSchemeAccess => {
-      browser.test.assertEq(true, isAllowedFileSchemeAccess, "isAllowedFileSchemeAccess is true");
+      browser.test.assertEq(false, isAllowedFileSchemeAccess, "isAllowedFileSchemeAccess is false");
       browser.test.notifyPass("isAllowedFileSchemeAccess");
     });
   }
 
   let extension = ExtensionTestUtils.loadExtension({
     background: `(${backgroundScript})()`,
     manifest: {},
   });
--- a/tools/mercurial/hgsetup/config.py
+++ b/tools/mercurial/hgsetup/config.py
@@ -148,53 +148,16 @@ class MercurialConfig(object):
         if 'diff' not in self._c:
             self._c['diff'] = {}
 
         d = self._c['diff']
         d['git'] = 1
         d['showfunc'] = 1
         d['unified'] = 8
 
-    def have_mqext_autocommit_mq(self):
-        if 'mqext' not in self._c:
-            return False
-        v = self._c['mqext'].get('mqcommit')
-        return v == 'auto' or v == 'yes'
-
-    def ensure_mqext_autocommit_mq(self):
-        if self.have_mqext_autocommit_mq():
-            return
-        if 'mqext' not in self._c:
-            self._c['mqext'] = {}
-        self._c['mqext']['mqcommit'] = 'auto'
-
-    def have_qnew_currentuser_default(self):
-        if 'defaults' not in self._c:
-            return False
-        d = self._c['defaults']
-        if 'qnew' not in d:
-            return False
-        argv = d['qnew'].split(' ')
-        for arg in argv:
-            if arg == '--currentuser' or re.match("-[^-]*U.*", arg):
-                return True
-        return False
-
-    def ensure_qnew_currentuser_default(self):
-        if self.have_qnew_currentuser_default():
-            return
-        if 'defaults' not in self._c:
-            self._c['defaults'] = {}
-
-        d = self._c['defaults']
-        if 'qnew' not in d:
-            d['qnew'] = '-U'
-        else:
-            d['qnew'] = '-U ' + d['qnew']
-
     def get_bugzilla_credentials(self):
         if 'bugzilla' not in self._c:
             return None, None, None, None, None
 
         b = self._c['bugzilla']
         return (
             b.get('username', None),
             b.get('password', None),
--- a/tools/mercurial/hgsetup/wizard.py
+++ b/tools/mercurial/hgsetup/wizard.py
@@ -39,22 +39,26 @@ want me changing without your permission
 If your config is up-to-date, I'm just going to ensure all 3rd party extensions
 are up to date and you won't have to do anything.
 
 To begin, press the enter/return key.
 '''.strip()
 
 # This should match MODERN_MERCURIAL_VERSION in
 # python/mozboot/mozboot/base.py.
-OLDEST_NON_LEGACY_VERSION = LooseVersion('3.5.2')
+OLDEST_NON_LEGACY_VERSION = LooseVersion('3.7.3')
 LEGACY_MERCURIAL = '''
 You are running an out of date Mercurial client (%s).
 
 For a faster and better Mercurial experience, we HIGHLY recommend you
 upgrade.
+
+Legacy versions of Mercurial have known security vulnerabilities. Failure
+to upgrade may leave you exposed. You are highly encouraged to upgrade
+in case you aren't running a patched version.
 '''.strip()
 
 MISSING_USERNAME = '''
 You don't have a username defined in your Mercurial config file. In order to
 send patches to Mozilla, you'll need to attach a name and email address. If you
 aren't comfortable giving us your full name, pseudonames are acceptable.
 
 (Relevant config option: ui.username)
@@ -62,72 +66,33 @@ aren't comfortable giving us your full n
 
 BAD_DIFF_SETTINGS = '''
 Mozilla developers produce patches in a standard format, but your Mercurial is
 not configured to produce patches in that format.
 
 (Relevant config options: diff.git, diff.showfunc, diff.unified)
 '''.strip()
 
-MQ_INFO = '''
-The mq extension manages patches as separate files. It provides an
-alternative to the recommended bookmark-based development workflow.
-
-If you are a newcomer to Mercurial or are coming from Git, it is
-recommended to avoid mq.
-
-(Relevant config option: extensions.mq)
-
-Would you like to activate the mq extension
-'''.strip()
-
 BZEXPORT_INFO = '''
 If you plan on uploading patches to Mozilla, there is an extension called
 bzexport that makes it easy to upload patches from the command line via the
 |hg bzexport| command. More info is available at
 https://hg.mozilla.org/hgcustom/version-control-tools/file/default/hgext/bzexport/README
 
 (Relevant config option: extensions.bzexport)
 
 Would you like to activate bzexport
 '''.strip()
 
-MQEXT_INFO = '''
-The mqext extension adds a number of features, including automatically committing
-changes to your mq patch queue. More info is available at
-https://hg.mozilla.org/hgcustom/version-control-tools/file/default/hgext/mqext/README.txt
-
-(Relevant config option: extensions.mqext)
-
-Would you like to activate mqext
-'''.strip()
-
-QIMPORTBZ_INFO = '''
-The qimportbz extension
-(https://hg.mozilla.org/hgcustom/version-control-tools/file/default/hgext/qimportbz/README) makes it possible to
-import patches from Bugzilla using a friendly bz:// URL handler. e.g.
-|hg qimport bz://123456|.
-
-(Relevant config option: extensions.qimportbz)
-
-Would you like to activate qimportbz
-'''.strip()
-
-QNEWCURRENTUSER_INFO = '''
-The mercurial queues command |hg qnew|, which creates new patches in your patch
-queue does not set patch author information by default. Author information
-should be included when uploading for review.
-'''.strip()
-
 FINISHED = '''
 Your Mercurial should now be properly configured and recommended extensions
 should be up to date!
 '''.strip()
 
-REVIEWBOARD_MINIMUM_VERSION = LooseVersion('3.3')
+REVIEWBOARD_MINIMUM_VERSION = LooseVersion('3.5')
 
 REVIEWBOARD_INCOMPATIBLE = '''
 Your Mercurial is too old to use the reviewboard extension, which is necessary
 to conduct code review.
 
 Please upgrade to Mercurial %s or newer to use this extension.
 '''.strip()
 
@@ -159,28 +124,28 @@ LEGACY_BUGZILLA_CREDENTIALS_DETECTED = '
 Your existing Mercurial config uses a legacy method for defining Bugzilla
 credentials. Bugzilla API Keys are the most secure and preferred method
 for defining Bugzilla credentials. Bugzilla API Keys are also required
 if you have enabled 2 Factor Authentication in Bugzilla.
 
 All consumers formerly looking at these options should support API Keys.
 '''.lstrip()
 
-BZPOST_MINIMUM_VERSION = LooseVersion('3.3')
+BZPOST_MINIMUM_VERSION = LooseVersion('3.5')
 
 BZPOST_INFO = '''
 The bzpost extension automatically records the URLs of pushed commits to
 referenced Bugzilla bugs after push.
 
 (Relevant config option: extensions.bzpost)
 
 Would you like to activate bzpost
 '''.strip()
 
-FIREFOXTREE_MINIMUM_VERSION = LooseVersion('3.3')
+FIREFOXTREE_MINIMUM_VERSION = LooseVersion('3.5')
 
 FIREFOXTREE_INFO = '''
 The firefoxtree extension makes interacting with the multiple Firefox
 repositories easier:
 
 * Aliases for common trees are pre-defined. e.g. `hg pull central`
 * Pulling from known Firefox trees will create "remote refs" appearing as
   tags. e.g. pulling from fx-team will produce a "fx-team" tag.
@@ -196,17 +161,17 @@ The firefoxtree extension is *strongly* 
 a) aggregate multiple Firefox repositories into a single local repo
 b) perform head/bookmark-based development (as opposed to mq)
 
 (Relevant config option: extensions.firefoxtree)
 
 Would you like to activate firefoxtree
 '''.strip()
 
-PUSHTOTRY_MINIMUM_VERSION = LooseVersion('3.3')
+PUSHTOTRY_MINIMUM_VERSION = LooseVersion('3.5')
 
 PUSHTOTRY_INFO = '''
 The push-to-try extension generates a temporary commit with a given
 try syntax and pushes it to the try server. The extension is intended
 to be used in concert with other tools generating try syntax so that
 they can push to try without depending on mq or other workarounds.
 
 (Relevant config option: extensions.push-to-try)
@@ -407,18 +372,16 @@ class MercurialSetupWizard(object):
                 ext_path = os.path.join(self.updater.hgwatchman_dir,
                                         'hgwatchman')
                 if self.can_use_extension(c, 'hgwatchman', ext_path):
                     c.activate_extension('hgwatchman', ext_path)
             except subprocess.CalledProcessError as e:
                 print('Error compiling hgwatchman; will not install hgwatchman')
                 print(e.output)
 
-        self.prompt_native_extension(c, 'mq', MQ_INFO)
-
         if 'reviewboard' not in c.extensions:
             if hg_version < REVIEWBOARD_MINIMUM_VERSION:
                 print(REVIEWBOARD_INCOMPATIBLE % REVIEWBOARD_MINIMUM_VERSION)
             else:
                 p = os.path.join(self.vcs_tools_dir, 'hgext', 'reviewboard',
                     'client.py')
                 self.prompt_external_extension(c, 'reviewboard',
                     'Would you like to enable the reviewboard extension so '
@@ -445,35 +408,16 @@ class MercurialSetupWizard(object):
 
         if hg_version >= PUSHTOTRY_MINIMUM_VERSION:
             self.prompt_external_extension(c, 'push-to-try', PUSHTOTRY_INFO)
 
         if not c.have_wip():
             if self._prompt_yn(WIP_INFO):
                 c.install_wip_alias()
 
-        if 'mq' in c.extensions:
-            self.prompt_external_extension(c, 'mqext', MQEXT_INFO)
-
-            if 'mqext' in c.extensions and not c.have_mqext_autocommit_mq():
-                if self._prompt_yn('Would you like to configure mqext to '
-                    'automatically commit changes as you modify patches'):
-                    c.ensure_mqext_autocommit_mq()
-                    print('Configured mqext to auto-commit.\n')
-
-            self.prompt_external_extension(c, 'qimportbz', QIMPORTBZ_INFO)
-
-            if not c.have_qnew_currentuser_default():
-                print(QNEWCURRENTUSER_INFO)
-                if self._prompt_yn('Would you like qnew to set patch author by '
-                                   'default'):
-                    c.ensure_qnew_currentuser_default()
-                    print('Configured qnew to set patch author by default.')
-                    print('')
-
         if 'reviewboard' in c.extensions or 'bzpost' in c.extensions:
             bzuser, bzpass, bzuserid, bzcookie, bzapikey = c.get_bugzilla_credentials()
 
             if not bzuser or not bzapikey:
                 print(MISSING_BUGZILLA_CREDENTIALS)
 
             if not bzuser:
                 bzuser = self._prompt('What is your Bugzilla email address? (optional)',
--- a/widget/cocoa/nsChildView.mm
+++ b/widget/cocoa/nsChildView.mm
@@ -1933,18 +1933,28 @@ nsChildView::ConfigureAPZControllerThrea
 
 LayoutDeviceIntRect
 nsChildView::RectContainingTitlebarControls()
 {
   // Start with a thin strip at the top of the window for the highlight line.
   NSRect rect = NSMakeRect(0, 0, [mView bounds].size.width,
                            [(ChildView*)mView cornerRadius]);
 
+  // If we draw the titlebar title string, increase the height to the default
+  // titlebar height. This height does not necessarily include all the titlebar
+  // controls because we may have moved them further down, but at least it will
+  // include the whole title text.
+  BaseWindow* window = (BaseWindow*)[mView window];
+  if ([window wantsTitleDrawn] && [window isKindOfClass:[ToolbarWindow class]]) {
+    CGFloat defaultTitlebarHeight = [(ToolbarWindow*)window titlebarHeight];
+    rect.size.height = std::max(rect.size.height, defaultTitlebarHeight);
+  }
+
   // Add the rects of the titlebar controls.
-  for (id view in [(BaseWindow*)[mView window] titlebarControls]) {
+  for (id view in [window titlebarControls]) {
     rect = NSUnionRect(rect, [mView convertRect:[view bounds] fromView:view]);
   }
   return CocoaPointsToDevPixels(rect);
 }
 
 void
 nsChildView::PrepareWindowEffects()
 {
--- a/xpcom/base/Logging.cpp
+++ b/xpcom/base/Logging.cpp
@@ -117,19 +117,30 @@ public:
   /**
    * Loads config from env vars if present.
    */
   void Init()
   {
     bool shouldAppend = false;
     bool addTimestamp = false;
     bool isSync = false;
-    const char* modules = PR_GetEnv("MOZ_LOG_MODULES");
+    const char* modules = PR_GetEnv("MOZ_LOG");
+    if (!modules || !modules[0]) {
+      modules = PR_GetEnv("MOZ_LOG_MODULES");
+      if (modules) {
+        NS_WARNING("MOZ_LOG_MODULES is deprecated."
+            "\nPlease use MOZ_LOG instead.");
+      }
+    }
     if (!modules || !modules[0]) {
       modules = PR_GetEnv("NSPR_LOG_MODULES");
+      if (modules) {
+        NS_WARNING("NSPR_LOG_MODULES is deprecated."
+            "\nPlease use MOZ_LOG instead.");
+      }
     }
 
     NSPRLogModulesParser(modules,
         [&shouldAppend, &addTimestamp, &isSync]
             (const char* aName, LogLevel aLevel) mutable {
           if (strcmp(aName, "append") == 0) {
             shouldAppend = true;
           } else if (strcmp(aName, "timestamp") == 0) {
--- a/xpcom/glue/nsThreadUtils.h
+++ b/xpcom/glue/nsThreadUtils.h
@@ -227,31 +227,39 @@ class Runnable : public nsIRunnable
 public:
   NS_DECL_THREADSAFE_ISUPPORTS
   NS_DECL_NSIRUNNABLE
 
   Runnable() {}
 
 protected:
   virtual ~Runnable() {}
+private:
+  Runnable(const Runnable&) = delete;
+  Runnable& operator=(const Runnable&) = delete;
+  Runnable& operator=(const Runnable&&) = delete;
 };
 
 // This class is designed to be subclassed.
 class CancelableRunnable : public Runnable,
                            public nsICancelableRunnable
 {
 public:
   NS_DECL_ISUPPORTS_INHERITED
   // nsICancelableRunnable
   virtual nsresult Cancel() override;
 
   CancelableRunnable() {}
 
 protected:
   virtual ~CancelableRunnable() {}
+private:
+  CancelableRunnable(const CancelableRunnable&) = delete;
+  CancelableRunnable& operator=(const CancelableRunnable&) = delete;
+  CancelableRunnable& operator=(const CancelableRunnable&&) = delete;
 };
 
 } // namespace mozilla
 
 // An event that can be used to call a C++11 functions or function objects,
 // including lambdas. The function must have no required arguments, and must
 // return void.
 template<typename Function>
--- a/xpcom/threads/TimerThread.cpp
+++ b/xpcom/threads/TimerThread.cpp
@@ -148,18 +148,16 @@ public:
     mTimer = nullptr;
     return NS_OK;
   }
 
   nsTimerEvent()
     : mTimer()
     , mGeneration(0)
   {
-    MOZ_COUNT_CTOR(nsTimerEvent);
-
     // Note: We override operator new for this class, and the override is
     // fallible!
     sAllocatorUsers++;
   }
 
   TimeStamp mInitTime;
 
   static void Init();
@@ -183,20 +181,22 @@ public:
 
   void SetTimer(already_AddRefed<nsTimerImpl> aTimer)
   {
     mTimer = aTimer;
     mGeneration = mTimer->GetGeneration();
   }
 
 private:
+  nsTimerEvent(const nsTimerEvent&) = delete;
+  nsTimerEvent& operator=(const nsTimerEvent&) = delete;
+  nsTimerEvent& operator=(const nsTimerEvent&&) = delete;
+
   ~nsTimerEvent()
   {
-    MOZ_COUNT_DTOR(nsTimerEvent);
-
     MOZ_ASSERT(!sCanDeleteAllocator || sAllocatorUsers > 0,
                "This will result in us attempting to deallocate the nsTimerEvent allocator twice");
     sAllocatorUsers--;
   }
 
   RefPtr<nsTimerImpl> mTimer;
   int32_t      mGeneration;