Merge autoland to mozilla-central. a=merge
authorGurzau Raul <rgurzau@mozilla.com>
Tue, 26 Mar 2019 11:51:06 +0200
changeset 466056 4572f6055a6a9377d213afe14a26556e6c410344
parent 466055 df256c98c3fdb56de9bc2900803a6c4d58671100 (current diff)
parent 466054 a60750794c2eed942c9008a14f6ca02a5e15f5e2 (diff)
child 466057 b8be9947361046b3577d4a20e3f21ac53f1afa29
child 466095 323398f596cc0d8770c130344f43c1663e1e0772
push id112550
push userrgurzau@mozilla.com
push dateTue, 26 Mar 2019 09:57:15 +0000
treeherdermozilla-inbound@b8be99473610 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone68.0a1
first release with
nightly linux32
4572f6055a6a / 68.0a1 / 20190326095147 / files
nightly linux64
4572f6055a6a / 68.0a1 / 20190326095147 / files
nightly mac
4572f6055a6a / 68.0a1 / 20190326095147 / files
nightly win32
4572f6055a6a / 68.0a1 / 20190326095147 / files
nightly win64
4572f6055a6a / 68.0a1 / 20190326095147 / files
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
releases
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Merge autoland to mozilla-central. a=merge
testing/marionette/client/marionette_driver/selection.py
--- a/.cargo/config.in
+++ b/.cargo/config.in
@@ -22,10 +22,15 @@ git = "https://github.com/glandium/cc-rs
 branch = "1.0.23-clang-cl-aarch64"
 replace-with = "vendored-sources"
 
 [source."https://github.com/rust-lang-nursery/packed_simd"]
 git = "https://github.com/hsivonen/packed_simd"
 branch = "rust_1_32"
 replace-with = "vendored-sources"
 
+[source."https://github.com/CraneStation/target-lexicon"]
+git = "https://github.com/glandium/target-lexicon"
+branch = "thumbv7neon-v0.2"
+replace-with = "vendored-sources"
+
 [source.vendored-sources]
 directory = '@top_srcdir@/third_party/rust'
--- a/.eslintrc.js
+++ b/.eslintrc.js
@@ -50,24 +50,16 @@ module.exports = {
     // TODO: Bug 1515949. Enable no-undef for gfx/
     "files": "gfx/layers/apz/test/mochitest/**",
     "rules": {
       "no-undef": "off",
     }
   }, {
     // TODO: Bug 1246594. Empty this list once the rule has landed for all dirs
     "files": [
-      "docshell/test/chrome/docshell_helpers.js",
-      "docshell/test/navigation/NavigationUtils.js",
-      "dom/asmjscache/test/**",
-      "dom/cache/test/mochitest/test_cache_tons_of_fd.html",
-      "dom/crypto/test/**",
-      "dom/indexedDB/test/**",
-      "dom/localstorage/test/unit/test_migration.js",
-      "dom/plugins/test/mochitest/head.js",
       "gfx/layers/apz/test/mochitest/**",
       "mobile/android/components/**",
       "mobile/android/modules/**",
       "modules/libmar/tests/unit/head_libmar.js",
       "netwerk/protocol/http/WellKnownOpportunisticUtils.jsm",
       "netwerk/test/httpserver/httpd.js",
       "netwerk/test/httpserver/test/**",
       "parser/htmlparser/tests/mochitest/parser_datreader.js",
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -165,17 +165,17 @@ dependencies = [
 name = "baldrdash"
 version = "0.1.0"
 dependencies = [
  "bindgen 0.43.2 (registry+https://github.com/rust-lang/crates.io-index)",
  "cranelift-codegen 0.29.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "cranelift-wasm 0.29.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "env_logger 0.5.6 (registry+https://github.com/rust-lang/crates.io-index)",
  "log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)",
- "target-lexicon 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "target-lexicon 0.2.0 (git+https://github.com/glandium/target-lexicon?branch=thumbv7neon-v0.2)",
 ]
 
 [[package]]
 name = "base64"
 version = "0.9.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
  "byteorder 1.2.7 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -586,17 +586,17 @@ version = "0.29.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
  "cranelift-bforest 0.29.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "cranelift-codegen-meta 0.29.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "cranelift-entity 0.29.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "failure 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)",
  "failure_derive 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)",
  "log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)",
- "target-lexicon 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "target-lexicon 0.2.0 (git+https://github.com/glandium/target-lexicon?branch=thumbv7neon-v0.2)",
 ]
 
 [[package]]
 name = "cranelift-codegen-meta"
 version = "0.29.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
  "cranelift-entity 0.29.0 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -609,17 +609,17 @@ source = "registry+https://github.com/ru
 
 [[package]]
 name = "cranelift-frontend"
 version = "0.29.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
  "cranelift-codegen 0.29.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)",
- "target-lexicon 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "target-lexicon 0.2.0 (git+https://github.com/glandium/target-lexicon?branch=thumbv7neon-v0.2)",
 ]
 
 [[package]]
 name = "cranelift-wasm"
 version = "0.29.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
  "cast 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -2799,17 +2799,17 @@ dependencies = [
  "quote 0.6.10 (registry+https://github.com/rust-lang/crates.io-index)",
  "syn 0.15.24 (registry+https://github.com/rust-lang/crates.io-index)",
  "unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
 name = "target-lexicon"
 version = "0.2.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
+source = "git+https://github.com/glandium/target-lexicon?branch=thumbv7neon-v0.2#b2d4b34509abb3e12b1c93d19b8593d02ddeed76"
 dependencies = [
  "failure 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)",
  "failure_derive 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)",
  "serde_json 1.0.26 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
 name = "tempdir"
@@ -3694,17 +3694,17 @@ dependencies = [
 "checksum string_cache_codegen 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "1eea1eee654ef80933142157fdad9dd8bc43cf7c74e999e369263496f04ff4da"
 "checksum string_cache_shared 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b1884d1bc09741d466d9b14e6d37ac89d6909cbcac41dd9ae982d4d063bbedfc"
 "checksum strsim 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b4d15c810519a91cf877e7e36e63fe068815c678181439f2f29e2562147c3694"
 "checksum strsim 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "bb4f380125926a99e52bc279241539c018323fab05ad6368b56f93d9369ff550"
 "checksum syn 0.13.1 (registry+https://github.com/rust-lang/crates.io-index)" = "91b52877572087400e83d24b9178488541e3d535259e04ff17a63df1e5ceff59"
 "checksum syn 0.14.6 (registry+https://github.com/rust-lang/crates.io-index)" = "4e4b5274d4a0a3d2749d5c158dc64d3403e60554dc61194648787ada5212473d"
 "checksum syn 0.15.24 (registry+https://github.com/rust-lang/crates.io-index)" = "734ecc29cd36e8123850d9bf21dfd62ef8300aaa8f879aabaa899721808be37c"
 "checksum synstructure 0.10.1 (registry+https://github.com/rust-lang/crates.io-index)" = "73687139bf99285483c96ac0add482c3776528beac1d97d444f6e91f203a2015"
-"checksum target-lexicon 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "4af5e2227f0b887d591d3724b796a96eff04226104d872f5b3883fcd427d64b9"
+"checksum target-lexicon 0.2.0 (git+https://github.com/glandium/target-lexicon?branch=thumbv7neon-v0.2)" = "<none>"
 "checksum tempdir 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)" = "87974a6f5c1dfb344d733055601650059a3363de2a6104819293baff662132d6"
 "checksum term 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)" = "fa63644f74ce96fbeb9b794f66aff2a52d601cbd5e80f4b97123e3899f4570f1"
 "checksum term_size 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e2b6b55df3198cc93372e85dd2ed817f0e38ce8cc0f22eb32391bfad9c4bf209"
 "checksum termcolor 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)" = "adc4587ead41bf016f11af03e55a624c06568b5a19db4e90fde573d805074f83"
 "checksum termion 1.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "689a3bdfaab439fd92bc87df5c4c78417d3cbe537487274e9b0b2dce76e92096"
 "checksum textwrap 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "c0b59b6b4b44d867f1370ef1bd91bfb262bf07bf0ae65c202ea2fbc16153b693"
 "checksum thin-slice 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "8eaa81235c7058867fa8c0e7314f33dcce9c215f535d1913822a2b3f5e289f3c"
 "checksum thin-vec 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "73fdf4b84c65a85168477b7fb6c498e0716bc9487fba24623389ea7f51708044"
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -57,8 +57,9 @@ panic = "abort"
 codegen-units = 1
 
 [patch.crates-io]
 libudev-sys = { path = "dom/webauthn/libudev-sys" }
 serde_derive = { git = "https://github.com/servo/serde", branch = "deserialize_from_enums9" }
 winapi = { git = "https://github.com/froydnj/winapi-rs", branch = "aarch64" }
 cc = { git = "https://github.com/glandium/cc-rs", branch = "1.0.23-clang-cl-aarch64" }
 packed_simd = { git = "https://github.com/hsivonen/packed_simd", branch = "rust_1_32" }
+target-lexicon = { git = "https://github.com/glandium/target-lexicon", branch = "thumbv7neon-v0.2" }
--- a/accessible/base/NotificationController.h
+++ b/accessible/base/NotificationController.h
@@ -209,32 +209,33 @@ class NotificationController final : pub
   /**
    * Process the generic notification synchronously if there are no pending
    * layout changes and no notifications are pending or being processed right
    * now. Otherwise, queue it up to process asynchronously.
    *
    * @note  The caller must guarantee that the given instance still exists when
    *        the notification is processed.
    */
-  template <class Class, class Arg>
+  template <class Class, class... Args>
   inline void HandleNotification(
-      Class* aInstance, typename TNotification<Class, Arg>::Callback aMethod,
-      Arg* aArg) {
+      Class* aInstance,
+      typename TNotification<Class, Args...>::Callback aMethod,
+      Args*... aArgs) {
     if (!IsUpdatePending()) {
 #ifdef A11Y_LOG
       if (mozilla::a11y::logging::IsEnabled(
               mozilla::a11y::logging::eNotifications))
         mozilla::a11y::logging::Text("sync notification processing");
 #endif
-      (aInstance->*aMethod)(aArg);
+      (aInstance->*aMethod)(aArgs...);
       return;
     }
 
     RefPtr<Notification> notification =
-        new TNotification<Class, Arg>(aInstance, aMethod, aArg);
+        new TNotification<Class, Args...>(aInstance, aMethod, aArgs...);
     if (notification && mNotifications.AppendElement(notification))
       ScheduleProcessing();
   }
 
   /**
    * Schedule the generic notification to process asynchronously.
    *
    * @note  The caller must guarantee that the given instance still exists when
--- a/accessible/generic/DocAccessible-inl.h
+++ b/accessible/generic/DocAccessible-inl.h
@@ -54,23 +54,23 @@ inline void DocAccessible::FireDelayedEv
   RefPtr<AccEvent> event = new AccEvent(aEventType, aTarget);
   FireDelayedEvent(event);
 }
 
 inline void DocAccessible::BindChildDocument(DocAccessible* aDocument) {
   mNotificationController->ScheduleChildDocBinding(aDocument);
 }
 
-template <class Class, class Arg>
+template <class Class, class... Args>
 inline void DocAccessible::HandleNotification(
-    Class* aInstance, typename TNotification<Class, Arg>::Callback aMethod,
-    Arg* aArg) {
+    Class* aInstance, typename TNotification<Class, Args...>::Callback aMethod,
+    Args*... aArgs) {
   if (mNotificationController) {
-    mNotificationController->HandleNotification<Class, Arg>(aInstance, aMethod,
-                                                            aArg);
+    mNotificationController->HandleNotification<Class, Args...>(
+        aInstance, aMethod, aArgs...);
   }
 }
 
 inline void DocAccessible::UpdateText(nsIContent* aTextNode) {
   NS_ASSERTION(mNotificationController, "The document was shut down!");
 
   // Ignore the notification if initial tree construction hasn't been done yet.
   if (mNotificationController && HasLoadState(eTreeConstructed))
--- a/accessible/generic/DocAccessible.h
+++ b/accessible/generic/DocAccessible.h
@@ -221,20 +221,20 @@ class DocAccessible : public HyperTextAc
 
   /**
    * Process the generic notification.
    *
    * @note  The caller must guarantee that the given instance still exists when
    *          notification is processed.
    * @see   NotificationController::HandleNotification
    */
-  template <class Class, class Arg>
-  void HandleNotification(Class* aInstance,
-                          typename TNotification<Class, Arg>::Callback aMethod,
-                          Arg* aArg);
+  template <class Class, class... Args>
+  void HandleNotification(
+      Class* aInstance,
+      typename TNotification<Class, Args...>::Callback aMethod, Args*... aArgs);
 
   /**
    * Return the cached accessible by the given DOM node if it's in subtree of
    * this document accessible or the document accessible itself, otherwise null.
    *
    * @return the accessible object
    */
   Accessible* GetAccessible(nsINode* aNode) const {
--- a/accessible/generic/RootAccessible.cpp
+++ b/accessible/generic/RootAccessible.cpp
@@ -156,33 +156,38 @@ const char* const kEventTypes[] = {
     "DOMMenuItemActive", "DOMMenuItemInactive", "DOMMenuBarActive",
     "DOMMenuBarInactive"};
 
 nsresult RootAccessible::AddEventListeners() {
   // EventTarget interface allows to register event listeners to
   // receive untrusted events (synthetic events generated by untrusted code).
   // For example, XBL bindings implementations for elements that are hosted in
   // non chrome document fire untrusted events.
-  nsCOMPtr<EventTarget> nstarget = mDocumentNode;
+  // We must use the window's parent target in order to receive events from
+  // iframes and shadow DOM; e.g. ValueChange events from a <select> in an
+  // iframe or shadow DOM. The root document itself doesn't receive these.
+  nsPIDOMWindowOuter* window = mDocumentNode->GetWindow();
+  nsCOMPtr<EventTarget> nstarget = window ? window->GetParentTarget() : nullptr;
 
   if (nstarget) {
     for (const char *const *e = kEventTypes, *const *e_end =
                                                  ArrayEnd(kEventTypes);
          e < e_end; ++e) {
       nsresult rv = nstarget->AddEventListener(NS_ConvertASCIItoUTF16(*e), this,
                                                true, true);
       NS_ENSURE_SUCCESS(rv, rv);
     }
   }
 
   return DocAccessible::AddEventListeners();
 }
 
 nsresult RootAccessible::RemoveEventListeners() {
-  nsCOMPtr<EventTarget> target = mDocumentNode;
+  nsPIDOMWindowOuter* window = mDocumentNode->GetWindow();
+  nsCOMPtr<EventTarget> target = window ? window->GetParentTarget() : nullptr;
   if (target) {
     for (const char *const *e = kEventTypes, *const *e_end =
                                                  ArrayEnd(kEventTypes);
          e < e_end; ++e) {
       target->RemoveEventListener(NS_ConvertASCIItoUTF16(*e), this, true);
     }
   }
 
@@ -198,16 +203,24 @@ nsresult RootAccessible::RemoveEventList
 void RootAccessible::DocumentActivated(DocAccessible* aDocument) {}
 
 ////////////////////////////////////////////////////////////////////////////////
 // nsIDOMEventListener
 
 NS_IMETHODIMP
 RootAccessible::HandleEvent(Event* aDOMEvent) {
   MOZ_ASSERT(aDOMEvent);
+  if (IsDefunct()) {
+    // Even though we've been shut down, RemoveEventListeners might not have
+    // removed the event handlers on the window's parent target if GetWindow
+    // returned null, so we might still get events here in this case. We should
+    // just ignore these events.
+    return NS_OK;
+  }
+
   nsCOMPtr<nsINode> origTargetNode =
       do_QueryInterface(aDOMEvent->GetOriginalTarget());
   if (!origTargetNode) return NS_OK;
 
 #ifdef A11Y_LOG
   if (logging::IsEnabled(logging::eDOMEvents)) {
     nsAutoString eventType;
     aDOMEvent->GetType(eventType);
@@ -217,56 +230,51 @@ RootAccessible::HandleEvent(Event* aDOME
 
   DocAccessible* document =
       GetAccService()->GetDocAccessible(origTargetNode->OwnerDoc());
 
   if (document) {
     // Root accessible exists longer than any of its descendant documents so
     // that we are guaranteed notification is processed before root accessible
     // is destroyed.
-    document->HandleNotification<RootAccessible, Event>(
-        this, &RootAccessible::ProcessDOMEvent, aDOMEvent);
+    // For shadow DOM, GetOriginalTarget on the Event returns null if we
+    // process the event async, so we must pass the target node as well.
+    document->HandleNotification<RootAccessible, Event, nsINode>(
+        this, &RootAccessible::ProcessDOMEvent, aDOMEvent, origTargetNode);
   }
 
   return NS_OK;
 }
 
 // RootAccessible protected
-void RootAccessible::ProcessDOMEvent(Event* aDOMEvent) {
+void RootAccessible::ProcessDOMEvent(Event* aDOMEvent, nsINode* aTarget) {
   MOZ_ASSERT(aDOMEvent);
-  nsCOMPtr<nsINode> origTargetNode =
-      do_QueryInterface(aDOMEvent->GetOriginalTarget());
+  MOZ_ASSERT(aTarget);
 
   nsAutoString eventType;
   aDOMEvent->GetType(eventType);
 
 #ifdef A11Y_LOG
   if (logging::IsEnabled(logging::eDOMEvents))
-    logging::DOMEvent("processed", origTargetNode, eventType);
+    logging::DOMEvent("processed", aTarget, eventType);
 #endif
 
-  if (!origTargetNode) {
-    // Original target has ceased to exist.
-    return;
-  }
-
   if (eventType.EqualsLiteral("popuphiding")) {
-    HandlePopupHidingEvent(origTargetNode);
+    HandlePopupHidingEvent(aTarget);
     return;
   }
 
   DocAccessible* targetDocument =
-      GetAccService()->GetDocAccessible(origTargetNode->OwnerDoc());
+      GetAccService()->GetDocAccessible(aTarget->OwnerDoc());
   if (!targetDocument) {
     // Document has ceased to exist.
     return;
   }
 
-  Accessible* accessible =
-      targetDocument->GetAccessibleOrContainer(origTargetNode);
+  Accessible* accessible = targetDocument->GetAccessibleOrContainer(aTarget);
   if (!accessible) return;
 
 #ifdef MOZ_XUL
   XULTreeAccessible* treeAcc = accessible->AsXULTree();
   if (treeAcc) {
     if (eventType.EqualsLiteral("TreeRowCountChanged")) {
       HandleTreeRowCountChangedEvent(aDOMEvent, treeAcc);
       return;
--- a/accessible/generic/RootAccessible.h
+++ b/accessible/generic/RootAccessible.h
@@ -49,17 +49,17 @@ class RootAccessible : public DocAccessi
    * Add/remove DOM event listeners.
    */
   virtual nsresult AddEventListeners() override;
   virtual nsresult RemoveEventListeners() override;
 
   /**
    * Process the DOM event.
    */
-  void ProcessDOMEvent(dom::Event* aEvent);
+  void ProcessDOMEvent(dom::Event* aDOMEvent, nsINode* aTarget);
 
   /**
    * Process "popupshown" event. Used by HandleEvent().
    */
   void HandlePopupShownEvent(Accessible* aAccessible);
 
   /*
    * Process "popuphiding" event. Used by HandleEvent().
--- a/accessible/tests/mochitest/events/test_valuechange.html
+++ b/accessible/tests/mochitest/events/test_valuechange.html
@@ -116,17 +116,17 @@
       };
     }
 
     function changeSelectValue(aID, aKey, aValue) {
       this.eventSeq =
         [ new invokerChecker(EVENT_TEXT_VALUE_CHANGE, getAccessible(aID)) ];
 
       this.invoke = function changeSelectValue_invoke() {
-        getNode(aID).focus();
+        getAccessible(aID).takeFocus();
         synthesizeKey(aKey, {}, window);
       };
 
       this.finalCheck = function changeSelectValue_finalCheck() {
         is(getAccessible(aID).value, aValue, "Wrong value for " + prettyName(aID));
       };
 
       this.getID = function changeSelectValue_getID() {
@@ -162,16 +162,22 @@
       gQueue.push(new changeValue("combobox", "hello"));
 
       gQueue.push(new changeProgressValue("progress", "50"));
       gQueue.push(new changeRangeValue("range"));
 
       gQueue.push(new changeSelectValue("select", "VK_DOWN", "2nd"));
       gQueue.push(new changeSelectValue("select", "3", "3rd"));
 
+      let iframeSelect = getAccessible("selectIframe").firstChild.firstChild;
+      gQueue.push(new changeSelectValue(iframeSelect, "VK_DOWN", "2"));
+
+      let shadowSelect = getAccessible("selectShadow").firstChild;
+      gQueue.push(new changeSelectValue(shadowSelect, "VK_DOWN", "2"));
+
       gQueue.invoke(); // Will call SimpleTest.finish();
     }
 
     SimpleTest.waitForExplicitFinish();
     addA11yLoadEvent(doTests);
   </script>
 </head>
 
@@ -241,10 +247,29 @@
   <!-- input@type="range" -->
   <input type="range" id="range" min="0" max="10" value="6">
 
   <select id="select">
     <option>1st</option>
     <option>2nd</option>
     <option>3rd</option>
   </select>
+
+  <iframe id="selectIframe"
+    src="data:text/html,<select id='iframeSelect'><option>1</option><option>2</option></select>">
+  </iframe>
+
+  <div id="selectShadow"></div>
+  <script>
+    let host = document.getElementById("selectShadow");
+    let shadow = host.attachShadow({mode: "open"});
+    let select = document.createElement("select");
+    select.id = "shadowSelect";
+    let option = document.createElement("option");
+    option.textContent = "1";
+    select.appendChild(option);
+    option = document.createElement("option");
+    option.textContent = "2";
+    select.appendChild(option);
+    shadow.appendChild(select);
+  </script>
 </body>
 </html>
--- a/browser/base/content/browser-context.inc
+++ b/browser/base/content/browser-context.inc
@@ -170,23 +170,21 @@
       <menuitem id="context-video-fullscreen"
                 accesskey="&videoFullScreen.accesskey;"
                 label="&videoFullScreen.label;"
                 oncommand="gContextMenu.mediaCommand('fullscreen');"/>
       <menuitem id="context-leave-dom-fullscreen"
                 accesskey="&leaveDOMFullScreen.accesskey;"
                 label="&leaveDOMFullScreen.label;"
                 oncommand="gContextMenu.leaveDOMFullScreen();"/>
-#ifdef NIGHTLY_BUILD
       <menuitem id="context-video-pictureinpicture"
                 accesskey="&pictureInPicture.accesskey;"
                 label="&pictureInPicture.label;"
                 type="checkbox"
                 oncommand="gContextMenu.mediaCommand('pictureinpicture');"/>
-#endif
       <menuseparator id="context-media-sep-commands"/>
       <menuitem id="context-reloadimage"
                 label="&reloadImageCmd.label;"
                 accesskey="&reloadImageCmd.accesskey;"
                 oncommand="gContextMenu.reloadImage();"/>
       <menuitem id="context-viewimage"
                 label="&viewImageCmd.label;"
                 accesskey="&viewImageCmd.accesskey;"
--- a/browser/base/content/browser-sync.js
+++ b/browser/base/content/browser-sync.js
@@ -130,19 +130,24 @@ var gSync = {
       // We are in a window without our elements - just abort now, without
       // setting this._initialized, so we don't attempt to remove observers.
       return;
     }
     let syncNow = document.getElementById("PanelUI-remotetabs-syncnow");
     let label = this.syncStrings.GetStringFromName("syncnow.label");
     syncIcon.setAttribute("label", label);
     syncNow.setAttribute("label", label);
-    // We start with every menuitem hidden, so that we don't need to init
-    // the sync UI on windows like pageInfo.xul (see bug 1384856).
+    // We start with every menuitem hidden (except for the "setup sync" state),
+    // so that we don't need to init the sync UI on windows like pageInfo.xul
+    // (see bug 1384856).
+    // maybeUpdateUIState() also optimizes for this - if we should be in the
+    // "setup sync" state, that function assumes we are already in it and
+    // doesn't re-initialize the UI elements.
     document.getElementById("sync-setup").hidden = false;
+    document.getElementById("PanelUI-remotetabs-setupsync").hidden = false;
 
     for (let topic of this._obs) {
       Services.obs.addObserver(this, topic, true);
     }
 
     this._generateNodeGetters();
 
     this.maybeUpdateUIState();
--- a/browser/base/content/browser.css
+++ b/browser/base/content/browser.css
@@ -713,25 +713,25 @@ html|input.urlbar-input {
 #urlbar:not([actiontype="switchtab"]):not([actiontype="extension"]) > #urlbar-display-box,
 #urlbar:not([actiontype="switchtab"]) > #urlbar-display-box > #switchtab,
 #urlbar:not([actiontype="extension"]) > #urlbar-display-box > #extension {
   display: none;
 }
 
 #PopupAutoComplete > richlistbox > richlistitem[originaltype="loginsFooter"] {
   -moz-box-pack: center;
-  color: var(--btn-text-color);
+  color: -moz-fieldText;
   min-height: 40px;
   border-top: 1px solid rgba(38,38,38,.15);
-  background-color: #EDEDED;
+  background-color: hsla(0,0%,80%,.35); /* match arrowpanel-dimmed */;
 }
 
 #PopupAutoComplete > richlistbox > richlistitem[originaltype="loginsFooter"]:hover,
 #PopupAutoComplete > richlistbox > richlistitem[originaltype="loginsFooter"][selected] {
-  background-color: #DCDCDE;
+  background-color: hsla(0,0%,80%,.5); /* match arrowpanel-dimmed-further */
 }
 
 #PopupAutoComplete[firstresultstyle="insecureWarning"] {
   min-width: 200px;
 }
 
 #PopupAutoComplete > richlistbox > richlistitem[originaltype="insecureWarning"] {
   -moz-binding: none;
--- a/browser/base/content/moz.build
+++ b/browser/base/content/moz.build
@@ -48,26 +48,26 @@ with Files("test/pageinfo/**"):
 
 with Files("test/performance/**"):
     BUG_COMPONENT = ("Firefox", "General")
 
 with Files("test/performance/browser_appmenu.js"):
     BUG_COMPONENT = ("Firefox", "Menus")
 
 with Files("test/permissions/**"):
-    BUG_COMPONENT = ("Firefox", "Preferences")
+    BUG_COMPONENT = ("Firefox", "Site Identity and Permission Panels")
 
 with Files("test/plugins/**"):
     BUG_COMPONENT = ("Core", "Plug-ins")
 
 with Files("test/popupNotifications/**"):
     BUG_COMPONENT = ("Toolkit", "Notifications and Alerts")
 
 with Files("test/popups/**"):
-    BUG_COMPONENT = ("Toolkit", "Notifications and Alerts")
+    BUG_COMPONENT = ("Firefox", "Site Identity and Permission Panels")
 
 with Files("test/referrer/**"):
     BUG_COMPONENT = ("Core", "Document Navigation")
 
 with Files("test/sanitize/**"):
     BUG_COMPONENT = ("Toolkit", "Data Sanitization")
 
 with Files("test/siteIdentity/**"):
--- a/browser/components/extensions/parent/ext-browser.js
+++ b/browser/components/extensions/parent/ext-browser.js
@@ -455,27 +455,36 @@ class TabTracker extends TabTrackerBase 
           // Handle the adoption.
           this.adopt(nativeTab, adoptedTab);
           if (adoptedTab.linkedPanel) {
             adoptedTab.linkedBrowser.messageManager.sendAsyncMessage("Extension:SetFrameData", {
               windowId: windowTracker.getId(nativeTab.ownerGlobal),
             });
           }
         } else {
-          if (!event.originalTarget.parentNode) {
-            // If the tab is already be destroyed, do nothing.
-            return;
-          }
+          // Save the size of the current tab, since the newly-created tab will
+          // likely be active by the time the promise below resolves and the
+          // event is dispatched.
           const currentTab = nativeTab.ownerGlobal.gBrowser.selectedTab;
           const {frameLoader} = currentTab.linkedBrowser;
           const currentTabSize = {
             width: frameLoader.lazyWidth,
             height: frameLoader.lazyHeight,
           };
-          this.emitCreated(event.originalTarget, currentTabSize);
+
+          // We need to delay sending this event until the next tick, since the
+          // tab can become selected immediately after "TabOpen", then onCreated
+          // should be fired with `active: true`.
+          Promise.resolve().then(() => {
+            if (!event.originalTarget.parentNode) {
+              // If the tab is already be destroyed, do nothing.
+              return;
+            }
+            this.emitCreated(event.originalTarget, currentTabSize);
+          });
         }
         break;
 
       case "TabClose":
         let {adoptedBy} = event.detail;
         if (adoptedBy) {
           // This tab is being closed because it was adopted by a new window.
           // Handle the adoption in case it was created as the first tab of a
@@ -483,31 +492,31 @@ class TabTracker extends TabTrackerBase 
           // opened.
           this.adopt(adoptedBy, nativeTab);
         } else {
           this.emitRemoved(nativeTab, false);
         }
         break;
 
       case "TabSelect":
-        // We need to delay sending this event because it shouldn't fire before
-        // onRemoved when the active tab is removed.
+        // Because we are delaying calling emitCreated above, we also need to
+        // delay sending this event because it shouldn't fire before onCreated.
         Promise.resolve().then(() => {
           if (!nativeTab.parentNode) {
             // If the tab is already be destroyed, do nothing.
             return;
           }
           this.emitActivated(nativeTab, event.detail.previousTab);
         });
         break;
 
       case "TabMultiSelect":
         if (this.has("tabs-highlighted")) {
-          // Because we are delaying calling emitActivated above, we also need to
-          // delay sending this event because it shouldn't fire before onActivated.
+          // Because we are delaying calling emitCreated above, we also need to
+          // delay sending this event because it shouldn't fire before onCreated.
           Promise.resolve().then(() => {
             this.emitHighlighted(event.target.ownerGlobal);
           });
         }
         break;
     }
   }
 
--- a/browser/components/extensions/test/browser/browser-common.ini
+++ b/browser/components/extensions/test/browser/browser-common.ini
@@ -217,16 +217,17 @@ skip-if = (verify && !debug && (os == 'm
 [browser_ext_tabs_lastAccessed.js]
 [browser_ext_tabs_lazy.js]
 [browser_ext_tabs_removeCSS.js]
 [browser_ext_tabs_move_array.js]
 [browser_ext_tabs_move_discarded.js]
 [browser_ext_tabs_move_window.js]
 [browser_ext_tabs_move_window_multiple.js]
 [browser_ext_tabs_move_window_pinned.js]
+[browser_ext_tabs_onCreated.js]
 [browser_ext_tabs_onHighlighted.js]
 [browser_ext_tabs_onUpdated.js]
 [browser_ext_tabs_onUpdated_filter.js]
 [browser_ext_tabs_opener.js]
 [browser_ext_tabs_printPreview.js]
 [browser_ext_tabs_query.js]
 [browser_ext_tabs_readerMode.js]
 [browser_ext_tabs_reload.js]
--- a/browser/components/extensions/test/browser/browser_ext_tabs_events.js
+++ b/browser/components/extensions/test/browser/browser_ext_tabs_events.js
@@ -396,23 +396,22 @@ add_task(async function testLastTabRemov
   const CLOSE_WINDOW_PREF = "browser.tabs.closeWindowWithLastTab";
   await SpecialPowers.pushPrefEnv({set: [
     [CLOSE_WINDOW_PREF, false],
   ]});
 
   async function background() {
     let windowId;
     browser.tabs.onCreated.addListener(tab => {
-      windowId = tab.windowId;
+      browser.test.assertEq(windowId, tab.windowId,
+                            "expecting onCreated after onRemoved on the same window");
       browser.test.sendMessage("tabCreated", `${tab.width}x${tab.height}`);
     });
     browser.tabs.onRemoved.addListener((tabId, info) => {
-      browser.test.assertEq(windowId, info.windowId,
-                            "expecting onRemoved after onCreated on the same window");
-      browser.test.sendMessage("tabRemoved");
+      windowId = info.windowId;
     });
   }
 
   let extension = ExtensionTestUtils.loadExtension({
     manifest: {
       "permissions": ["tabs"],
     },
     background,
@@ -422,17 +421,16 @@ add_task(async function testLastTabRemov
   await extension.startup();
 
   const oldBrowser = newWin.gBrowser.selectedBrowser;
   const expectedDims = `${oldBrowser.clientWidth}x${oldBrowser.clientHeight}`;
   BrowserTestUtils.removeTab(newWin.gBrowser.selectedTab);
 
   const actualDims = await extension.awaitMessage("tabCreated");
   is(actualDims, expectedDims, "created tab reports a size same to the removed last tab");
-  await extension.awaitMessage("tabRemoved");
 
   await extension.unload();
   await BrowserTestUtils.closeWindow(newWin);
   SpecialPowers.clearUserPref(CLOSE_WINDOW_PREF);
 });
 
 add_task(async function testTabActivationEvent() {
   async function background() {
new file mode 100644
--- /dev/null
+++ b/browser/components/extensions/test/browser/browser_ext_tabs_onCreated.js
@@ -0,0 +1,26 @@
+/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set sts=2 sw=2 et tw=80: */
+/* eslint-disable mozilla/no-arbitrary-setTimeout */
+"use strict";
+
+add_task(async function test_onCreated_active() {
+  let extension = ExtensionTestUtils.loadExtension({
+    manifest: {
+      "permissions": ["tabs"],
+    },
+    background: function() {
+      browser.tabs.onCreated.addListener(tab => {
+        browser.tabs.remove(tab.id);
+        browser.test.sendMessage("onCreated", tab);
+      });
+    },
+  });
+
+  await extension.startup();
+  BrowserOpenTab();
+
+  let tab = await extension.awaitMessage("onCreated");
+  is(true, tab.active, "Tab should be active");
+
+  await extension.unload();
+});
--- a/browser/themes/shared/customizableui/panelUI.inc.css
+++ b/browser/themes/shared/customizableui/panelUI.inc.css
@@ -727,17 +727,24 @@ toolbarbutton[constrain-size="true"][cui
 
 .PanelUI-fxa-signin-instruction-callout {
   font-size: 1.3em;
   font-weight: normal;
   padding: .6em 0 .6em;
 }
 
 .PanelUI-fxa-signin-instruction-label {
+  /* Use 'lighter' font for this to de-emphasize it compared to the title.
+   * We use 300 on Linux because 100 is too light (lacks contrast with
+   * the background) for some fonts in combination with anti-aliasing. */
+%if defined(XP_MACOSX) || defined(XP_WIN)
   font-weight: lighter;
+%else
+  font-weight: 300;
+%endif
 }
 
 .fxa-avatar-subpanel {
   padding-top: 8px;
   padding-bottom: 4px;
 }
 
 .fxa-avatar-subpanel-description {
--- a/build/unix/stdc++compat/stdc++compat.cpp
+++ b/build/unix/stdc++compat/stdc++compat.cpp
@@ -4,35 +4,27 @@
 
 #include <ostream>
 #include <istream>
 #include <string>
 #include <stdarg.h>
 #include <stdio.h>
 #include <mozilla/Assertions.h>
 
-/* GLIBCXX_3.4.8  is from gcc 4.1.1 (111691)
-   GLIBCXX_3.4.9  is from gcc 4.2.0 (111690)
-   GLIBCXX_3.4.10 is from gcc 4.3.0 (126287)
-   GLIBCXX_3.4.11 is from gcc 4.4.0 (133006)
-   GLIBCXX_3.4.12 is from gcc 4.4.1 (147138)
-   GLIBCXX_3.4.13 is from gcc 4.4.2 (151127)
-   GLIBCXX_3.4.14 is from gcc 4.5.0 (151126)
-   GLIBCXX_3.4.15 is from gcc 4.6.0 (160071)
-   GLIBCXX_3.4.16 is from gcc 4.6.1 (172240)
+/* GLIBCXX_3.4.16 is from gcc 4.6.1 (172240)
    GLIBCXX_3.4.17 is from gcc 4.7.0 (174383)
    GLIBCXX_3.4.18 is from gcc 4.8.0 (190787)
    GLIBCXX_3.4.19 is from gcc 4.8.1 (199309)
    GLIBCXX_3.4.20 is from gcc 4.9.0 (199307)
    GLIBCXX_3.4.21 is from gcc 5.0 (210290)
    GLIBCXX_3.4.22 is from gcc 6.0 (222482)
 
 This file adds the necessary compatibility tricks to avoid symbols with
-version GLIBCXX_3.4.16 and bigger, keeping binary compatibility with
-libstdc++ 4.6.1.
+version GLIBCXX_3.4.17 and bigger, keeping binary compatibility with
+libstdc++ 4.7.
 
 WARNING: all symbols from this file must be defined weak when they
 overlap with libstdc++.
 */
 
 #define GLIBCXX_VERSION(a, b, c) (((a) << 16) | ((b) << 8) | (c))
 
 #if MOZ_LIBSTDCXX_VERSION >= GLIBCXX_VERSION(3, 4, 18)
--- a/devtools/client/aboutdebugging-new/src/constants.js
+++ b/devtools/client/aboutdebugging-new/src/constants.js
@@ -90,16 +90,18 @@ const MESSAGE_LEVEL = {
 };
 
 const PAGE_TYPES = {
   RUNTIME: "runtime",
   CONNECT: "connect",
 };
 
 const PREFERENCES = {
+  // Preference that drives the display of the "Tabs" category on This Firefox.
+  LOCAL_TAB_DEBUGGING_ENABLED: "devtools.aboutdebugging.local-tab-debugging",
   // Preference that drives the display of the "Processes" debug target category.
   PROCESS_DEBUGGING_ENABLED: "devtools.aboutdebugging.process-debugging",
   // Preference that drives the display of system addons in about:debugging.
   SHOW_SYSTEM_ADDONS: "devtools.aboutdebugging.showSystemAddons",
 };
 
 const RUNTIME_PREFERENCE = {
   CHROME_DEBUG_ENABLED: "devtools.chrome.enabled",
--- a/devtools/client/aboutdebugging-new/src/modules/debug-target-support.js
+++ b/devtools/client/aboutdebugging-new/src/modules/debug-target-support.js
@@ -8,35 +8,42 @@ const { DEBUG_TARGET_PANE, PREFERENCES, 
 
 const Services = require("Services");
 
 // Process target debugging is disabled by default.
 function isProcessDebuggingSupported() {
   return Services.prefs.getBoolPref(PREFERENCES.PROCESS_DEBUGGING_ENABLED, false);
 }
 
+// Process target debugging is disabled by default.
+function isLocalTabDebuggingSupported() {
+  return Services.prefs.getBoolPref(PREFERENCES.LOCAL_TAB_DEBUGGING_ENABLED, false);
+}
+
 const ALL_DEBUG_TARGET_PANES = [
   DEBUG_TARGET_PANE.INSTALLED_EXTENSION,
   ...(isProcessDebuggingSupported() ? [DEBUG_TARGET_PANE.PROCESSES] : []),
   DEBUG_TARGET_PANE.OTHER_WORKER,
   DEBUG_TARGET_PANE.SERVICE_WORKER,
   DEBUG_TARGET_PANE.SHARED_WORKER,
   DEBUG_TARGET_PANE.TAB,
   DEBUG_TARGET_PANE.TEMPORARY_EXTENSION,
 ];
 
 // All debug target panes except temporary extensions
 const REMOTE_DEBUG_TARGET_PANES = ALL_DEBUG_TARGET_PANES.filter(p =>
   p !== DEBUG_TARGET_PANE.TEMPORARY_EXTENSION);
 
-// Main process debugging is not available for This Firefox.
-// At the moment only the main process is listed under processes, so remove the category
-// for this runtime.
-const THIS_FIREFOX_DEBUG_TARGET_PANES = ALL_DEBUG_TARGET_PANES.filter(p =>
-  p !== DEBUG_TARGET_PANE.PROCESSES);
+const THIS_FIREFOX_DEBUG_TARGET_PANES = ALL_DEBUG_TARGET_PANES
+  // Main process debugging is not available for This Firefox.
+  // At the moment only the main process is listed under processes, so remove the category
+  // for this runtime.
+  .filter(p => p !== DEBUG_TARGET_PANE.PROCESSES)
+  // Showing tab targets for This Firefox is behind a preference.
+  .filter(p => p !== DEBUG_TARGET_PANE.TAB || isLocalTabDebuggingSupported());
 
 const SUPPORTED_TARGET_PANE_BY_RUNTIME = {
   [RUNTIMES.THIS_FIREFOX]: THIS_FIREFOX_DEBUG_TARGET_PANES,
   [RUNTIMES.USB]: REMOTE_DEBUG_TARGET_PANES,
   [RUNTIMES.NETWORK]: REMOTE_DEBUG_TARGET_PANES,
 };
 
 /**
--- a/devtools/client/aboutdebugging-new/test/browser/browser_aboutdebugging_debug-target-pane_usb_runtime.js
+++ b/devtools/client/aboutdebugging-new/test/browser/browser_aboutdebugging_debug-target-pane_usb_runtime.js
@@ -8,18 +8,25 @@ Services.scriptloader.loadSubScript(CHRO
 
 const RUNTIME_ID = "test-runtime-id";
 const RUNTIME_DEVICE_NAME = "test device name";
 const RUNTIME_APP_NAME = "TestApp";
 
 // Test that the expected supported categories are displayed for USB runtimes.
 add_task(async function() {
   const mocks = new Mocks();
+  await checkTargetPanes({ enableLocalTabs: false }, mocks);
 
-  const { document, tab, window } = await openAboutDebugging();
+  info("Check that enableLocalTabs has no impact on the categories displayed for remote" +
+    " runtimes.");
+  await checkTargetPanes({ enableLocalTabs: true }, mocks);
+});
+
+async function checkTargetPanes({ enableLocalTabs }, mocks) {
+  const { document, tab, window } = await openAboutDebugging({ enableLocalTabs });
   await selectThisFirefoxPage(document, window.AboutDebugging.store);
 
   mocks.createUSBRuntime(RUNTIME_ID, {
     deviceName: RUNTIME_DEVICE_NAME,
     name: RUNTIME_APP_NAME,
   });
   mocks.emitUSBUpdate();
 
@@ -46,9 +53,9 @@ add_task(async function() {
   info("Remove USB runtime");
   mocks.removeUSBRuntime(RUNTIME_ID);
   mocks.emitUSBUpdate();
 
   info("Wait until the USB sidebar item disappears");
   await waitUntil(() => !findSidebarItemByText(RUNTIME_DEVICE_NAME, document));
 
   await removeTab(tab);
-});
+}
--- a/devtools/client/aboutdebugging-new/test/browser/browser_aboutdebugging_thisfirefox.js
+++ b/devtools/client/aboutdebugging-new/test/browser/browser_aboutdebugging_thisfirefox.js
@@ -1,46 +1,67 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 "use strict";
 
-/**
- * Check that the This Firefox page is displayed by default when opening the new
- * about:debugging and that it contains the expected categories.
- */
-
 const EXPECTED_TARGET_PANES = [
   "Temporary Extensions",
   "Extensions",
   "Tabs",
   "Service Workers",
   "Shared Workers",
   "Other Workers",
 ];
 
-add_task(async function() {
-  const { document, tab, window } = await openAboutDebugging();
+/**
+ * Check that the This Firefox runtime page contains the expected categories if
+ * the preference to enable local tab debugging is true.
+ */
+add_task(async function testThisFirefoxWithLocalTab() {
+  const { document, tab, window } = await openAboutDebugging({ enableLocalTabs: true });
   await selectThisFirefoxPage(document, window.AboutDebugging.store);
 
+  // Expect all target panes to be displayed including tabs.
+  await checkThisFirefoxTargetPanes(document, EXPECTED_TARGET_PANES);
+
+  await removeTab(tab);
+});
+
+/**
+ * Check that the This Firefox runtime page contains the expected categories if
+ * the preference to enable local tab debugging is false.
+ */
+add_task(async function testThisFirefoxWithoutLocalTab() {
+  const { document, tab, window } = await openAboutDebugging({ enableLocalTabs: false });
+  await selectThisFirefoxPage(document, window.AboutDebugging.store);
+
+  // Expect all target panes but tabs to be displayed.
+  const expectedTargetPanesWithoutTabs = EXPECTED_TARGET_PANES.filter(p => p !== "Tabs");
+  await checkThisFirefoxTargetPanes(document, expectedTargetPanesWithoutTabs);
+
+  await removeTab(tab);
+});
+
+async function checkThisFirefoxTargetPanes(doc, expectedTargetPanes) {
+  const win = doc.ownerGlobal;
   // Check that the selected sidebar item is "This Firefox"/"This Nightly"/...
-  const selectedSidebarItem = document.querySelector(".js-sidebar-item-selected");
+  const selectedSidebarItem = doc.querySelector(".js-sidebar-item-selected");
   ok(selectedSidebarItem, "An item is selected in the sidebar");
 
-  const thisFirefoxString = getThisFirefoxString(window);
+  const thisFirefoxString = getThisFirefoxString(win);
   is(selectedSidebarItem.textContent, thisFirefoxString,
     "The selected sidebar item is " + thisFirefoxString);
 
-  const paneTitlesEls = document.querySelectorAll(".js-debug-target-pane-title");
-  is(paneTitlesEls.length, EXPECTED_TARGET_PANES.length,
-    "This Firefox has the expecte number of debug target categories");
+  const paneTitlesEls = doc.querySelectorAll(".js-debug-target-pane-title");
+  is(paneTitlesEls.length, expectedTargetPanes.length,
+    "This Firefox has the expected number of debug target categories");
 
   const paneTitles = [...paneTitlesEls].map(el => el.textContent);
 
-  for (let i = 0; i < EXPECTED_TARGET_PANES.length; i++) {
-    const expectedPaneTitle = EXPECTED_TARGET_PANES[i];
+  for (let i = 0; i < expectedTargetPanes.length; i++) {
+    const expectedPaneTitle = expectedTargetPanes[i];
     const actualPaneTitle = paneTitles[i];
     ok(actualPaneTitle.startsWith(expectedPaneTitle),
        `Expected debug target category found: ${ expectedPaneTitle }`);
   }
+}
 
-  await removeTab(tab);
-});
--- a/devtools/client/aboutdebugging-new/test/browser/head.js
+++ b/devtools/client/aboutdebugging-new/test/browser/head.js
@@ -38,21 +38,25 @@ registerCleanupFunction(async function()
 
 /**
  * Enable the new about:debugging panel.
  */
 async function enableNewAboutDebugging() {
   await pushPref("devtools.aboutdebugging.new-enabled", true);
 }
 
-async function openAboutDebugging({ enableWorkerUpdates } = {}) {
+async function openAboutDebugging({ enableWorkerUpdates, enableLocalTabs = true } = {}) {
   if (!enableWorkerUpdates) {
     silenceWorkerUpdates();
   }
 
+  // This preference changes value depending on the build type, tests need to use a
+  // consistent value regarless of the build used.
+  await pushPref("devtools.aboutdebugging.local-tab-debugging", enableLocalTabs);
+
   await enableNewAboutDebugging();
 
   info("opening about:debugging");
 
   const tab = await addTab("about:debugging");
   const browser = tab.linkedBrowser;
   const document = browser.contentDocument;
   const window = browser.contentWindow;
--- a/devtools/client/preferences/devtools-client.js
+++ b/devtools/client/preferences/devtools-client.js
@@ -336,16 +336,24 @@ pref("devtools.responsive.show-setting-t
 #if defined(NIGHTLY_BUILD)
 pref("devtools.responsive.showUserAgentInput", true);
 #else
 pref("devtools.responsive.showUserAgentInput", false);
 #endif
 
 // Enable new about:debugging.
 pref("devtools.aboutdebugging.new-enabled", false);
+
+// Show tab debug targets for This Firefox (on by default for local builds).
+#ifdef MOZILLA_OFFICIAL
+  pref("devtools.aboutdebugging.local-tab-debugging", false);
+#else
+  pref("devtools.aboutdebugging.local-tab-debugging", true);
+#endif
+
 // Show process debug targets.
 pref("devtools.aboutdebugging.process-debugging", false);
 // Stringified array of network locations that users can connect to.
 pref("devtools.aboutdebugging.network-locations", "[]");
 // Debug target pane collapse/expand settings.
 pref("devtools.aboutdebugging.collapsibilities.installedExtension", false);
 pref("devtools.aboutdebugging.collapsibilities.otherWorker", false);
 pref("devtools.aboutdebugging.collapsibilities.serviceWorker", false);
--- a/devtools/client/shared/test/test-actor.js
+++ b/devtools/client/shared/test/test-actor.js
@@ -10,17 +10,17 @@
 
 const { Ci, Cu } = require("chrome");
 const Services = require("Services");
 const {
   getRect, getAdjustedQuads, getWindowDimensions,
 } = require("devtools/shared/layout/utils");
 const defer = require("devtools/shared/defer");
 const {
-  isContentStylesheet,
+  isAuthorStylesheet,
   getCSSStyleRules,
 } = require("devtools/shared/inspector/css-logic");
 const InspectorUtils = require("InspectorUtils");
 
 // Set up a dummy environment so that EventUtils works. We need to be careful to
 // pass a window object into each EventUtils method we call rather than having
 // it rely on the |window| global.
 const EventUtils = {};
@@ -768,17 +768,17 @@ var TestActor = exports.TestActor = prot
     const domRules = getCSSStyleRules(node);
 
     const sheets = [];
 
     for (let i = 0, n = domRules.length; i < n; i++) {
       const sheet = domRules[i].parentStyleSheet;
       sheets.push({
         href: sheet.href,
-        isContentSheet: isContentStylesheet(sheet),
+        isContentSheet: isAuthorStylesheet(sheet),
       });
     }
 
     return sheets;
   },
 
   /**
    * Returns the window's dimensions for the `window` given.
--- a/devtools/server/actors/inspector/css-logic.js
+++ b/devtools/server/actors/inspector/css-logic.js
@@ -30,23 +30,30 @@
 "use strict";
 
 const { Cu } = require("chrome");
 const nodeConstants = require("devtools/shared/dom-node-constants");
 const {
   getBindingElementAndPseudo,
   getCSSStyleRules,
   l10n,
-  isContentStylesheet,
+  isAgentStylesheet,
+  isAuthorStylesheet,
+  isUserStylesheet,
   shortSource,
   FILTER,
   STATUS,
 } = require("devtools/shared/inspector/css-logic");
 const InspectorUtils = require("InspectorUtils");
 
+const COMPAREMODE = {
+  "BOOLEAN": "bool",
+  "INTEGER": "int",
+};
+
 /**
  * @param {function} isInherited A function that determines if the CSS property
  *                   is inherited.
  */
 function CssLogic(isInherited) {
   // The cache of examined CSS properties.
   this._isInherited = isInherited;
   this._propertyInfos = {};
@@ -165,18 +172,17 @@ CssLogic.prototype = {
   set sourceFilter(value) {
     const oldValue = this._sourceFilter;
     this._sourceFilter = value;
 
     let ruleCount = 0;
 
     // Update the CssSheet objects.
     this.forEachSheet(function(sheet) {
-      sheet._sheetAllowed = -1;
-      if (sheet.contentSheet && sheet.sheetAllowed) {
+      if (sheet.authorSheet && sheet.sheetAllowed) {
         ruleCount += sheet.ruleCount;
       }
     }, this);
 
     this._ruleCount = ruleCount;
 
     // Full update is needed because the this.processMatchedSelectors() method
     // skips UA stylesheets if the filter does not allow such sheets.
@@ -276,17 +282,17 @@ CssLogic.prototype = {
    */
   get sheets() {
     if (!this._sheetsCached) {
       this._cacheSheets();
     }
 
     const sheets = [];
     this.forEachSheet(function(sheet) {
-      if (sheet.contentSheet) {
+      if (sheet.authorSheet) {
         sheets.push(sheet);
       }
     }, this);
 
     return sheets;
   },
 
   /**
@@ -319,37 +325,34 @@ CssLogic.prototype = {
     } else if (domSheet.ownerNode && domSheet.ownerNode.ownerDocument) {
       cacheId = domSheet.ownerNode.ownerDocument.location;
     }
 
     let sheet = null;
     let sheetFound = false;
 
     if (cacheId in this._sheets) {
-      for (let i = 0, numSheets = this._sheets[cacheId].length;
-           i < numSheets;
-           i++) {
-        sheet = this._sheets[cacheId][i];
+      for (sheet of this._sheets[cacheId]) {
         if (sheet.domSheet === domSheet) {
           if (index != -1) {
             sheet.index = index;
           }
           sheetFound = true;
           break;
         }
       }
     }
 
     if (!sheetFound) {
       if (!(cacheId in this._sheets)) {
         this._sheets[cacheId] = [];
       }
 
       sheet = new CssSheet(this, domSheet, index);
-      if (sheet.sheetAllowed && sheet.contentSheet) {
+      if (sheet.sheetAllowed && sheet.authorSheet) {
         this._ruleCount += sheet.ruleCount;
       }
 
       this._sheets[cacheId].push(sheet);
     }
 
     return sheet;
   },
@@ -432,29 +435,28 @@ CssLogic.prototype = {
 
     if (!this._matchedRules) {
       this._buildMatchedRules();
     }
 
     this._matchedSelectors = [];
     this._passId++;
 
-    for (let i = 0; i < this._matchedRules.length; i++) {
-      const rule = this._matchedRules[i][0];
-      const status = this._matchedRules[i][1];
+    for (const matchedRule of this._matchedRules) {
+      const [rule, status, distance] = matchedRule;
 
       rule.selectors.forEach(function(selector) {
         if (selector._matchId !== this._matchId &&
-           (selector.elementStyle ||
+           (selector.inlineStyle ||
             this.selectorMatchesElement(rule.domRule,
                                         selector.selectorIndex))) {
           selector._matchId = this._matchId;
-          this._matchedSelectors.push([ selector, status ]);
+          this._matchedSelectors.push([ selector, status, distance ]);
           if (callback) {
-            callback.call(scope, selector, status);
+            callback.call(scope, selector, status, distance);
           }
         }
       }, this);
 
       rule._passId = this._passId;
     }
   },
 
@@ -527,16 +529,23 @@ CssLogic.prototype = {
    * @private
    */
   _buildMatchedRules: function() {
     let domRules;
     let element = this.viewedElement;
     const filter = this.sourceFilter;
     let sheetIndex = 0;
 
+    // distance is used to tell us how close an ancestor is to an element e.g.
+    //  0: The rule is directly applied to the current element.
+    // -1: The rule is inherited from the current element's first parent.
+    // -2: The rule is inherited from the current element's second parent.
+    // etc.
+    let distance = 0;
+
     this._matchId++;
     this._passId++;
     this._matchedRules = [];
 
     if (!element) {
       return;
     }
 
@@ -547,50 +556,50 @@ CssLogic.prototype = {
       try {
         domRules = getCSSStyleRules(element);
       } catch (ex) {
         console.log("CL__buildMatchedRules error: " + ex);
         continue;
       }
 
       // getCSSStyleRules can return null with a shadow DOM element.
-      const numDomRules = domRules ? domRules.length : 0;
-      for (let i = 0; i < numDomRules; i++) {
-        const domRule = domRules[i];
+      for (const domRule of domRules || []) {
         if (domRule.type !== CSSRule.STYLE_RULE) {
           continue;
         }
 
         const sheet = this.getSheet(domRule.parentStyleSheet, -1);
         if (sheet._passId !== this._passId) {
           sheet.index = sheetIndex++;
           sheet._passId = this._passId;
         }
 
-        if (filter === FILTER.USER && !sheet.contentSheet) {
+        if (filter === FILTER.USER && !sheet.authorSheet) {
           continue;
         }
 
         const rule = sheet.getRule(domRule);
         if (rule._passId === this._passId) {
           continue;
         }
 
         rule._matchId = this._matchId;
         rule._passId = this._passId;
-        this._matchedRules.push([rule, status]);
+        this._matchedRules.push([rule, status, distance]);
       }
 
       // Add element.style information.
       if (element.style && element.style.length > 0) {
         const rule = new CssRule(null, { style: element.style }, element);
         rule._matchId = this._matchId;
         rule._passId = this._passId;
-        this._matchedRules.push([rule, status]);
+        this._matchedRules.push([rule, status, distance]);
       }
+
+      distance--;
     } while ((element = element.parentNode) &&
               element.nodeType === nodeConstants.ELEMENT_NODE);
   },
 
   /**
    * Tells if the given DOM CSS object matches the current view media.
    *
    * @param {object} domObject The DOM CSS object to check.
@@ -715,17 +724,17 @@ CssLogic.href = function(sheet) {
  * this CssSheet object.
  * @param {CSSStyleSheet} domSheet reference to a DOM CSSStyleSheet object.
  * @param {number} index tells the index/position of the stylesheet within the
  * main document.
  */
 function CssSheet(cssLogic, domSheet, index) {
   this._cssLogic = cssLogic;
   this.domSheet = domSheet;
-  this.index = this.contentSheet ? index : -100 * index;
+  this.index = this.authorSheet ? index : -100 * index;
 
   // Cache of the sheets href. Cached by the getter.
   this._href = null;
   // Short version of href for use in select boxes etc. Cached by getter.
   this._shortSource = null;
 
   // null for uncached.
   this._sheetAllowed = null;
@@ -733,33 +742,59 @@ function CssSheet(cssLogic, domSheet, in
   // Cached CssRules from the given stylesheet.
   this._rules = {};
 
   this._ruleCount = -1;
 }
 
 CssSheet.prototype = {
   _passId: null,
-  _contentSheet: null,
+  _agentSheet: null,
+  _authorSheet: null,
+  _userSheet: null,
 
   /**
-   * Tells if the stylesheet is provided by the browser or not.
+   * Check if the stylesheet is an agent stylesheet (provided by the browser).
    *
-   * @return {boolean} false if this is a browser-provided stylesheet, or true
-   * otherwise.
+   * @return {boolean} true if this is an agent stylesheet, false otherwise.
    */
-  get contentSheet() {
-    if (this._contentSheet === null) {
-      this._contentSheet = isContentStylesheet(this.domSheet);
+  get agentSheet() {
+    if (this._agentSheet === null) {
+      this._agentSheet = isAgentStylesheet(this.domSheet);
     }
-    return this._contentSheet;
+    return this._agentSheet;
   },
 
   /**
-   * Tells if the stylesheet is disabled or not.
+   * Check if the stylesheet is an author stylesheet (provided by the content page).
+   *
+   * @return {boolean} true if this is an author stylesheet, false otherwise.
+   */
+  get authorSheet() {
+    if (this._authorSheet === null) {
+      this._authorSheet = isAuthorStylesheet(this.domSheet);
+    }
+    return this._authorSheet;
+  },
+
+  /**
+   * Check if the stylesheet is a user stylesheet (provided by userChrome.css or
+   * userContent.css).
+   *
+   * @return {boolean} true if this is a user stylesheet, false otherwise.
+   */
+  get userSheet() {
+    if (this._userSheet === null) {
+      this._userSheet = isUserStylesheet(this.domSheet);
+    }
+    return this._userSheet;
+  },
+
+  /**
+   * Check if the stylesheet is disabled or not.
    * @return {boolean} true if this stylesheet is disabled, or false otherwise.
    */
   get disabled() {
     return this.domSheet.disabled;
   },
 
   /**
    * Get a source for a stylesheet, using CssLogic.href
@@ -798,17 +833,17 @@ CssSheet.prototype = {
   get sheetAllowed() {
     if (this._sheetAllowed !== null) {
       return this._sheetAllowed;
     }
 
     this._sheetAllowed = true;
 
     const filter = this._cssLogic.sourceFilter;
-    if (filter === FILTER.USER && !this.contentSheet) {
+    if (filter === FILTER.USER && !this.authorSheet) {
       this._sheetAllowed = false;
     }
     if (filter !== FILTER.USER && filter !== FILTER.UA) {
       this._sheetAllowed = (filter === this.href);
     }
 
     return this._sheetAllowed;
   },
@@ -856,20 +891,17 @@ CssSheet.prototype = {
    */
   getRule: function(domRule) {
     const cacheId = domRule.type + domRule.selectorText;
 
     let rule = null;
     let ruleFound = false;
 
     if (cacheId in this._rules) {
-      for (let i = 0, rulesLen = this._rules[cacheId].length;
-           i < rulesLen;
-           i++) {
-        rule = this._rules[cacheId][i];
+      for (rule of this._rules[cacheId]) {
         if (rule.domRule === domRule) {
           ruleFound = true;
           break;
         }
       }
     }
 
     if (!ruleFound) {
@@ -916,23 +948,27 @@ function CssRule(cssSheet, domRule, elem
     this._selectors = null;
     this.line = InspectorUtils.getRuleLine(this.domRule);
     this.column = InspectorUtils.getRuleColumn(this.domRule);
     this.source = this._cssSheet.shortSource + ":" + this.line;
     if (this.mediaText) {
       this.source += " @media " + this.mediaText;
     }
     this.href = this._cssSheet.href;
-    this.contentRule = this._cssSheet.contentSheet;
+    this.authorRule = this._cssSheet.authorSheet;
+    this.userRule = this._cssSheet.userSheet;
+    this.agentRule = this._cssSheet.agentSheet;
   } else if (element) {
     this._selectors = [ new CssSelector(this, "@element.style", 0) ];
     this.line = -1;
     this.source = l10n("rule.sourceElement");
     this.href = "#";
-    this.contentRule = true;
+    this.authorRule = true;
+    this.userRule = false;
+    this.agentRule = false;
     this.sourceElement = element;
   }
 }
 
 CssRule.prototype = {
   _passId: null,
 
   mediaText: "",
@@ -1022,17 +1058,17 @@ CssRule.prototype = {
  * @constructor
  * @param {CssRule} cssRule the CssRule instance from where the selector comes.
  * @param {string} selector The selector that we wish to investigate.
  * @param {Number} index The index of the selector within it's rule.
  */
 function CssSelector(cssRule, selector, index) {
   this.cssRule = cssRule;
   this.text = selector;
-  this.elementStyle = this.text == "@element.style";
+  this.inlineStyle = this.text == "@element.style";
   this._specificity = null;
   this.selectorIndex = index;
 }
 
 exports.CssSelector = CssSelector;
 
 CssSelector.prototype = {
   _matchId: null,
@@ -1064,23 +1100,41 @@ CssSelector.prototype = {
    *
    * @return {string} the address of the CssSelector.
    */
   get href() {
     return this.cssRule.href;
   },
 
   /**
-   * Check if the selector comes from a browser-provided stylesheet.
+   * Check if the selector comes from an agent stylesheet (provided by the browser).
+   *
+   * @return {boolean} true if this is an agent stylesheet, false otherwise.
+   */
+  get agentRule() {
+    return this.cssRule.agentRule;
+  },
+
+  /**
+   * Check if the selector comes from an author stylesheet (provided by the content page).
    *
-   * @return {boolean} true if the selector comes from a content-provided
-   * stylesheet, or false otherwise.
+   * @return {boolean} true if this is an author stylesheet, false otherwise.
    */
-  get contentRule() {
-    return this.cssRule.contentRule;
+  get authorRule() {
+    return this.cssRule.authorRule;
+  },
+
+  /**
+   * Check if the selector comes from a user stylesheet (provided by userChrome.css or
+   * userContent.css).
+   *
+   * @return {boolean} true if this is a user stylesheet, false otherwise.
+   */
+  get userRule() {
+    return this.cssRule.userRule;
   },
 
   /**
    * Check if the parent stylesheet is allowed by the CssLogic.sourceFilter.
    *
    * @return {boolean} true if the parent stylesheet is allowed by the current
    * sourceFilter, or false otherwise.
    */
@@ -1122,31 +1176,29 @@ CssSelector.prototype = {
    * Retrieve specificity information for the current selector.
    *
    * @see http://www.w3.org/TR/css3-selectors/#specificity
    * @see http://www.w3.org/TR/CSS2/selector.html
    *
    * @return {Number} The selector's specificity.
    */
   get specificity() {
-    if (this.elementStyle) {
+    if (this.inlineStyle) {
       // We can't ask specificity from DOMUtils as element styles don't provide
       // CSSStyleRule interface DOMUtils expect. However, specificity of element
       // style is constant, 1,0,0,0 or 0x40000000, just return the constant
       // directly. @see http://www.w3.org/TR/CSS2/cascade.html#specificity
       return 0x40000000;
     }
 
-    if (this._specificity) {
-      return this._specificity;
+    if (typeof this._specificity !== "number") {
+      this._specificity = InspectorUtils.getSpecificity(this.cssRule.domRule,
+        this.selectorIndex);
     }
 
-    this._specificity = InspectorUtils.getSpecificity(this.cssRule.domRule,
-                                                      this.selectorIndex);
-
     return this._specificity;
   },
 
   toString: function() {
     return this.text;
   },
 };
 
@@ -1227,21 +1279,16 @@ CssPropertyInfo.prototype = {
   _findMatchedSelectors: function() {
     this._matchedSelectors = [];
     this.needRefilter = false;
 
     this._cssLogic.processMatchedSelectors(this._processMatchedSelector, this);
 
     // Sort the selectors by how well they match the given element.
     this._matchedSelectors.sort(function(selectorInfo1, selectorInfo2) {
-      if (selectorInfo1.status > selectorInfo2.status) {
-        return -1;
-      } else if (selectorInfo2.status > selectorInfo1.status) {
-        return 1;
-      }
       return selectorInfo1.compareTo(selectorInfo2);
     });
 
     // Now we know which of the matches is best, we can mark it BEST_MATCH.
     if (this._matchedSelectors.length > 0 &&
         this._matchedSelectors[0].status > STATUS.UNMATCHED) {
       this._matchedSelectors[0].status = STATUS.BEST;
     }
@@ -1249,25 +1296,25 @@ CssPropertyInfo.prototype = {
 
   /**
    * Process a matched CssSelector object.
    *
    * @private
    * @param {CssSelector} selector the matched CssSelector object.
    * @param {STATUS} status the CssSelector match status.
    */
-  _processMatchedSelector: function(selector, status) {
+  _processMatchedSelector: function(selector, status, distance) {
     const cssRule = selector.cssRule;
     const value = cssRule.getPropertyValue(this.property);
     if (value &&
         (status == STATUS.MATCHED ||
          (status == STATUS.PARENT_MATCH &&
           this._isInherited(this.property)))) {
       const selectorInfo = new CssSelectorInfo(selector, this.property, value,
-          status);
+          status, distance);
       this._matchedSelectors.push(selectorInfo);
     }
   },
 
   /**
    * Refilter the matched selectors array when the CssLogic.sourceFilter
    * changes. This allows for quick filter changes.
    * @private
@@ -1306,20 +1353,21 @@ CssPropertyInfo.prototype = {
  *        present information.
  * @param {string} property The property for which information should
  *        be retrieved.
  * @param {string} value The property value from the CssRule that owns
  *        the selector.
  * @param {STATUS} status The selector match status.
  * @constructor
  */
-function CssSelectorInfo(selector, property, value, status) {
+function CssSelectorInfo(selector, property, value, status, distance) {
   this.selector = selector;
   this.property = property;
   this.status = status;
+  this.distance = distance;
   this.value = value;
   const priority = this.selector.cssRule.getPropertyPriority(this.property);
   this.important = (priority === "important");
 }
 
 CssSelectorInfo.prototype = {
   /**
    * Retrieve the CssSelector source, which is the source of the CssSheet owning
@@ -1353,18 +1401,18 @@ CssSelectorInfo.prototype = {
   },
 
   /**
    * Check if the CssSelector comes from element.style or not.
    *
    * @return {boolean} true if the CssSelector comes from element.style, or
    * false otherwise.
    */
-  get elementStyle() {
-    return this.selector.elementStyle;
+  get inlineStyle() {
+    return this.selector.inlineStyle;
   },
 
   /**
    * Retrieve specificity information for the current selector.
    *
    * @return {object} an object holding specificity information for the current
    * selector.
    */
@@ -1413,83 +1461,161 @@ CssSelectorInfo.prototype = {
   },
 
   /**
    * Check if the selector comes from a browser-provided stylesheet.
    *
    * @return {boolean} true if the selector comes from a browser-provided
    * stylesheet, or false otherwise.
    */
-  get contentRule() {
-    return this.selector.contentRule;
+  get agentRule() {
+    return this.selector.agentRule;
+  },
+
+  /**
+   * Check if the selector comes from a webpage-provided stylesheet.
+   *
+   * @return {boolean} true if the selector comes from a webpage-provided
+   * stylesheet, or false otherwise.
+   */
+  get authorRule() {
+    return this.selector.authorRule;
+  },
+
+  /**
+   * Check if the selector comes from a user stylesheet (userChrome.css or
+   * userContent.css).
+   *
+   * @return {boolean} true if the selector comes from a webpage-provided
+   * stylesheet, or false otherwise.
+   */
+  get userRule() {
+    return this.selector.userRule;
   },
 
   /**
    * Compare the current CssSelectorInfo instance to another instance, based on
-   * specificity information.
+   * the CSS cascade (see https://www.w3.org/TR/css-cascade-4/#cascading):
+   *
+   * The cascade sorts declarations according to the following criteria, in
+   * descending order of priority:
+   *
+   * - Rules targetting a node directly must always win over rules targetting an
+   *   ancestor.
+   *
+   * - Origin and Importance
+   *   The origin of a declaration is based on where it comes from and its
+   *   importance is whether or not it is declared !important (see below). For
+   *   our purposes here we can safely ignore Transition declarations and
+   *   Animation declarations.
+   *   The precedence of the various origins is, in descending order:
+   *   - Transition declarations (ignored)
+   *   - Important user agent declarations (User-Agent & !important)
+   *   - Important user declarations (User & !important)
+   *   - Important author declarations (Author & !important)
+   *   - Animation declarations (ignored)
+   *   - Normal author declarations (Author, normal weight)
+   *   - Normal user declarations (User, normal weight)
+   *   - Normal user agent declarations (User-Agent, normal weight)
    *
-   * @param {CssSelectorInfo} that The instance to compare ourselves against.
-   * @return number -1, 0, 1 depending on how that compares with this.
+   * - Specificity (see https://www.w3.org/TR/selectors/#specificity)
+   *   - A selector’s specificity is calculated for a given element as follows:
+   *     - count the number of ID selectors in the selector (= A)
+   *     - count the number of class selectors, attributes selectors, and
+   *       pseudo-classes in the selector (= B)
+   *     - count the number of type selectors and pseudo-elements in the
+   *       selector (= C)
+   *     - ignore the universal selector
+   *   - So "UL OL LI.red" has a specificity of a=0 b=1 c=3.
+   *
+   * - Order of Appearance
+   *   - The last declaration in document order wins. For this purpose:
+   *     - Declarations from imported style sheets are ordered as if their style
+   *       sheets were substituted in place of the @import rule.
+   *     - Declarations from style sheets independently linked by the
+   *       originating document are treated as if they were concatenated in
+   *       linking order, as determined by the host document language.
+   *     - Declarations from style attributes are ordered according to the
+   *       document order of the element the style attribute appears on, and are
+   *       all placed after any style sheets.
+   *   - We use three methods to calculate this:
+   *     - Sheet index
+   *     - Rule line
+   *     - Rule column
+   *
+   * @param  {CssSelectorInfo} that
+   *         The instance to compare ourselves against.
+   * @return {Number}
+   *         -1, 0, 1 depending on how that compares with this.
    */
   compareTo: function(that) {
-    if (!this.contentRule && that.contentRule) {
-      return 1;
-    }
-    if (this.contentRule && !that.contentRule) {
-      return -1;
-    }
+    let current = null;
 
-    if (this.elementStyle && !that.elementStyle) {
-      if (!this.important && that.important) {
-        return 1;
-      }
-      return -1;
-    }
-
-    if (!this.elementStyle && that.elementStyle) {
-      if (this.important && !that.important) {
-        return -1;
-      }
-      return 1;
+    // Rules targetting the node must always win over rules targetting a node's
+    // ancestor.
+    current = this.compare(that, "distance", COMPAREMODE.INTEGER);
+    if (current) {
+      return current;
     }
 
-    if (this.important && !that.important) {
-      return -1;
-    }
-    if (that.important && !this.important) {
-      return 1;
+    if (this.important) {
+      // User-Agent & !important
+      // User & !important
+      // Author & !important
+      for (const propName of ["agentRule", "userRule", "authorRule"]) {
+        current = this.compare(that, propName, COMPAREMODE.BOOLEAN);
+        if (current) {
+          return current;
+        }
+      }
     }
 
-    if (this.specificity > that.specificity) {
-      return -1;
-    }
-    if (that.specificity > this.specificity) {
-      return 1;
+    // Author, normal weight
+    // User, normal weight
+    // User-Agent, normal weight
+    for (const propName of ["authorRule", "userRule", "agentRule"]) {
+      current = this.compare(that, propName, COMPAREMODE.BOOLEAN);
+      if (current) {
+        return current;
+      }
     }
 
-    if (this.sheetIndex > that.sheetIndex) {
-      return -1;
-    }
-    if (that.sheetIndex > this.sheetIndex) {
-      return 1;
+    // Specificity
+    // Sheet index
+    // Rule line
+    // Rule column
+    for (const propName of ["specificity", "sheetIndex", "ruleLine", "ruleColumn"]) {
+      current = this.compare(that, propName, COMPAREMODE.INTEGER);
+      if (current) {
+        return current;
+      }
     }
 
-    if (this.ruleLine > that.ruleLine) {
-      return -1;
-    }
-    if (that.ruleLine > this.ruleLine) {
-      return 1;
-    }
+    // A rule has been compared against itself so return 0.
+    return 0;
+  },
 
-    if (this.ruleColumn > that.ruleColumn) {
-      return -1;
+  compare: function(that, propertyName, type) {
+    switch (type) {
+      case COMPAREMODE.BOOLEAN:
+        if (this[propertyName] && !that[propertyName]) {
+          return -1;
+        }
+        if (!this[propertyName] && that[propertyName]) {
+          return 1;
+        }
+        break;
+      case COMPAREMODE.INTEGER:
+        if (this[propertyName] > that[propertyName]) {
+          return -1;
+        }
+        if (this[propertyName] < that[propertyName]) {
+          return 1;
+        }
+        break;
     }
-    if (that.ruleColumn > this.ruleColumn) {
-      return 1;
-    }
-
     return 0;
   },
 
   toString: function() {
     return this.selector + " -> " + this.value;
   },
 };
--- a/devtools/server/actors/styles.js
+++ b/devtools/server/actors/styles.js
@@ -478,17 +478,17 @@ var PageStyleActor = protocol.ActorClass
       sheets: [...sheets],
     };
   },
 
   // Get a selector source for a CssSelectorInfo relative to a given
   // node.
   getSelectorSource: function(selectorInfo, relativeTo) {
     let result = selectorInfo.selector.text;
-    if (selectorInfo.elementStyle) {
+    if (selectorInfo.inlineStyle) {
       const source = selectorInfo.sourceElement;
       if (source === relativeTo) {
         result = "this";
       } else {
         result = CssLogic.getShortName(source);
       }
       result += ".style";
     }
@@ -637,17 +637,17 @@ var PageStyleActor = protocol.ActorClass
 
     const rules = [];
 
     // getCSSStyleRules returns ordered from least-specific to
     // most-specific.
     for (let i = domRules.length - 1; i >= 0; i--) {
       const domRule = domRules[i];
 
-      const isSystem = !SharedCssLogic.isContentStylesheet(domRule.parentStyleSheet);
+      const isSystem = !SharedCssLogic.isAuthorStylesheet(domRule.parentStyleSheet);
 
       if (isSystem && options.filter != SharedCssLogic.FILTER.UA) {
         continue;
       }
 
       if (inherited) {
         // Don't include inherited rules if none of its properties
         // are inheritable.
--- a/devtools/server/actors/stylesheets.js
+++ b/devtools/server/actors/stylesheets.js
@@ -393,17 +393,17 @@ var StyleSheetActor = protocol.ActorClas
     }
 
     const form = {
       actor: this.actorID,  // actorID is set when this actor is added to a pool
       href: this.href,
       nodeHref: docHref,
       disabled: this.rawSheet.disabled,
       title: this.rawSheet.title,
-      system: !CssLogic.isContentStylesheet(this.rawSheet),
+      system: !CssLogic.isAuthorStylesheet(this.rawSheet),
       styleSheetIndex: this.styleSheetIndex,
       sourceMapURL: this.rawSheet.sourceMapURL,
     };
 
     try {
       form.ruleCount = this.rawSheet.cssRules.length;
     } catch (e) {
       // stylesheet had an @import rule that wasn't loaded yet
--- a/devtools/shared/inspector/css-logic.js
+++ b/devtools/shared/inspector/css-logic.js
@@ -107,24 +107,46 @@ exports.CSSRuleTypeName = {
  *
  * @param {String} name
  *        The key to lookup.
  * @returns {String} A localized version of the given key.
  */
 exports.l10n = name => styleInspectorL10N.getStr(name);
 
 /**
- * Is the given property sheet a content stylesheet?
+ * Is the given property sheet an author stylesheet?
+ *
+ * @param {CSSStyleSheet} sheet a stylesheet
+ * @return {boolean} true if the given stylesheet is an author stylesheet,
+ * false otherwise.
+ */
+exports.isAuthorStylesheet = function(sheet) {
+  return sheet.parsingMode === "author";
+};
+
+/**
+ * Is the given property sheet a user stylesheet?
  *
  * @param {CSSStyleSheet} sheet a stylesheet
- * @return {boolean} true if the given stylesheet is a content stylesheet,
+ * @return {boolean} true if the given stylesheet is a user stylesheet,
  * false otherwise.
  */
-exports.isContentStylesheet = function(sheet) {
-  return sheet.parsingMode !== "agent";
+exports.isUserStylesheet = function(sheet) {
+  return sheet.parsingMode === "user";
+};
+
+/**
+ * Is the given property sheet a agent stylesheet?
+ *
+ * @param {CSSStyleSheet} sheet a stylesheet
+ * @return {boolean} true if the given stylesheet is a agent stylesheet,
+ * false otherwise.
+ */
+exports.isAgentStylesheet = function(sheet) {
+  return sheet.parsingMode === "agent";
 };
 
 /**
  * Return a shortened version of a style sheet's source.
  *
  * @param {CSSStyleSheet} sheet the DOM object for the style sheet.
  */
 exports.shortSource = function(sheet) {
--- a/docshell/test/chrome/docshell_helpers.js
+++ b/docshell/test/chrome/docshell_helpers.js
@@ -107,35 +107,35 @@ function doPageNavigation(params) {
     eventsToListenFor.length == 0 ? undefined : params.unexpectedEvents;
   let preventBFCache = (typeof[params.preventBFCache] == "undefined") ?
     false : params.preventBFCache;
   let waitOnly = (typeof(params.waitForEventsOnly) == "boolean"
     && params.waitForEventsOnly);
 
   // Do some sanity checking on arguments.
   if (back && forward)
-    throw "Can't specify both back and forward";
+    throw new Error("Can't specify both back and forward");
   if (back && uri)
-    throw "Can't specify both back and a uri";
+    throw new Error("Can't specify both back and a uri");
   if (forward && uri)
-    throw "Can't specify both forward and a uri";
+    throw new Error("Can't specify both forward and a uri");
   if (reload && (forward || back || uri))
-    throw "Can't specify reload and another navigation type";
+    throw new Error("Can't specify reload and another navigation type");
   if (!back && !forward && !uri && !reload && !waitOnly)
-    throw "Must specify back or foward or reload or uri";
+    throw new Error("Must specify back or foward or reload or uri");
   if (params.onNavComplete && eventsToListenFor.length == 0)
-    throw "Can't use onNavComplete when eventsToListenFor == []";
+    throw new Error("Can't use onNavComplete when eventsToListenFor == []");
   if (params.preventBFCache && eventsToListenFor.length == 0)
-    throw "Can't use preventBFCache when eventsToListenFor == []";
+    throw new Error("Can't use preventBFCache when eventsToListenFor == []");
   if (params.preventBFCache && waitOnly)
-    throw "Can't prevent bfcaching when only waiting for events";
+    throw new Error("Can't prevent bfcaching when only waiting for events");
   if (waitOnly && typeof(params.onNavComplete) == "undefined")
-    throw "Must specify onNavComplete when specifying waitForEventsOnly";
+    throw new Error("Must specify onNavComplete when specifying waitForEventsOnly");
   if (waitOnly && (back || forward || reload || uri))
-    throw "Can't specify a navigation type when using waitForEventsOnly";
+    throw new Error("Can't specify a navigation type when using waitForEventsOnly");
   for (let anEventType of eventsToListenFor) {
     let eventFound = false;
     if ( (anEventType == "pageshow") && (!gExpectedEvents) )
       eventFound = true;
     if (gExpectedEvents) {
       for (let anExpectedEvent of gExpectedEvents) {
         if (anExpectedEvent.type == anEventType)
           eventFound = true;
@@ -143,18 +143,18 @@ function doPageNavigation(params) {
     }
     if (gUnexpectedEvents) {
       for (let anExpectedEventType of gUnexpectedEvents) {
         if (anExpectedEventType == anEventType)
           eventFound = true;
       }
     }
     if (!eventFound)
-      throw "Event type " + anEventType + " is specified in " +
-        "eventsToListenFor, but not in expectedEvents";
+      throw new Error(`Event type ${anEventType} is specified in ` +
+                      "eventsToListenFor, but not in expectedEvents");
   }
 
   // If the test explicitly sets .eventsToListenFor to [], don't wait for any
   // events.
   gFinalEvent = eventsToListenFor.length == 0;
 
   // Add an event listener for each type of event in the .eventsToListenFor
   // property of the input parameters.
@@ -175,17 +175,17 @@ function doPageNavigation(params) {
     gNavType = NAV_URI;
     BrowserTestUtils.loadURI(TestWindow.getBrowser(), uri);
   } else if (reload) {
     gNavType = NAV_RELOAD;
     TestWindow.getBrowser().reload();
   } else if (waitOnly) {
     gNavType = NAV_NONE;
   } else {
-    throw "No valid navigation type passed to doPageNavigation!";
+    throw new Error("No valid navigation type passed to doPageNavigation!");
   }
 
   // If we're listening for events and there is an .onNavComplete callback,
   // wait for all events to occur, and then call doPageNavigation_complete().
   if (eventsToListenFor.length > 0 && params.onNavComplete) {
     waitForTrue(
       function() { return gFinalEvent; },
       function() {
--- a/docshell/test/navigation/NavigationUtils.js
+++ b/docshell/test/navigation/NavigationUtils.js
@@ -158,17 +158,17 @@ function xpcWaitForFinishedFrames(callba
 
     if (finishedFrameCount == numFrames) {
       clearInterval(frameWaitInterval);
       setTimeout(callback, 0);
       return;
     }
 
     if (finishedFrameCount > numFrames)
-      throw "Too many frames loaded.";
+      throw new Error("Too many frames loaded.");
   }
 
   var finishedWindows = [];
 
   function contains(obj, arr) {
     for (var i = 0; i < arr.length; i++) {
       if (obj === arr[i])
         return true;
--- a/dom/asmjscache/test/file_slow.js
+++ b/dom/asmjscache/test/file_slow.js
@@ -21,24 +21,24 @@ if (this.jsFuns)
     ok(jsFuns.isAsmJSModule(f2), "f2 is an asm.js module");
 var i32 = new Int32Array(16384); // Smallest allowed buffer size is 64KBy
 for (let i = 0; i < i32.length; i++)
     i32[i] = i;
 var f2Main = f2(this, null, i32.buffer);
 if (this.jsFuns)
     ok(jsFuns.isAsmJSFunction(f2Main), "f2.main is an asm.js function");
 if (f2Main(4) !== 6)
-    throw "f2Main(4)";
+    throw new Error("f2Main(4)");
 if (f2Main(100) !== 4950)
-    throw "f2.main(100)";
+    throw new Error("f2.main(100)");
 var sum = (((i32.length - 1) * i32.length) / 2);
 if (f2Main(i32.length) !== sum)
-    throw "f2.main(" + i32.length + ")";
+    throw new Error(`f2.main(${i32.length})`);
 if (f2Main(i32.length + 100) !== sum)
-    throw "f2.main(" + i32.length + ")";
+    throw new Error(`f2.main(${i32.length})`);
 
 /* eslint-disable no-unused-vars,no-shadow */
 function f3(stdlib, foreign, buffer) {
     "use asm";
     var doneFunc = foreign.done;
     var i32 = new stdlib.Int32Array(buffer);
     function main() {
         var i = 0;
@@ -54,25 +54,25 @@ function f3(stdlib, foreign, buffer) {
     return main;
 }
 /* eslint-enable no-unused-vars,no-shadow */
 
 var begin;
 var lastSum;
 function done(sumInner) {
     if (sumInner !== ((lastSum + 499500)|0))
-        throw "bad sum: " + sumInner + ", " + lastSum + ", " + ((lastSum + 499500)|0);
+        throw new Error(`bad sum: ${sumInner}, ${lastSum}, ${((lastSum + 499500)|0)}`);
     lastSum = sumInner;
     return (Date.now() - begin) > 3000;
 }
 var f3Main = f3(this, {done}, i32.buffer);
 if (this.jsFuns)
     ok(jsFuns.isAsmJSFunction(f3Main), "f3.main is an asm.js function");
 
 begin = Date.now();
 lastSum = 0;
 if (f3Main() !== lastSum)
-    throw "f3.main()";
+    throw new Error("f3.main()");
 
 if (!this.jsFuns)
     postMessage("ok");
 else
     complete();
--- a/dom/asmjscache/test/test_cachingBasic.html
+++ b/dom/asmjscache/test/test_cachingBasic.html
@@ -43,17 +43,17 @@ https://bugzilla.mozilla.org/show_bug.cg
         state++;
         evalAsync(code);
         break;
       case 1:
         ok(jsFuns.isAsmJSModule(module), "module");
         SimpleTest.finish();
         break;
       default:
-        throw "huh?";
+        throw new Error("huh?");
     }
   }
 
   function runTest() {
       // generate a big ol asm.js module and compile it async so that we can hit
       // the asm.js cache.
       SimpleTest.waitForExplicitFinish();
       evalAsync(code);
--- a/dom/asmjscache/test/test_cachingMulti.html
+++ b/dom/asmjscache/test/test_cachingMulti.html
@@ -48,17 +48,17 @@ https://bugzilla.mozilla.org/show_bug.cg
     document.body.appendChild(script);
   }
 
   var finishedCount = 0;
   function finishedEvalAsync() {
     finishedCount++;
 
     if (finishedCount < 1 || finishedCount > 2 * N) {
-      throw "Huh?!";
+      throw new Error("Huh?!");
     } else if (finishedCount == N) {
       for (let i = 0; i < N; i++)
         evalAsync(codes[i]);
     } else if (finishedCount == 2 * N) {
       SimpleTest.finish();
     }
   }
 
--- a/dom/asmjscache/test/test_workers.html
+++ b/dom/asmjscache/test/test_workers.html
@@ -51,17 +51,17 @@ https://bugzilla.mozilla.org/show_bug.cg
               ok(e.data === "ok", "Received second message");
               received = 2;
 
               var script = document.createElement("script");
               script.src = URL.createObjectURL(mainBlob);
               document.body.appendChild(script);
               break;
             default:
-              throw "Huh?";
+              throw new Error("Huh?");
           }
       };
 
       SimpleTest.waitForExplicitFinish();
   }
 
   if (!jsFuns.isAsmJSCompilationAvailable()) {
       ok(true, "isAsmJSCompilationAvailable is false, skipping this test!");
--- a/dom/cache/test/mochitest/test_cache_tons_of_fd.html
+++ b/dom/cache/test/mochitest/test_cache_tons_of_fd.html
@@ -49,40 +49,40 @@ async function testCreateTonsOfFD() {
   for (let i = 0; i < number_of_fd; ++i) {
     let promise = cache.match(request);
     promise_array.push(promise);
   }
   let cached_response_array = [];
   try {
     cached_response_array = await Promise.all(promise_array);
   } catch (e) {
-    throw ("Fail to open tons of files with error: " + e);
+    throw new Error("Fail to open tons of files with error: " + e);
   }
 
   if (cached_response_array.length != number_of_fd) {
-    throw ("Fail to cache.match the cached responses");
+    throw new Error("Fail to cache.match the cached responses");
   }
 
   info("Stage C: Consume the cached body");
   for (let i = 0; i < number_of_fd; ++i) {
     if (!cached_response_array[i]) {
       // Reduce the checking message.
-      throw ("The cached response doesn't exist");
+      throw new Error("The cached response doesn't exist");
     }
 
     let bodyText = "";
     try {
       bodyText = await cached_response_array[i].text();
     } catch (e) {
-      throw ("Fail to consume the cached response's body with error: " + e);
+      throw new Error("Fail to consume the cached response's body with error: " + e);
     }
 
     if (bodyText != body) {
       // Reduce the checking message.
-      throw ("The cached body doeen't be the same as original one");
+      throw new Error("The cached body doeen't be the same as original one");
     }
   }
 
   ok(true, "Doesn't crash or timeout");
   return Promise.resolve();
 }
 
 SimpleTest.waitForExplicitFinish();
--- a/dom/crypto/test/test_WebCrypto.html
+++ b/dom/crypto/test/test_WebCrypto.html
@@ -44,17 +44,17 @@ TestArray.addTest(
 
 // -----------------------------------------------------------------------------
 TestArray.addTest(
   "Clean failure on a mal-formed algorithm",
   function() {
     var that = this;
     var alg = {
       get name() {
-        throw "Oh no, no name!";
+        throw new Error("Oh no, no name!");
       },
     };
 
     crypto.subtle.importKey("raw", tv.raw, alg, true, ["encrypt"])
       .then(
         error(that),
         complete(that, function(x) { return true; })
       );
@@ -65,24 +65,24 @@ TestArray.addTest(
 TestArray.addTest(
   "Import / export round-trip with 'raw'",
   function() {
     var that = this;
     var alg = "AES-GCM";
 
     function doExport(x) {
       if (!hasKeyFields(x)) {
-        throw "Invalid key; missing field(s)";
+        throw new Error("Invalid key; missing field(s)");
       } else if ((x.algorithm.name != alg) ||
         (x.algorithm.length != 8 * tv.raw.length) ||
         (x.type != "secret") ||
         (!x.extractable) ||
         (x.usages.length != 1) ||
         (x.usages[0] != "encrypt")) {
-        throw "Invalid key: incorrect key data";
+        throw new Error("Invalid key: incorrect key data");
       }
       return crypto.subtle.exportKey("raw", x);
     }
 
     crypto.subtle.importKey("raw", tv.raw, alg, true, ["encrypt"])
       .then(doExport)
       .then(
         memcmp_complete(that, tv.raw),
@@ -131,26 +131,26 @@ TestArray.addTest(
 TestArray.addTest(
   "Import / export round-trip with 'pkcs8'",
   function() {
     var that = this;
     var alg = { name: "RSASSA-PKCS1-v1_5", hash: "SHA-1" };
 
     function doExport(x) {
       if (!hasKeyFields(x)) {
-        throw "Invalid key; missing field(s)";
+        throw new Error("Invalid key; missing field(s)");
       } else if ((x.algorithm.name != alg.name) ||
         (x.algorithm.hash.name != alg.hash) ||
         (x.algorithm.modulusLength != 512) ||
         (x.algorithm.publicExponent.byteLength != 3) ||
         (x.type != "private") ||
         (!x.extractable) ||
         (x.usages.length != 1) ||
         (x.usages[0] != "sign")) {
-        throw "Invalid key: incorrect key data";
+        throw new Error("Invalid key: incorrect key data");
       }
       return crypto.subtle.exportKey("pkcs8", x);
     }
 
     crypto.subtle.importKey("pkcs8", tv.pkcs8, alg, true, ["sign"])
       .then(doExport)
       .then(
         memcmp_complete(that, tv.pkcs8),
@@ -178,25 +178,25 @@ TestArray.addTest(
     var that = this;
     var alg = {
       name: "RSASSA-PKCS1-v1_5",
       hash: "SHA-256",
     };
 
     function doExport(x) {
       if (!hasKeyFields(x)) {
-        throw "Invalid key; missing field(s)";
+        throw new Error("Invalid key; missing field(s)");
       } else if ((x.algorithm.name != alg.name) ||
         (x.algorithm.modulusLength != 1024) ||
         (x.algorithm.publicExponent.byteLength != 3) ||
         (x.type != "public") ||
         (!x.extractable) ||
         (x.usages.length != 1) ||
         (x.usages[0] != "verify")) {
-        throw "Invalid key: incorrect key data";
+        throw new Error("Invalid key: incorrect key data");
       }
       return crypto.subtle.exportKey("spki", x);
     }
 
     crypto.subtle.importKey("spki", tv.spki, alg, true, ["verify"])
       .then(doExport, error(that))
       .then(
         memcmp_complete(that, tv.spki),
--- a/dom/crypto/test/test_WebCrypto_ECDH.html
+++ b/dom/crypto/test/test_WebCrypto_ECDH.html
@@ -67,17 +67,17 @@ TestArray.addTest(
     }
 
     crypto.subtle.generateKey(alg, false, ["deriveBits"])
       .then(setKeyPair, error(that))
       .then(doDerive(2), error(that))
       .then(function(x) {
         // Deriving less bytes works.
         if (x.byteLength != 2) {
-          throw "should have derived two bytes";
+          throw new Error("should have derived two bytes");
         }
       })
       // Deriving more than the curve yields doesn't.
       .then(doDerive(33), error(that))
       .then(error(that), complete(that));
   }
 );
 
@@ -252,17 +252,17 @@ TestArray.addTest(
         .then(setPub, error(that)),
     ]).then(doExportPub, error(that))
       .then(function(x) {
         var tp = tv.ecdh_p256.jwk_pub;
         if ((tp.kty != x.kty) &&
             (tp.crv != x.crv) &&
             (tp.x != x.x) &&
             (tp.y != x.y)) {
-          throw "exported public key doesn't match";
+          throw new Error("exported public key doesn't match");
         }
       }, error(that))
       .then(doExportPriv, error(that))
       .then(complete(that, function(x) {
         var tp = tv.ecdh_p256.jwk_priv;
         return (tp.kty == x.kty) &&
                (tp.crv == x.crv) &&
                (tp.d == x.d) &&
@@ -336,22 +336,22 @@ TestArray.addTest(
     var pubKey, privKey;
     function setPub(x) { pubKey = x; }
     function setPriv(x) { privKey = x; }
 
     function doDerive() {
       return crypto.subtle.deriveKey({ name: "ECDH", public: pubKey }, privKey, algDerived, false, ["sign", "verify"])
         .then(function(x) {
           if (!hasKeyFields(x)) {
-            throw "Invalid key; missing field(s)";
+            throw new Error("Invalid key; missing field(s)");
           }
 
           // 512 bit is the default for HMAC-SHA1.
           if (x.algorithm.length != 512) {
-            throw "Invalid key; incorrect length";
+            throw new Error("Invalid key; incorrect length");
           }
 
           return x;
         });
     }
 
     function doSignAndVerify(x) {
       var data = crypto.getRandomValues(new Uint8Array(1024));
@@ -390,17 +390,17 @@ TestArray.addTest(
 
     function nextKey() {
       var key = keys.shift();
       var imported = doImport(key);
       var derived = imported.then(doExport);
 
       return derived.then(function(x) {
         if (!util.memcmp(x, tv.ecdh_p256.spki)) {
-          throw "exported key is invalid";
+          throw new Error("exported key is invalid");
         }
 
         if (keys.length) {
           return nextKey();
         }
         return Promise.resolve();
       });
     }
--- a/dom/crypto/test/test_WebCrypto_PBKDF2.html
+++ b/dom/crypto/test/test_WebCrypto_PBKDF2.html
@@ -68,17 +68,17 @@ TestArray.addTest(
   "Import raw PBKDF2 key and derive bits using HMAC-SHA-1",
   function() {
     var that = this;
     var alg = "PBKDF2";
     var key = tv.pbkdf2_sha1.password;
 
     function doDerive(x) {
       if (!hasKeyFields(x)) {
-        throw "Invalid key; missing field(s)";
+        throw new Error("Invalid key; missing field(s)");
       }
 
       var algo = {
         name: "PBKDF2",
         hash: "SHA-1",
         salt: tv.pbkdf2_sha1.salt,
         iterations: tv.pbkdf2_sha1.iterations,
       };
@@ -96,17 +96,17 @@ TestArray.addTest(
 TestArray.addTest(
   "Import a PBKDF2 key in JWK format and derive bits using HMAC-SHA-1",
   function() {
     var that = this;
     var alg = "PBKDF2";
 
     function doDerive(x) {
       if (!hasKeyFields(x)) {
-        throw "Invalid key; missing field(s)";
+        throw new Error("Invalid key; missing field(s)");
       }
 
       var algo = {
         name: "PBKDF2",
         hash: "SHA-1",
         salt: tv.pbkdf2_sha1.salt,
         iterations: tv.pbkdf2_sha1.iterations,
       };
@@ -125,17 +125,17 @@ TestArray.addTest(
   "Import raw PBKDF2 key and derive a new key using HMAC-SHA-1",
   function() {
     var that = this;
     var alg = "PBKDF2";
     var key = tv.pbkdf2_sha1.password;
 
     function doDerive(x) {
       if (!hasKeyFields(x)) {
-        throw "Invalid key; missing field(s)";
+        throw new Error("Invalid key; missing field(s)");
       }
 
       var algo = {
         name: "PBKDF2",
         hash: "SHA-1",
         salt: tv.pbkdf2_sha1.salt,
         iterations: tv.pbkdf2_sha1.iterations,
       };
@@ -143,21 +143,21 @@ TestArray.addTest(
       var algDerived = {
         name: "HMAC",
         hash: {name: "SHA-1"},
       };
 
       return crypto.subtle.deriveKey(algo, x, algDerived, false, ["sign", "verify"])
         .then(function(y) {
           if (!hasKeyFields(y)) {
-            throw "Invalid key; missing field(s)";
+            throw new Error("Invalid key; missing field(s)");
           }
 
           if (y.algorithm.length != 512) {
-            throw "Invalid key; incorrect length";
+            throw new Error("Invalid key; incorrect length");
           }
 
           return y;
         });
     }
 
     function doSignAndVerify(x) {
       var data = new Uint8Array(1024);
@@ -209,17 +209,17 @@ TestArray.addTest(
   "Import raw PBKDF2 key and derive bits using HMAC-SHA-256",
   function() {
     var that = this;
     var alg = "PBKDF2";
     var key = tv.pbkdf2_sha256.password;
 
     function doDerive(x) {
       if (!hasKeyFields(x)) {
-        throw "Invalid key; missing field(s)";
+        throw new Error("Invalid key; missing field(s)");
       }
 
       var algo = {
         name: "PBKDF2",
         hash: "SHA-256",
         salt: tv.pbkdf2_sha256.salt,
         iterations: tv.pbkdf2_sha256.iterations,
       };
@@ -238,17 +238,17 @@ TestArray.addTest(
   "Import raw PBKDF2 zero-length key and derive bits using HMAC-SHA-256",
   function() {
     var that = this;
     var alg = "PBKDF2";
     var key = tv.pbkdf2_sha256_no_pwd.password;
 
     function doDerive(x) {
       if (!hasKeyFields(x)) {
-        throw "Invalid key; missing field(s)";
+        throw new Error("Invalid key; missing field(s)");
       }
 
       var algo = {
         name: "PBKDF2",
         hash: "SHA-256",
         salt: tv.pbkdf2_sha256_no_pwd.salt,
         iterations: tv.pbkdf2_sha256_no_pwd.iterations,
       };
@@ -267,17 +267,17 @@ TestArray.addTest(
   "Import raw PBKDF2 key and derive bits using HMAC-SHA-256 with zero-length salt",
   function() {
     var that = this;
     var importAlg = { name: "PBKDF2", hash: "SHA-256" };
     var key = tv.pbkdf2_sha256_no_salt.password;
 
     function doDerive(x) {
       if (!hasKeyFields(x)) {
-        throw "Invalid key; missing field(s)";
+        throw new Error("Invalid key; missing field(s)");
       }
 
       var deriveAlg = {
         name: "PBKDF2",
         hash: "SHA-256",
         salt: new Uint8Array(0),
         iterations: tv.pbkdf2_sha256_no_salt.iterations,
       };
--- a/dom/indexedDB/test/exceptions_in_events_iframe.html
+++ b/dom/indexedDB/test/exceptions_in_events_iframe.html
@@ -57,17 +57,17 @@
       // abort the versionchange transaction and fire an error at the request.
       let request = indexedDB.open(window.location.pathname, 1);
       request.onerror = errorHandler;
       request.onsuccess = unexpectedSuccessHandler;
       request.onupgradeneeded = function() {
         let transaction = request.transaction;
         transaction.oncomplete = unexpectedSuccessHandler;
         transaction.onabort = grabEventAndContinueHandler;
-        throw "STOP";
+        throw new Error("STOP");
       };
 
       let event = yield undefined;
       is(event.type, "abort",
          "Throwing during an upgradeneeded event should abort the transaction.");
       is(event.target.error.name, "AbortError", "Got AbortError object");
 
       request.onerror = grabEventAndContinueHandler;
@@ -101,17 +101,17 @@
 
       let objectStore = db.createObjectStore("foo");
 
       is(db.objectStoreNames.length, 1, "Correct objectStoreNames length");
       ok(db.objectStoreNames.contains("foo"), "Has correct objectStore");
 
       request = objectStore.add({}, 1);
       request.onsuccess = function(event) {
-        throw "foo";
+        throw new Error("foo");
       };
 
       event = yield undefined;
 
       is(event.type, "abort", "Got transaction abort event");
       is(event.target.error.name, "AbortError", "Got AbortError object");
       openrequest.onerror = grabEventAndContinueHandler;
 
@@ -150,17 +150,17 @@
       ok(db.objectStoreNames.contains("foo"), "Has correct objectStore");
 
       request = objectStore.add({}, 1);
       request.onerror = errorHandler;
       request = objectStore.add({}, 1);
       request.onsuccess = unexpectedSuccessHandler;
       request.onerror = function(event) {
         event.preventDefault();
-        throw "STOP";
+        throw new Error("STOP");
       };
 
       event = yield undefined;
 
       is(event.type, "abort", "Got transaction abort event");
       is(event.target.error.name, "AbortError", "Got AbortError object");
       openrequest.onerror = grabEventAndContinueHandler;
 
--- a/dom/indexedDB/test/test_filehandle_workers.html
+++ b/dom/indexedDB/test/test_filehandle_workers.html
@@ -40,17 +40,17 @@
     request.onsuccess = grabEventAndContinueHandler;
     event = yield undefined;
 
     let mutableFile = event.target.result;
 
     function dummyWorkerScript() {
       /* eslint-env worker */
       onmessage = function(event) {
-        throw ("Shouldn't be called!");
+        throw new Error("Shouldn't be called!");
       };
     }
 
     let url =
       URL.createObjectURL(new Blob(["(", dummyWorkerScript.toSource(), ")()"]));
 
     let worker1 = new Worker(url);
     try {
--- a/dom/indexedDB/test/unit/xpcshell-head-parent-process.js
+++ b/dom/indexedDB/test/unit/xpcshell-head-parent-process.js
@@ -183,32 +183,32 @@ function compareKeys(k1, k2) {
     return true;
   }
 
   return false;
 }
 
 function addPermission(permission, url)
 {
-  throw "addPermission";
+  throw new Error("addPermission");
 }
 
 function removePermission(permission, url)
 {
-  throw "removePermission";
+  throw new Error("removePermission");
 }
 
 function allowIndexedDB(url)
 {
-  throw "allowIndexedDB";
+  throw new Error("allowIndexedDB");
 }
 
 function disallowIndexedDB(url)
 {
-  throw "disallowIndexedDB";
+  throw new Error("disallowIndexedDB");
 }
 
 function enableExperimental()
 {
   SpecialPowers.setBoolPref("dom.indexedDB.experimental", true);
 }
 
 function resetExperimental()
--- a/dom/localstorage/test/unit/test_migration.js
+++ b/dom/localstorage/test/unit/test_migration.js
@@ -110,15 +110,15 @@ async function testSteps() {
         await requestFinished(request);
 
         clearedOrigins.push(7, 8, 9);
 
         break;
       }
 
       default: {
-        throw ("Unknown type: " + type);
+        throw new Error("Unknown type: " + type);
       }
     }
 
     verifyData(clearedOrigins);
   }
 }
--- a/dom/media/GraphRunner.cpp
+++ b/dom/media/GraphRunner.cpp
@@ -21,91 +21,93 @@ static void Start(void* aArg) {
   th->Run();
 }
 
 GraphRunner::GraphRunner(MediaStreamGraphImpl* aGraph)
     : mMonitor("GraphRunner::mMonitor"),
       mGraph(aGraph),
       mStateEnd(0),
       mStillProcessing(true),
-      mShutdown(false),
-      mStarted(false),
+      mThreadState(ThreadState::Wait),
       // Note that mThread needs to be initialized last, as it may pre-empt the
       // thread running this ctor and enter Run() with uninitialized members.
       mThread(PR_CreateThread(PR_SYSTEM_THREAD, &Start, this,
                               PR_PRIORITY_URGENT, PR_GLOBAL_THREAD,
                               PR_JOINABLE_THREAD, 0)) {
   MOZ_COUNT_CTOR(GraphRunner);
 }
 
 GraphRunner::~GraphRunner() {
   MOZ_COUNT_DTOR(GraphRunner);
-#ifdef DEBUG
-  {
-    MonitorAutoLock lock(mMonitor);
-    MOZ_ASSERT(mShutdown);
-  }
-#endif
-  PR_JoinThread(mThread);
+  MOZ_ASSERT(mThreadState == ThreadState::Shutdown);
 }
 
 void GraphRunner::Shutdown() {
-  MonitorAutoLock lock(mMonitor);
-  mShutdown = true;
-  mMonitor.Notify();
+  {
+    MonitorAutoLock lock(mMonitor);
+    MOZ_ASSERT(mThreadState == ThreadState::Wait);
+    mThreadState = ThreadState::Shutdown;
+    mMonitor.Notify();
+  }
+  // We need to wait for runner thread shutdown here for the sake of the
+  // xpcomWillShutdown case, so that the main thread is not shut down before
+  // cleanup messages are sent for objects destroyed in
+  // CycleCollectedJSContext shutdown.
+  PR_JoinThread(mThread);
+  mThread = nullptr;
 }
 
 bool GraphRunner::OneIteration(GraphTime aStateEnd) {
   TRACE_AUDIO_CALLBACK();
 
   MonitorAutoLock lock(mMonitor);
-  MOZ_ASSERT(!mShutdown);
+  MOZ_ASSERT(mThreadState == ThreadState::Wait);
   mStateEnd = aStateEnd;
 
-  if (!mStarted) {
-    mMonitor.Wait();
-    MOZ_ASSERT(mStarted);
-  }
-
 #ifdef DEBUG
   if (auto audioDriver = mGraph->CurrentDriver()->AsAudioCallbackDriver()) {
     mAudioDriverThreadId = audioDriver->ThreadId();
   } else if (auto clockDriver =
                  mGraph->CurrentDriver()->AsSystemClockDriver()) {
     mClockDriverThread = clockDriver->Thread();
   } else {
     MOZ_CRASH("Unknown GraphDriver");
   }
 #endif
-
-  mMonitor.Notify();  // Signal that mStateEnd was updated
-  mMonitor.Wait();    // Wait for mStillProcessing to update
+  // Signal that mStateEnd was updated
+  mThreadState = ThreadState::Run;
+  mMonitor.Notify();
+  // Wait for mStillProcessing to update
+  do {
+    mMonitor.Wait();
+  } while (mThreadState == ThreadState::Run);
 
 #ifdef DEBUG
   mAudioDriverThreadId = std::thread::id();
   mClockDriverThread = nullptr;
 #endif
 
   return mStillProcessing;
 }
 
 void GraphRunner::Run() {
   PR_SetCurrentThreadName("GraphRunner");
   MonitorAutoLock lock(mMonitor);
-  mStarted = true;
-  mMonitor.Notify();  // Signal that mStarted was set, in case the audio
-                      // callback is already waiting for us
   while (true) {
-    mMonitor.Wait();  // Wait for mStateEnd or mShutdown to update
-    if (mShutdown) {
+    while (mThreadState == ThreadState::Wait) {
+      mMonitor.Wait();  // Wait for mStateEnd to update or for shutdown
+    }
+    if (mThreadState == ThreadState::Shutdown) {
       break;
     }
     TRACE();
     mStillProcessing = mGraph->OneIterationImpl(mStateEnd);
-    mMonitor.Notify();  // Signal that mStillProcessing was updated
+    // Signal that mStillProcessing was updated
+    mThreadState = ThreadState::Wait;
+    mMonitor.Notify();
   }
 
   dom::WorkletThread::DeleteCycleCollectedJSContext();
 }
 
 bool GraphRunner::OnThread() { return PR_GetCurrentThread() == mThread; }
 
 #ifdef DEBUG
--- a/dom/media/GraphRunner.h
+++ b/dom/media/GraphRunner.h
@@ -60,23 +60,32 @@ class GraphRunner {
   // The MediaStreamGraph we're running. Weakptr beecause this graph owns us and
   // guarantees that our lifetime will not go beyond that of itself.
   MediaStreamGraphImpl* const mGraph;
   // GraphTime being handed over to the graph through OneIteration. Protected by
   // mMonitor.
   GraphTime mStateEnd;
   // Reply from mGraph's OneIteration. Protected by mMonitor.
   bool mStillProcessing;
-  // True after Shutdown(). Protected by mMonitor.
-  bool mShutdown;
-  // True after mThread has started running and has entered its main loop.
-  // Protected by mMonitor.
-  bool mStarted;
-  // The thread running mGraph.
-  PRThread* const mThread;
+
+  enum class ThreadState {
+    Wait,      // Waiting for a message.  This is the initial state.
+               // A transition from Run back to Wait occurs on the runner
+               // thread after it processes as far as mStateEnd and sets
+               // mStillProcessing.
+    Run,       // Set on driver thread after each mStateEnd update.
+    Shutdown,  // Set when Shutdown() is called on main thread.
+  };
+  // Protected by mMonitor until set to Shutdown, after which this is not
+  // modified.
+  ThreadState mThreadState;
+
+  // The thread running mGraph.  Set on construction, after other members are
+  // initialized.  Cleared at the end of Shutdown().
+  PRThread* mThread;
 
 #ifdef DEBUG
   // Set to mGraph's audio callback driver's thread id, if run by an
   // AudioCallbackDriver, while OneIteration() is running.
   std::thread::id mAudioDriverThreadId = std::thread::id();
   // Set to mGraph's system clock driver's thread, if run by a
   // SystemClockDriver, while OneIteration() is running.
   nsIThread* mClockDriverThread = nullptr;
--- a/dom/media/RTCStatsReport.jsm
+++ b/dom/media/RTCStatsReport.jsm
@@ -8,18 +8,20 @@ var EXPORTED_SYMBOLS = ["convertToRTCSta
 
 function convertToRTCStatsReport(dict) {
   function appendStats(stats, report) {
     stats.forEach(function(stat) {
         report[stat.id] = stat;
       });
   }
   let report = {};
-  appendStats(dict.inboundRTPStreamStats, report);
-  appendStats(dict.outboundRTPStreamStats, report);
+  appendStats(dict.inboundRtpStreamStats, report);
+  appendStats(dict.outboundRtpStreamStats, report);
+  appendStats(dict.remoteInboundRtpStreamStats, report);
+  appendStats(dict.remoteOutboundRtpStreamStats, report);
   appendStats(dict.rtpContributingSourceStats, report);
   appendStats(dict.mediaStreamTrackStats, report);
   appendStats(dict.mediaStreamStats, report);
   appendStats(dict.transportStats, report);
   appendStats(dict.iceComponentStats, report);
   appendStats(dict.iceCandidatePairStats, report);
   appendStats(dict.iceCandidateStats, report);
   appendStats(dict.codecStats, report);
--- a/dom/media/VideoFrameConverter.h
+++ b/dom/media/VideoFrameConverter.h
@@ -36,17 +36,17 @@ static mozilla::LazyLogModule gVideoFram
 
 class VideoConverterListener {
  public:
   NS_INLINE_DECL_THREADSAFE_REFCOUNTING(VideoConverterListener)
 
   virtual void OnVideoFrameConverted(const webrtc::VideoFrame& aVideoFrame) = 0;
 
  protected:
-  virtual ~VideoConverterListener() {}
+  virtual ~VideoConverterListener() = default;
 };
 
 // An async video frame format converter.
 //
 // Input is typically a MediaStreamTrackListener driven by MediaStreamGraph.
 //
 // Output is passed through to all added VideoConverterListeners on a TaskQueue
 // thread whenever a frame is converted.
@@ -64,17 +64,17 @@ class VideoFrameConverter {
         mBufferPool(false, CONVERTER_BUFFER_POOL_SIZE),
         mLastFrameQueuedForProcessing(TimeStamp::Now()),
         mEnabled(true) {
     MOZ_COUNT_CTOR(VideoFrameConverter);
   }
 
   void QueueVideoChunk(const VideoChunk& aChunk, bool aForceBlack) {
     gfx::IntSize size = aChunk.mFrame.GetIntrinsicSize();
-    if (size.width == 0 || size.width == 0) {
+    if (size.width == 0 || size.height == 0) {
       return;
     }
 
     TimeStamp t = aChunk.mTimeStamp;
     MOZ_ASSERT(!t.IsNull());
 
     if (!mLastFrameQueuedForPacing.IsNull() && t < mLastFrameQueuedForPacing) {
       // With a direct listener we can have buffered up future frames in
@@ -90,18 +90,18 @@ class VideoFrameConverter {
       mPacingTimer->Cancel();
     }
 
     mLastFrameQueuedForPacing = t;
 
     mPacingTimer->WaitUntil(t, __func__)
         ->Then(mTaskQueue, __func__,
                [self = RefPtr<VideoFrameConverter>(this), this,
-                image = RefPtr<layers::Image>(aChunk.mFrame.GetImage()),
-                t = std::move(t), size = std::move(size), aForceBlack] {
+                image = RefPtr<layers::Image>(aChunk.mFrame.GetImage()), t,
+                size, aForceBlack]() mutable {
                  QueueForProcessing(std::move(image), t, size, aForceBlack);
                },
                [] {});
   }
 
   void SetTrackEnabled(bool aEnabled) {
     nsresult rv = mTaskQueue->Dispatch(NS_NewRunnableFunction(
         __func__, [self = RefPtr<VideoFrameConverter>(this), this, aEnabled] {
@@ -224,26 +224,26 @@ class VideoFrameConverter {
                (mLastFrameQueuedForProcessing - aTime).ToSeconds()));
       return;
     }
 
     mLastImage = serial;
     mLastFrameQueuedForProcessing = aTime;
 
     nsresult rv = mTaskQueue->Dispatch(
-        NewRunnableMethod<StoreCopyPassByRRef<RefPtr<layers::Image>>, TimeStamp,
+        NewRunnableMethod<StoreCopyPassByLRef<RefPtr<layers::Image>>, TimeStamp,
                           gfx::IntSize, bool>(
             "VideoFrameConverter::ProcessVideoFrame", this,
             &VideoFrameConverter::ProcessVideoFrame, std::move(aImage), aTime,
             aSize, aForceBlack || !mEnabled));
     MOZ_DIAGNOSTIC_ASSERT(NS_SUCCEEDED(rv));
     Unused << rv;
   }
 
-  void ProcessVideoFrame(RefPtr<layers::Image> aImage, TimeStamp aTime,
+  void ProcessVideoFrame(const RefPtr<layers::Image>& aImage, TimeStamp aTime,
                          gfx::IntSize aSize, bool aForceBlack) {
     MOZ_ASSERT(mTaskQueue->IsCurrentThreadIn());
 
     if (aTime < mLastFrameQueuedForProcessing) {
       MOZ_LOG(gVideoFrameConverterLog, LogLevel::Debug,
               ("Dropping a frame that is %.3f seconds behind latest",
                (mLastFrameQueuedForProcessing - aTime).ToSeconds()));
       return;
--- a/dom/media/encoder/TrackEncoder.cpp
+++ b/dom/media/encoder/TrackEncoder.cpp
@@ -422,17 +422,38 @@ void VideoTrackEncoder::AppendVideoSegme
   if (mCanceled) {
     return;
   }
 
   if (mEndOfStream) {
     return;
   }
 
-  mIncomingBuffer.AppendFrom(&aSegment);
+  for (VideoSegment::ConstChunkIterator iter(aSegment); !iter.IsEnded();
+       iter.Next()) {
+    if (iter->IsNull()) {
+      // A null image was sent. This is a signal from the source that we should
+      // clear any images buffered in the future.
+      mIncomingBuffer.Clear();
+      continue;  // Don't append iter, as it is null.
+    }
+    if (VideoChunk* c = mIncomingBuffer.GetLastChunk()) {
+      if (iter->mTimeStamp < c->mTimeStamp) {
+        // Time went backwards. This can happen when a MediaDecoder seeks.
+        // We need to handle this by removing any frames buffered in the future
+        // and start over at iter->mTimeStamp.
+        mIncomingBuffer.Clear();
+      }
+    }
+    mIncomingBuffer.AppendFrame(do_AddRef(iter->mFrame.GetImage()),
+                                iter->mFrame.GetIntrinsicSize(),
+                                iter->mFrame.GetPrincipalHandle(),
+                                iter->mFrame.GetForceBlack(), iter->mTimeStamp);
+  }
+  aSegment.Clear();
 }
 
 void VideoTrackEncoder::TakeTrackData(VideoSegment& aSegment) {
   MOZ_ASSERT(!mWorkerThread || mWorkerThread->IsCurrentThreadIn());
 
   if (mCanceled) {
     return;
   }
@@ -570,18 +591,16 @@ void VideoTrackEncoder::AdvanceCurrentTi
   if (mCanceled) {
     return;
   }
 
   if (mEndOfStream) {
     return;
   }
 
-  MOZ_ASSERT(!mStartTime.IsNull());
-
   if (mSuspended) {
     TRACK_LOG(
         LogLevel::Verbose,
         ("[VideoTrackEncoder %p]: AdvanceCurrentTime() suspended at %.3fs",
          this, (mCurrentTime - mStartTime).ToSeconds()));
     mCurrentTime = aTime;
     mIncomingBuffer.ForgetUpToTime(mCurrentTime);
     return;
--- a/dom/media/gtest/TestVideoTrackEncoder.cpp
+++ b/dom/media/gtest/TestVideoTrackEncoder.cpp
@@ -57,19 +57,19 @@ class TestVP8TrackEncoder : public VP8Tr
   ::testing::AssertionResult TestInit(const InitParam& aParam) {
     nsresult result =
         Init(aParam.mWidth, aParam.mHeight, aParam.mWidth, aParam.mHeight);
 
     if (((NS_FAILED(result) && aParam.mShouldSucceed)) ||
         (NS_SUCCEEDED(result) && !aParam.mShouldSucceed)) {
       return ::testing::AssertionFailure()
              << " width = " << aParam.mWidth << " height = " << aParam.mHeight;
-    } else {
-      return ::testing::AssertionSuccess();
     }
+
+    return ::testing::AssertionSuccess();
   }
 };
 
 // Init test
 TEST(VP8VideoTrackEncoder, Initialization) {
   InitParam params[] = {
       // Failure cases.
       {false, 0, 0},  // Height/ width should be larger than 1.
@@ -78,61 +78,61 @@ TEST(VP8VideoTrackEncoder, Initializatio
 
       // Success cases
       {true, 640, 480},  // Standard VGA
       {true, 800, 480},  // Standard WVGA
       {true, 960, 540},  // Standard qHD
       {true, 1280, 720}  // Standard HD
   };
 
-  for (size_t i = 0; i < ArrayLength(params); i++) {
+  for (const InitParam& param : params) {
     TestVP8TrackEncoder encoder;
-    EXPECT_TRUE(encoder.TestInit(params[i]));
+    EXPECT_TRUE(encoder.TestInit(param));
   }
 }
 
 // Get MetaData test
 TEST(VP8VideoTrackEncoder, FetchMetaData) {
   InitParam params[] = {
       // Success cases
       {true, 640, 480},  // Standard VGA
       {true, 800, 480},  // Standard WVGA
       {true, 960, 540},  // Standard qHD
       {true, 1280, 720}  // Standard HD
   };
 
-  for (size_t i = 0; i < ArrayLength(params); i++) {
+  for (const InitParam& param : params) {
     TestVP8TrackEncoder encoder;
-    EXPECT_TRUE(encoder.TestInit(params[i]));
+    EXPECT_TRUE(encoder.TestInit(param));
 
     RefPtr<TrackMetadataBase> meta = encoder.GetMetadata();
     RefPtr<VP8Metadata> vp8Meta(static_cast<VP8Metadata*>(meta.get()));
 
     // METADATA should be depend on how to initiate encoder.
-    EXPECT_TRUE(vp8Meta->mWidth == params[i].mWidth);
-    EXPECT_TRUE(vp8Meta->mHeight == params[i].mHeight);
+    EXPECT_EQ(vp8Meta->mWidth, param.mWidth);
+    EXPECT_EQ(vp8Meta->mHeight, param.mHeight);
   }
 }
 
 // Encode test
 TEST(VP8VideoTrackEncoder, FrameEncode) {
   TestVP8TrackEncoder encoder;
+  TimeStamp now = TimeStamp::Now();
 
   // Create YUV images as source.
   nsTArray<RefPtr<Image>> images;
   YUVBufferGenerator generator;
   generator.Init(mozilla::gfx::IntSize(640, 480));
   images.AppendElement(generator.GenerateI420Image());
   images.AppendElement(generator.GenerateNV12Image());
   images.AppendElement(generator.GenerateNV21Image());
 
   // Put generated YUV frame into video segment.
   // Duration of each frame is 1 second.
   VideoSegment segment;
-  TimeStamp now = TimeStamp::Now();
   for (nsTArray<RefPtr<Image>>::size_type i = 0; i < images.Length(); i++) {
     RefPtr<Image> image = images[i];
     segment.AppendFrame(image.forget(), generator.GetSize(),
                         PRINCIPAL_HANDLE_NONE, false,
                         now + TimeDuration::FromSeconds(i));
   }
 
   encoder.SetStartOffset(now);
@@ -142,22 +142,22 @@ TEST(VP8VideoTrackEncoder, FrameEncode) 
   // Pull Encoded Data back from encoder.
   EncodedFrameContainer container;
   EXPECT_TRUE(NS_SUCCEEDED(encoder.GetEncodedTrack(container)));
 }
 
 // Test that encoding a single frame gives useful output.
 TEST(VP8VideoTrackEncoder, SingleFrameEncode) {
   TestVP8TrackEncoder encoder;
+  TimeStamp now = TimeStamp::Now();
+  YUVBufferGenerator generator;
+  generator.Init(mozilla::gfx::IntSize(640, 480));
 
   // Pass a half-second frame to the encoder.
-  YUVBufferGenerator generator;
-  generator.Init(mozilla::gfx::IntSize(640, 480));
   VideoSegment segment;
-  TimeStamp now = TimeStamp::Now();
   segment.AppendFrame(generator.GenerateI420Image(), generator.GetSize(),
                       PRINCIPAL_HANDLE_NONE, false, now);
 
   encoder.SetStartOffset(now);
   encoder.AppendVideoSegment(std::move(segment));
   encoder.AdvanceCurrentTime(now + TimeDuration::FromSeconds(0.5));
   encoder.NotifyEndOfStream();
 
@@ -176,22 +176,22 @@ TEST(VP8VideoTrackEncoder, SingleFrameEn
 
   const uint64_t halfSecond = PR_USEC_PER_SEC / 2;
   EXPECT_EQ(halfSecond, frames[0]->GetDuration());
 }
 
 // Test that encoding a couple of identical images gives useful output.
 TEST(VP8VideoTrackEncoder, SameFrameEncode) {
   TestVP8TrackEncoder encoder;
+  TimeStamp now = TimeStamp::Now();
+  YUVBufferGenerator generator;
+  generator.Init(mozilla::gfx::IntSize(640, 480));
 
   // Pass 15 100ms frames to the encoder.
-  YUVBufferGenerator generator;
-  generator.Init(mozilla::gfx::IntSize(640, 480));
   RefPtr<Image> image = generator.GenerateI420Image();
-  TimeStamp now = TimeStamp::Now();
   VideoSegment segment;
   for (uint32_t i = 0; i < 15; ++i) {
     segment.AppendFrame(do_AddRef(image), generator.GetSize(),
                         PRINCIPAL_HANDLE_NONE, false,
                         now + TimeDuration::FromSeconds(i * 0.1));
   }
 
   encoder.SetStartOffset(now);
@@ -214,20 +214,20 @@ TEST(VP8VideoTrackEncoder, SameFrameEnco
 }
 
 // Test encoding a track that has to skip frames.
 TEST(VP8VideoTrackEncoder, SkippedFrames) {
   TestVP8TrackEncoder encoder;
   YUVBufferGenerator generator;
   generator.Init(mozilla::gfx::IntSize(640, 480));
   TimeStamp now = TimeStamp::Now();
-  VideoSegment segment;
 
   // Pass 100 frames of the shortest possible duration where we don't get
   // rounding errors between input/output rate.
+  VideoSegment segment;
   for (uint32_t i = 0; i < 100; ++i) {
     segment.AppendFrame(generator.GenerateI420Image(), generator.GetSize(),
                         PRINCIPAL_HANDLE_NONE, false,
                         now + TimeDuration::FromMilliseconds(i));
   }
 
   encoder.SetStartOffset(now);
   encoder.AppendVideoSegment(std::move(segment));
@@ -249,20 +249,20 @@ TEST(VP8VideoTrackEncoder, SkippedFrames
 }
 
 // Test encoding a track with frames subject to rounding errors.
 TEST(VP8VideoTrackEncoder, RoundingErrorFramesEncode) {
   TestVP8TrackEncoder encoder;
   YUVBufferGenerator generator;
   generator.Init(mozilla::gfx::IntSize(640, 480));
   TimeStamp now = TimeStamp::Now();
-  VideoSegment segment;
 
   // Pass nine frames with timestamps not expressable in 90kHz sample rate,
   // then one frame to make the total duration one second.
+  VideoSegment segment;
   uint32_t usPerFrame = 99999;  // 99.999ms
   for (uint32_t i = 0; i < 9; ++i) {
     segment.AppendFrame(generator.GenerateI420Image(), generator.GetSize(),
                         PRINCIPAL_HANDLE_NONE, false,
                         now + TimeDuration::FromMicroseconds(i * usPerFrame));
   }
 
   // This last frame has timestamp start + 0.9s and duration 0.1s.
@@ -287,20 +287,20 @@ TEST(VP8VideoTrackEncoder, RoundingError
   }
   const uint64_t oneSecond = PR_USEC_PER_SEC;
   EXPECT_EQ(oneSecond, totalDuration);
 }
 
 // Test that we're encoding timestamps rather than durations.
 TEST(VP8VideoTrackEncoder, TimestampFrameEncode) {
   TestVP8TrackEncoder encoder;
-
   YUVBufferGenerator generator;
   generator.Init(mozilla::gfx::IntSize(640, 480));
   TimeStamp now = TimeStamp::Now();
+
   VideoSegment segment;
   segment.AppendFrame(generator.GenerateI420Image(), generator.GetSize(),
                       PRINCIPAL_HANDLE_NONE, false, now);
   segment.AppendFrame(generator.GenerateI420Image(), generator.GetSize(),
                       PRINCIPAL_HANDLE_NONE, false,
                       now + TimeDuration::FromSeconds(0.05));
   segment.AppendFrame(generator.GenerateI420Image(), generator.GetSize(),
                       PRINCIPAL_HANDLE_NONE, false,
@@ -329,17 +329,16 @@ TEST(VP8VideoTrackEncoder, TimestampFram
   }
   const uint64_t pointThree = (PR_USEC_PER_SEC / 10) * 3;
   EXPECT_EQ(pointThree, totalDuration);
 }
 
 // Test that we're compensating for drift when encoding.
 TEST(VP8VideoTrackEncoder, DriftingFrameEncode) {
   TestVP8TrackEncoder encoder;
-
   YUVBufferGenerator generator;
   generator.Init(mozilla::gfx::IntSize(640, 480));
   TimeStamp now = TimeStamp::Now();
 
   // Set up major drift -- audio that goes twice as fast as video.
   // This should make the given video durations double as they get encoded.
   EXPECT_CALL(*encoder.DriftCompensator(), GetVideoTime(_, _))
       .WillRepeatedly(Invoke(
@@ -378,45 +377,54 @@ TEST(VP8VideoTrackEncoder, DriftingFrame
   }
   const uint64_t pointSix = (PR_USEC_PER_SEC / 10) * 6;
   EXPECT_EQ(pointSix, totalDuration);
 }
 
 // Test that suspending an encoding works.
 TEST(VP8VideoTrackEncoder, Suspended) {
   TestVP8TrackEncoder encoder;
+  TimeStamp now = TimeStamp::Now();
+  YUVBufferGenerator generator;
+  generator.Init(mozilla::gfx::IntSize(640, 480));
 
   // Pass 3 frames with duration 0.1s. We suspend before and resume after the
   // second frame.
-  YUVBufferGenerator generator;
-  generator.Init(mozilla::gfx::IntSize(640, 480));
-  TimeStamp now = TimeStamp::Now();
-  VideoSegment segment;
-  segment.AppendFrame(generator.GenerateI420Image(), generator.GetSize(),
-                      PRINCIPAL_HANDLE_NONE, false, now);
 
-  encoder.SetStartOffset(now);
-  encoder.AppendVideoSegment(std::move(segment));
-  encoder.AdvanceCurrentTime(now + TimeDuration::FromSeconds(0.1));
+  {
+    VideoSegment segment;
+    segment.AppendFrame(generator.GenerateI420Image(), generator.GetSize(),
+                        PRINCIPAL_HANDLE_NONE, false, now);
+
+    encoder.SetStartOffset(now);
+    encoder.AppendVideoSegment(std::move(segment));
+    encoder.AdvanceCurrentTime(now + TimeDuration::FromSeconds(0.1));
+  }
 
   encoder.Suspend(now + TimeDuration::FromSeconds(0.1));
 
-  segment.AppendFrame(generator.GenerateI420Image(), generator.GetSize(),
-                      PRINCIPAL_HANDLE_NONE, false,
-                      now + TimeDuration::FromSeconds(0.1));
-  encoder.AppendVideoSegment(std::move(segment));
-  encoder.AdvanceCurrentTime(now + TimeDuration::FromSeconds(0.2));
+  {
+    VideoSegment segment;
+    segment.AppendFrame(generator.GenerateI420Image(), generator.GetSize(),
+                        PRINCIPAL_HANDLE_NONE, false,
+                        now + TimeDuration::FromSeconds(0.1));
+    encoder.AppendVideoSegment(std::move(segment));
+    encoder.AdvanceCurrentTime(now + TimeDuration::FromSeconds(0.2));
+  }
 
   encoder.Resume(now + TimeDuration::FromSeconds(0.2));
 
-  segment.AppendFrame(generator.GenerateI420Image(), generator.GetSize(),
-                      PRINCIPAL_HANDLE_NONE, false,
-                      now + TimeDuration::FromSeconds(0.2));
-  encoder.AppendVideoSegment(std::move(segment));
-  encoder.AdvanceCurrentTime(now + TimeDuration::FromSeconds(0.3));
+  {
+    VideoSegment segment;
+    segment.AppendFrame(generator.GenerateI420Image(), generator.GetSize(),
+                        PRINCIPAL_HANDLE_NONE, false,
+                        now + TimeDuration::FromSeconds(0.2));
+    encoder.AppendVideoSegment(std::move(segment));
+    encoder.AdvanceCurrentTime(now + TimeDuration::FromSeconds(0.3));
+  }
 
   encoder.NotifyEndOfStream();
 
   EncodedFrameContainer container;
   ASSERT_TRUE(NS_SUCCEEDED(encoder.GetEncodedTrack(container)));
 
   EXPECT_TRUE(encoder.IsEncodingComplete());
 
@@ -430,36 +438,42 @@ TEST(VP8VideoTrackEncoder, Suspended) {
   }
   const uint64_t pointTwo = (PR_USEC_PER_SEC / 10) * 2;
   EXPECT_EQ(pointTwo, totalDuration);
 }
 
 // Test that ending a track while the video track encoder is suspended works.
 TEST(VP8VideoTrackEncoder, SuspendedUntilEnd) {
   TestVP8TrackEncoder encoder;
-
-  // Pass 2 frames with duration 0.1s. We suspend before the second frame.
   YUVBufferGenerator generator;
   generator.Init(mozilla::gfx::IntSize(640, 480));
   TimeStamp now = TimeStamp::Now();
-  VideoSegment segment;
-  segment.AppendFrame(generator.GenerateI420Image(), generator.GetSize(),
-                      PRINCIPAL_HANDLE_NONE, false, now);
+
+  // Pass 2 frames with duration 0.1s. We suspend before the second frame.
 
-  encoder.SetStartOffset(now);
-  encoder.AppendVideoSegment(std::move(segment));
-  encoder.AdvanceCurrentTime(now + TimeDuration::FromSeconds(0.1));
+  {
+    VideoSegment segment;
+    segment.AppendFrame(generator.GenerateI420Image(), generator.GetSize(),
+                        PRINCIPAL_HANDLE_NONE, false, now);
+
+    encoder.SetStartOffset(now);
+    encoder.AppendVideoSegment(std::move(segment));
+    encoder.AdvanceCurrentTime(now + TimeDuration::FromSeconds(0.1));
+  }
 
   encoder.Suspend(now + TimeDuration::FromSeconds(0.1));
 
-  segment.AppendFrame(generator.GenerateI420Image(), generator.GetSize(),
-                      PRINCIPAL_HANDLE_NONE, false,
-                      now + TimeDuration::FromSeconds(0.1));
-  encoder.AppendVideoSegment(std::move(segment));
-  encoder.AdvanceCurrentTime(now + TimeDuration::FromSeconds(0.2));
+  {
+    VideoSegment segment;
+    segment.AppendFrame(generator.GenerateI420Image(), generator.GetSize(),
+                        PRINCIPAL_HANDLE_NONE, false,
+                        now + TimeDuration::FromSeconds(0.1));
+    encoder.AppendVideoSegment(std::move(segment));
+    encoder.AdvanceCurrentTime(now + TimeDuration::FromSeconds(0.2));
+  }
 
   encoder.NotifyEndOfStream();
 
   EncodedFrameContainer container;
   ASSERT_TRUE(NS_SUCCEEDED(encoder.GetEncodedTrack(container)));
 
   EXPECT_TRUE(encoder.IsEncodingComplete());
 
@@ -473,22 +487,21 @@ TEST(VP8VideoTrackEncoder, SuspendedUnti
   }
   const uint64_t pointOne = PR_USEC_PER_SEC / 10;
   EXPECT_EQ(pointOne, totalDuration);
 }
 
 // Test that ending a track that was always suspended works.
 TEST(VP8VideoTrackEncoder, AlwaysSuspended) {
   TestVP8TrackEncoder encoder;
+  YUVBufferGenerator generator;
+  generator.Init(mozilla::gfx::IntSize(640, 480));
+  TimeStamp now = TimeStamp::Now();
 
   // Suspend and then pass a frame with duration 2s.
-  YUVBufferGenerator generator;
-  generator.Init(mozilla::gfx::IntSize(640, 480));
-
-  TimeStamp now = TimeStamp::Now();
 
   encoder.Suspend(now);
 
   VideoSegment segment;
   segment.AppendFrame(generator.GenerateI420Image(), generator.GetSize(),
                       PRINCIPAL_HANDLE_NONE, false, now);
 
   encoder.SetStartOffset(now);
@@ -505,38 +518,43 @@ TEST(VP8VideoTrackEncoder, AlwaysSuspend
   // Verify that we have no encoded frames.
   const uint64_t none = 0;
   EXPECT_EQ(none, container.GetEncodedFrames().Length());
 }
 
 // Test that encoding a track that is suspended in the beginning works.
 TEST(VP8VideoTrackEncoder, SuspendedBeginning) {
   TestVP8TrackEncoder encoder;
+  YUVBufferGenerator generator;
+  generator.Init(mozilla::gfx::IntSize(640, 480));
   TimeStamp now = TimeStamp::Now();
 
   // Suspend and pass a frame with duration 0.5s. Then resume and pass one more.
   encoder.Suspend(now);
 
-  YUVBufferGenerator generator;
-  generator.Init(mozilla::gfx::IntSize(640, 480));
-  VideoSegment segment;
-  segment.AppendFrame(generator.GenerateI420Image(), generator.GetSize(),
-                      PRINCIPAL_HANDLE_NONE, false, now);
+  {
+    VideoSegment segment;
+    segment.AppendFrame(generator.GenerateI420Image(), generator.GetSize(),
+                        PRINCIPAL_HANDLE_NONE, false, now);
 
-  encoder.SetStartOffset(now);
-  encoder.AppendVideoSegment(std::move(segment));
-  encoder.AdvanceCurrentTime(now + TimeDuration::FromSeconds(0.5));
+    encoder.SetStartOffset(now);
+    encoder.AppendVideoSegment(std::move(segment));
+    encoder.AdvanceCurrentTime(now + TimeDuration::FromSeconds(0.5));
+  }
 
   encoder.Resume(now + TimeDuration::FromSeconds(0.5));
 
-  segment.AppendFrame(generator.GenerateI420Image(), generator.GetSize(),
-                      PRINCIPAL_HANDLE_NONE, false,
-                      now + TimeDuration::FromSeconds(0.5));
-  encoder.AppendVideoSegment(std::move(segment));
-  encoder.AdvanceCurrentTime(now + TimeDuration::FromSeconds(1));
+  {
+    VideoSegment segment;
+    segment.AppendFrame(generator.GenerateI420Image(), generator.GetSize(),
+                        PRINCIPAL_HANDLE_NONE, false,
+                        now + TimeDuration::FromSeconds(0.5));
+    encoder.AppendVideoSegment(std::move(segment));
+    encoder.AdvanceCurrentTime(now + TimeDuration::FromSeconds(1));
+  }
 
   encoder.NotifyEndOfStream();
 
   EncodedFrameContainer container;
   ASSERT_TRUE(NS_SUCCEEDED(encoder.GetEncodedTrack(container)));
 
   EXPECT_TRUE(encoder.IsEncodingComplete());
 
@@ -551,40 +569,45 @@ TEST(VP8VideoTrackEncoder, SuspendedBegi
   const uint64_t half = PR_USEC_PER_SEC / 2;
   EXPECT_EQ(half, totalDuration);
 }
 
 // Test that suspending and resuming in the middle of already pushed data
 // works.
 TEST(VP8VideoTrackEncoder, SuspendedOverlap) {
   TestVP8TrackEncoder encoder;
-
-  // Pass a 1s frame and suspend after 0.5s.
   YUVBufferGenerator generator;
   generator.Init(mozilla::gfx::IntSize(640, 480));
   TimeStamp now = TimeStamp::Now();
-  VideoSegment segment;
-  segment.AppendFrame(generator.GenerateI420Image(), generator.GetSize(),
-                      PRINCIPAL_HANDLE_NONE, false, now);
 
-  encoder.SetStartOffset(now);
-  encoder.AppendVideoSegment(std::move(segment));
+  {
+    // Pass a 1s frame and suspend after 0.5s.
+    VideoSegment segment;
+    segment.AppendFrame(generator.GenerateI420Image(), generator.GetSize(),
+                        PRINCIPAL_HANDLE_NONE, false, now);
+
+    encoder.SetStartOffset(now);
+    encoder.AppendVideoSegment(std::move(segment));
+  }
 
   encoder.AdvanceCurrentTime(now + TimeDuration::FromSeconds(0.5));
   encoder.Suspend(now + TimeDuration::FromSeconds(0.5));
 
-  // Pass another 1s frame and resume after 0.3 of this new frame.
-  segment.AppendFrame(generator.GenerateI420Image(), generator.GetSize(),
-                      PRINCIPAL_HANDLE_NONE, false,
-                      now + TimeDuration::FromSeconds(1));
-  encoder.AppendVideoSegment(std::move(segment));
+  {
+    // Pass another 1s frame and resume after 0.3 of this new frame.
+    VideoSegment segment;
+    segment.AppendFrame(generator.GenerateI420Image(), generator.GetSize(),
+                        PRINCIPAL_HANDLE_NONE, false,
+                        now + TimeDuration::FromSeconds(1));
+    encoder.AppendVideoSegment(std::move(segment));
+  }
+
   encoder.AdvanceCurrentTime(now + TimeDuration::FromSeconds(1.3));
   encoder.Resume(now + TimeDuration::FromSeconds(1.3));
   encoder.AdvanceCurrentTime(now + TimeDuration::FromSeconds(2));
-
   encoder.NotifyEndOfStream();
 
   EncodedFrameContainer container;
   ASSERT_TRUE(NS_SUCCEEDED(encoder.GetEncodedTrack(container)));
 
   EXPECT_TRUE(encoder.IsEncodingComplete());
 
   // Verify that we have two encoded frames and a total duration of 0.1s.
@@ -594,21 +617,21 @@ TEST(VP8VideoTrackEncoder, SuspendedOver
   EXPECT_EQ(pointFive, container.GetEncodedFrames()[0]->GetDuration());
   const uint64_t pointSeven = (PR_USEC_PER_SEC / 10) * 7;
   EXPECT_EQ(pointSeven, container.GetEncodedFrames()[1]->GetDuration());
 }
 
 // Test that ending a track in the middle of already pushed data works.
 TEST(VP8VideoTrackEncoder, PrematureEnding) {
   TestVP8TrackEncoder encoder;
-
-  // Pass a 1s frame and end the track after 0.5s.
   YUVBufferGenerator generator;
   generator.Init(mozilla::gfx::IntSize(640, 480));
   TimeStamp now = TimeStamp::Now();
+
+  // Pass a 1s frame and end the track after 0.5s.
   VideoSegment segment;
   segment.AppendFrame(generator.GenerateI420Image(), generator.GetSize(),
                       PRINCIPAL_HANDLE_NONE, false, now);
 
   encoder.SetStartOffset(now);
   encoder.AppendVideoSegment(std::move(segment));
   encoder.AdvanceCurrentTime(now + TimeDuration::FromSeconds(0.5));
   encoder.NotifyEndOfStream();
@@ -624,22 +647,22 @@ TEST(VP8VideoTrackEncoder, PrematureEndi
   }
   const uint64_t half = PR_USEC_PER_SEC / 2;
   EXPECT_EQ(half, totalDuration);
 }
 
 // Test that a track that starts at t > 0 works as expected.
 TEST(VP8VideoTrackEncoder, DelayedStart) {
   TestVP8TrackEncoder encoder;
+  YUVBufferGenerator generator;
+  generator.Init(mozilla::gfx::IntSize(640, 480));
+  TimeStamp now = TimeStamp::Now();
 
   // Pass a 2s frame, start (pass first CurrentTime) at 0.5s, end at 1s.
   // Should result in a 0.5s encoding.
-  YUVBufferGenerator generator;
-  generator.Init(mozilla::gfx::IntSize(640, 480));
-  TimeStamp now = TimeStamp::Now();
   VideoSegment segment;
   segment.AppendFrame(generator.GenerateI420Image(), generator.GetSize(),
                       PRINCIPAL_HANDLE_NONE, false, now);
 
   encoder.SetStartOffset(now + TimeDuration::FromSeconds(0.5));
   encoder.AppendVideoSegment(std::move(segment));
   encoder.AdvanceCurrentTime(now + TimeDuration::FromSeconds(1));
   encoder.NotifyEndOfStream();
@@ -656,22 +679,22 @@ TEST(VP8VideoTrackEncoder, DelayedStart)
   const uint64_t half = PR_USEC_PER_SEC / 2;
   EXPECT_EQ(half, totalDuration);
 }
 
 // Test that a track that starts at t > 0 works as expected, when
 // SetStartOffset comes after AppendVideoSegment.
 TEST(VP8VideoTrackEncoder, DelayedStartOtherEventOrder) {
   TestVP8TrackEncoder encoder;
+  YUVBufferGenerator generator;
+  generator.Init(mozilla::gfx::IntSize(640, 480));
+  TimeStamp now = TimeStamp::Now();
 
   // Pass a 2s frame, start (pass first CurrentTime) at 0.5s, end at 1s.
   // Should result in a 0.5s encoding.
-  YUVBufferGenerator generator;
-  generator.Init(mozilla::gfx::IntSize(640, 480));
-  TimeStamp now = TimeStamp::Now();
   VideoSegment segment;
   segment.AppendFrame(generator.GenerateI420Image(), generator.GetSize(),
                       PRINCIPAL_HANDLE_NONE, false, now);
 
   encoder.AppendVideoSegment(std::move(segment));
   encoder.SetStartOffset(now + TimeDuration::FromSeconds(0.5));
   encoder.AdvanceCurrentTime(now + TimeDuration::FromSeconds(1));
   encoder.NotifyEndOfStream();
@@ -687,22 +710,22 @@ TEST(VP8VideoTrackEncoder, DelayedStartO
   }
   const uint64_t half = PR_USEC_PER_SEC / 2;
   EXPECT_EQ(half, totalDuration);
 }
 
 // Test that a track that starts at t >>> 0 works as expected.
 TEST(VP8VideoTrackEncoder, VeryDelayedStart) {
   TestVP8TrackEncoder encoder;
+  YUVBufferGenerator generator;
+  generator.Init(mozilla::gfx::IntSize(640, 480));
+  TimeStamp now = TimeStamp::Now();
 
   // Pass a 1s frame, start (pass first CurrentTime) at 10s, end at 10.5s.
   // Should result in a 0.5s encoding.
-  YUVBufferGenerator generator;
-  generator.Init(mozilla::gfx::IntSize(640, 480));
-  TimeStamp now = TimeStamp::Now();
   VideoSegment segment;
   segment.AppendFrame(generator.GenerateI420Image(), generator.GetSize(),
                       PRINCIPAL_HANDLE_NONE, false, now);
 
   encoder.SetStartOffset(now + TimeDuration::FromSeconds(10));
   encoder.AppendVideoSegment(std::move(segment));
   encoder.AdvanceCurrentTime(now + TimeDuration::FromSeconds(10.5));
   encoder.NotifyEndOfStream();
@@ -719,24 +742,25 @@ TEST(VP8VideoTrackEncoder, VeryDelayedSt
   const uint64_t half = PR_USEC_PER_SEC / 2;
   EXPECT_EQ(half, totalDuration);
 }
 
 // Test that a video frame that hangs around for a long time gets encoded every
 // second.
 TEST(VP8VideoTrackEncoder, LongFramesReEncoded) {
   TestVP8TrackEncoder encoder;
+  YUVBufferGenerator generator;
+  generator.Init(mozilla::gfx::IntSize(640, 480));
+  TimeStamp now = TimeStamp::Now();
 
   // Pass a frame at t=0 and start encoding.
   // Advancing the current time by 1.5s should encode a 1s frame.
   // Advancing the current time by another 9.5s should encode another 10 1s
   // frames.
-  YUVBufferGenerator generator;
-  generator.Init(mozilla::gfx::IntSize(640, 480));
-  TimeStamp now = TimeStamp::Now();
+
   VideoSegment segment;
   segment.AppendFrame(generator.GenerateI420Image(), generator.GetSize(),
                       PRINCIPAL_HANDLE_NONE, false, now);
 
   encoder.SetStartOffset(now);
   encoder.AppendVideoSegment(std::move(segment));
 
   {
@@ -772,23 +796,23 @@ TEST(VP8VideoTrackEncoder, LongFramesReE
     EXPECT_EQ(10U, container.GetEncodedFrames().Length());
   }
 }
 
 // Test that an encoding with a defined key frame interval encodes keyframes
 // as expected. Short here means shorter than the default (1s).
 TEST(VP8VideoTrackEncoder, ShortKeyFrameInterval) {
   TestVP8TrackEncoder encoder;
+  YUVBufferGenerator generator;
+  generator.Init(mozilla::gfx::IntSize(640, 480));
+  TimeStamp now = TimeStamp::Now();
 
   // Give the encoder a keyframe interval of 500ms.
   // Pass frames at 0, 400ms, 600ms, 750ms, 900ms, 1100ms
   // Expected keys: ^         ^^^^^                ^^^^^^
-  YUVBufferGenerator generator;
-  generator.Init(mozilla::gfx::IntSize(640, 480));
-  TimeStamp now = TimeStamp::Now();
   VideoSegment segment;
   segment.AppendFrame(generator.GenerateI420Image(), generator.GetSize(),
                       PRINCIPAL_HANDLE_NONE, false, now);
   segment.AppendFrame(generator.GenerateI420Image(), generator.GetSize(),
                       PRINCIPAL_HANDLE_NONE, false,
                       now + TimeDuration::FromMilliseconds(400));
   segment.AppendFrame(generator.GenerateI420Image(), generator.GetSize(),
                       PRINCIPAL_HANDLE_NONE, false,
@@ -841,23 +865,23 @@ TEST(VP8VideoTrackEncoder, ShortKeyFrame
   EXPECT_EQ(PR_USEC_PER_SEC / 1000 * 100UL, frames[5]->GetDuration());
   EXPECT_EQ(EncodedFrame::VP8_I_FRAME, frames[5]->GetFrameType());
 }
 
 // Test that an encoding with a defined key frame interval encodes keyframes
 // as expected. Long here means longer than the default (1s).
 TEST(VP8VideoTrackEncoder, LongKeyFrameInterval) {
   TestVP8TrackEncoder encoder;
+  YUVBufferGenerator generator;
+  generator.Init(mozilla::gfx::IntSize(640, 480));
+  TimeStamp now = TimeStamp::Now();
 
   // Give the encoder a keyframe interval of 2000ms.
   // Pass frames at 0, 600ms, 900ms, 1100ms, 1900ms, 2100ms
   // Expected keys: ^                ^^^^^^          ^^^^^^
-  YUVBufferGenerator generator;
-  generator.Init(mozilla::gfx::IntSize(640, 480));
-  TimeStamp now = TimeStamp::Now();
   VideoSegment segment;
   segment.AppendFrame(generator.GenerateI420Image(), generator.GetSize(),
                       PRINCIPAL_HANDLE_NONE, false, now);
   segment.AppendFrame(generator.GenerateI420Image(), generator.GetSize(),
                       PRINCIPAL_HANDLE_NONE, false,
                       now + TimeDuration::FromMilliseconds(600));
   segment.AppendFrame(generator.GenerateI420Image(), generator.GetSize(),
                       PRINCIPAL_HANDLE_NONE, false,
@@ -910,22 +934,22 @@ TEST(VP8VideoTrackEncoder, LongKeyFrameI
   EXPECT_EQ(PR_USEC_PER_SEC / 1000 * 100UL, frames[5]->GetDuration());
   EXPECT_EQ(EncodedFrame::VP8_I_FRAME, frames[5]->GetFrameType());
 }
 
 // Test that an encoding with no defined key frame interval encodes keyframes
 // as expected. Default interval should be 1000ms.
 TEST(VP8VideoTrackEncoder, DefaultKeyFrameInterval) {
   TestVP8TrackEncoder encoder;
+  YUVBufferGenerator generator;
+  generator.Init(mozilla::gfx::IntSize(640, 480));
+  TimeStamp now = TimeStamp::Now();
 
   // Pass frames at 0, 600ms, 900ms, 1100ms, 1900ms, 2100ms
   // Expected keys: ^                ^^^^^^          ^^^^^^
-  YUVBufferGenerator generator;
-  generator.Init(mozilla::gfx::IntSize(640, 480));
-  TimeStamp now = TimeStamp::Now();
   VideoSegment segment;
   segment.AppendFrame(generator.GenerateI420Image(), generator.GetSize(),
                       PRINCIPAL_HANDLE_NONE, false, now);
   segment.AppendFrame(generator.GenerateI420Image(), generator.GetSize(),
                       PRINCIPAL_HANDLE_NONE, false,
                       now + TimeDuration::FromMilliseconds(600));
   segment.AppendFrame(generator.GenerateI420Image(), generator.GetSize(),
                       PRINCIPAL_HANDLE_NONE, false,
@@ -977,101 +1001,110 @@ TEST(VP8VideoTrackEncoder, DefaultKeyFra
   EXPECT_EQ(PR_USEC_PER_SEC / 1000 * 100UL, frames[5]->GetDuration());
   EXPECT_EQ(EncodedFrame::VP8_I_FRAME, frames[5]->GetFrameType());
 }
 
 // Test that an encoding where the key frame interval is updated dynamically
 // encodes keyframes as expected.
 TEST(VP8VideoTrackEncoder, DynamicKeyFrameIntervalChanges) {
   TestVP8TrackEncoder encoder;
+  YUVBufferGenerator generator;
+  generator.Init(mozilla::gfx::IntSize(640, 480));
+  EncodedFrameContainer container;
+  TimeStamp now = TimeStamp::Now();
 
   // Set keyframe interval to 100ms.
   // Pass frames at 0, 100ms, 120ms, 130ms, 200ms, 300ms
   // Expected keys: ^  ^^^^^                ^^^^^  ^^^^^
 
   // Then increase keyframe interval to 1100ms. (default is 1000)
   // Pass frames at 500ms, 1300ms, 1400ms, 2400ms
   // Expected keys:        ^^^^^^          ^^^^^^
 
   // Then decrease keyframe interval to 200ms.
   // Pass frames at 2500ms, 2600ms, 2800ms, 2900ms
   // Expected keys:         ^^^^^^  ^^^^^^
-  YUVBufferGenerator generator;
-  generator.Init(mozilla::gfx::IntSize(640, 480));
-  EncodedFrameContainer container;
-  TimeStamp now = TimeStamp::Now();
-  VideoSegment segment;
-  segment.AppendFrame(generator.GenerateI420Image(), generator.GetSize(),
-                      PRINCIPAL_HANDLE_NONE, false, now);
-  segment.AppendFrame(generator.GenerateI420Image(), generator.GetSize(),
-                      PRINCIPAL_HANDLE_NONE, false,
-                      now + TimeDuration::FromMilliseconds(100));
-  segment.AppendFrame(generator.GenerateI420Image(), generator.GetSize(),
-                      PRINCIPAL_HANDLE_NONE, false,
-                      now + TimeDuration::FromMilliseconds(120));
-  segment.AppendFrame(generator.GenerateI420Image(), generator.GetSize(),
-                      PRINCIPAL_HANDLE_NONE, false,
-                      now + TimeDuration::FromMilliseconds(130));
-  segment.AppendFrame(generator.GenerateI420Image(), generator.GetSize(),
-                      PRINCIPAL_HANDLE_NONE, false,
-                      now + TimeDuration::FromMilliseconds(200));
-  segment.AppendFrame(generator.GenerateI420Image(), generator.GetSize(),
-                      PRINCIPAL_HANDLE_NONE, false,
-                      now + TimeDuration::FromMilliseconds(300));
 
-  // The underlying encoder only gets passed frame N when frame N+1 is known,
-  // so we pass in the next frame *before* the keyframe interval change.
-  segment.AppendFrame(generator.GenerateI420Image(), generator.GetSize(),
-                      PRINCIPAL_HANDLE_NONE, false,
-                      now + TimeDuration::FromMilliseconds(500));
+  {
+    VideoSegment segment;
+    segment.AppendFrame(generator.GenerateI420Image(), generator.GetSize(),
+                        PRINCIPAL_HANDLE_NONE, false, now);
+    segment.AppendFrame(generator.GenerateI420Image(), generator.GetSize(),
+                        PRINCIPAL_HANDLE_NONE, false,
+                        now + TimeDuration::FromMilliseconds(100));
+    segment.AppendFrame(generator.GenerateI420Image(), generator.GetSize(),
+                        PRINCIPAL_HANDLE_NONE, false,
+                        now + TimeDuration::FromMilliseconds(120));
+    segment.AppendFrame(generator.GenerateI420Image(), generator.GetSize(),
+                        PRINCIPAL_HANDLE_NONE, false,
+                        now + TimeDuration::FromMilliseconds(130));
+    segment.AppendFrame(generator.GenerateI420Image(), generator.GetSize(),
+                        PRINCIPAL_HANDLE_NONE, false,
+                        now + TimeDuration::FromMilliseconds(200));
+    segment.AppendFrame(generator.GenerateI420Image(), generator.GetSize(),
+                        PRINCIPAL_HANDLE_NONE, false,
+                        now + TimeDuration::FromMilliseconds(300));
 
-  encoder.SetStartOffset(now);
-  encoder.SetKeyFrameInterval(100);
-  encoder.AppendVideoSegment(std::move(segment));
+    // The underlying encoder only gets passed frame N when frame N+1 is known,
+    // so we pass in the next frame *before* the keyframe interval change.
+    segment.AppendFrame(generator.GenerateI420Image(), generator.GetSize(),
+                        PRINCIPAL_HANDLE_NONE, false,
+                        now + TimeDuration::FromMilliseconds(500));
+
+    encoder.SetStartOffset(now);
+    encoder.SetKeyFrameInterval(100);
+    encoder.AppendVideoSegment(std::move(segment));
+  }
 
   // Advancing 501ms, so the first bit of the frame starting at 500ms is
   // included.
   encoder.AdvanceCurrentTime(now + TimeDuration::FromMilliseconds(501));
   ASSERT_TRUE(NS_SUCCEEDED(encoder.GetEncodedTrack(container)));
 
-  segment.AppendFrame(generator.GenerateI420Image(), generator.GetSize(),
-                      PRINCIPAL_HANDLE_NONE, false,
-                      now + TimeDuration::FromMilliseconds(1300));
-  segment.AppendFrame(generator.GenerateI420Image(), generator.GetSize(),
-                      PRINCIPAL_HANDLE_NONE, false,
-                      now + TimeDuration::FromMilliseconds(1400));
-  segment.AppendFrame(generator.GenerateI420Image(), generator.GetSize(),
-                      PRINCIPAL_HANDLE_NONE, false,
-                      now + TimeDuration::FromMilliseconds(2400));
+  {
+    VideoSegment segment;
+    segment.AppendFrame(generator.GenerateI420Image(), generator.GetSize(),
+                        PRINCIPAL_HANDLE_NONE, false,
+                        now + TimeDuration::FromMilliseconds(1300));
+    segment.AppendFrame(generator.GenerateI420Image(), generator.GetSize(),
+                        PRINCIPAL_HANDLE_NONE, false,
+                        now + TimeDuration::FromMilliseconds(1400));
+    segment.AppendFrame(generator.GenerateI420Image(), generator.GetSize(),
+                        PRINCIPAL_HANDLE_NONE, false,
+                        now + TimeDuration::FromMilliseconds(2400));
 
-  // The underlying encoder only gets passed frame N when frame N+1 is known,
-  // so we pass in the next frame *before* the keyframe interval change.
-  segment.AppendFrame(generator.GenerateI420Image(), generator.GetSize(),
-                      PRINCIPAL_HANDLE_NONE, false,
-                      now + TimeDuration::FromMilliseconds(2500));
+    // The underlying encoder only gets passed frame N when frame N+1 is known,
+    // so we pass in the next frame *before* the keyframe interval change.
+    segment.AppendFrame(generator.GenerateI420Image(), generator.GetSize(),
+                        PRINCIPAL_HANDLE_NONE, false,
+                        now + TimeDuration::FromMilliseconds(2500));
 
-  encoder.SetKeyFrameInterval(1100);
-  encoder.AppendVideoSegment(std::move(segment));
+    encoder.SetKeyFrameInterval(1100);
+    encoder.AppendVideoSegment(std::move(segment));
+  }
 
   // Advancing 2000ms from 501ms to 2501ms
   encoder.AdvanceCurrentTime(now + TimeDuration::FromMilliseconds(2501));
   ASSERT_TRUE(NS_SUCCEEDED(encoder.GetEncodedTrack(container)));
 
-  segment.AppendFrame(generator.GenerateI420Image(), generator.GetSize(),
-                      PRINCIPAL_HANDLE_NONE, false,
-                      now + TimeDuration::FromMilliseconds(2600));
-  segment.AppendFrame(generator.GenerateI420Image(), generator.GetSize(),
-                      PRINCIPAL_HANDLE_NONE, false,
-                      now + TimeDuration::FromMilliseconds(2800));
-  segment.AppendFrame(generator.GenerateI420Image(), generator.GetSize(),
-                      PRINCIPAL_HANDLE_NONE, false,
-                      now + TimeDuration::FromMilliseconds(2900));
+  {
+    VideoSegment segment;
+    segment.AppendFrame(generator.GenerateI420Image(), generator.GetSize(),
+                        PRINCIPAL_HANDLE_NONE, false,
+                        now + TimeDuration::FromMilliseconds(2600));
+    segment.AppendFrame(generator.GenerateI420Image(), generator.GetSize(),
+                        PRINCIPAL_HANDLE_NONE, false,
+                        now + TimeDuration::FromMilliseconds(2800));
+    segment.AppendFrame(generator.GenerateI420Image(), generator.GetSize(),
+                        PRINCIPAL_HANDLE_NONE, false,
+                        now + TimeDuration::FromMilliseconds(2900));
 
-  encoder.SetKeyFrameInterval(200);
-  encoder.AppendVideoSegment(std::move(segment));
+    encoder.SetKeyFrameInterval(200);
+    encoder.AppendVideoSegment(std::move(segment));
+  }
 
   // Advancing 499ms (compensating back 1ms from the first advancement)
   // from 2501ms to 3000ms.
   encoder.AdvanceCurrentTime(now + TimeDuration::FromMilliseconds(3000));
 
   encoder.NotifyEndOfStream();
 
   ASSERT_TRUE(NS_SUCCEEDED(encoder.GetEncodedTrack(container)));
@@ -1137,26 +1170,27 @@ TEST(VP8VideoTrackEncoder, DynamicKeyFra
   EXPECT_EQ(PR_USEC_PER_SEC / 1000 * 100UL, frames[13]->GetDuration());
   EXPECT_EQ(EncodedFrame::VP8_P_FRAME, frames[13]->GetFrameType());
 }
 
 // Test that an encoding which is disabled on a frame timestamp encodes
 // frames as expected.
 TEST(VP8VideoTrackEncoder, DisableOnFrameTime) {
   TestVP8TrackEncoder encoder;
+  YUVBufferGenerator generator;
+  generator.Init(mozilla::gfx::IntSize(640, 480));
+  EncodedFrameContainer container;
+  TimeStamp now = TimeStamp::Now();
 
   // Pass a frame in at t=0.
   // Pass another frame in at t=100ms.
   // Disable the track at t=100ms.
   // Stop encoding at t=200ms.
   // Should yield 2 frames, 1 real; 1 black.
-  YUVBufferGenerator generator;
-  generator.Init(mozilla::gfx::IntSize(640, 480));
-  EncodedFrameContainer container;
-  TimeStamp now = TimeStamp::Now();
+
   VideoSegment segment;
   segment.AppendFrame(generator.GenerateI420Image(), generator.GetSize(),
                       PRINCIPAL_HANDLE_NONE, false, now);
   segment.AppendFrame(generator.GenerateI420Image(), generator.GetSize(),
                       PRINCIPAL_HANDLE_NONE, false,
                       now + TimeDuration::FromMilliseconds(100));
 
   encoder.SetStartOffset(now);
@@ -1180,26 +1214,27 @@ TEST(VP8VideoTrackEncoder, DisableOnFram
   // [100ms, 200ms)
   EXPECT_EQ(PR_USEC_PER_SEC / 1000 * 100UL, frames[1]->GetDuration());
 }
 
 // Test that an encoding which is disabled between two frame timestamps encodes
 // frames as expected.
 TEST(VP8VideoTrackEncoder, DisableBetweenFrames) {
   TestVP8TrackEncoder encoder;
+  YUVBufferGenerator generator;
+  generator.Init(mozilla::gfx::IntSize(640, 480));
+  EncodedFrameContainer container;
+  TimeStamp now = TimeStamp::Now();
 
   // Pass a frame in at t=0.
   // Disable the track at t=50ms.
   // Pass another frame in at t=100ms.
   // Stop encoding at t=200ms.
   // Should yield 3 frames, 1 real [0, 50); 2 black [50, 100) and [100, 200).
-  YUVBufferGenerator generator;
-  generator.Init(mozilla::gfx::IntSize(640, 480));
-  EncodedFrameContainer container;
-  TimeStamp now = TimeStamp::Now();
+
   VideoSegment segment;
   segment.AppendFrame(generator.GenerateI420Image(), generator.GetSize(),
                       PRINCIPAL_HANDLE_NONE, false, now);
   segment.AppendFrame(generator.GenerateI420Image(), generator.GetSize(),
                       PRINCIPAL_HANDLE_NONE, false,
                       now + TimeDuration::FromMilliseconds(100));
 
   encoder.SetStartOffset(now);
@@ -1223,27 +1258,28 @@ TEST(VP8VideoTrackEncoder, DisableBetwee
   // [100ms, 200ms)
   EXPECT_EQ(PR_USEC_PER_SEC / 1000 * 100UL, frames[2]->GetDuration());
 }
 
 // Test that an encoding which is enabled on a frame timestamp encodes
 // frames as expected.
 TEST(VP8VideoTrackEncoder, EnableOnFrameTime) {
   TestVP8TrackEncoder encoder;
+  YUVBufferGenerator generator;
+  generator.Init(mozilla::gfx::IntSize(640, 480));
+  EncodedFrameContainer container;
+  TimeStamp now = TimeStamp::Now();
 
   // Disable the track at t=0.
   // Pass a frame in at t=0.
   // Pass another frame in at t=100ms.
   // Enable the track at t=100ms.
   // Stop encoding at t=200ms.
   // Should yield 2 frames, 1 black; 1 real.
-  YUVBufferGenerator generator;
-  generator.Init(mozilla::gfx::IntSize(640, 480));
-  EncodedFrameContainer container;
-  TimeStamp now = TimeStamp::Now();
+
   VideoSegment segment;
   segment.AppendFrame(generator.GenerateI420Image(), generator.GetSize(),
                       PRINCIPAL_HANDLE_NONE, false, now);
   segment.AppendFrame(generator.GenerateI420Image(), generator.GetSize(),
                       PRINCIPAL_HANDLE_NONE, false,
                       now + TimeDuration::FromMilliseconds(100));
 
   encoder.SetStartOffset(now);
@@ -1268,27 +1304,28 @@ TEST(VP8VideoTrackEncoder, EnableOnFrame
   // [100ms, 200ms)
   EXPECT_EQ(PR_USEC_PER_SEC / 1000 * 100UL, frames[1]->GetDuration());
 }
 
 // Test that an encoding which is enabled between two frame timestamps encodes
 // frames as expected.
 TEST(VP8VideoTrackEncoder, EnableBetweenFrames) {
   TestVP8TrackEncoder encoder;
+  YUVBufferGenerator generator;
+  generator.Init(mozilla::gfx::IntSize(640, 480));
+  EncodedFrameContainer container;
+  TimeStamp now = TimeStamp::Now();
 
   // Disable the track at t=0.
   // Pass a frame in at t=0.
   // Enable the track at t=50ms.
   // Pass another frame in at t=100ms.
   // Stop encoding at t=200ms.
   // Should yield 3 frames, 1 black [0, 50); 2 real [50, 100) and [100, 200).
-  YUVBufferGenerator generator;
-  generator.Init(mozilla::gfx::IntSize(640, 480));
-  EncodedFrameContainer container;
-  TimeStamp now = TimeStamp::Now();
+
   VideoSegment segment;
   segment.AppendFrame(generator.GenerateI420Image(), generator.GetSize(),
                       PRINCIPAL_HANDLE_NONE, false, now);
   segment.AppendFrame(generator.GenerateI420Image(), generator.GetSize(),
                       PRINCIPAL_HANDLE_NONE, false,
                       now + TimeDuration::FromMilliseconds(100));
 
   encoder.SetStartOffset(now);
@@ -1309,16 +1346,156 @@ TEST(VP8VideoTrackEncoder, EnableBetween
 
   // [50ms, 100ms)
   EXPECT_EQ(PR_USEC_PER_SEC / 1000 * 50UL, frames[1]->GetDuration());
 
   // [100ms, 200ms)
   EXPECT_EQ(PR_USEC_PER_SEC / 1000 * 100UL, frames[2]->GetDuration());
 }
 
+// Test that making time go backwards removes any future frames in the encoder.
+TEST(VP8VideoTrackEncoder, BackwardsTimeResets) {
+  TestVP8TrackEncoder encoder;
+  YUVBufferGenerator generator;
+  generator.Init(mozilla::gfx::IntSize(640, 480));
+  EncodedFrameContainer container;
+  TimeStamp now = TimeStamp::Now();
+
+  encoder.SetStartOffset(now);
+
+  // Pass frames in at t=0, t=100ms, t=200ms, t=300ms.
+  // Advance time to t=125ms.
+  // Pass frames in at t=150ms, t=250ms, t=350ms.
+  // Stop encoding at t=300ms.
+  // Should yield 4 frames, at t=0, t=100ms, t=150ms, t=250ms.
+
+  {
+    VideoSegment segment;
+    segment.AppendFrame(generator.GenerateI420Image(), generator.GetSize(),
+                        PRINCIPAL_HANDLE_NONE, false, now);
+    segment.AppendFrame(generator.GenerateI420Image(), generator.GetSize(),
+                        PRINCIPAL_HANDLE_NONE, false,
+                        now + TimeDuration::FromMilliseconds(100));
+    segment.AppendFrame(generator.GenerateI420Image(), generator.GetSize(),
+                        PRINCIPAL_HANDLE_NONE, false,
+                        now + TimeDuration::FromMilliseconds(200));
+    segment.AppendFrame(generator.GenerateI420Image(), generator.GetSize(),
+                        PRINCIPAL_HANDLE_NONE, false,
+                        now + TimeDuration::FromMilliseconds(300));
+
+    encoder.AppendVideoSegment(std::move(segment));
+  }
+
+  encoder.AdvanceCurrentTime(now + TimeDuration::FromMilliseconds(125));
+
+  {
+    VideoSegment segment;
+    segment.AppendFrame(generator.GenerateI420Image(), generator.GetSize(),
+                        PRINCIPAL_HANDLE_NONE, false,
+                        now + TimeDuration::FromMilliseconds(150));
+    segment.AppendFrame(generator.GenerateI420Image(), generator.GetSize(),
+                        PRINCIPAL_HANDLE_NONE, false,
+                        now + TimeDuration::FromMilliseconds(250));
+    segment.AppendFrame(generator.GenerateI420Image(), generator.GetSize(),
+                        PRINCIPAL_HANDLE_NONE, false,
+                        now + TimeDuration::FromMilliseconds(350));
+
+    encoder.AppendVideoSegment(std::move(segment));
+  }
+
+  encoder.AdvanceCurrentTime(now + TimeDuration::FromMilliseconds(300));
+  encoder.NotifyEndOfStream();
+  ASSERT_TRUE(NS_SUCCEEDED(encoder.GetEncodedTrack(container)));
+  EXPECT_TRUE(encoder.IsEncodingComplete());
+
+  const nsTArray<RefPtr<EncodedFrame>>& frames = container.GetEncodedFrames();
+  ASSERT_EQ(4UL, frames.Length());
+
+  // [0, 100ms)
+  EXPECT_EQ(PR_USEC_PER_SEC / 1000 * 100UL, frames[0]->GetDuration());
+
+  // [100ms, 150ms)
+  EXPECT_EQ(PR_USEC_PER_SEC / 1000 * 50UL, frames[1]->GetDuration());
+
+  // [150ms, 250ms)
+  EXPECT_EQ(PR_USEC_PER_SEC / 1000 * 100UL, frames[2]->GetDuration());
+
+  // [250ms, 300ms)
+  EXPECT_EQ(PR_USEC_PER_SEC / 1000 * 50UL, frames[3]->GetDuration());
+}
+
+// Test that trying to encode a null image removes any future frames in the
+// encoder.
+TEST(VP8VideoTrackEncoder, NullImageResets) {
+  TestVP8TrackEncoder encoder;
+  YUVBufferGenerator generator;
+  generator.Init(mozilla::gfx::IntSize(640, 480));
+  EncodedFrameContainer container;
+  TimeStamp now = TimeStamp::Now();
+
+  encoder.SetStartOffset(now);
+
+  // Pass frames in at t=0, t=100ms, t=200ms, t=300ms.
+  // Advance time to t=125ms.
+  // Pass in a null image at t=125ms.
+  // Pass frames in at t=250ms, t=350ms.
+  // Stop encoding at t=300ms.
+  // Should yield 3 frames, at t=0, t=100ms, t=250ms.
+
+  {
+    VideoSegment segment;
+    segment.AppendFrame(generator.GenerateI420Image(), generator.GetSize(),
+                        PRINCIPAL_HANDLE_NONE, false, now);
+    segment.AppendFrame(generator.GenerateI420Image(), generator.GetSize(),
+                        PRINCIPAL_HANDLE_NONE, false,
+                        now + TimeDuration::FromMilliseconds(100));
+    segment.AppendFrame(generator.GenerateI420Image(), generator.GetSize(),
+                        PRINCIPAL_HANDLE_NONE, false,
+                        now + TimeDuration::FromMilliseconds(200));
+    segment.AppendFrame(generator.GenerateI420Image(), generator.GetSize(),
+                        PRINCIPAL_HANDLE_NONE, false,
+                        now + TimeDuration::FromMilliseconds(300));
+
+    encoder.AppendVideoSegment(std::move(segment));
+  }
+
+  encoder.AdvanceCurrentTime(now + TimeDuration::FromMilliseconds(125));
+
+  {
+    VideoSegment segment;
+    segment.AppendFrame(nullptr, generator.GetSize(), PRINCIPAL_HANDLE_NONE,
+                        false, now + TimeDuration::FromMilliseconds(125));
+    segment.AppendFrame(generator.GenerateI420Image(), generator.GetSize(),
+                        PRINCIPAL_HANDLE_NONE, false,
+                        now + TimeDuration::FromMilliseconds(250));
+    segment.AppendFrame(generator.GenerateI420Image(), generator.GetSize(),
+                        PRINCIPAL_HANDLE_NONE, false,
+                        now + TimeDuration::FromMilliseconds(350));
+
+    encoder.AppendVideoSegment(std::move(segment));
+  }
+
+  encoder.AdvanceCurrentTime(now + TimeDuration::FromMilliseconds(300));
+  encoder.NotifyEndOfStream();
+  ASSERT_TRUE(NS_SUCCEEDED(encoder.GetEncodedTrack(container)));
+  EXPECT_TRUE(encoder.IsEncodingComplete());
+
+  const nsTArray<RefPtr<EncodedFrame>>& frames = container.GetEncodedFrames();
+  ASSERT_EQ(3UL, frames.Length());
+
+  // [0, 100ms)
+  EXPECT_EQ(PR_USEC_PER_SEC / 1000 * 100UL, frames[0]->GetDuration());
+
+  // [100ms, 250ms)
+  EXPECT_EQ(PR_USEC_PER_SEC / 1000 * 150UL, frames[1]->GetDuration());
+
+  // [250ms, 300ms)
+  EXPECT_EQ(PR_USEC_PER_SEC / 1000 * 50UL, frames[2]->GetDuration());
+}
+
 // EOS test
 TEST(VP8VideoTrackEncoder, EncodeComplete) {
   TestVP8TrackEncoder encoder;
 
   // track end notification.
   encoder.NotifyEndOfStream();
 
   // Pull Encoded Data back from encoder. Since we have sent
--- a/dom/media/gtest/YUVBufferGenerator.cpp
+++ b/dom/media/gtest/YUVBufferGenerator.cpp
@@ -1,160 +1,151 @@
-/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
-/* vim: set ts=8 sts=2 et sw=2 tw=80: */
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-#include "YUVBufferGenerator.h"
-
-using namespace mozilla::layers;
-using namespace mozilla;
-
-void YUVBufferGenerator::Init(const mozilla::gfx::IntSize& aSize)
-{
-  mImageSize = aSize;
-
-  int yPlaneLen = aSize.width * aSize.height;
-  int cbcrPlaneLen = (yPlaneLen + 1) / 2;
-  int frameLen = yPlaneLen + cbcrPlaneLen;
-
-  // Generate source buffer.
-  mSourceBuffer.SetLength(frameLen);
-
-  // Fill Y plane.
-  memset(mSourceBuffer.Elements(), 0x10, yPlaneLen);
-
-  // Fill Cb/Cr planes.
-  memset(mSourceBuffer.Elements() + yPlaneLen, 0x80, cbcrPlaneLen);
-}
-
-mozilla::gfx::IntSize YUVBufferGenerator::GetSize() const
-{
-  return mImageSize;
-}
-
-already_AddRefed<Image> YUVBufferGenerator::GenerateI420Image()
-{
-  return do_AddRef(CreateI420Image());
-}
-
-already_AddRefed<Image> YUVBufferGenerator::GenerateNV12Image()
-{
-  return do_AddRef(CreateNV12Image());
-}
-
-already_AddRefed<Image> YUVBufferGenerator::GenerateNV21Image()
-{
-  return do_AddRef(CreateNV21Image());
-}
-
-Image* YUVBufferGenerator::CreateI420Image()
-{
-  PlanarYCbCrImage* image = new RecyclingPlanarYCbCrImage(new BufferRecycleBin());
-  PlanarYCbCrData data;
-  data.mPicSize = mImageSize;
-
-  const uint32_t yPlaneSize = mImageSize.width * mImageSize.height;
-  const uint32_t halfWidth = (mImageSize.width + 1) / 2;
-  const uint32_t halfHeight = (mImageSize.height + 1) / 2;
-  const uint32_t uvPlaneSize = halfWidth * halfHeight;
-
-  // Y plane.
-  uint8_t* y = mSourceBuffer.Elements();
-  data.mYChannel = y;
-  data.mYSize.width = mImageSize.width;
-  data.mYSize.height = mImageSize.height;
-  data.mYStride = mImageSize.width;
-  data.mYSkip = 0;
-
-  // Cr plane.
-  uint8_t* cr = y + yPlaneSize + uvPlaneSize;
-  data.mCrChannel = cr;
-  data.mCrSkip = 0;
-
-  // Cb plane
-  uint8_t* cb = y + yPlaneSize;
-  data.mCbChannel = cb;
-  data.mCbSkip = 0;
-
-  // CrCb plane vectors.
-  data.mCbCrStride = halfWidth;
-  data.mCbCrSize.width = halfWidth;
-  data.mCbCrSize.height = halfHeight;
-
-  image->CopyData(data);
-  return image;
-}
-
-Image* YUVBufferGenerator::CreateNV12Image()
-{
-  NVImage* image = new NVImage();
-  PlanarYCbCrData data;
-  data.mPicSize = mImageSize;
-
-  const uint32_t yPlaneSize = mImageSize.width * mImageSize.height;
-  const uint32_t halfWidth = (mImageSize.width + 1) / 2;
-  const uint32_t halfHeight = (mImageSize.height + 1) / 2;
-
-  // Y plane.
-  uint8_t* y = mSourceBuffer.Elements();
-  data.mYChannel = y;
-  data.mYSize.width = mImageSize.width;
-  data.mYSize.height = mImageSize.height;
-  data.mYStride = mImageSize.width;
-  data.mYSkip = 0;
-
-  // Cr plane.
-  uint8_t* cr = y + yPlaneSize;
-  data.mCrChannel = cr;
-  data.mCrSkip = 1;
-
-  // Cb plane
-  uint8_t* cb = y + yPlaneSize + 1;
-  data.mCbChannel = cb;
-  data.mCbSkip = 1;
-
-  // 4:2:0.
-  data.mCbCrStride = mImageSize.width;
-  data.mCbCrSize.width = halfWidth;
-  data.mCbCrSize.height = halfHeight;
-
-  image->SetData(data);
-  return image;
-}
-
-Image* YUVBufferGenerator::CreateNV21Image()
-{
-  NVImage* image = new NVImage();
-  PlanarYCbCrData data;
-  data.mPicSize = mImageSize;
-
-  const uint32_t yPlaneSize = mImageSize.width * mImageSize.height;
-  const uint32_t halfWidth = (mImageSize.width + 1) / 2;
-  const uint32_t halfHeight = (mImageSize.height + 1) / 2;
-
-  // Y plane.
-  uint8_t* y = mSourceBuffer.Elements();
-  data.mYChannel = y;
-  data.mYSize.width = mImageSize.width;
-  data.mYSize.height = mImageSize.height;
-  data.mYStride = mImageSize.width;
-  data.mYSkip = 0;
-
-  // Cr plane.
-  uint8_t* cr = y + yPlaneSize + 1;
-  data.mCrChannel = cr;
-  data.mCrSkip = 1;
-
-  // Cb plane
-  uint8_t* cb = y + yPlaneSize;
-  data.mCbChannel = cb;
-  data.mCbSkip = 1;
-
-  // 4:2:0.
-  data.mCbCrStride = mImageSize.width;
-  data.mCbCrSize.width = halfWidth;
-  data.mCbCrSize.height = halfHeight;
-
-  image->SetData(data);
-  return image;
-}
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "YUVBufferGenerator.h"
+
+using namespace mozilla::layers;
+using namespace mozilla;
+
+void YUVBufferGenerator::Init(const mozilla::gfx::IntSize& aSize) {
+  mImageSize = aSize;
+
+  int yPlaneLen = aSize.width * aSize.height;
+  int cbcrPlaneLen = (yPlaneLen + 1) / 2;
+  int frameLen = yPlaneLen + cbcrPlaneLen;
+
+  // Generate source buffer.
+  mSourceBuffer.SetLength(frameLen);
+
+  // Fill Y plane.
+  memset(mSourceBuffer.Elements(), 0x10, yPlaneLen);
+
+  // Fill Cb/Cr planes.
+  memset(mSourceBuffer.Elements() + yPlaneLen, 0x80, cbcrPlaneLen);
+}
+
+mozilla::gfx::IntSize YUVBufferGenerator::GetSize() const { return mImageSize; }
+
+already_AddRefed<Image> YUVBufferGenerator::GenerateI420Image() {
+  return do_AddRef(CreateI420Image());
+}
+
+already_AddRefed<Image> YUVBufferGenerator::GenerateNV12Image() {
+  return do_AddRef(CreateNV12Image());
+}
+
+already_AddRefed<Image> YUVBufferGenerator::GenerateNV21Image() {
+  return do_AddRef(CreateNV21Image());
+}
+
+Image* YUVBufferGenerator::CreateI420Image() {
+  PlanarYCbCrImage* image =
+      new RecyclingPlanarYCbCrImage(new BufferRecycleBin());
+  PlanarYCbCrData data;
+  data.mPicSize = mImageSize;
+
+  const uint32_t yPlaneSize = mImageSize.width * mImageSize.height;
+  const uint32_t halfWidth = (mImageSize.width + 1) / 2;
+  const uint32_t halfHeight = (mImageSize.height + 1) / 2;
+  const uint32_t uvPlaneSize = halfWidth * halfHeight;
+
+  // Y plane.
+  uint8_t* y = mSourceBuffer.Elements();
+  data.mYChannel = y;
+  data.mYSize.width = mImageSize.width;
+  data.mYSize.height = mImageSize.height;
+  data.mYStride = mImageSize.width;
+  data.mYSkip = 0;
+
+  // Cr plane.
+  uint8_t* cr = y + yPlaneSize + uvPlaneSize;
+  data.mCrChannel = cr;
+  data.mCrSkip = 0;
+
+  // Cb plane
+  uint8_t* cb = y + yPlaneSize;
+  data.mCbChannel = cb;
+  data.mCbSkip = 0;
+
+  // CrCb plane vectors.
+  data.mCbCrStride = halfWidth;
+  data.mCbCrSize.width = halfWidth;
+  data.mCbCrSize.height = halfHeight;
+
+  image->CopyData(data);
+  return image;
+}
+
+Image* YUVBufferGenerator::CreateNV12Image() {
+  NVImage* image = new NVImage();
+  PlanarYCbCrData data;
+  data.mPicSize = mImageSize;
+
+  const uint32_t yPlaneSize = mImageSize.width * mImageSize.height;
+  const uint32_t halfWidth = (mImageSize.width + 1) / 2;
+  const uint32_t halfHeight = (mImageSize.height + 1) / 2;
+
+  // Y plane.
+  uint8_t* y = mSourceBuffer.Elements();
+  data.mYChannel = y;
+  data.mYSize.width = mImageSize.width;
+  data.mYSize.height = mImageSize.height;
+  data.mYStride = mImageSize.width;
+  data.mYSkip = 0;
+
+  // Cr plane.
+  uint8_t* cr = y + yPlaneSize;
+  data.mCrChannel = cr;
+  data.mCrSkip = 1;
+
+  // Cb plane
+  uint8_t* cb = y + yPlaneSize + 1;
+  data.mCbChannel = cb;
+  data.mCbSkip = 1;
+
+  // 4:2:0.
+  data.mCbCrStride = mImageSize.width;
+  data.mCbCrSize.width = halfWidth;
+  data.mCbCrSize.height = halfHeight;
+
+  image->SetData(data);
+  return image;
+}
+
+Image* YUVBufferGenerator::CreateNV21Image() {
+  NVImage* image = new NVImage();
+  PlanarYCbCrData data;
+  data.mPicSize = mImageSize;
+
+  const uint32_t yPlaneSize = mImageSize.width * mImageSize.height;
+  const uint32_t halfWidth = (mImageSize.width + 1) / 2;
+  const uint32_t halfHeight = (mImageSize.height + 1) / 2;
+
+  // Y plane.
+  uint8_t* y = mSourceBuffer.Elements();
+  data.mYChannel = y;
+  data.mYSize.width = mImageSize.width;
+  data.mYSize.height = mImageSize.height;
+  data.mYStride = mImageSize.width;
+  data.mYSkip = 0;
+
+  // Cr plane.
+  uint8_t* cr = y + yPlaneSize + 1;
+  data.mCrChannel = cr;
+  data.mCrSkip = 1;
+
+  // Cb plane
+  uint8_t* cb = y + yPlaneSize;
+  data.mCbChannel = cb;
+  data.mCbSkip = 1;
+
+  // 4:2:0.
+  data.mCbCrStride = mImageSize.width;
+  data.mCbCrSize.width = halfWidth;
+  data.mCbCrSize.height = halfHeight;
+
+  image->SetData(data);
+  return image;
+}
--- a/dom/media/gtest/YUVBufferGenerator.h
+++ b/dom/media/gtest/YUVBufferGenerator.h
@@ -1,32 +1,32 @@
-/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
-/* vim: set ts=8 sts=2 et sw=2 tw=80: */
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-#ifndef YUVBufferGenerator_h
-#define YUVBufferGenerator_h
-
-#include "ImageContainer.h"
-#include "mozilla/AlreadyAddRefed.h"
-#include "nsTArray.h"
-#include "Point.h" // mozilla::gfx::IntSize
-
-// A helper object to generate of different YUV planes.
-class YUVBufferGenerator {
-public:
-  void Init(const mozilla::gfx::IntSize& aSize);
-  mozilla::gfx::IntSize GetSize() const;
-  already_AddRefed<mozilla::layers::Image> GenerateI420Image();
-  already_AddRefed<mozilla::layers::Image> GenerateNV12Image();
-  already_AddRefed<mozilla::layers::Image> GenerateNV21Image();
-
-private:
-  mozilla::layers::Image* CreateI420Image();
-  mozilla::layers::Image* CreateNV12Image();
-  mozilla::layers::Image* CreateNV21Image();
-  mozilla::gfx::IntSize mImageSize;
-  nsTArray<uint8_t> mSourceBuffer;
-};
-
-#endif  // YUVBufferGenerator_h
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef YUVBufferGenerator_h
+#define YUVBufferGenerator_h
+
+#include "ImageContainer.h"
+#include "mozilla/AlreadyAddRefed.h"
+#include "nsTArray.h"
+#include "Point.h"  // mozilla::gfx::IntSize
+
+// A helper object to generate of different YUV planes.
+class YUVBufferGenerator {
+ public:
+  void Init(const mozilla::gfx::IntSize& aSize);
+  mozilla::gfx::IntSize GetSize() const;
+  already_AddRefed<mozilla::layers::Image> GenerateI420Image();
+  already_AddRefed<mozilla::layers::Image> GenerateNV12Image();
+  already_AddRefed<mozilla::layers::Image> GenerateNV21Image();
+
+ private:
+  mozilla::layers::Image* CreateI420Image();
+  mozilla::layers::Image* CreateNV12Image();
+  mozilla::layers::Image* CreateNV21Image();
+  mozilla::gfx::IntSize mImageSize;
+  nsTArray<uint8_t> mSourceBuffer;
+};
+
+#endif  // YUVBufferGenerator_h
--- a/dom/media/platforms/agnostic/VPXDecoder.h
+++ b/dom/media/platforms/agnostic/VPXDecoder.h
@@ -103,18 +103,19 @@ class VPXDecoder : public MediaDataDecod
           1             0         YUV 4:2:2
           1             1         YUV 4:2:0
     */
     bool mSubSampling_x = true;
     bool mSubSampling_y = true;
 
     bool IsCompatible(const VPXStreamInfo& aOther) const {
       return mImage == aOther.mImage && mProfile == aOther.mProfile &&
-             mBitDepth == aOther.mBitDepth && mSubSampling_x &&
-             aOther.mSubSampling_x && mSubSampling_y == aOther.mSubSampling_y;
+             mBitDepth == aOther.mBitDepth &&
+             mSubSampling_x == aOther.mSubSampling_x &&
+             mSubSampling_y == aOther.mSubSampling_y;
     }
   };
 
   static bool GetStreamInfo(Span<const uint8_t> aBuffer, VPXStreamInfo& aInfo,
                             Codec aCodec);
 
  private:
   ~VPXDecoder();
--- a/dom/media/test/reftest/reftest.list
+++ b/dom/media/test/reftest/reftest.list
@@ -1,5 +1,5 @@
 skip-if(Android) fuzzy-if(OSX,0-22,0-49977) fuzzy-if(webrender&&cocoaWidget,23-23,76795-76795) skip-if(winWidget) fuzzy-if(gtkWidget&&layersGPUAccelerated,0-70,0-600) HTTP(..) == short.mp4.firstframe.html short.mp4.firstframe-ref.html
 skip-if(Android) fuzzy-if(OSX,0-23,0-51392) fuzzy-if(webrender&&cocoaWidget,23-23,76798-76798) fuzzy-if(winWidget,0-59,0-76797) fuzzy-if(gtkWidget&&layersGPUAccelerated,0-60,0-1800) HTTP(..) == short.mp4.lastframe.html short.mp4.lastframe-ref.html
 skip-if(Android) skip-if(winWidget) fuzzy-if(gtkWidget&&layersGPUAccelerated,0-55,0-4281) fuzzy-if(OSX,0-3,0-111852) HTTP(..) == bipbop_300_215kbps.mp4.lastframe.html bipbop_300_215kbps.mp4.lastframe-ref.html
-skip-if(Android) fuzzy-if(OSX,0-25,0-175921) fuzzy-if(winWidget,0-71,0-179198) HTTP(..) == gizmo.mp4.seek.html gizmo.mp4.55thframe-ref.html
-skip-if(Android) == vtt_update_display_after_removed_cue.html vtt_update_display_after_removed_cue_ref.html
+skip-if(Android) fuzzy-if(OSX,0-25,0-175921) fuzzy-if((/^Windows\x20NT\x2010\.0/.test(http.oscpu))&&(/^aarch64-msvc/.test(xulRuntime.XPCOMABI)),224-224,178000-178820) fuzzy-if(winWidget,0-71,0-179198) HTTP(..) == gizmo.mp4.seek.html gizmo.mp4.55thframe-ref.html
+skip-if(Android) fuzzy-if((/^Windows\x20NT\x2010\.0/.test(http.oscpu))&&(/^aarch64-msvc/.test(xulRuntime.XPCOMABI)),136-136,427600-427680) == vtt_update_display_after_removed_cue.html vtt_update_display_after_removed_cue_ref.html
--- a/dom/media/webrtc/MediaEngine.h
+++ b/dom/media/webrtc/MediaEngine.h
@@ -46,14 +46,14 @@ class MediaEngine : public DeviceChangeC
                                 nsTArray<RefPtr<MediaDevice>>*) = 0;
 
   virtual void ReleaseResourcesForWindow(uint64_t aWindowId) = 0;
   virtual void Shutdown() = 0;
 
   virtual void SetFakeDeviceChangeEvents() {}
 
  protected:
-  virtual ~MediaEngine() {}
+  virtual ~MediaEngine() = default;
 };
 
 }  // namespace mozilla
 
 #endif /* MEDIAENGINE_H_ */
--- a/dom/media/webrtc/MediaEngineDefault.cpp
+++ b/dom/media/webrtc/MediaEngineDefault.cpp
@@ -59,17 +59,17 @@ static nsString DefaultVideoName() {
 /**
  * Default video source.
  */
 
 MediaEngineDefaultVideoSource::MediaEngineDefaultVideoSource()
     : mTimer(nullptr),
       mName(DefaultVideoName()) {}
 
-MediaEngineDefaultVideoSource::~MediaEngineDefaultVideoSource() {}
+MediaEngineDefaultVideoSource::~MediaEngineDefaultVideoSource() = default;
 
 nsString MediaEngineDefaultVideoSource::GetName() const { return mName; }
 
 nsCString MediaEngineDefaultVideoSource::GetUUID() const {
   return NS_LITERAL_CSTRING("1041FCBD-3F12-4F7B-9E9B-1EC556DD5676");
 }
 
 nsString MediaEngineDefaultVideoSource::GetGroupId() const {
@@ -338,17 +338,17 @@ void MediaEngineDefaultVideoSource::Pull
 
 /**
  * Default audio source.
  */
 
 MediaEngineDefaultAudioSource::MediaEngineDefaultAudioSource()
     : mMutex("MediaEngineDefaultAudioSource::mMutex") {}
 
-MediaEngineDefaultAudioSource::~MediaEngineDefaultAudioSource() {}
+MediaEngineDefaultAudioSource::~MediaEngineDefaultAudioSource() = default;
 
 nsString MediaEngineDefaultAudioSource::GetName() const {
   return NS_LITERAL_STRING(u"Default Audio Device");
 }
 
 nsCString MediaEngineDefaultAudioSource::GetUUID() const {
   return NS_LITERAL_CSTRING("B7CBD7C1-53EF-42F9-8353-73F61C70C092");
 }
--- a/dom/media/webrtc/MediaEngineRemoteVideoSource.cpp
+++ b/dom/media/webrtc/MediaEngineRemoteVideoSource.cpp
@@ -867,17 +867,17 @@ bool MediaEngineRemoteVideoSource::Choos
         // We'll adjust the width too so the aspect ratio is retained.
         cap.width = cap.height * prefWidth / prefHeight;
       }
 
       if (candidateSet.Contains(cap, CapabilityComparator())) {
         continue;
       }
       LogCapability("Hardcoded capability", cap, 0);
-      candidateSet.AppendElement(CapabilityCandidate(std::move(cap)));
+      candidateSet.AppendElement(cap);
     }
   }
 
   // First, filter capabilities by required constraints (min, max, exact).
 
   for (size_t i = 0; i < candidateSet.Length();) {
     auto& candidate = candidateSet[i];
     candidate.mDistance =
--- a/dom/media/webrtc/MediaEngineRemoteVideoSource.h
+++ b/dom/media/webrtc/MediaEngineRemoteVideoSource.h
@@ -61,20 +61,19 @@ enum DistanceCalculation { kFitness, kFe
 /**
  * The WebRTC implementation of the MediaEngine interface.
  */
 class MediaEngineRemoteVideoSource : public MediaEngineSource,
                                      public camera::FrameRelay {
   ~MediaEngineRemoteVideoSource();
 
   struct CapabilityCandidate {
-    explicit CapabilityCandidate(webrtc::CaptureCapability&& aCapability,
+    explicit CapabilityCandidate(webrtc::CaptureCapability aCapability,
                                  uint32_t aDistance = 0)
-        : mCapability(std::forward<webrtc::CaptureCapability>(aCapability)),
-          mDistance(aDistance) {}
+        : mCapability(aCapability), mDistance(aDistance) {}
 
     const webrtc::CaptureCapability mCapability;
     uint32_t mDistance;
   };
 
   class CapabilityComparator {
    public:
     bool Equals(const CapabilityCandidate& aCandidate,
@@ -108,18 +107,18 @@ class MediaEngineRemoteVideoSource : pub
       const nsTArray<const NormalizedConstraintSet*>& aConstraintSets,
       const nsString& aDeviceId) const override;
 
  public:
   MediaEngineRemoteVideoSource(int aIndex, camera::CaptureEngine aCapEngine,
                                bool aScary);
 
   // ExternalRenderer
-  int DeliverFrame(uint8_t* buffer,
-                   const camera::VideoFrameProperties& properties) override;
+  int DeliverFrame(uint8_t* aBuffer,
+                   const camera::VideoFrameProperties& aProps) override;
 
   // MediaEngineSource
   dom::MediaSourceEnum GetMediaSource() const override;
   nsresult Allocate(const dom::MediaTrackConstraints& aConstraints,
                     const MediaEnginePrefs& aPrefs, const nsString& aDeviceId,
                     const ipc::PrincipalInfo& aPrincipalInfo,
                     AllocationHandle** aOutHandle,
                     const char** aOutBadConstraint) override;
--- a/dom/media/webrtc/MediaEngineSource.h
+++ b/dom/media/webrtc/MediaEngineSource.h
@@ -43,17 +43,17 @@ class MediaEnginePhotoCallback {
   // aBlob is the image captured by MediaEngineSource. It is
   // called on main thread.
   virtual nsresult PhotoComplete(already_AddRefed<dom::Blob> aBlob) = 0;
 
   // It is called on main thread. aRv is the error code.
   virtual nsresult PhotoError(nsresult aRv) = 0;
 
  protected:
-  virtual ~MediaEnginePhotoCallback() {}
+  virtual ~MediaEnginePhotoCallback() = default;
 };
 
 /**
  * Lifecycle state of MediaEngineSource.
  */
 enum MediaEngineSourceState {
   kAllocated,  // Allocated, not yet started.
   kStarted,    // Previously allocated or stopped, then started.
--- a/dom/media/webrtc/MediaEngineTabVideoSource.cpp
+++ b/dom/media/webrtc/MediaEngineTabVideoSource.cpp
@@ -28,17 +28,17 @@
 #include "nsIPrefService.h"
 #include "MediaTrackConstraints.h"
 #include "Tracing.h"
 
 namespace mozilla {
 
 using namespace mozilla::gfx;
 
-MediaEngineTabVideoSource::MediaEngineTabVideoSource() {}
+MediaEngineTabVideoSource::MediaEngineTabVideoSource() = default;
 
 nsresult MediaEngineTabVideoSource::StartRunnable::Run() {
   MOZ_ASSERT(NS_IsMainThread());
   mVideoSource->mStreamMain = mStream;
   mVideoSource->mTrackIDMain = mTrackID;
   mVideoSource->mPrincipalHandleMain = mPrincipal;
   mVideoSource->Draw();
   mVideoSource->mTimer->InitWithNamedFuncCallback(
@@ -90,17 +90,19 @@ nsresult MediaEngineTabVideoSource::Init
     nsresult rv;
     mVideoSource->mTabSource =
         do_GetService(NS_TABSOURCESERVICE_CONTRACTID, &rv);
     NS_ENSURE_SUCCESS(rv, rv);
 
     nsCOMPtr<mozIDOMWindowProxy> win;
     rv = mVideoSource->mTabSource->GetTabToStream(getter_AddRefs(win));
     NS_ENSURE_SUCCESS(rv, rv);
-    if (!win) return NS_OK;
+    if (!win) {
+      return NS_OK;
+    }
 
     mVideoSource->mWindow = nsPIDOMWindowOuter::From(win);
     MOZ_ASSERT(mVideoSource->mWindow);
   }
   mVideoSource->mTimer = NS_NewTimer();
   nsCOMPtr<nsIRunnable> start(
       new StartRunnable(mVideoSource, mStream, mTrackID, mPrincipal));
   start->Run();
--- a/dom/media/webrtc/MediaEngineTabVideoSource.h
+++ b/dom/media/webrtc/MediaEngineTabVideoSource.h
@@ -109,24 +109,24 @@ class MediaEngineTabVideoSource : public
     explicit DestroyRunnable(MediaEngineTabVideoSource* videoSource)
         : Runnable("MediaEngineTabVideoSource::DestroyRunnable"),
           mVideoSource(videoSource) {}
     NS_IMETHOD Run() override;
     const RefPtr<MediaEngineTabVideoSource> mVideoSource;
   };
 
  protected:
-  ~MediaEngineTabVideoSource() {}
+  ~MediaEngineTabVideoSource() = default;
 
  private:
   // These are accessed only on main thread.
   int32_t mBufWidthMax = 0;
   int32_t mBufHeightMax = 0;
   int64_t mWindowId = 0;
-  bool mScrollWithPage = 0;
+  bool mScrollWithPage = false;
   int32_t mViewportOffsetX = 0;
   int32_t mViewportOffsetY = 0;
   int32_t mViewportWidth = 0;
   int32_t mViewportHeight = 0;
   int32_t mTimePerFrame = 0;
   RefPtr<layers::ImageContainer> mImageContainer;
 
   nsCOMPtr<nsPIDOMWindowOuter> mWindow;
--- a/dom/media/webrtc/MediaEngineWebRTCAudio.h
+++ b/dom/media/webrtc/MediaEngineWebRTCAudio.h
@@ -23,19 +23,19 @@ class AudioInputProcessing;
 //   the main thread and then the MSG thread so that it can be used as part of
 //   the graph processing. On destruction, similarly, a message is sent to the
 //   graph so that it stops using it, and then it is deleted.
 // - mSettings is created on the MediaManager thread is always ever accessed on
 //   the Main Thread. It is const.
 class MediaEngineWebRTCMicrophoneSource : public MediaEngineSource {
  public:
   MediaEngineWebRTCMicrophoneSource(RefPtr<AudioDeviceInfo> aInfo,
-                                    const nsString& aName,
-                                    const nsCString& aUuid,
-                                    const nsString& aGroupId,
+                                    const nsString& aDeviceName,
+                                    const nsCString& aDeviceUUID,
+                                    const nsString& aDeviceGroup,
                                     uint32_t aMaxChannelCount,
                                     bool aDelayAgnostic, bool aExtendedFilter);
 
   bool RequiresSharing() const override { return false; }
 
   nsString GetName() const override;
   nsCString GetUUID() const override;
   nsString GetGroupId() const override;
--- a/dom/media/webrtc/MediaTrackConstraints.cpp
+++ b/dom/media/webrtc/MediaTrackConstraints.cpp
@@ -383,17 +383,17 @@ uint32_t MediaConstraintsHelper::Feasibi
                      (std::abs(aN - aRange.mIdeal.value()) * 1000) /
                      std::max(std::abs(aN), std::abs(aRange.mIdeal.value()))));
 }
 
 // Fitness distance returned as integer math * 1000. Infinity = UINT32_MAX
 
 /* static */
 uint32_t MediaConstraintsHelper::FitnessDistance(
-    nsString aN, const NormalizedConstraintSet::StringRange& aParams) {
+    const nsString& aN, const NormalizedConstraintSet::StringRange& aParams) {
   if (!aParams.mExact.empty() &&
       aParams.mExact.find(aN) == aParams.mExact.end()) {
     return UINT32_MAX;
   }
   if (!aParams.mIdeal.empty() &&
       aParams.mIdeal.find(aN) == aParams.mIdeal.end()) {
     return 1000;
   }
@@ -435,18 +435,18 @@ uint32_t MediaConstraintsHelper::Fitness
   // Order devices by shortest distance
   for (auto& ordinal : ordered) {
     aDevices.RemoveElement(ordinal.second);
     aDevices.AppendElement(ordinal.second);
   }
 
   // Then apply advanced constraints.
 
-  for (int i = 0; i < int(c.mAdvanced.size()); i++) {
-    aggregateConstraints.AppendElement(&c.mAdvanced[i]);
+  for (const auto& advanced : c.mAdvanced) {
+    aggregateConstraints.AppendElement(&advanced);
     nsTArray<RefPtr<MediaDevice>> rejects;
     for (uint32_t j = 0; j < aDevices.Length();) {
       uint32_t distance =
           aDevices[j]->GetBestFitnessDistance(aggregateConstraints, aIsChrome);
       if (distance == UINT32_MAX) {
         rejects.AppendElement(std::move(aDevices[j]));
         aDevices.RemoveElementAt(j);
       } else {
@@ -522,22 +522,22 @@ uint32_t MediaConstraintsHelper::Fitness
       aMediaEngineSource->GetGroupId(), NS_LITERAL_STRING("")));
   return FindBadConstraint(aConstraints, devices);
 }
 
 static void LogConstraintStringRange(
     const NormalizedConstraintSet::StringRange& aRange) {
   if (aRange.mExact.size() <= 1 && aRange.mIdeal.size() <= 1) {
     LOG("  %s: { exact: [%s], ideal: [%s] }", aRange.mName,
-        (aRange.mExact.size()
-             ? NS_ConvertUTF16toUTF8(*aRange.mExact.begin()).get()
-             : ""),
-        (aRange.mIdeal.size()
-             ? NS_ConvertUTF16toUTF8(*aRange.mIdeal.begin()).get()
-             : ""));
+        (aRange.mExact.empty()
+             ? ""
+             : NS_ConvertUTF16toUTF8(*aRange.mExact.begin()).get()),
+        (aRange.mIdeal.empty()
+             ? ""
+             : NS_ConvertUTF16toUTF8(*aRange.mIdeal.begin()).get()));
   } else {
     LOG("  %s: { exact: [", aRange.mName);
     for (auto& entry : aRange.mExact) {
       LOG("      %s,", NS_ConvertUTF16toUTF8(entry).get());
     }
     LOG("    ], ideal: [");
     for (auto& entry : aRange.mIdeal) {
       LOG("      %s,", NS_ConvertUTF16toUTF8(entry).get());
--- a/dom/media/webrtc/MediaTrackConstraints.h
+++ b/dom/media/webrtc/MediaTrackConstraints.h
@@ -47,17 +47,17 @@ class NormalizedConstraintSet {
 
     BaseRange(MemberPtrType aMemberPtr, const char* aName,
               nsTArray<MemberPtrType>* aList)
         : mName(aName) {
       if (aList) {
         aList->AppendElement(aMemberPtr);
       }
     }
-    virtual ~BaseRange() {}
+    virtual ~BaseRange() = default;
 
    public:
     virtual bool Merge(const BaseRange& aOther) = 0;
     virtual void FinalizeMerge() = 0;
 
     const char* mName;
   };
 
@@ -71,17 +71,17 @@ class NormalizedConstraintSet {
     Maybe<ValueType> mIdeal;
 
     Range(MemberPtrType aMemberPtr, const char* aName, ValueType aMin,
           ValueType aMax, nsTArray<MemberPtrType>* aList)
         : BaseRange(aMemberPtr, aName, aList),
           mMin(aMin),
           mMax(aMax),
           mMergeDenominator(0) {}
-    virtual ~Range(){};
+    virtual ~Range() = default;
 
     template <class ConstrainRange>
     void SetFrom(const ConstrainRange& aOther);
     ValueType Clamp(ValueType n) const {
       return std::max(mMin, std::min(n, mMax));
     }
     ValueType Get(ValueType defaultValue) const {
       return Clamp(mIdeal.valueOr(defaultValue));
@@ -201,17 +201,17 @@ class NormalizedConstraintSet {
         bool advanced, nsTArray<MemberPtrType>* aList);
 
     StringRange(StringPtrType aMemberPtr, const char* aName,
                 const nsString& aOther, nsTArray<MemberPtrType>* aList)
         : BaseRange((MemberPtrType)aMemberPtr, aName, aList) {
       mIdeal.insert(aOther);
     }
 
-    ~StringRange() {}
+    ~StringRange() = default;
 
     void SetFrom(const dom::ConstrainDOMStringParameters& aOther);
     ValueType Clamp(const ValueType& n) const;
     ValueType Get(const ValueType& defaultValue) const {
       return Clamp(mIdeal.empty() ? defaultValue : mIdeal);
     }
     bool Intersects(const StringRange& aOther) const;
     void Intersect(const StringRange& aOther);
@@ -306,17 +306,17 @@ struct FlattenedConstraints : public Nor
 class MediaConstraintsHelper {
  public:
   template <class ValueType, class NormalizedRange>
   static uint32_t FitnessDistance(ValueType aN, const NormalizedRange& aRange);
   template <class ValueType, class NormalizedRange>
   static uint32_t FeasibilityDistance(ValueType aN,
                                       const NormalizedRange& aRange);
   static uint32_t FitnessDistance(
-      nsString aN, const NormalizedConstraintSet::StringRange& aConstraint);
+      const nsString& aN, const NormalizedConstraintSet::StringRange& aParams);
 
  protected:
   static bool SomeSettingsFit(const NormalizedConstraints& aConstraints,
                               const nsTArray<RefPtr<MediaDevice>>& aDevices);
 
  public:
   static uint32_t GetMinimumFitnessDistance(
       const NormalizedConstraintSet& aConstraints, const nsString& aDeviceId);
--- a/dom/media/webrtc/PeerIdentity.h
+++ b/dom/media/webrtc/PeerIdentity.h
@@ -26,17 +26,17 @@ namespace mozilla {
  * http://tools.ietf.org/html/draft-ietf-rtcweb-security-arch-09#section-5.6.5.3.3.1
  */
 class PeerIdentity final : public RefCounted<PeerIdentity> {
  public:
   MOZ_DECLARE_REFCOUNTED_TYPENAME(PeerIdentity)
 
   explicit PeerIdentity(const nsAString& aPeerIdentity)
       : mPeerIdentity(aPeerIdentity) {}
-  ~PeerIdentity() {}
+  ~PeerIdentity() = default;
 
   bool Equals(const PeerIdentity& aOther) const;
   bool Equals(const nsAString& aOtherString) const;
   const nsString& ToString() const { return mPeerIdentity; }
 
  private:
   static void GetUser(const nsAString& aPeerIdentity, nsAString& aUser);
   static void GetHost(const nsAString& aPeerIdentity, nsAString& aHost);
--- a/dom/media/webrtc/RTCIdentityProviderRegistrar.cpp
+++ b/dom/media/webrtc/RTCIdentityProviderRegistrar.cpp
@@ -23,17 +23,17 @@ NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(RT
                                       mValidateAssertionCallback)
 
 RTCIdentityProviderRegistrar::RTCIdentityProviderRegistrar(
     nsIGlobalObject* aGlobal)
     : mGlobal(aGlobal),
       mGenerateAssertionCallback(nullptr),
       mValidateAssertionCallback(nullptr) {}
 
-RTCIdentityProviderRegistrar::~RTCIdentityProviderRegistrar() {}
+RTCIdentityProviderRegistrar::~RTCIdentityProviderRegistrar() = default;
 
 nsIGlobalObject* RTCIdentityProviderRegistrar::GetParentObject() const {
   return mGlobal;
 }
 
 JSObject* RTCIdentityProviderRegistrar::WrapObject(
     JSContext* aCx, JS::Handle<JSObject*> aGivenProto) {
   return RTCIdentityProviderRegistrar_Binding::Wrap(aCx, this, aGivenProto);
--- a/dom/media/webrtc/WebrtcGlobal.h
+++ b/dom/media/webrtc/WebrtcGlobal.h
@@ -88,22 +88,24 @@ struct ParamTraits<mozilla::dom::RTCStat
   typedef mozilla::dom::RTCStatsReportInternal paramType;
 
   static void Write(Message* aMsg, const paramType& aParam) {
     WriteParam(aMsg, aParam.mClosed);
     WriteParam(aMsg, aParam.mCodecStats);
     WriteParam(aMsg, aParam.mIceCandidatePairStats);
     WriteParam(aMsg, aParam.mIceCandidateStats);
     WriteParam(aMsg, aParam.mIceComponentStats);
-    WriteParam(aMsg, aParam.mInboundRTPStreamStats);
+    WriteParam(aMsg, aParam.mInboundRtpStreamStats);
     WriteParam(aMsg, aParam.mLocalSdp);
     WriteParam(aMsg, aParam.mMediaStreamStats);
     WriteParam(aMsg, aParam.mMediaStreamTrackStats);
-    WriteParam(aMsg, aParam.mOutboundRTPStreamStats);
+    WriteParam(aMsg, aParam.mOutboundRtpStreamStats);
     WriteParam(aMsg, aParam.mPcid);
+    WriteParam(aMsg, aParam.mRemoteInboundRtpStreamStats);
+    WriteParam(aMsg, aParam.mRemoteOutboundRtpStreamStats);
     WriteParam(aMsg, aParam.mRemoteSdp);
     WriteParam(aMsg, aParam.mTimestamp);
     WriteParam(aMsg, aParam.mIceRestarts);
     WriteParam(aMsg, aParam.mIceRollbacks);
     WriteParam(aMsg, aParam.mTransportStats);
     WriteParam(aMsg, aParam.mRtpContributingSourceStats);
     WriteParam(aMsg, aParam.mOfferer);
     WriteParam(aMsg, aParam.mTrickledIceCandidateStats);
@@ -113,22 +115,24 @@ struct ParamTraits<mozilla::dom::RTCStat
 
   static bool Read(const Message* aMsg, PickleIterator* aIter,
                    paramType* aResult) {
     if (!ReadParam(aMsg, aIter, &(aResult->mClosed)) ||
         !ReadParam(aMsg, aIter, &(aResult->mCodecStats)) ||
         !ReadParam(aMsg, aIter, &(aResult->mIceCandidatePairStats)) ||
         !ReadParam(aMsg, aIter, &(aResult->mIceCandidateStats)) ||
         !ReadParam(aMsg, aIter, &(aResult->mIceComponentStats)) ||
-        !ReadParam(aMsg, aIter, &(aResult->mInboundRTPStreamStats)) ||
+        !ReadParam(aMsg, aIter, &(aResult->mInboundRtpStreamStats)) ||
         !ReadParam(aMsg, aIter, &(aResult->mLocalSdp)) ||
         !ReadParam(aMsg, aIter, &(aResult->mMediaStreamStats)) ||
         !ReadParam(aMsg, aIter, &(aResult->mMediaStreamTrackStats)) ||
-        !ReadParam(aMsg, aIter, &(aResult->mOutboundRTPStreamStats)) ||
+        !ReadParam(aMsg, aIter, &(aResult->mOutboundRtpStreamStats)) ||
         !ReadParam(aMsg, aIter, &(aResult->mPcid)) ||
+        !ReadParam(aMsg, aIter, &(aResult->mRemoteInboundRtpStreamStats)) ||
+        !ReadParam(aMsg, aIter, &(aResult->mRemoteOutboundRtpStreamStats)) ||
         !ReadParam(aMsg, aIter, &(aResult->mRemoteSdp)) ||
         !ReadParam(aMsg, aIter, &(aResult->mTimestamp)) ||
         !ReadParam(aMsg, aIter, &(aResult->mIceRestarts)) ||
         !ReadParam(aMsg, aIter, &(aResult->mIceRollbacks)) ||
         !ReadParam(aMsg, aIter, &(aResult->mTransportStats)) ||
         !ReadParam(aMsg, aIter, &(aResult->mRtpContributingSourceStats)) ||
         !ReadParam(aMsg, aIter, &(aResult->mOfferer)) ||
         !ReadParam(aMsg, aIter, &(aResult->mTrickledIceCandidateStats)) ||
@@ -286,126 +290,179 @@ struct ParamTraits<mozilla::dom::RTCIceC
     if (!ReadParam(aMsg, aIter, &(aResult->mActiveConnection)) ||
         !ReadParam(aMsg, aIter, &(aResult->mBytesReceived)) ||
         !ReadParam(aMsg, aIter, &(aResult->mBytesSent)) ||
         !ReadParam(aMsg, aIter, &(aResult->mComponent)) ||
         !ReadParam(aMsg, aIter, &(aResult->mTransportId)) ||
         !ReadRTCStats(aMsg, aIter, aResult)) {
       return false;
     }
-
     return true;
   }
 };
 
 static void WriteRTCRtpStreamStats(
     Message* aMsg, const mozilla::dom::RTCRtpStreamStats& aParam) {
-  WriteParam(aMsg, aParam.mBitrateMean);
-  WriteParam(aMsg, aParam.mBitrateStdDev);
-  WriteParam(aMsg, aParam.mCodecId);
-  WriteParam(aMsg, aParam.mFirCount);
-  WriteParam(aMsg, aParam.mFramerateMean);
-  WriteParam(aMsg, aParam.mFramerateStdDev);
+  WriteParam(aMsg, aParam.mSsrc);
+  WriteParam(aMsg, aParam.mMediaType);
   WriteParam(aMsg, aParam.mKind);
-  WriteParam(aMsg, aParam.mLocalId);
-  WriteParam(aMsg, aParam.mMediaTrackId);
-  WriteParam(aMsg, aParam.mMediaType);
-  WriteParam(aMsg, aParam.mNackCount);
-  WriteParam(aMsg, aParam.mPliCount);
-  WriteParam(aMsg, aParam.mQpSum);
-  WriteParam(aMsg, aParam.mRemoteId);
-  WriteParam(aMsg, aParam.mSsrc);
   WriteParam(aMsg, aParam.mTransportId);
+  WriteRTCStats(aMsg, aParam);
 }
 
 static bool ReadRTCRtpStreamStats(const Message* aMsg, PickleIterator* aIter,
                                   mozilla::dom::RTCRtpStreamStats* aResult) {
-  if (!ReadParam(aMsg, aIter, &(aResult->mBitrateMean)) ||
-      !ReadParam(aMsg, aIter, &(aResult->mBitrateStdDev)) ||
-      !ReadParam(aMsg, aIter, &(aResult->mCodecId)) ||
-      !ReadParam(aMsg, aIter, &(aResult->mFirCount)) ||
-      !ReadParam(aMsg, aIter, &(aResult->mFramerateMean)) ||
-      !ReadParam(aMsg, aIter, &(aResult->mFramerateStdDev)) ||
-      !ReadParam(aMsg, aIter, &(aResult->mKind)) ||
-      !ReadParam(aMsg, aIter, &(aResult->mLocalId)) ||
-      !ReadParam(aMsg, aIter, &(aResult->mMediaTrackId)) ||
-      !ReadParam(aMsg, aIter, &(aResult->mMediaType)) ||
-      !ReadParam(aMsg, aIter, &(aResult->mNackCount)) ||
-      !ReadParam(aMsg, aIter, &(aResult->mPliCount)) ||
-      !ReadParam(aMsg, aIter, &(aResult->mQpSum)) ||
-      !ReadParam(aMsg, aIter, &(aResult->mRemoteId)) ||
-      !ReadParam(aMsg, aIter, &(aResult->mSsrc)) ||
-      !ReadParam(aMsg, aIter, &(aResult->mTransportId))) {
-    return false;
-  }
+  return ReadParam(aMsg, aIter, &(aResult->mSsrc)) &&
+         ReadParam(aMsg, aIter, &(aResult->mMediaType)) &&
+         ReadParam(aMsg, aIter, &(aResult->mKind)) &&
+         ReadParam(aMsg, aIter, &(aResult->mTransportId)) &&
+         ReadRTCStats(aMsg, aIter, aResult);
+}
 
-  return true;
+static void WriteRTCReceivedRtpStreamStats(
+    Message* aMsg, const mozilla::dom::RTCReceivedRtpStreamStats& aParam) {
+  WriteParam(aMsg, aParam.mPacketsReceived);
+  WriteParam(aMsg, aParam.mPacketsLost);
+  WriteParam(aMsg, aParam.mJitter);
+  WriteParam(aMsg, aParam.mDiscardedPackets);
+  WriteParam(aMsg, aParam.mPacketsDiscarded);
+  WriteRTCRtpStreamStats(aMsg, aParam);
+}
+
+static bool ReadRTCReceivedRtpStreamStats(
+    const Message* aMsg, PickleIterator* aIter,
+    mozilla::dom::RTCReceivedRtpStreamStats* aResult) {
+  return ReadParam(aMsg, aIter, &(aResult->mPacketsReceived)) &&
+         ReadParam(aMsg, aIter, &(aResult->mPacketsLost)) &&
+         ReadParam(aMsg, aIter, &(aResult->mJitter)) &&
+         ReadParam(aMsg, aIter, &(aResult->mDiscardedPackets)) &&
+         ReadParam(aMsg, aIter, &(aResult->mPacketsDiscarded)) &&
+         ReadRTCRtpStreamStats(aMsg, aIter, aResult);
 }
 
 template <>
-struct ParamTraits<mozilla::dom::RTCInboundRTPStreamStats> {
-  typedef mozilla::dom::RTCInboundRTPStreamStats paramType;
+struct ParamTraits<mozilla::dom::RTCInboundRtpStreamStats> {
+  typedef mozilla::dom::RTCInboundRtpStreamStats paramType;
 
   static void Write(Message* aMsg, const paramType& aParam) {
-    WriteParam(aMsg, aParam.mBytesReceived);
-    WriteParam(aMsg, aParam.mDiscardedPackets);
+    WriteParam(aMsg, aParam.mRemoteId);
     WriteParam(aMsg, aParam.mFramesDecoded);
-    WriteParam(aMsg, aParam.mJitter);
-    WriteParam(aMsg, aParam.mPacketsLost);
-    WriteParam(aMsg, aParam.mPacketsReceived);
-    WriteParam(aMsg, aParam.mRoundTripTime);
-    WriteRTCRtpStreamStats(aMsg, aParam);
-    WriteRTCStats(aMsg, aParam);
+    WriteParam(aMsg, aParam.mBytesReceived);
+    WriteParam(aMsg, aParam.mNackCount);
+    WriteParam(aMsg, aParam.mFirCount);
+    WriteParam(aMsg, aParam.mPliCount);
+    WriteParam(aMsg, aParam.mBitrateMean);
+    WriteParam(aMsg, aParam.mBitrateStdDev);
+    WriteParam(aMsg, aParam.mFramerateMean);
+    WriteParam(aMsg, aParam.mFramerateStdDev);
+    WriteRTCReceivedRtpStreamStats(aMsg, aParam);
   }
 
   static bool Read(const Message* aMsg, PickleIterator* aIter,
                    paramType* aResult) {
-    if (!ReadParam(aMsg, aIter, &(aResult->mBytesReceived)) ||
-        !ReadParam(aMsg, aIter, &(aResult->mDiscardedPackets)) ||
-        !ReadParam(aMsg, aIter, &(aResult->mFramesDecoded)) ||
-        !ReadParam(aMsg, aIter, &(aResult->mJitter)) ||
-        !ReadParam(aMsg, aIter, &(aResult->mPacketsLost)) ||
-        !ReadParam(aMsg, aIter, &(aResult->mPacketsReceived)) ||
-        !ReadParam(aMsg, aIter, &(aResult->mRoundTripTime)) ||
-        !ReadRTCRtpStreamStats(aMsg, aIter, aResult) ||
-        !ReadRTCStats(aMsg, aIter, aResult)) {
-      return false;
-    }
+    return ReadParam(aMsg, aIter, &(aResult->mRemoteId)) &&
+           ReadParam(aMsg, aIter, &(aResult->mFramesDecoded)) &&
+           ReadParam(aMsg, aIter, &(aResult->mBytesReceived)) &&
+           ReadParam(aMsg, aIter, &(aResult->mNackCount)) &&
+           ReadParam(aMsg, aIter, &(aResult->mFirCount)) &&
+           ReadParam(aMsg, aIter, &(aResult->mPliCount)) &&
+           ReadParam(aMsg, aIter, &(aResult->mBitrateMean)) &&
+           ReadParam(aMsg, aIter, &(aResult->mBitrateStdDev)) &&
+           ReadParam(aMsg, aIter, &(aResult->mFramerateMean)) &&
+           ReadParam(aMsg, aIter, &(aResult->mFramerateStdDev)) &&
+           ReadRTCReceivedRtpStreamStats(aMsg, aIter, aResult);
+  }
+};
+
+static void WriteRTCSentRtpStreamStats(
+    Message* aMsg, const mozilla::dom::RTCSentRtpStreamStats& aParam) {
+  WriteParam(aMsg, aParam.mPacketsSent);
+  WriteParam(aMsg, aParam.mBytesSent);
+  WriteRTCRtpStreamStats(aMsg, aParam);
+}
+
+static bool ReadRTCSentRtpStreamStats(
+    const Message* aMsg, PickleIterator* aIter,
+    mozilla::dom::RTCSentRtpStreamStats* aResult) {
+  return ReadParam(aMsg, aIter, &(aResult->mPacketsSent)) &&
+         ReadParam(aMsg, aIter, &(aResult->mBytesSent)) &&
+         ReadRTCRtpStreamStats(aMsg, aIter, aResult);
+}
 
-    return true;
+template <>
+struct ParamTraits<mozilla::dom::RTCOutboundRtpStreamStats> {
+  typedef mozilla::dom::RTCOutboundRtpStreamStats paramType;
+
+  static void Write(Message* aMsg, const paramType& aParam) {
+    WriteParam(aMsg, aParam.mRemoteId);
+    WriteParam(aMsg, aParam.mFramesEncoded);
+    WriteParam(aMsg, aParam.mQpSum);
+    WriteParam(aMsg, aParam.mNackCount);
+    WriteParam(aMsg, aParam.mFirCount);
+    WriteParam(aMsg, aParam.mPliCount);
+    WriteParam(aMsg, aParam.mBitrateMean);
+    WriteParam(aMsg, aParam.mBitrateStdDev);
+    WriteParam(aMsg, aParam.mFramerateMean);
+    WriteParam(aMsg, aParam.mFramerateStdDev);
+    WriteParam(aMsg, aParam.mDroppedFrames);
+    WriteRTCSentRtpStreamStats(aMsg, aParam);
+  }
+
+  static bool Read(const Message* aMsg, PickleIterator* aIter,
+                   paramType* aResult) {
+    return ReadParam(aMsg, aIter, &(aResult->mRemoteId)) &&
+           ReadParam(aMsg, aIter, &(aResult->mFramesEncoded)) &&
+           ReadParam(aMsg, aIter, &(aResult->mQpSum)) &&
+           ReadParam(aMsg, aIter, &(aResult->mNackCount)) &&
+           ReadParam(aMsg, aIter, &(aResult->mFirCount)) &&
+           ReadParam(aMsg, aIter, &(aResult->mPliCount)) &&
+           ReadParam(aMsg, aIter, &(aResult->mBitrateMean)) &&
+           ReadParam(aMsg, aIter, &(aResult->mBitrateStdDev)) &&
+           ReadParam(aMsg, aIter, &(aResult->mFramerateMean)) &&
+           ReadParam(aMsg, aIter, &(aResult->mFramerateStdDev)) &&
+           ReadParam(aMsg, aIter, &(aResult->mDroppedFrames)) &&
+           ReadRTCSentRtpStreamStats(aMsg, aIter, aResult);
   }
 };
 
 template <>
-struct ParamTraits<mozilla::dom::RTCOutboundRTPStreamStats> {
-  typedef mozilla::dom::RTCOutboundRTPStreamStats paramType;
+struct ParamTraits<mozilla::dom::RTCRemoteInboundRtpStreamStats> {
+  typedef mozilla::dom::RTCRemoteInboundRtpStreamStats paramType;
 
   static void Write(Message* aMsg, const paramType& aParam) {
-    WriteParam(aMsg, aParam.mBytesSent);
-    WriteParam(aMsg, aParam.mDroppedFrames);
-    WriteParam(aMsg, aParam.mFramesEncoded);
-    WriteParam(aMsg, aParam.mPacketsSent);
-    WriteParam(aMsg, aParam.mTargetBitrate);
-    WriteRTCRtpStreamStats(aMsg, aParam);
-    WriteRTCStats(aMsg, aParam);
+    WriteParam(aMsg, aParam.mLocalId);
+    WriteParam(aMsg, aParam.mBytesReceived);  // To be removed in Bug 1529405
+    WriteParam(aMsg, aParam.mRoundTripTime);
+    WriteRTCReceivedRtpStreamStats(aMsg, aParam);
   }
 
   static bool Read(const Message* aMsg, PickleIterator* aIter,
                    paramType* aResult) {
-    if (!ReadParam(aMsg, aIter, &(aResult->mBytesSent)) ||
-        !ReadParam(aMsg, aIter, &(aResult->mDroppedFrames)) ||
-        !ReadParam(aMsg, aIter, &(aResult->mFramesEncoded)) ||
-        !ReadParam(aMsg, aIter, &(aResult->mPacketsSent)) ||
-        !ReadParam(aMsg, aIter, &(aResult->mTargetBitrate)) ||
-        !ReadRTCRtpStreamStats(aMsg, aIter, aResult) ||
-        !ReadRTCStats(aMsg, aIter, aResult)) {
-      return false;
-    }
+    return ReadParam(aMsg, aIter, &(aResult->mLocalId)) &&
+           ReadParam(
+               aMsg, aIter,
+               &(aResult->mBytesReceived)) &&  // To be removed in Bug 1529405
+           ReadParam(aMsg, aIter, &(aResult->mRoundTripTime)) &&
+           ReadRTCReceivedRtpStreamStats(aMsg, aIter, aResult);
+  }
+};
 
-    return true;
+template <>
+struct ParamTraits<mozilla::dom::RTCRemoteOutboundRtpStreamStats> {
+  typedef mozilla::dom::RTCRemoteOutboundRtpStreamStats paramType;
+
+  static void Write(Message* aMsg, const paramType& aParam) {
+    WriteParam(aMsg, aParam.mLocalId);
+    WriteRTCSentRtpStreamStats(aMsg, aParam);
+  }
+
+  static bool Read(const Message* aMsg, PickleIterator* aIter,
+                   paramType* aResult) {
+    return ReadParam(aMsg, aIter, &(aResult->mLocalId)) &&
+           ReadRTCSentRtpStreamStats(aMsg, aIter, aResult);
   }
 };
 
 template <>
 struct ParamTraits<mozilla::dom::RTCMediaStreamStats> {
   typedef mozilla::dom::RTCMediaStreamStats paramType;
 
   static void Write(Message* aMsg, const paramType& aParam) {
--- a/dom/payments/PaymentMethodChangeEvent.cpp
+++ b/dom/payments/PaymentMethodChangeEvent.cpp
@@ -116,18 +116,17 @@ void PaymentMethodChangeEvent::GetMethod
             !rawDetails.billingAddress.dependentLocality.IsEmpty() ||
             !rawDetails.billingAddress.postalCode.IsEmpty() ||
             !rawDetails.billingAddress.sortingCode.IsEmpty() ||
             !rawDetails.billingAddress.organization.IsEmpty() ||
             !rawDetails.billingAddress.recipient.IsEmpty() ||
             !rawDetails.billingAddress.phone.IsEmpty()) {
           nsCOMPtr<nsPIDOMWindowInner> window =
               do_QueryInterface(GetParentObject());
-          basicCardDetails.mBillingAddress.Construct();
-          basicCardDetails.mBillingAddress.Value() =
+          basicCardDetails.mBillingAddress =
               new PaymentAddress(window, rawDetails.billingAddress.country,
                                  rawDetails.billingAddress.addressLine,
                                  rawDetails.billingAddress.region,
                                  rawDetails.billingAddress.regionCode,
                                  rawDetails.billingAddress.city,
                                  rawDetails.billingAddress.dependentLocality,
                                  rawDetails.billingAddress.postalCode,
                                  rawDetails.billingAddress.sortingCode,
--- a/dom/payments/PaymentResponse.cpp
+++ b/dom/payments/PaymentResponse.cpp
@@ -92,45 +92,40 @@ void PaymentResponse::GetDetails(JSConte
       const GeneralData& rawData = mDetails.generalData();
       DeserializeToJSObject(rawData.data, aCx, aRetVal);
       break;
     }
     case ResponseData::BasicCardResponse: {
       const BasicCardData& rawData = mDetails.basicCardData();
       BasicCardResponse basicCardResponse;
       if (!rawData.cardholderName.IsEmpty()) {
-        basicCardResponse.mCardholderName.Construct();
-        basicCardResponse.mCardholderName.Value() = rawData.cardholderName;
+        basicCardResponse.mCardholderName = rawData.cardholderName;
       }
       basicCardResponse.mCardNumber = rawData.cardNumber;
       if (!rawData.expiryMonth.IsEmpty()) {
-        basicCardResponse.mExpiryMonth.Construct();
-        basicCardResponse.mExpiryMonth.Value() = rawData.expiryMonth;
+        basicCardResponse.mExpiryMonth = rawData.expiryMonth;
       }
       if (!rawData.expiryYear.IsEmpty()) {
-        basicCardResponse.mExpiryYear.Construct();
-        basicCardResponse.mExpiryYear.Value() = rawData.expiryYear;
+        basicCardResponse.mExpiryYear = rawData.expiryYear;
       }
       if (!rawData.cardSecurityCode.IsEmpty()) {
-        basicCardResponse.mCardSecurityCode.Construct();
-        basicCardResponse.mCardSecurityCode.Value() = rawData.cardSecurityCode;
+        basicCardResponse.mCardSecurityCode = rawData.cardSecurityCode;
       }
       if (!rawData.billingAddress.country.IsEmpty() ||
           !rawData.billingAddress.addressLine.IsEmpty() ||
           !rawData.billingAddress.region.IsEmpty() ||
           !rawData.billingAddress.regionCode.IsEmpty() ||
           !rawData.billingAddress.city.IsEmpty() ||
           !rawData.billingAddress.dependentLocality.IsEmpty() ||
           !rawData.billingAddress.postalCode.IsEmpty() ||
           !rawData.billingAddress.sortingCode.IsEmpty() ||
           !rawData.billingAddress.organization.IsEmpty() ||
           !rawData.billingAddress.recipient.IsEmpty() ||
           !rawData.billingAddress.phone.IsEmpty()) {
-        basicCardResponse.mBillingAddress.Construct();
-        basicCardResponse.mBillingAddress.Value() = new PaymentAddress(
+        basicCardResponse.mBillingAddress = new PaymentAddress(
             GetOwner(), rawData.billingAddress.country,
             rawData.billingAddress.addressLine, rawData.billingAddress.region,
             rawData.billingAddress.regionCode, rawData.billingAddress.city,
             rawData.billingAddress.dependentLocality,
             rawData.billingAddress.postalCode,
             rawData.billingAddress.sortingCode,
             rawData.billingAddress.organization,
             rawData.billingAddress.recipient, rawData.billingAddress.phone);
--- a/dom/plugins/test/mochitest/head.js
+++ b/dom/plugins/test/mochitest/head.js
@@ -50,17 +50,17 @@ function getPlatform() {
  * uitls sendNativeMouseScrollEvent.
  */
 function nativeVerticalWheelEventMsg() {
   switch (getPlatform()) {
     case "windows": return 0x020A; // WM_MOUSEWHEEL
     case "mac": return 0; // value is unused, can be anything
     case "linux": return 4; // value is unused, pass GDK_SCROLL_SMOOTH anyway
   }
-  throw "Native wheel events not supported on platform " + getPlatform();
+  throw new Error("Native wheel events not supported on platform " + getPlatform());
 }
 
 /**
  * Waits for the first dom "scroll" event.
  */
 function waitScrollStart(aTarget) {
   return new Promise((resolve, reject) => {
     aTarget.addEventListener("scroll", function(event) {
--- a/dom/quota/ActorsParent.cpp
+++ b/dom/quota/ActorsParent.cpp
@@ -7642,62 +7642,102 @@ void ClearRequestBase::DeleteFiles(Quota
     nsCString origin;
     bool persisted;
     rv = aQuotaManager->GetDirectoryMetadata2WithRestore(
         file, persistent, &timestamp, &persisted, suffix, group, origin);
     if (NS_WARN_IF(NS_FAILED(rv))) {
       return;
     }
 
+    bool initialized;
+    if (aPersistenceType == PERSISTENCE_TYPE_PERSISTENT) {
+      initialized = aQuotaManager->IsOriginInitialized(origin);
+    } else {
+      initialized = aQuotaManager->IsTemporaryStorageInitialized();
+    }
+
     UsageInfo usageInfo;
 
     if (!mClientType.IsNull()) {
-      Client::Type clientType = mClientType.Value();
-
-      nsAutoString clientDirectoryName;
-      rv = Client::TypeToText(clientType, clientDirectoryName);
-      if (NS_WARN_IF(NS_FAILED(rv))) {
-        return;
-      }
-
-      rv = file->Append(clientDirectoryName);
-      if (NS_WARN_IF(NS_FAILED(rv))) {
-        return;
-      }
-
-      bool exists;
-      rv = file->Exists(&exists);
-      if (NS_WARN_IF(NS_FAILED(rv))) {
+      // Checking whether there is any other client in the directory is needed.
+      // If there is not, removing whole directory is needed.
+      nsCOMPtr<nsIDirectoryEnumerator> originEntries;
+      bool hasOtherClient = false;
+      if (NS_WARN_IF(NS_FAILED(
+              file->GetDirectoryEntries(getter_AddRefs(originEntries)))) ||
+          !originEntries) {
         return;
       }
 
-      if (!exists) {
-        continue;
-      }
-
-      bool initialized;
-      if (aPersistenceType == PERSISTENCE_TYPE_PERSISTENT) {
-        initialized = aQuotaManager->IsOriginInitialized(origin);
-      } else {
-        initialized = aQuotaManager->IsTemporaryStorageInitialized();
-      }
-
-      Client* client = aQuotaManager->GetClient(clientType);
-      MOZ_ASSERT(client);
-
-      Atomic<bool> dummy(false);
+      nsCOMPtr<nsIFile> clientFile;
+      while (NS_SUCCEEDED((rv = originEntries->GetNextFile(
+                               getter_AddRefs(clientFile)))) &&
+             clientFile) {
+        bool isDirectory;
+        rv = clientFile->IsDirectory(&isDirectory);
+        if (NS_WARN_IF(NS_FAILED(rv))) {
+          return;
+        }
+
+        if (!isDirectory) {
+          continue;
+        }
+
+        nsString leafName;
+        rv = clientFile->GetLeafName(leafName);
+        if (NS_WARN_IF(NS_FAILED(rv))) {
+          return;
+        }
+
+        Client::Type clientType;
+        rv = Client::TypeFromText(leafName, clientType);
+        if (NS_FAILED(rv)) {
+          UNKNOWN_FILE_WARNING(leafName);
+          continue;
+        }
+
+        if (clientType != mClientType.Value()) {
+          hasOtherClient = true;
+          break;
+        }
+      }
+
+      if (hasOtherClient) {
+        nsAutoString clientDirectoryName;
+        rv = Client::TypeToText(mClientType.Value(), clientDirectoryName);
+        if (NS_WARN_IF(NS_FAILED(rv))) {
+          return;
+        }
+
+        rv = file->Append(clientDirectoryName);
+        if (NS_WARN_IF(NS_FAILED(rv))) {
+          return;
+        }
+
+        bool exists;
+        rv = file->Exists(&exists);
+        if (NS_WARN_IF(NS_FAILED(rv))) {
+          return;
+        }
+
+        if (!exists) {
+          continue;
+        }
+      }
+
       if (initialized) {
+        Client* client = aQuotaManager->GetClient(mClientType.Value());
+        MOZ_ASSERT(client);
+
+        Atomic<bool> dummy(false);
         rv = client->GetUsageForOrigin(aPersistenceType, group, origin, dummy,
                                        &usageInfo);
-      } else {
-        rv = client->InitOrigin(aPersistenceType, group, origin, dummy,
-                                &usageInfo);
-      }
-      if (NS_WARN_IF(NS_FAILED(rv))) {
-        return;
+        if (NS_WARN_IF(NS_FAILED(rv))) {
+          return;
+        }
       }
     }
 
     for (uint32_t index = 0; index < 10; index++) {
       // We can't guarantee that this will always succeed on Windows...
       if (NS_SUCCEEDED((rv = file->Remove(true)))) {
         break;
       }
@@ -7706,16 +7746,22 @@ void ClearRequestBase::DeleteFiles(Quota
 
       PR_Sleep(PR_MillisecondsToInterval(200));
     }
 
     if (NS_FAILED(rv)) {
       NS_WARNING("Failed to remove directory, giving up!");
     }
 
+    // If it hasn't been initialized, we don't need to update the quota and
+    // notify the removing client.
+    if (!initialized) {
+      return;
+    }
+
     if (aPersistenceType != PERSISTENCE_TYPE_PERSISTENT) {
       if (mClientType.IsNull()) {
         aQuotaManager->RemoveQuotaForOrigin(aPersistenceType, group, origin);
       } else {
         aQuotaManager->DecreaseUsageForOrigin(aPersistenceType, group, origin,
                                               usageInfo.TotalUsage());
       }
     }
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..7d7985ddd04de0a65bfb80635e928f546cd7aa02
GIT binary patch
literal 7380
zc%0=}X;4#F6n+qv$6^QrQ8s1QDi|=Lpj03r9V9?l1W{24h={Tk!r}s=5KJj3hzn{F
ztT?q=+1yg<AR?s;;yO;%;$T&jYSE~rQkT*^GD+@zdD$5H$HKfJ4BYR#=R4my_njNc
zBx&g**jIH`RtN%op$G-Z;-qJ?lM-DbqYaUU=Bk)0Q!8VJG)$3D;VM(%f1t?PU=Fm(
z7+6JH7!eyG{G5m_M>H#Aro9&}AFK`zx#&va_SG|@pL8+zwE3pT9n09dvam#OGGI4t
zz>E2;J(+Jlr+~}T(wlgcdXCl77BJ=4s`9~E$nt0VweM+otXlr2y1lvlqXGJaf&&TL
zChiz~AD?|A_)2zWeeacP_a^2>8@=jF(G9D$o4X?NX6lmX8+%*5Hf|uh?2QQPasTp+
z(czoL6klFnXG`xpelltsQgO;#YyS28GtKAgx-Xcqbg|E~8tRyLt{o(E>fffME$g~}
zk^k~g-YntY{u$WI9d^IevT6QEV_U-rm$KQ+>3HPn&W#LLyPDJNz6K*w`wgoHOrJ_?
z0~f(H>w$pRw_8#4Q|FZ$p-f%W_&c8CJE2%wX2__Nq6B=)&4#4Z6G|By8_e)yMMO(7
zfIZ?25HB+Zm*Eo#E+<Z)71459tlV5C#8DHH;^N~3!9h_1qbOczp=N~{X&S{+quG*D
zG)(omghc#i(_TpOC@I!r?4!D_7{G$oMGbiE9&?0*c?c*%M==}?1@P0V#MSKV6pkdA
z-bTC@0r{|prxC1P!{KB&IXUI#=F(ErHzcQ|uxaU;NfOW>Mk;Wirctm$PO5_@uZoud
z=NazANskA1Qx6zQ35G6a7_9arceqj(CY2j7-DuPAW*}*C{y*vQ5$z>)udmtOF>Wyp
z4|UwSe&<kB>E>iB7II6#k1aH`xllgaYv-+r#SL>W+hWC$pU}YR&9U_5O{UixaT_>U
zmbJR`a}ww)1_IsBHpkZ4*9-(rYQ5gm<JWEGRpw-AQaXzpedy_uM(57HjEw%j-+Hs>
zj2PG!n0@{r+e$#z>LxelJbh9!#Pku=a=%JTIplP=S6CIzR2JrkwFob@MQKr(6WJ4Q
z4CO`Qst87jLzJVtjR76BifonQt>QgK23#S7t{JyN7VVf-kR_wIiW`Nt*d?sT(;DJ)
zgP|`pwtvzcJ8k!+t(U0h7`rxXedfQGp?xH+H}|ov*7N&4ZV&Cb%NXvv-&hx)tq!8!
zb=X(RyUgle+{p`=aUpZh-g>i{b{^{uht>tX%Fgy^TX}?3YuOzVIQ_2!QC@c2W^|Y4
zM-Kaba=3peKK<nCQ=3c{q@6!nO7TU_X%@S-znxh`d0904>Ou#(p@{kd4Y8P~bEc{2
zY25ec(2#qkbDOx6V{{)}4R^CHt)WgXwYb&s>@aKU!*pIz+4+%4*Kd!Q`0A8RU3##m
z|2E~6AooOmS>)`m9~e`(wcM(%j}1Ke<VSvm6>WSHCTzxfp8ZpxB8Y|c9D@~bHUKpq
zXB$K(XZnxT2C0c0_9`}qjcqZ&ecfX05pA(Qbl5~NPk)s6i>t@USQ(-aFS9I5rf=YR
zAq~hDl`+`!;WSWUAsiIh1?LM;lW{)hf5)Bk_VRs^n8!}dNJ*q6q^C+cq+=7l2S36A
zDmeN<K(OuFG~wGW9#nyQ@t}~97_u1s!!Q@|!RM?(FCt3k_#D{s@+y0r;`6FX50y!9
ztlSijbFJWaJ6$ywCa@FMB&uO<x;n5>?6&_Cd(+jl37i4tHe4_!e4G(&{|h6E_G_1x
z5r{8$6}AZ-<n5pR9F0Aq?G3et&3Vl9CXq<FYyIQX7$L`2W){#YoOXqUhsPah+~F)b
z<D*9W1002fG=<x7QFA*pbLjGSWN|O#M8Kt)KQ6k-ynIDFlS>Z_)c@wsp-TVJ3BGd?
z$=hMHabO$L(4>;&U7{F@Kl%(ZupiQi9F<(Dh!CXnICwu`3?S5RnF}BQSw|%!Ik*|w
z3k50Ed&U+G1iW5AB_34-VGKd=K?r<cQYdL)pslQ+z+MwULCz+QQTDz76ud+eA-Fw2
zCASA4lI-09h}21dBzI0kB{vBmf$Tj3NMKG<nLu>5p^{q#P-Tn+Y-Vnd%9-ksK<bNU
zMMd^7hwQOOiy)`?G$?bND+GC05u}oQC}VPim^`)BX95$0w?CE`(Ei>~Vrs_7hN}=b
zN78u_oFrW#VwR4|j)0iNBO{&(4AB!}I;yLFh`8{GNU;GE90B3*qEOYtlb%wjWZVRq
zut^FmEpv$JG655`dGRoXhXfUKfFOU-Q=k}58W>&Pj3q^HXQb)Nl?HxAM<o*%2rfHs
zf#By=0|yJnrU@<>i~y7yPOPbc$^?*3;@5OVjldad1a5}PeM=;mdW~(nnk<T14W^v@
zI{ae{OgOU<i6}dyfPUCNL8WwwUzk;M1RaX&g*p^aIJPQyf(~WN#7I32;kcCLYXKBj
z)|j|(8G}QVzCMF5)}ZYRx7bruk`wBh;x}nL3;4F|0<pBH$uj!N4iahWU`SJh-y4M;
J!Os}`?O!YHgsK1l
--- a/dom/quota/test/unit/head.js
+++ b/dom/quota/test/unit/head.js
@@ -151,16 +151,27 @@ function initChromeOrigin(persistence, c
 function clear(callback)
 {
   let request = SpecialPowers._getQuotaManager().clear();
   request.callback = callback;
 
   return request;
 }
 
+function clearClient(principal, persistence, client, callback)
+{
+  let request =
+    SpecialPowers._getQuotaManager().clearStoragesForPrincipal(principal,
+                                                               persistence,
+                                                               client);
+  request.callback = callback;
+
+  return request;
+}
+
 function clearOrigin(principal, persistence, callback)
 {
   let request =
     SpecialPowers._getQuotaManager().clearStoragesForPrincipal(principal,
                                                                persistence);
   request.callback = callback;
 
   return request;
new file mode 100644
--- /dev/null
+++ b/dom/quota/test/unit/test_clearStorageForPrincipal.js
@@ -0,0 +1,54 @@
+/**
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+/**
+ * This test is an unit test for clearStorageForPrincipal. It verifies that if
+ * the removing client is the last client in the targeting origin, then it is
+ * expected to remove the origin directory as well.
+ */
+
+async function testSteps()
+{
+  const testingOrigins = [
+    {
+      origin: "http://example.com",
+      path: "storage/default/http+++example.com/",
+      only_idb: false
+    },
+    {
+      origin: "http://www.mozilla.org",
+      path: "storage/default/http+++www.mozilla.org/",
+      only_idb: true
+    }
+  ];
+  const removingClient = "idb";
+
+  info("Installing package to create the environment");
+  // The package is manually created and it contains:
+  // - storage/default/http+++www.mozilla.org/idb/
+  // - storage/default/http+++www.example.com/idb/
+  // - storage/default/http+++www.example.com/cache/
+  installPackage("clearStorageForPrincipal_profile");
+
+  let request;
+  let file;
+  for (let i = 0; i < testingOrigins.length; ++i) {
+    info("Clearing");
+    request = clearClient(getPrincipal(testingOrigins[i].origin), null,
+                          removingClient);
+    await requestFinished(request);
+
+    info("Verifying");
+    file = getRelativeFile(testingOrigins[i].path + removingClient);
+    ok(!file.exists(), "Client file doesn't exist");
+
+    file = getRelativeFile(testingOrigins[i].path);
+    if (testingOrigins[i].only_idb) {
+      ok(!file.exists(), "Origin file doesn't exist");
+    } else {
+      ok(file.exists(), "Origin file does exist");
+    }
+  }
+}
--- a/dom/quota/test/unit/xpcshell.ini
+++ b/dom/quota/test/unit/xpcshell.ini
@@ -1,16 +1,17 @@
 # This Source Code Form is subject to the terms of the Mozilla Public
 # License, v. 2.0. If a copy of the MPL was not distributed with this
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 [DEFAULT]
 head = head.js
 support-files =
   basics_profile.zip
+  clearStorageForPrincipal_profile.zip
   createLocalStorage_profile.zip
   defaultStorageUpgrade_profile.zip
   getUsage_profile.zip
   groupMismatch_profile.zip
   idbSubdirUpgrade1_profile.zip
   idbSubdirUpgrade2_profile.zip
   morgueCleanup_profile.zip
   obsoleteOriginAttributes_profile.zip
@@ -21,16 +22,17 @@ support-files =
   storagePersistentUpgrade_profile.zip
   tempMetadataCleanup_profile.zip
   version2_1upgrade_profile.zip
   version2_2upgrade_profile.zip
 
 [test_basics.js]
 [test_bad_origin_directory.js]
 [test_createLocalStorage.js]
+[test_clearStorageForPrincipal.js]
 [test_defaultStorageUpgrade.js]
 [test_getUsage.js]
 [test_groupMismatch.js]
 [test_idbSubdirUpgrade.js]
 [test_initTemporaryStorage.js]
 [test_listInitializedOrigins.js]
 [test_morgueCleanup.js]
 [test_obsoleteOriginAttributesUpgrade.js]
--- a/dom/webidl/BasicCardPayment.webidl
+++ b/dom/webidl/BasicCardPayment.webidl
@@ -1,32 +1,32 @@
 /* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* 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/.
  *
  * The origin of this WebIDL file is
- *   https://www.w3.org/TR/payment-request/#paymentrequest-interface
+ *   https://www.w3.org/TR/payment-method-basic-card/
  */
 
 dictionary BasicCardRequest {
   sequence<DOMString> supportedNetworks = [];
 };
 
 dictionary BasicCardResponse {
-           DOMString cardholderName;
+           DOMString cardholderName = "";
   required DOMString cardNumber;
-           DOMString expiryMonth;
-           DOMString expiryYear;
-           DOMString cardSecurityCode;
-           PaymentAddress? billingAddress;
+           DOMString expiryMonth = "";
+           DOMString expiryYear = "";
+           DOMString cardSecurityCode = "";
+           PaymentAddress? billingAddress = null;
 };
 
 dictionary BasicCardChangeDetails {
-  PaymentAddress? billingAddress;
+  PaymentAddress? billingAddress = null;
 };
 
 dictionary BasicCardErrors {
   DOMString cardNumber;
   DOMString cardholderName;
   DOMString cardSecurityCode;
   DOMString expiryMonth;
   DOMString expiryYear;
--- a/dom/webidl/RTCStatsReport.webidl
+++ b/dom/webidl/RTCStatsReport.webidl
@@ -28,56 +28,67 @@ dictionary RTCStats {
   DOMString id;
 };
 
 dictionary RTCRtpStreamStats : RTCStats {
   unsigned long ssrc;
   DOMString mediaType;
   DOMString kind;
   DOMString transportId;
-  DOMString codecId;
-  // Local only measurements, RTCP related but not communicated via RTCP. Not
-  // present in RTCP case. See Bug 1367562
+};
+
+dictionary RTCReceivedRtpStreamStats: RTCRtpStreamStats {
+  unsigned long packetsReceived;
+  unsigned long packetsLost;
+  double jitter;
+  unsigned long discardedPackets; // non-standard alias for packetsDiscarded
+  unsigned long packetsDiscarded;
+};
+
+dictionary RTCInboundRtpStreamStats : RTCReceivedRtpStreamStats {
+  DOMString remoteId;
+  unsigned long framesDecoded;
+  unsigned long long bytesReceived;
+  unsigned long nackCount;
   unsigned long firCount;
   unsigned long pliCount;
-  unsigned long nackCount;
-  unsigned long long qpSum;
-
-  DOMString remoteId; // See Bug 1515716
-  DOMString localId;  // See Bug 1515716
-  DOMString mediaTrackId;
-
-  // Video encoder/decoder measurements, not present in RTCP case
-  double bitrateMean;
-  double bitrateStdDev;
-  double framerateMean;
-  double framerateStdDev;
+  double bitrateMean; // deprecated, to be removed in Bug 1367562
+  double bitrateStdDev; // deprecated, to be removed in Bug 1367562
+  double framerateMean; // deprecated, to be removed in Bug 1367562
+  double framerateStdDev; // deprecated, to be removed in Bug 1367562
 };
 
-dictionary RTCInboundRTPStreamStats : RTCRtpStreamStats {
-  unsigned long packetsReceived;
-  unsigned long long bytesReceived;
-  double jitter;
-  unsigned long packetsLost;
+dictionary RTCRemoteInboundRtpStreamStats : RTCReceivedRtpStreamStats {
+  DOMString localId;
+  long long bytesReceived; // Deprecated, to be removed in Bug 1529405
   double roundTripTime;
+};
 
-  // Video decoder measurement, not present in RTCP case
-  unsigned long discardedPackets;
-  unsigned long framesDecoded;
+dictionary RTCSentRtpStreamStats : RTCRtpStreamStats {
+  unsigned long packetsSent;
+  unsigned long long bytesSent;
 };
 
+dictionary RTCOutboundRtpStreamStats : RTCSentRtpStreamStats {
+  DOMString remoteId;
+  unsigned long framesEncoded;
+  unsigned long long qpSum;
+  unsigned long nackCount;
+  unsigned long firCount;
+  unsigned long pliCount;
+  double bitrateMean; // deprecated, to be removed in Bug 1367562
+  double bitrateStdDev; // deprecated, to be removed in Bug 1367562
+  double framerateMean; // deprecated, to be removed in Bug 1367562
+  double framerateStdDev; // deprecated, to be removed in Bug 1367562
+  unsigned long droppedFrames; // non-spec alias for framesDropped
+  							   // to be deprecated in Bug 1225720
+};
 
-dictionary RTCOutboundRTPStreamStats : RTCRtpStreamStats {
-  unsigned long packetsSent;
-  unsigned long long bytesSent;
-  double targetBitrate;  // config encoder bitrate target of this SSRC in bits/s
-
-  // Video encoder measurements, not present in RTCP case
-  unsigned long droppedFrames;
-  unsigned long framesEncoded;
+dictionary RTCRemoteOutboundRtpStreamStats : RTCSentRtpStreamStats {
+  DOMString localId;
 };
 
 dictionary RTCMediaStreamTrackStats : RTCStats {
   DOMString trackIdentifier;      // track.id property
   boolean remoteSource;
   sequence<DOMString> ssrcIds;
   // Stuff that makes sense for video
   unsigned long frameWidth;
@@ -172,37 +183,39 @@ dictionary RTCCodecStats : RTCStats {
   unsigned long channels;          // 2=stereo, missing for most other cases.
   DOMString parameters;            // From SDP description line
 };
 
 // This is the internal representation of the report in this implementation
 // to be received from c++
 
 dictionary RTCStatsReportInternal {
-  DOMString                               pcid = "";
-  sequence<RTCInboundRTPStreamStats>      inboundRTPStreamStats;
-  sequence<RTCOutboundRTPStreamStats>     outboundRTPStreamStats;
-  sequence<RTCRTPContributingSourceStats> rtpContributingSourceStats;
-  sequence<RTCMediaStreamTrackStats>      mediaStreamTrackStats;
-  sequence<RTCMediaStreamStats>           mediaStreamStats;
-  sequence<RTCTransportStats>             transportStats;
-  sequence<RTCIceComponentStats>          iceComponentStats;
-  sequence<RTCIceCandidatePairStats>      iceCandidatePairStats;
-  sequence<RTCIceCandidateStats>          iceCandidateStats;
-  sequence<RTCCodecStats>                 codecStats;
-  DOMString                               localSdp;
-  DOMString                               remoteSdp;
-  DOMHighResTimeStamp                     timestamp;
-  unsigned long                           iceRestarts;
-  unsigned long                           iceRollbacks;
-  boolean                                 offerer; // Is the PC the offerer
-  boolean                                 closed; // Is the PC now closed
-  sequence<RTCIceCandidateStats>          trickledIceCandidateStats;
-  sequence<DOMString>                     rawLocalCandidates;
-  sequence<DOMString>                     rawRemoteCandidates;
+  DOMString                                 pcid = "";
+  sequence<RTCInboundRtpStreamStats>        inboundRtpStreamStats;
+  sequence<RTCOutboundRtpStreamStats>       outboundRtpStreamStats;
+  sequence<RTCRemoteInboundRtpStreamStats>  remoteInboundRtpStreamStats;
+  sequence<RTCRemoteOutboundRtpStreamStats> remoteOutboundRtpStreamStats;
+  sequence<RTCRTPContributingSourceStats>   rtpContributingSourceStats;
+  sequence<RTCMediaStreamTrackStats>        mediaStreamTrackStats;
+  sequence<RTCMediaStreamStats>             mediaStreamStats;
+  sequence<RTCTransportStats>               transportStats;
+  sequence<RTCIceComponentStats>            iceComponentStats;
+  sequence<RTCIceCandidatePairStats>        iceCandidatePairStats;
+  sequence<RTCIceCandidateStats>            iceCandidateStats;
+  sequence<RTCCodecStats>                   codecStats;
+  DOMString                                 localSdp;
+  DOMString                                 remoteSdp;
+  DOMHighResTimeStamp                       timestamp;
+  unsigned long                             iceRestarts;
+  unsigned long                             iceRollbacks;
+  boolean                                   offerer; // Is the PC the offerer
+  boolean                                   closed; // Is the PC now closed
+  sequence<RTCIceCandidateStats>            trickledIceCandidateStats;
+  sequence<DOMString>                       rawLocalCandidates;
+  sequence<DOMString>                       rawRemoteCandidates;
 };
 
 [Pref="media.peerconnection.enabled",
  JSImplementation="@mozilla.org/dom/rtcstatsreport;1"]
 interface RTCStatsReport {
   readonly maplike<DOMString, object>;
   [ChromeOnly]
   readonly attribute DOMString mozPcid;
--- a/gfx/layers/AnimationHelper.cpp
+++ b/gfx/layers/AnimationHelper.cpp
@@ -161,18 +161,17 @@ static AnimationHelper::SampleResult Sam
   // call site that the value that would have been computed matches the stored
   // value that we end up using. This flag is used to ensure we populate
   // aAnimationValue in this scenario.
   bool shouldBeSkipped = false;
 #endif
   // Process in order, since later animations override earlier ones.
   for (PropertyAnimation& animation : aPropertyAnimations) {
     MOZ_ASSERT(
-        (!animation.mOriginTime.IsNull() &&
-         animation.mStartTime.type() == MaybeTimeDuration::TTimeDuration) ||
+        (!animation.mOriginTime.IsNull() && animation.mStartTime.isSome()) ||
             animation.mIsNotPlaying,
         "If we are playing, we should have an origin time and a start time");
 
     // Determine if the animation was play-pending and used a ready time later
     // than the previous frame time.
     //
     // To determine this, _all_ of the following conditions need to hold:
     //
@@ -187,17 +186,17 @@ static AnimationHelper::SampleResult Sam
         !aPreviousFrameTime.IsNull()) {
       // This is the inverse of the calculation performed in
       // AnimationInfo::StartPendingAnimations to calculate the start time of
       // play-pending animations.
       // Note that we have to calculate (TimeStamp + TimeDuration) last to avoid
       // underflow in the middle of the calulation.
       const TimeStamp readyTime =
           animation.mOriginTime +
-          (animation.mStartTime.get_TimeDuration() +
+          (animation.mStartTime.ref() +
            animation.mHoldTime.MultDouble(1.0 / animation.mPlaybackRate));
       hasFutureReadyTime =
           !readyTime.IsNull() && readyTime > aPreviousFrameTime;
     }
     // Use the previous vsync time to make main thread animations and compositor
     // more closely aligned.
     //
     // On the first frame where we have animations the previous timestamp will
@@ -211,21 +210,19 @@ static AnimationHelper::SampleResult Sam
     // jumping backwards into the range prior to when the animation starts.
     const TimeStamp& timeStamp =
         aPreviousFrameTime.IsNull() || hasFutureReadyTime ? aCurrentFrameTime
                                                           : aPreviousFrameTime;
 
     // If the animation is not currently playing, e.g. paused or
     // finished, then use the hold time to stay at the same position.
     TimeDuration elapsedDuration =
-        animation.mIsNotPlaying ||
-                animation.mStartTime.type() != MaybeTimeDuration::TTimeDuration
+        animation.mIsNotPlaying || animation.mStartTime.isNothing()
             ? animation.mHoldTime
-            : (timeStamp - animation.mOriginTime -
-               animation.mStartTime.get_TimeDuration())
+            : (timeStamp - animation.mOriginTime - animation.mStartTime.ref())
                   .MultDouble(animation.mPlaybackRate);
 
     ComputedTiming computedTiming = dom::AnimationEffect::GetComputedTimingAt(
         dom::Nullable<TimeDuration>(elapsedDuration), animation.mTiming,
         animation.mPlaybackRate);
 
     if (computedTiming.mProgress.IsNull()) {
       continue;
--- a/gfx/layers/AnimationHelper.h
+++ b/gfx/layers/AnimationHelper.h
@@ -47,17 +47,17 @@ struct PropertyAnimation {
   dom::Nullable<double> mProgressOnLastCompose;
   uint64_t mCurrentIterationOnLastCompose = 0;
   // These two variables are used for a similar optimization above but are
   // applied to the timing function in each keyframe.
   uint32_t mSegmentIndexOnLastCompose = 0;
   dom::Nullable<double> mPortionInSegmentOnLastCompose;
 
   TimeStamp mOriginTime;
-  MaybeTimeDuration mStartTime;
+  Maybe<TimeDuration> mStartTime;
   TimeDuration mHoldTime;
   float mPlaybackRate;
   dom::IterationCompositeOperation mIterationComposite;
   bool mIsNotPlaying;
 };
 
 struct PropertyAnimationGroup {
   nsCSSPropertyID mProperty;
--- a/gfx/layers/AnimationInfo.cpp
+++ b/gfx/layers/AnimationInfo.cpp
@@ -83,33 +83,31 @@ void AnimationInfo::SetCompositorAnimati
 bool AnimationInfo::StartPendingAnimations(const TimeStamp& aReadyTime) {
   bool updated = false;
   for (size_t animIdx = 0, animEnd = mAnimations.Length(); animIdx < animEnd;
        animIdx++) {
     Animation& anim = mAnimations[animIdx];
 
     // If the animation is doing an async update of its playback rate, then we
     // want to match whatever its current time would be at *aReadyTime*.
-    if (!std::isnan(anim.previousPlaybackRate()) &&
-        anim.startTime().type() == MaybeTimeDuration::TTimeDuration &&
+    if (!std::isnan(anim.previousPlaybackRate()) && anim.startTime().isSome() &&
         !anim.originTime().IsNull() && !anim.isNotPlaying()) {
       TimeDuration readyTime = aReadyTime - anim.originTime();
       anim.holdTime() = dom::Animation::CurrentTimeFromTimelineTime(
-          readyTime, anim.startTime().get_TimeDuration(),
-          anim.previousPlaybackRate());
+          readyTime, anim.startTime().ref(), anim.previousPlaybackRate());
       // Make start time null so that we know to update it below.
-      anim.startTime() = null_t();
+      anim.startTime() = Nothing();
     }
 
     // If the animation is play-pending, resolve the start time.
-    if (anim.startTime().type() == MaybeTimeDuration::Tnull_t &&
-        !anim.originTime().IsNull() && !anim.isNotPlaying()) {
+    if (anim.startTime().isNothing() && !anim.originTime().IsNull() &&
+        !anim.isNotPlaying()) {
       TimeDuration readyTime = aReadyTime - anim.originTime();
-      anim.startTime() = dom::Animation::StartTimeFromTimelineTime(
-          readyTime, anim.holdTime(), anim.playbackRate());
+      anim.startTime() = Some(dom::Animation::StartTimeFromTimelineTime(
+          readyTime, anim.holdTime(), anim.playbackRate()));
       updated = true;
     }
   }
   return updated;
 }
 
 void AnimationInfo::TransferMutatedFlagToLayer(Layer* aLayer) {
   if (mMutated) {
--- a/gfx/layers/apz/src/AndroidDynamicToolbarAnimator.cpp
+++ b/gfx/layers/apz/src/AndroidDynamicToolbarAnimator.cpp
@@ -1001,16 +1001,17 @@ void AndroidDynamicToolbarAnimator::Noti
     return;
   }
 
   if (mControllerState == eAnimationStopPending) {
     mControllerState = eNothingPending;
   }
 
   mControllerToolbarHeight = aHeight;
+  RequestComposite();
 }
 
 void AndroidDynamicToolbarAnimator::RequestComposite() {
   if (!CompositorThreadHolder::IsInCompositorThread()) {
     CompositorThreadHolder::Loop()->PostTask(NewRunnableMethod(
         "AndroidDynamicToolbarAnimator::RequestComposite", this,
         &AndroidDynamicToolbarAnimator::RequestComposite));
     return;
--- a/gfx/layers/client/TiledContentClient.cpp
+++ b/gfx/layers/client/TiledContentClient.cpp
@@ -714,19 +714,19 @@ TileDescriptor TileClient::GetTileDescri
   bool readLocked = mFrontBuffer->OnForwardedToHost();
   bool readLockedOnWhite = false;
 
   if (mFrontBufferOnWhite) {
     readLockedOnWhite = mFrontBufferOnWhite->OnForwardedToHost();
   }
 
   return TexturedTileDescriptor(
-      nullptr, mFrontBuffer->GetIPDLActor(),
-      mFrontBufferOnWhite ? MaybeTexture(mFrontBufferOnWhite->GetIPDLActor())
-                          : MaybeTexture(null_t()),
+      nullptr, mFrontBuffer->GetIPDLActor(), Nothing(),
+      mFrontBufferOnWhite ? Some(mFrontBufferOnWhite->GetIPDLActor())
+                          : Nothing(),
       mUpdateRect, readLocked, readLockedOnWhite, wasPlaceholder);
 }
 
 void ClientTiledLayerBuffer::UnlockTile(TileClient& aTile) {
   // We locked the back buffer, and flipped so we now need to unlock the front
   if (aTile.mFrontBuffer && aTile.mFrontBuffer->IsLocked()) {
     aTile.mFrontBuffer->Unlock();
     aTile.mFrontBuffer->SyncWithObject(
--- a/gfx/layers/composite/TiledContentHost.cpp
+++ b/gfx/layers/composite/TiledContentHost.cpp
@@ -304,19 +304,19 @@ bool TiledLayerBufferComposite::UseTiles
 
         if (lockedTexturePid) {
           MOZ_ASSERT(lockedTexturePid == actor->OtherPid());
         }
         lockedTexturePid = actor->OtherPid();
       }
     }
 
-    if (texturedDesc.textureOnWhite().type() == MaybeTexture::TPTextureParent) {
-      tile.mTextureHostOnWhite = TextureHost::AsTextureHost(
-          texturedDesc.textureOnWhite().get_PTextureParent());
+    if (texturedDesc.textureOnWhiteParent().isSome()) {
+      tile.mTextureHostOnWhite =
+          TextureHost::AsTextureHost(texturedDesc.textureOnWhiteParent().ref());
       if (texturedDesc.readLockedOnWhite()) {
         tile.mTextureHostOnWhite->SetReadLocked();
         auto actor = tile.mTextureHostOnWhite->GetIPDLActor();
         if (actor && tile.mTextureHostOnWhite->IsDirectMap()) {
           lockedTextureSerials.AppendElement(
               TextureHost::GetTextureSerial(actor));
         }
       }
--- a/gfx/layers/ipc/CompositableTransactionParent.cpp
+++ b/gfx/layers/ipc/CompositableTransactionParent.cpp
@@ -128,20 +128,19 @@ bool CompositableParentManager::ReceiveC
         RefPtr<TextureHost> texture =
             TextureHost::AsTextureHost(texturedDesc.textureParent());
         if (texture) {
           texture->SetLastFwdTransactionId(mFwdTransactionId);
           // Make sure that each texture was handled by the compositable
           // because the recycling logic depends on it.
           MOZ_ASSERT(texture->NumCompositableRefs() > 0);
         }
-        if (texturedDesc.textureOnWhite().type() ==
-            MaybeTexture::TPTextureParent) {
+        if (texturedDesc.textureOnWhiteParent().isSome()) {
           texture = TextureHost::AsTextureHost(
-              texturedDesc.textureOnWhite().get_PTextureParent());
+              texturedDesc.textureOnWhiteParent().ref());
           if (texture) {
             texture->SetLastFwdTransactionId(mFwdTransactionId);
             // Make sure that each texture was handled by the compositable
             // because the recycling logic depends on it.
             MOZ_ASSERT(texture->NumCompositableRefs() > 0);
           }
         }
       }
--- a/gfx/layers/ipc/LayersMessages.ipdlh
+++ b/gfx/layers/ipc/LayersMessages.ipdlh
@@ -157,21 +157,16 @@ union TransformFunction {
   Scale;
   Skew;
   SkewX;
   SkewY;
   Translation;
   TransformMatrix;
 };
 
-union MaybeTimeDuration {
-  null_t;
-  TimeDuration;
-};
-
 union Animatable {
   null_t;
   float;
   nscolor;
   Rotate;
   Scale;
   Translation;
   TransformFunction[];
@@ -209,17 +204,17 @@ union AnimationData {
 };
 
 struct Animation {
   // The zero time of this Animation's timeline. May be null if isNotPlaying is
   // true.
   TimeStamp originTime;
   // The start time is relative to the originTime. This allows us to represent
   // start times in the distant past that cannot be expressed using a TimeStamp.
-  MaybeTimeDuration startTime;
+  TimeDuration? startTime;
   TimeDuration delay;
   TimeDuration endDelay;
   // The value of the animation's current time at the moment it was sent to the
   // compositor.  This value will be used for below cases:
   // 1) Animations that are play-pending. Initially these animations will have a
   //    null |startTime|. Once the animation is ready to start (i.e. painting
   //    has finished), we calculate an appropriate value of |startTime| such
   //    that playback begins from |holdTime|.
@@ -369,24 +364,19 @@ struct CrossProcessSemaphoreDescriptor {
 
 union ReadLockDescriptor {
   ShmemSection;
   CrossProcessSemaphoreDescriptor;
   uintptr_t;
   null_t;
 };
 
-union MaybeTexture {
-  PTexture;
-  null_t;
-};
-
 struct TexturedTileDescriptor {
   PTexture texture;
-  MaybeTexture textureOnWhite;
+  PTexture? textureOnWhite;
   IntRect updateRect;
   bool readLocked;
   bool readLockedOnWhite;
   bool wasPlaceholder;
 };
 
 struct PlaceholderTileDescriptor {
 };
@@ -452,21 +442,16 @@ struct OpUseTexture {
 
 struct OpUseComponentAlphaTextures {
   PTexture textureOnBlack;
   PTexture textureOnWhite;
   bool readLockedBlack;
   bool readLockedWhite;
 };
 
-union MaybeRegion {
-  nsIntRegion;
-  null_t;
-};
-
 struct OpNotifyNotUsed {
   uint64_t TextureId;
   uint64_t fwdTransactionId;
 };
 
 union CompositableOperationDetail {
   OpPaintTextureRegion;
 
--- a/gfx/layers/ipc/LayersSurfaces.ipdlh
+++ b/gfx/layers/ipc/LayersSurfaces.ipdlh
@@ -14,26 +14,16 @@ using mozilla::gfx::SurfaceFormat from "
 using mozilla::gfx::IntRect from "mozilla/gfx/Rect.h";
 using mozilla::gfx::IntSize from "mozilla/gfx/Point.h";
 using mozilla::ipc::SharedMemoryBasic::Handle from "mozilla/ipc/SharedMemoryBasic.h";
 using gfxImageFormat from "gfxTypes.h";
 
 namespace mozilla {
 namespace layers {
 
-union OverlayHandle {
-  int32_t;
-  null_t;
-};
-
-struct OverlaySource {
-  OverlayHandle handle;
-  IntSize size;
-};
-
 struct SurfaceDescriptorFileMapping {
   WindowsHandle handle;
   SurfaceFormat format;
   IntSize size;
 };
 
 struct SurfaceDescriptorDIB {
   // gfxWindowsSurface*
--- a/gfx/layers/ipc/WebRenderMessages.ipdlh
+++ b/gfx/layers/ipc/WebRenderMessages.ipdlh
@@ -33,26 +33,16 @@ using struct mozilla::void_t from "ipc/I
 
 namespace mozilla {
 namespace layers {
 
 struct RefCountedShmem {
   Shmem buffer;
 };
 
-union OptionalTransform {
-  Matrix4x4;
-  void_t;
-};
-
-union OptionalOpacity {
-  float;
-  void_t;
-};
-
 struct OpAddExternalImage {
   ExternalImageId externalImageId;
   ImageKey key;
 };
 
 struct OpPushExternalImageForTexture {
   ExternalImageId externalImageId;
   ImageKey key;
--- a/gfx/vr/VRDisplayClient.cpp
+++ b/gfx/vr/VRDisplayClient.cpp
@@ -153,20 +153,20 @@ void VRDisplayClient::FireGamepadEvents(
 
     // Send events to notify that new controllers are added
     RefPtr<dom::Gamepad> existing =
         gamepadManager->GetGamepad(gamepadId, dom::GamepadServiceType::VR);
     // ControllerState in OpenVR action-based API gets delay to query btn and
     // axis count. So, we need to check if they are more than zero.
     if ((lastState.controllerName[0] == '\0' || !existing) &&
         (state.numButtons > 0 || state.numAxes > 0)) {
-      dom::GamepadAdded info(NS_ConvertUTF8toUTF16(state.controllerName),
-                             dom::GamepadMappingType::_empty, state.hand,
-                             mDisplayInfo.mDisplayID, state.numButtons,
-                             state.numAxes, state.numHaptics);
+      dom::GamepadAdded info(
+          NS_ConvertUTF8toUTF16(state.controllerName, kVRControllerNameMaxLen),
+          dom::GamepadMappingType::_empty, state.hand, mDisplayInfo.mDisplayID,
+          state.numButtons, state.numAxes, state.numHaptics);
       dom::GamepadChangeEventBody body(info);
       dom::GamepadChangeEvent event(gamepadId, dom::GamepadServiceType::VR,
                                     body);
       gamepadManager->Update(event);
       bIsNew = true;
     }
 
     // Send events for handedness changes
--- a/gfx/vr/VRDisplayHost.cpp
+++ b/gfx/vr/VRDisplayHost.cpp
@@ -181,17 +181,17 @@ void VRDisplayHost::RemoveLayer(VRLayerP
 }
 
 void VRDisplayHost::StartFrame() {
   AUTO_PROFILER_TRACING("VR", "GetSensorState", OTHER);
 
   TimeStamp now = TimeStamp::Now();
 #if defined(MOZ_WIDGET_ANDROID)
   const TimeStamp lastFrameStart =
-      mDisplayInfo.mLastFrameStart[mDisplayInfo.mFrameId % kVRMaxLatencyFrames];
+      mLastFrameStart[mDisplayInfo.mFrameId % kVRMaxLatencyFrames];
   const bool isPresenting = mLastUpdateDisplayInfo.GetPresentingGroups() != 0;
   double duration =
       lastFrameStart.IsNull() ? 0.0 : (now - lastFrameStart).ToMilliseconds();
   /**
    * Do not start more VR frames until the last submitted frame is already
    * processed.
    */
   if (isPresenting && mLastStartedFrame > 0 &&
@@ -199,17 +199,17 @@ void VRDisplayHost::StartFrame() {
       duration < (double)ANDROID_MAX_FRAME_DURATION) {
     return;
   }
 #endif  // !defined(MOZ_WIDGET_ANDROID)
 
   ++mDisplayInfo.mFrameId;
   size_t bufferIndex = mDisplayInfo.mFrameId % kVRMaxLatencyFrames;
   mDisplayInfo.mLastSensorState[bufferIndex] = GetSensorState();
-  mDisplayInfo.mLastFrameStart[bufferIndex] = now;
+  mLastFrameStart[bufferIndex] = now;
   mFrameStarted = true;
 #if defined(MOZ_WIDGET_ANDROID)
   mLastStartedFrame = mDisplayInfo.mFrameId;
 #endif  // !defined(MOZ_WIDGET_ANDROID)
 }
 
 void VRDisplayHost::NotifyVSync() {
   /**
@@ -251,17 +251,17 @@ void VRDisplayHost::CheckWatchDog() {
    * rate, which avoids inadvertent triggering of the watchdog during
    * Oculus ASW even if every second frame is dropped.
    */
   bool bShouldStartFrame = false;
 
   // If content fails to call VRDisplay.submitFrame, we must eventually
   // time-out and trigger a new frame.
   TimeStamp lastFrameStart =
-      mDisplayInfo.mLastFrameStart[mDisplayInfo.mFrameId % kVRMaxLatencyFrames];
+      mLastFrameStart[mDisplayInfo.mFrameId % kVRMaxLatencyFrames];
   if (lastFrameStart.IsNull()) {
     bShouldStartFrame = true;
   } else {
     TimeDuration duration = TimeStamp::Now() - lastFrameStart;
     if (duration.ToMilliseconds() > gfxPrefs::VRDisplayRafMaxDuration()) {
       bShouldStartFrame = true;
     }
   }
--- a/gfx/vr/VRDisplayHost.h
+++ b/gfx/vr/VRDisplayHost.h
@@ -80,16 +80,17 @@ class VRDisplayHost {
   // This SubmitFrame() must be overridden by children and block until
   // the next frame is ready to start and the resources in aTexture can
   // safely be released.
   virtual bool SubmitFrame(const layers::SurfaceDescriptor& aTexture,
                            uint64_t aFrameId, const gfx::Rect& aLeftEyeRect,
                            const gfx::Rect& aRightEyeRect) = 0;
 
   VRDisplayInfo mDisplayInfo;
+  TimeStamp mLastFrameStart[kVRMaxLatencyFrames];
 
   nsTArray<VRLayerParent*> mLayers;
   // Weak reference to mLayers entries are cleared in
   // VRLayerParent destructor
 
  protected:
   virtual VRHMDSensorState& GetSensorState() = 0;
 
--- a/gfx/vr/VRManager.cpp
+++ b/gfx/vr/VRManager.cpp
@@ -113,33 +113,33 @@ VRManager::~VRManager() {
     VRServiceManager::Get().Shutdown();
   }
 #endif
   MOZ_COUNT_DTOR(VRManager);
 }
 
 void VRManager::Destroy() {
   StopTasks();
-  mVRDisplays.Clear();
-  mVRControllers.Clear();
+  mVRDisplayIDs.Clear();
+  mVRControllerIDs.Clear();
   for (uint32_t i = 0; i < mManagers.Length(); ++i) {
     mManagers[i]->Destroy();
   }
 #if !defined(MOZ_WIDGET_ANDROID)
   if (VRServiceManager::Get().IsServiceValid()) {
     VRServiceManager::Get().Shutdown();
   }
 #endif
   Shutdown();
   mInitialized = false;
 }
 
 void VRManager::Shutdown() {
-  mVRDisplays.Clear();
-  mVRControllers.Clear();
+  mVRDisplayIDs.Clear();
+  mVRControllerIDs.Clear();
   for (uint32_t i = 0; i < mManagers.Length(); ++i) {
     mManagers[i]->Shutdown();
   }
 #if !defined(MOZ_WIDGET_ANDROID)
   if (VRServiceManager::Get().IsServiceValid()) {
     VRServiceManager::Get().Stop();
   }
   // XRE_IsGPUProcess() is helping us to check some platforms like
@@ -290,17 +290,17 @@ void VRManager::RunTasks() {
 
 uint32_t VRManager::GetOptimalTaskInterval() {
   /**
    * When either VR content is detected or VR hardware
    * has already been activated, we schedule tasks more
    * frequently.
    */
   bool wantGranularTasks = mVRDisplaysRequested || mVRControllersRequested ||
-                           mVRDisplays.Count() || mVRControllers.Count();
+                           mVRDisplayIDs.Length() || mVRControllerIDs.Length();
   if (wantGranularTasks) {
     return kVRActiveTaskInterval;
   }
 
   return kVRIdleTaskInterval;
 }
 
 /**
@@ -310,19 +310,21 @@ uint32_t VRManager::GetOptimalTaskInterv
  * called once per VSync if it wasn't
  * called within the last 1ms.
  */
 void VRManager::Run1msTasks(double aDeltaTime) {
   for (const auto& manager : mManagers) {
     manager->Run1msTasks(aDeltaTime);
   }
 
-  for (auto iter = mVRDisplays.Iter(); !iter.Done(); iter.Next()) {
-    gfx::VRDisplayHost* display = iter.UserData();
-    display->Run1msTasks(aDeltaTime);
+  for (const auto& displayID : mVRDisplayIDs) {
+    RefPtr<VRDisplayHost> display(GetDisplay(displayID));
+    if (display) {
+      display->Run1msTasks(aDeltaTime);
+    }
   }
 }
 
 /**
  * Run10msTasks() is guaranteed not to be
  * called more than once within 10ms.
  * When VR is not active, this will be
  * called once per VSync if it wasn't
@@ -330,19 +332,21 @@ void VRManager::Run1msTasks(double aDelt
  */
 void VRManager::Run10msTasks() {
   UpdateRequestedDevices();
 
   for (const auto& manager : mManagers) {
     manager->Run10msTasks();
   }
 
-  for (auto iter = mVRDisplays.Iter(); !iter.Done(); iter.Next()) {
-    gfx::VRDisplayHost* display = iter.UserData();
-    display->Run10msTasks();
+  for (const auto& displayID : mVRDisplayIDs) {
+    RefPtr<VRDisplayHost> display(GetDisplay(displayID));
+    if (display) {
+      display->Run10msTasks();
+    }
   }
 }
 
 /**
  * Run100msTasks() is guaranteed not to be
  * called more than once within 100ms.
  * When VR is not active, this will be
  * called once per VSync if it wasn't
@@ -360,19 +364,21 @@ void VRManager::Run100msTasks() {
   RefreshVRControllers();
 
   CheckForInactiveTimeout();
 
   for (const auto& manager : mManagers) {
     manager->Run100msTasks();
   }
 
-  for (auto iter = mVRDisplays.Iter(); !iter.Done(); iter.Next()) {
-    gfx::VRDisplayHost* display = iter.UserData();
-    display->Run100msTasks();
+  for (const auto& displayID : mVRDisplayIDs) {
+    RefPtr<VRDisplayHost> display(GetDisplay(displayID));
+    if (display) {
+      display->Run100msTasks();
+    }
   }
 }
 
 void VRManager::CheckForInactiveTimeout() {
   // Shut down the VR devices when not in use
   if (mVRDisplaysRequested || mVRDisplaysRequestedNonFocus ||
       mVRControllersRequested) {
     // We are using a VR device, keep it alive
@@ -476,16 +482,37 @@ void VRManager::EnumerateVRDisplays() {
      * erraneous redundant enumeration of the same HMD by multiple managers.
      * XXX - Perhaps there will be a better way to detect duplicate displays
      * in the future.
      */
     if (manager->ShouldInhibitEnumeration()) {
       return;
     }
   }
+
+  nsTArray<RefPtr<gfx::VRDisplayHost>> displays;
+  for (const auto& manager : mManagers) {
+    manager->GetHMDs(displays);
+  }
+
+  mVRDisplayIDs.Clear();
+  for (const auto& display : displays) {
+    mVRDisplayIDs.AppendElement(display->GetDisplayInfo().GetDisplayID());
+  }
+
+  nsTArray<RefPtr<gfx::VRControllerHost>> controllers;
+  for (const auto& manager : mManagers) {
+    manager->GetControllers(controllers);
+  }
+
+  mVRControllerIDs.Clear();
+  for (const auto& controller : controllers) {
+    mVRControllerIDs.AppendElement(
+        controller->GetControllerInfo().GetControllerID());
+  }
 }
 
 void VRManager::RefreshVRDisplays(bool aMustDispatch) {
   /**
    * If we aren't viewing WebVR content, don't enumerate
    * new hardware, as it will cause some devices to power on
    * or interrupt other VR activities.
    */
@@ -505,17 +532,17 @@ void VRManager::RefreshVRDisplays(bool a
   nsTArray<RefPtr<gfx::VRDisplayHost>> displays;
   for (const auto& manager : mManagers) {
     manager->GetHMDs(displays);
   }
 
   bool displayInfoChanged = false;
   bool displaySetChanged = false;
 
-  if (displays.Length() != mVRDisplays.Count()) {
+  if (displays.Length() != mVRDisplayIDs.Length()) {
     // Catch cases where a VR display has been removed
     displaySetChanged = true;
   }
 
   for (const auto& display : displays) {
     if (!GetDisplay(display->GetDisplayInfo().GetDisplayID())) {
       // This is a new display
       displaySetChanged = true;
@@ -526,19 +553,19 @@ void VRManager::RefreshVRDisplays(bool a
       // This display's info has changed
       displayInfoChanged = true;
       break;
     }
   }
 
   // Rebuild the HashMap if there are additions or removals
   if (displaySetChanged) {
-    mVRDisplays.Clear();
+    mVRDisplayIDs.Clear();
     for (const auto& display : displays) {
-      mVRDisplays.Put(display->GetDisplayInfo().GetDisplayID(), display);
+      mVRDisplayIDs.AppendElement(display->GetDisplayInfo().GetDisplayID());
     }
   }
 
   if (displayInfoChanged || displaySetChanged || aMustDispatch) {
     DispatchVRDisplayInfoUpdate();
   }
 }
 
@@ -552,79 +579,120 @@ void VRManager::DispatchVRDisplayInfoUpd
 }
 
 /**
  * Get any VR displays that have already been enumerated without
  * activating any new devices.
  */
 void VRManager::GetVRDisplayInfo(nsTArray<VRDisplayInfo>& aDisplayInfo) {
   aDisplayInfo.Clear();
-  for (auto iter = mVRDisplays.Iter(); !iter.Done(); iter.Next()) {
-    gfx::VRDisplayHost* display = iter.UserData();
-    aDisplayInfo.AppendElement(VRDisplayInfo(display->GetDisplayInfo()));
+  for (const auto& displayID : mVRDisplayIDs) {
+    RefPtr<VRDisplayHost> display(GetDisplay(displayID));
+    if (display) {
+      aDisplayInfo.AppendElement(display->GetDisplayInfo());
+    }
   }
 }
 
+// Verify aDisplayID matches the exisiting displayID from mVRDisplayIDs list,
+// then using the exiting displayID to get VRDisplayHost from VRManagers.
 RefPtr<gfx::VRDisplayHost> VRManager::GetDisplay(const uint32_t& aDisplayID) {
-  RefPtr<gfx::VRDisplayHost> display;
-  if (mVRDisplays.Get(aDisplayID, getter_AddRefs(display))) {
-    return display;
+  bool found = false;
+  for (const auto& displayID : mVRDisplayIDs) {
+    if (displayID == aDisplayID) {
+      found = true;
+      break;
+    }
   }
+
+  if (found) {
+    nsTArray<RefPtr<gfx::VRDisplayHost>> displays;
+    for (const auto& manager : mManagers) {
+      manager->GetHMDs(displays);
+      for (const auto& display : displays) {
+        if (display->GetDisplayInfo().GetDisplayID() == aDisplayID) {
+          return display;
+        }
+      }
+    }
+  }
+
   return nullptr;
 }
 
+// Verify aControllerID matches the exisiting controllerID from mVRControllerIDs
+// list, then using the exiting controllerID to get VRControllerHost from
+// VRManagers.
 RefPtr<gfx::VRControllerHost> VRManager::GetController(
     const uint32_t& aControllerID) {
-  RefPtr<gfx::VRControllerHost> controller;
-  if (mVRControllers.Get(aControllerID, getter_AddRefs(controller))) {
-    return controller;
+  bool found = false;
+  for (const auto& controllerID : mVRControllerIDs) {
+    if (controllerID == aControllerID) {
+      found = true;
+      break;
+    }
   }
+
+  if (found) {
+    nsTArray<RefPtr<gfx::VRControllerHost>> controllers;
+    for (const auto& manager : mManagers) {
+      manager->GetControllers(controllers);
+      for (const auto& controller : controllers) {
+        if (controller->GetControllerInfo().GetControllerID() ==
+            aControllerID) {
+          return controller;
+        }
+      }
+    }
+  }
+
   return nullptr;
 }
 
 void VRManager::GetVRControllerInfo(
     nsTArray<VRControllerInfo>& aControllerInfo) {
   aControllerInfo.Clear();
-  for (auto iter = mVRControllers.Iter(); !iter.Done(); iter.Next()) {
-    gfx::VRControllerHost* controller = iter.UserData();
-    aControllerInfo.AppendElement(
-        VRControllerInfo(controller->GetControllerInfo()));
+  for (const auto& controllerID : mVRControllerIDs) {
+    RefPtr<VRControllerHost> controller(GetController(controllerID));
+    if (controller) {
+      aControllerInfo.AppendElement(controller->GetControllerInfo());
+    }
   }
 }
 
 void VRManager::RefreshVRControllers() {
   ScanForControllers();
 
   nsTArray<RefPtr<gfx::VRControllerHost>> controllers;
 
   for (uint32_t i = 0; i < mManagers.Length() && controllers.Length() == 0;
        ++i) {
     mManagers[i]->GetControllers(controllers);
   }
 
   bool controllerInfoChanged = false;
 
-  if (controllers.Length() != mVRControllers.Count()) {
+  if (controllers.Length() != mVRControllerIDs.Length()) {
     // Catch cases where VR controllers has been removed
     controllerInfoChanged = true;
   }
 
   for (const auto& controller : controllers) {
     if (!GetController(controller->GetControllerInfo().GetControllerID())) {
       // This is a new controller
       controllerInfoChanged = true;
       break;
     }
   }
 
   if (controllerInfoChanged) {
-    mVRControllers.Clear();
+    mVRControllerIDs.Clear();
     for (const auto& controller : controllers) {
-      mVRControllers.Put(controller->GetControllerInfo().GetControllerID(),
-                         controller);
+      mVRControllerIDs.AppendElement(
+          controller->GetControllerInfo().GetControllerID());
     }
   }
 }
 
 void VRManager::ScanForControllers() {
   // We don't have to do this every frame, so check if we
   // have enumerated recently
   if (!mLastControllerEnumerationTime.IsNull()) {
@@ -645,17 +713,17 @@ void VRManager::ScanForControllers() {
 
   mLastControllerEnumerationTime = TimeStamp::Now();
 }
 
 void VRManager::RemoveControllers() {
   for (uint32_t i = 0; i < mManagers.Length(); ++i) {
     mManagers[i]->RemoveControllers();
   }
-  mVRControllers.Clear();
+  mVRControllerIDs.Clear();
 }
 
 void VRManager::CreateVRTestSystem() {
   if (mPuppetManager) {
     mPuppetManager->ClearTestDisplays();
     return;
   }
 
--- a/gfx/vr/VRManager.h
+++ b/gfx/vr/VRManager.h
@@ -90,24 +90,18 @@ class VRManager {
   void EnumerateVRDisplays();
   void CheckForInactiveTimeout();
 
   typedef nsTHashtable<nsRefPtrHashKey<VRManagerParent>> VRManagerParentSet;
   VRManagerParentSet mVRManagerParents;
 
   typedef nsTArray<RefPtr<VRSystemManager>> VRSystemManagerArray;
   VRSystemManagerArray mManagers;
-
-  typedef nsRefPtrHashtable<nsUint32HashKey, gfx::VRDisplayHost>
-      VRDisplayHostHashMap;
-  VRDisplayHostHashMap mVRDisplays;
-
-  typedef nsRefPtrHashtable<nsUint32HashKey, gfx::VRControllerHost>
-      VRControllerHostHashMap;
-  VRControllerHostHashMap mVRControllers;
+  nsTArray<uint32_t> mVRDisplayIDs;
+  nsTArray<uint32_t> mVRControllerIDs;
 
   Atomic<bool> mInitialized;
 
   TimeStamp mLastControllerEnumerationTime;
   TimeStamp mLastDisplayEnumerationTime;
   TimeStamp mLastActiveTime;
   TimeStamp mLastTickTime;
   double mAccumulator100ms;
--- a/gfx/vr/gfxVR.h
+++ b/gfx/vr/gfxVR.h
@@ -64,17 +64,16 @@ struct VRDisplayInfo {
   VRDeviceType mType;
   uint32_t mPresentingGroups;
   uint32_t mGroupMask;
   uint64_t mFrameId;
   VRDisplayState mDisplayState;
   VRControllerState mControllerState[kVRControllerMaxCount];
 
   VRHMDSensorState mLastSensorState[kVRMaxLatencyFrames];
-  TimeStamp mLastFrameStart[kVRMaxLatencyFrames];
   const VRHMDSensorState& GetSensorState() const {
     return mLastSensorState[mFrameId % kVRMaxLatencyFrames];
   }
 
   VRDeviceType GetType() const { return mType; }
   uint32_t GetDisplayID() const { return mDisplayID; }
   const char* GetDisplayName() const { return mDisplayState.displayName; }
   VRDisplayCapabilityFlags GetCapabilities() const {
--- a/gfx/vr/gfxVRExternal.cpp
+++ b/gfx/vr/gfxVRExternal.cpp
@@ -330,18 +330,17 @@ void VRDisplayExternal::VibrateHaptic(ui
 
   // Populate the selected slot with new haptic state
   size_t bufferIndex = mDisplayInfo.mFrameId % kVRMaxLatencyFrames;
   VRHapticState& bestSlot = mBrowserState.hapticState[bestSlotIndex];
   bestSlot.inputFrameID =
       mDisplayInfo.mLastSensorState[bufferIndex].inputFrameID;
   bestSlot.controllerIndex = aControllerIdx;
   bestSlot.hapticIndex = aHapticIndex;
-  bestSlot.pulseStart =
-      (now - mDisplayInfo.mLastFrameStart[bufferIndex]).ToSeconds();
+  bestSlot.pulseStart = (now - mLastFrameStart[bufferIndex]).ToSeconds();
   bestSlot.pulseDuration = aDuration;
   bestSlot.pulseIntensity = aIntensity;
   // Convert from seconds to ms
   mHapticPulseRemaining[bestSlotIndex] = aDuration * 1000.0f;
   MOZ_ASSERT(bestSlotIndex <= mHapticPromises.Length());
   if (bestSlotIndex == mHapticPromises.Length()) {
     mHapticPromises.AppendElement(
         UniquePtr<VRManagerPromise>(new VRManagerPromise(aPromise)));
--- a/gfx/vr/ipc/VRMessageUtils.h
+++ b/gfx/vr/ipc/VRMessageUtils.h
@@ -13,361 +13,35 @@
 #include "mozilla/dom/GamepadMessageUtils.h"
 #include "VRManager.h"
 
 #include "gfxVR.h"
 
 namespace IPC {
 
 template <>
-struct ParamTraits<mozilla::gfx::VRDeviceType>
-    : public ContiguousEnumSerializer<
-          mozilla::gfx::VRDeviceType, mozilla::gfx::VRDeviceType(0),
-          mozilla::gfx::VRDeviceType(
-              mozilla::gfx::VRDeviceType::NumVRDeviceTypes)> {};
-
-template <>
-struct ParamTraits<mozilla::gfx::VRDisplayCapabilityFlags>
-    : public BitFlagsEnumSerializer<
-          mozilla::gfx::VRDisplayCapabilityFlags,
-          mozilla::gfx::VRDisplayCapabilityFlags::Cap_All> {};
-
-template <>
 struct ParamTraits<mozilla::gfx::OpenVRControllerType>
     : public ContiguousEnumSerializer<
           mozilla::gfx::OpenVRControllerType,
           mozilla::gfx::OpenVRControllerType::Vive,
           mozilla::gfx::OpenVRControllerType::NumOpenVRControllerTypes> {};
 
-template <>
-struct ParamTraits<mozilla::gfx::VRDisplayState> {
-  typedef mozilla::gfx::VRDisplayState paramType;
-
-  static void Write(Message* aMsg, const paramType& aParam) {
-    // TODO - VRDisplayState is asserted to be a POD type
-    //        A simple memcpy may be sufficient here, or
-    //        this code can be refactored out if we use
-    //        shmem between the VR and content process.
-    nsCString displayName;
-    displayName.Assign(aParam.displayName);
-    WriteParam(aMsg, displayName);
-    WriteParam(aMsg, aParam.capabilityFlags);
-    WriteParam(aMsg, aParam.eyeResolution.width);
-    WriteParam(aMsg, aParam.eyeResolution.height);
-    WriteParam(aMsg, aParam.suppressFrames);
-    WriteParam(aMsg, aParam.isConnected);
-    WriteParam(aMsg, aParam.isMounted);
-    WriteParam(aMsg, aParam.stageSize.width);
-    WriteParam(aMsg, aParam.stageSize.height);
-    WriteParam(aMsg, aParam.lastSubmittedFrameId);
-    WriteParam(aMsg, aParam.presentingGeneration);
-    for (int i = 0; i < 16; i++) {
-      // TODO - Should probably memcpy the whole array or
-      // convert Maxtrix4x4 to a POD type and use it
-      // instead
-      WriteParam(aMsg, aParam.sittingToStandingTransform[i]);
-    }
-    for (int i = 0; i < mozilla::gfx::VRDisplayState::NumEyes; i++) {
-      WriteParam(aMsg, aParam.eyeFOV[i]);
-      WriteParam(aMsg, aParam.eyeTranslation[i].x);
-      WriteParam(aMsg, aParam.eyeTranslation[i].y);
-      WriteParam(aMsg, aParam.eyeTranslation[i].z);
-    }
-  }
-
-  static bool Read(const Message* aMsg, PickleIterator* aIter,
-                   paramType* aResult) {
-    nsCString displayName;
-    if (!ReadParam(aMsg, aIter, &(displayName)) ||
-        !ReadParam(aMsg, aIter, &(aResult->capabilityFlags)) ||
-        !ReadParam(aMsg, aIter, &(aResult->eyeResolution.width)) ||
-        !ReadParam(aMsg, aIter, &(aResult->eyeResolution.height)) ||
-        !ReadParam(aMsg, aIter, &(aResult->suppressFrames)) ||
-        !ReadParam(aMsg, aIter, &(aResult->isConnected)) ||
-        !ReadParam(aMsg, aIter, &(aResult->isMounted)) ||
-        !ReadParam(aMsg, aIter, &(aResult->stageSize.width)) ||
-        !ReadParam(aMsg, aIter, &(aResult->stageSize.height)) ||
-        !ReadParam(aMsg, aIter, &(aResult->lastSubmittedFrameId)) ||
-        !ReadParam(aMsg, aIter, &(aResult->presentingGeneration))) {
-      return false;
-    }
-    for (int i = 0; i < 16; i++) {
-      if (!ReadParam(aMsg, aIter, &(aResult->sittingToStandingTransform[i]))) {
-        return false;
-      }
-    }
-    strncpy(aResult->displayName, displayName.BeginReading(),
-            mozilla::gfx::kVRDisplayNameMaxLen);
-    for (int i = 0; i < mozilla::gfx::VRDisplayState::NumEyes; i++) {
-      if (!ReadParam(aMsg, aIter, &(aResult->eyeFOV[i])) ||
-          !ReadParam(aMsg, aIter, &(aResult->eyeTranslation[i].x)) ||
-          !ReadParam(aMsg, aIter, &(aResult->eyeTranslation[i].y)) ||
-          !ReadParam(aMsg, aIter, &(aResult->eyeTranslation[i].z))) {
-        return false;
-      }
-    }
-    return true;
-  }
-};
-
+// VRHMDSensorState is POD, we can use PlainOldDataSerializer
+static_assert(std::is_pod<mozilla::gfx::VRHMDSensorState>::value,
+              "mozilla::gfx::VRHMDSensorState must be a POD type.");
 template <>
-struct ParamTraits<mozilla::gfx::VRDisplayInfo> {
-  typedef mozilla::gfx::VRDisplayInfo paramType;
-
-  static void Write(Message* aMsg, const paramType& aParam) {
-    WriteParam(aMsg, aParam.mType);
-    WriteParam(aMsg, aParam.mDisplayID);
-    WriteParam(aMsg, aParam.mPresentingGroups);
-    WriteParam(aMsg, aParam.mGroupMask);
-    WriteParam(aMsg, aParam.mFrameId);
-    WriteParam(aMsg, aParam.mDisplayState);
-    for (size_t i = 0; i < mozilla::ArrayLength(aParam.mLastSensorState); i++) {
-      WriteParam(aMsg, aParam.mLastSensorState[i]);
-    }
-    for (size_t i = 0; i < mozilla::ArrayLength(aParam.mLastFrameStart); i++) {
-      WriteParam(aMsg, aParam.mLastFrameStart[i]);
-    }
-    for (size_t i = 0; i < mozilla::ArrayLength(aParam.mControllerState); i++) {
-      WriteParam(aMsg, aParam.mControllerState[i]);
-    }
-  }
-
-  static bool Read(const Message* aMsg, PickleIterator* aIter,
-                   paramType* aResult) {
-    if (!ReadParam(aMsg, aIter, &(aResult->mType)) ||
-        !ReadParam(aMsg, aIter, &(aResult->mDisplayID)) ||
-        !ReadParam(aMsg, aIter, &(aResult->mPresentingGroups)) ||
-        !ReadParam(aMsg, aIter, &(aResult->mGroupMask)) ||
-        !ReadParam(aMsg, aIter, &(aResult->mFrameId)) ||
-        !ReadParam(aMsg, aIter, &(aResult->mDisplayState))) {
-      return false;
-    }
-    for (size_t i = 0; i < mozilla::ArrayLength(aResult->mLastSensorState);
-         i++) {
-      if (!ReadParam(aMsg, aIter, &(aResult->mLastSensorState[i]))) {
-        return false;
-      }
-    }
-    for (size_t i = 0; i < mozilla::ArrayLength(aResult->mLastFrameStart);
-         i++) {
-      if (!ReadParam(aMsg, aIter, &(aResult->mLastFrameStart[i]))) {
-        return false;
-      }
-    }
-    for (size_t i = 0; i < mozilla::ArrayLength(aResult->mControllerState);
-         i++) {
-      if (!ReadParam(aMsg, aIter, &(aResult->mControllerState[i]))) {
-        return false;
-      }
-    }
-    return true;
-  }
-};
-
-template <>
-struct ParamTraits<mozilla::gfx::VRPose> {
-  typedef mozilla::gfx::VRPose paramType;
-
-  static void Write(Message* aMsg, const paramType& aParam) {
-    WriteParam(aMsg, aParam.orientation[0]);
-    WriteParam(aMsg, aParam.orientation[1]);
-    WriteParam(aMsg, aParam.orientation[2]);
-    WriteParam(aMsg, aParam.orientation[3]);
-    WriteParam(aMsg, aParam.position[0]);
-    WriteParam(aMsg, aParam.position[1]);
-    WriteParam(aMsg, aParam.position[2]);
-    WriteParam(aMsg, aParam.angularVelocity[0]);
-    WriteParam(aMsg, aParam.angularVelocity[1]);
-    WriteParam(aMsg, aParam.angularVelocity[2]);
-    WriteParam(aMsg, aParam.angularAcceleration[0]);
-    WriteParam(aMsg, aParam.angularAcceleration[1]);
-    WriteParam(aMsg, aParam.angularAcceleration[2]);
-    WriteParam(aMsg, aParam.linearVelocity[0]);
-    WriteParam(aMsg, aParam.linearVelocity[1]);
-    WriteParam(aMsg, aParam.linearVelocity[2]);
-    WriteParam(aMsg, aParam.linearAcceleration[0]);
-    WriteParam(aMsg, aParam.linearAcceleration[1]);
-    WriteParam(aMsg, aParam.linearAcceleration[2]);
-  }
+struct ParamTraits<mozilla::gfx::VRHMDSensorState>
+    : public PlainOldDataSerializer<mozilla::gfx::VRHMDSensorState> {};
 
-  static bool Read(const Message* aMsg, PickleIterator* aIter,
-                   paramType* aResult) {
-    if (!ReadParam(aMsg, aIter, &(aResult->orientation[0])) ||
-        !ReadParam(aMsg, aIter, &(aResult->orientation[1])) ||
-        !ReadParam(aMsg, aIter, &(aResult->orientation[2])) ||
-        !ReadParam(aMsg, aIter, &(aResult->orientation[3])) ||
-        !ReadParam(aMsg, aIter, &(aResult->position[0])) ||
-        !ReadParam(aMsg, aIter, &(aResult->position[1])) ||
-        !ReadParam(aMsg, aIter, &(aResult->position[2])) ||
-        !ReadParam(aMsg, aIter, &(aResult->angularVelocity[0])) ||
-        !ReadParam(aMsg, aIter, &(aResult->angularVelocity[1])) ||
-        !ReadParam(aMsg, aIter, &(aResult->angularVelocity[2])) ||
-        !ReadParam(aMsg, aIter, &(aResult->angularAcceleration[0])) ||
-        !ReadParam(aMsg, aIter, &(aResult->angularAcceleration[1])) ||
-        !ReadParam(aMsg, aIter, &(aResult->angularAcceleration[2])) ||
-        !ReadParam(aMsg, aIter, &(aResult->linearVelocity[0])) ||
-        !ReadParam(aMsg, aIter, &(aResult->linearVelocity[1])) ||
-        !ReadParam(aMsg, aIter, &(aResult->linearVelocity[2])) ||
-        !ReadParam(aMsg, aIter, &(aResult->linearAcceleration[0])) ||
-        !ReadParam(aMsg, aIter, &(aResult->linearAcceleration[1])) ||
-        !ReadParam(aMsg, aIter, &(aResult->linearAcceleration[2]))) {
-      return false;
-    }
-    return true;
-  }
-};
-
-template <>
-struct ParamTraits<mozilla::gfx::VRHMDSensorState> {
-  typedef mozilla::gfx::VRHMDSensorState paramType;
-
-  static void Write(Message* aMsg, const paramType& aParam) {
-    WriteParam(aMsg, aParam.timestamp);
-    WriteParam(aMsg, aParam.inputFrameID);
-    WriteParam(aMsg, aParam.flags);
-    WriteParam(aMsg, aParam.pose);
-    for (size_t i = 0; i < mozilla::ArrayLength(aParam.leftViewMatrix); i++) {
-      WriteParam(aMsg, aParam.leftViewMatrix[i]);
-    }
-    for (size_t i = 0; i < mozilla::ArrayLength(aParam.rightViewMatrix); i++) {
-      WriteParam(aMsg, aParam.rightViewMatrix[i]);
-    }
-  }
-
-  static bool Read(const Message* aMsg, PickleIterator* aIter,
-                   paramType* aResult) {
-    if (!ReadParam(aMsg, aIter, &(aResult->timestamp)) ||
-        !ReadParam(aMsg, aIter, &(aResult->inputFrameID)) ||
-        !ReadParam(aMsg, aIter, &(aResult->flags)) ||
-        !ReadParam(aMsg, aIter, &(aResult->pose))) {
-      return false;
-    }
-    for (size_t i = 0; i < mozilla::ArrayLength(aResult->leftViewMatrix); i++) {
-      if (!ReadParam(aMsg, aIter, &(aResult->leftViewMatrix[i]))) {
-        return false;
-      }
-    }
-    for (size_t i = 0; i < mozilla::ArrayLength(aResult->rightViewMatrix);
-         i++) {
-      if (!ReadParam(aMsg, aIter, &(aResult->rightViewMatrix[i]))) {
-        return false;
-      }
-    }
-    return true;
-  }
-};
-
+// VRDisplayInfo is POD, we can use PlainOldDataSerializer
+static_assert(std::is_pod<mozilla::gfx::VRDisplayInfo>::value,
+              "mozilla::gfx::VRDisplayInfo must be a POD type.");
 template <>
-struct ParamTraits<mozilla::gfx::VRFieldOfView> {
-  typedef mozilla::gfx::VRFieldOfView paramType;
-
-  static void Write(Message* aMsg, const paramType& aParam) {
-    WriteParam(aMsg, aParam.upDegrees);
-    WriteParam(aMsg, aParam.rightDegrees);
-    WriteParam(aMsg, aParam.downDegrees);
-    WriteParam(aMsg, aParam.leftDegrees);
-  }
-
-  static bool Read(const Message* aMsg, PickleIterator* aIter,
-                   paramType* aResult) {
-    if (!ReadParam(aMsg, aIter, &(aResult->upDegrees)) ||
-        !ReadParam(aMsg, aIter, &(aResult->rightDegrees)) ||
-        !ReadParam(aMsg, aIter, &(aResult->downDegrees)) ||
-        !ReadParam(aMsg, aIter, &(aResult->leftDegrees))) {
-      return false;
-    }
-
-    return true;
-  }
-};
-
-template <>
-struct ParamTraits<mozilla::gfx::VRControllerState> {
-  typedef mozilla::gfx::VRControllerState paramType;
-
-  static void Write(Message* aMsg, const paramType& aParam) {
-    nsCString controllerName;
-    controllerName.Assign(
-        aParam.controllerName);  // FINDME!! HACK! - Bounds checking?
-    WriteParam(aMsg, controllerName);
-    WriteParam(aMsg, aParam.hand);
-    WriteParam(aMsg, aParam.numButtons);
-    WriteParam(aMsg, aParam.numAxes);
-    WriteParam(aMsg, aParam.numHaptics);
-    WriteParam(aMsg, aParam.buttonPressed);
-    WriteParam(aMsg, aParam.buttonTouched);
-    WriteParam(aMsg, aParam.flags);
-    WriteParam(aMsg, aParam.pose);
-    WriteParam(aMsg, aParam.isPositionValid);
-    WriteParam(aMsg, aParam.isOrientationValid);
-    for (size_t i = 0; i < mozilla::ArrayLength(aParam.axisValue); i++) {
-      WriteParam(aMsg, aParam.axisValue[i]);
-    }
-    for (size_t i = 0; i < mozilla::ArrayLength(aParam.triggerValue); i++) {
-      WriteParam(aMsg, aParam.triggerValue[i]);
-    }
-  }
-
-  static bool Read(const Message* aMsg, PickleIterator* aIter,
-                   paramType* aResult) {
-    nsCString controllerName;
-    if (!ReadParam(aMsg, aIter, &(controllerName)) ||
-        !ReadParam(aMsg, aIter, &(aResult->hand)) ||
-        !ReadParam(aMsg, aIter, &(aResult->numButtons)) ||
-        !ReadParam(aMsg, aIter, &(aResult->numAxes)) ||
-        !ReadParam(aMsg, aIter, &(aResult->numHaptics)) ||
-        !ReadParam(aMsg, aIter, &(aResult->buttonPressed)) ||
-        !ReadParam(aMsg, aIter, &(aResult->buttonTouched)) ||
-        !ReadParam(aMsg, aIter, &(aResult->flags)) ||
-        !ReadParam(aMsg, aIter, &(aResult->pose)) ||
-        !ReadParam(aMsg, aIter, &(aResult->isPositionValid)) ||
-        !ReadParam(aMsg, aIter, &(aResult->isOrientationValid))) {
-      return false;
-    }
-    for (size_t i = 0; i < mozilla::ArrayLength(aResult->axisValue); i++) {
-      if (!ReadParam(aMsg, aIter, &(aResult->axisValue[i]))) {
-        return false;
-      }
-    }
-    for (size_t i = 0; i < mozilla::ArrayLength(aResult->triggerValue); i++) {
-      if (!ReadParam(aMsg, aIter, &(aResult->triggerValue[i]))) {
-        return false;
-      }
-    }
-    strncpy(aResult->controllerName, controllerName.BeginReading(),
-            mozilla::gfx::kVRControllerNameMaxLen);  // FINDME! TODO! HACK!
-                                                     // Safe? Better way?
-
-    return true;
-  }
-};
-
-template <>
-struct ParamTraits<mozilla::gfx::VRControllerInfo> {
-  typedef mozilla::gfx::VRControllerInfo paramType;
-
-  static void Write(Message* aMsg, const paramType& aParam) {
-    WriteParam(aMsg, aParam.mType);
-    WriteParam(aMsg, aParam.mControllerID);
-    WriteParam(aMsg, aParam.mMappingType);
-    WriteParam(aMsg, aParam.mControllerState);
-  }
-
-  static bool Read(const Message* aMsg, PickleIterator* aIter,
-                   paramType* aResult) {
-    if (!ReadParam(aMsg, aIter, &(aResult->mType)) ||
-        !ReadParam(aMsg, aIter, &(aResult->mControllerID)) ||
-        !ReadParam(aMsg, aIter, &(aResult->mMappingType)) ||
-        !ReadParam(aMsg, aIter, &(aResult->mControllerState))) {
-      return false;
-    }
-
-    return true;
-  }
-};
+struct ParamTraits<mozilla::gfx::VRDisplayInfo>
+    : public PlainOldDataSerializer<mozilla::gfx::VRDisplayInfo> {};
 
 template <>
 struct ParamTraits<mozilla::gfx::VRSubmitFrameResultInfo> {
   typedef mozilla::gfx::VRSubmitFrameResultInfo paramType;
 
   static void Write(Message* aMsg, const paramType& aParam) {
     WriteParam(aMsg, aParam.mBase64Image);
     WriteParam(aMsg, aParam.mFormat);
--- a/js/src/jit/IonAnalysis.cpp
+++ b/js/src/jit/IonAnalysis.cpp
@@ -99,19 +99,20 @@ static bool DepthFirstSearchUse(MIRGener
           cphi->isImplicitlyUsed()) {
         // The information got cached on the Phi the last time it
         // got visited, or when flagging operands of removed
         // instructions.
         return push(producer, use);
       }
 
       if (cphi->isInWorklist() || cphi == producer) {
-        // We are already iterating over the uses of this Phi
-        // instruction. Skip it.
-        continue;
+        // We are already iterating over the uses of this Phi instruction which
+        // are part of a loop, instead of trying to handle loops, conservatively
+        // mark them as used.
+        return push(producer, use);
       }
 
       if (cphi->getUsageAnalysis() == PhiUsage::Unused) {
         // The instruction already got visited and is known to have
         // no uses. Skip it.
         continue;
       }
 
--- a/js/src/tests/jstests.list
+++ b/js/src/tests/jstests.list
@@ -9,16 +9,21 @@ skip script non262/String/normalize-gene
 # Timeouts on arm and cgc builds.
 slow script test262/built-ins/decodeURI/S15.1.3.1_A2.5_T1.js
 slow script test262/built-ins/decodeURIComponent/S15.1.3.2_A2.5_T1.js
 
 # Fields are not fully implemented yet
 # https://bugzilla.mozilla.org/show_bug.cgi?id=1499448
 skip script non262/reflect-parse/class-fields.js
 
+# Windows10-aarch64 fails certain tests.
+# https://bugzilla.mozilla.org/show_bug.cgi?id=1526003
+# https://bugzilla.mozilla.org/show_bug.cgi?id=1526012
+skip-if((xulRuntime.XPCOMABI.match(/aarch64/))&&(xulRuntime.OS=="WINNT")) script non262/Math/fround.js
+skip-if((xulRuntime.XPCOMABI.match(/aarch64/))&&(xulRuntime.OS=="WINNT")) script non262/Math/log2-approx.js
 
 ###########################################################################
 # Generated jstests.list for test262 when inline |reftest| isn't possible #
 ###########################################################################
 
 include test262/jstests.list
 
 
rename from testing/marionette/client/marionette_driver/selection.py
rename to layout/base/tests/marionette/selection.py
--- a/testing/marionette/client/marionette_driver/selection.py
+++ b/layout/base/tests/marionette/selection.py
@@ -1,15 +1,75 @@
 # -*- coding: utf-8 -*-
 # This Source Code Form is subject to the terms of the Mozilla Public
 # License, v. 2.0. If a copy of the MPL was not distributed with this
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 from __future__ import absolute_import
 
+from marionette_driver.marionette import Actions
+
+
+class CaretActions(Actions):
+    def __init__(self, marionette):
+        super(CaretActions, self).__init__(marionette)
+        self._reset_action_chain()
+
+    def _reset_action_chain(self):
+        self.mouse_chain = self.sequence("pointer", "pointer_id", {"pointerType": "mouse"})
+        self.key_chain = self.sequence("key", "keyboard_id")
+
+    def flick(self, element, x1, y1, x2, y2, duration=200):
+        """Perform a flick gesture on the target element.
+
+        :param element: The element to perform the flick gesture on.
+        :param x1: Starting x-coordinate of flick, relative to the top left
+                   corner of the element.
+        :param y1: Starting y-coordinate of flick, relative to the top left
+                   corner of the element.
+        :param x2: Ending x-coordinate of flick, relative to the top left
+                   corner of the element.
+        :param y2: Ending y-coordinate of flick, relative to the top left
+                   corner of the element.
+
+        """
+        rect = element.rect
+        el_x, el_y = rect['x'], rect['y']
+
+        # Add element's (x, y) to make the coordinate relative to the viewport.
+        from_x, from_y = int(el_x + x1), int(el_y + y1)
+        to_x, to_y = int(el_x + x2), int(el_y + y2)
+
+        self.mouse_chain.pointer_move(from_x, from_y) \
+                        .pointer_down() \
+                        .pointer_move(to_x, to_y, duration=duration) \
+                        .pointer_up()
+        return self
+
+    def send_keys(self, keys):
+        """Perform a keyDown and keyUp action for each character in `keys`.
+
+        :param keys: String of keys to perform key actions with.
+
+        """
+        self.key_chain.send_keys(keys)
+        return self
+
+    def perform(self):
+        """Perform the action chain built so far to the server side for execution
+        and clears the current chain of actions.
+
+        Warning: This method performs all the mouse actions before all the key
+        actions!
+
+        """
+        self.mouse_chain.perform()
+        self.key_chain.perform()
+        self._reset_action_chain()
+
 
 class SelectionManager(object):
     '''Interface for manipulating the selection and carets of the element.
 
     We call the blinking cursor (nsCaret) as cursor, and call AccessibleCaret as
     caret for short.
 
     Simple usage example:
--- a/layout/base/tests/marionette/test_accessiblecaret_cursor_mode.py
+++ b/layout/base/tests/marionette/test_accessiblecaret_cursor_mode.py
@@ -1,18 +1,25 @@
 # -*- coding: utf-8 -*-
 # This Source Code Form is subject to the terms of the Mozilla Public
 # License, v. 2.0. If a copy of the MPL was not distributed with this
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 import string
+import sys
+import os
 
+# Add this directory to the import path.
+sys.path.append(os.path.dirname(__file__))
+
+from selection import (
+    CaretActions,
+    SelectionManager,
+)
 from marionette_driver.by import By
-from marionette_driver.legacy_actions import Actions
-from marionette_driver.selection import SelectionManager
 from marionette_harness.marionette_test import (
     MarionetteTestCase,
     parameterized,
 )
 
 
 class AccessibleCaretCursorModeTestCase(MarionetteTestCase):
     '''Test cases for AccessibleCaret under cursor mode.
@@ -36,17 +43,21 @@ class AccessibleCaretCursorModeTestCase(
         super(AccessibleCaretCursorModeTestCase, self).setUp()
         self.caret_tested_pref = 'layout.accessiblecaret.enabled'
         self.hide_carets_for_mouse = 'layout.accessiblecaret.hide_carets_for_mouse_input'
         self.prefs = {
             self.caret_tested_pref: True,
             self.hide_carets_for_mouse: False,
         }
         self.marionette.set_prefs(self.prefs)
-        self.actions = Actions(self.marionette)
+        self.actions = CaretActions(self.marionette)
+
+    def tearDown(self):
+        self.marionette.actions.release()
+        super(AccessibleCaretCursorModeTestCase, self).tearDown()
 
     def open_test_html(self, test_html):
         self.marionette.navigate(self.marionette.absolute_url(test_html))
 
     @parameterized(_input_id, el_id=_input_id)
     @parameterized(_textarea_id, el_id=_textarea_id)
     @parameterized(_contenteditable_id, el_id=_contenteditable_id)
     def test_move_cursor_to_the_right_by_one_character(self, el_id):
@@ -67,17 +78,17 @@ class AccessibleCaretCursorModeTestCase(
 
         # Tap the front of the input to make first caret appear.
         el.tap(cursor0_x, cursor0_y)
 
         # Move first caret.
         self.actions.flick(el, first_caret0_x, first_caret0_y,
                            first_caret1_x, first_caret1_y).perform()
 
-        self.actions.key_down(content_to_add).key_up(content_to_add).perform()
+        self.actions.send_keys(content_to_add).perform()
         self.assertEqual(target_content, sel.content)
 
     @parameterized(_input_id, el_id=_input_id)
     @parameterized(_textarea_id, el_id=_textarea_id)
     @parameterized(_contenteditable_id, el_id=_contenteditable_id)
     def test_move_cursor_to_end_by_dragging_caret_to_bottom_right_corner(self, el_id):
         self.open_test_html(self._cursor_html)
         el = self.marionette.find_element(By.ID, el_id)
@@ -90,17 +101,17 @@ class AccessibleCaretCursorModeTestCase(
         sel.move_cursor_to_front()
         el.tap(*sel.cursor_location())
 
         # Move first caret to the bottom-right corner of the element.
         src_x, src_y = sel.first_caret_location()
         dest_x, dest_y = el.rect['width'], el.rect['height']
         self.actions.flick(el, src_x, src_y, dest_x, dest_y).perform()
 
-        self.actions.key_down(content_to_add).key_up(content_to_add).perform()
+        self.actions.send_keys(content_to_add).perform()
         self.assertEqual(target_content, sel.content)
 
     @parameterized(_input_id, el_id=_input_id)
     @parameterized(_textarea_id, el_id=_textarea_id)
     @parameterized(_contenteditable_id, el_id=_contenteditable_id)
     def test_move_cursor_to_front_by_dragging_caret_to_front(self, el_id):
         self.open_test_html(self._cursor_html)
         el = self.marionette.find_element(By.ID, el_id)
@@ -120,17 +131,17 @@ class AccessibleCaretCursorModeTestCase(
         sel.move_cursor_to_end()
         sel.move_cursor_by_offset(1, backward=True)
         el.tap(*sel.cursor_location())
         src_x, src_y = sel.first_caret_location()
 
         # Move first caret to the front of the input box.
         self.actions.flick(el, src_x, src_y, dest_x, dest_y).perform()
 
-        self.actions.key_down(content_to_add).key_up(content_to_add).perform()
+        self.actions.send_keys(content_to_add).perform()
         self.assertEqual(target_content, sel.content)
 
     def test_caret_not_appear_when_typing_in_scrollable_content(self):
         self.open_test_html(self._cursor_html)
         el = self.marionette.find_element(By.ID, self._input_id)
         sel = SelectionManager(el)
         content_to_add = '!'
         non_target_content = content_to_add + sel.content + string.ascii_letters
@@ -169,17 +180,17 @@ class AccessibleCaretCursorModeTestCase(
         # on the Y-axis.
         el.tap()
         sel.move_cursor_to_front()
         el.tap(*sel.cursor_location())
         x, y = sel.first_caret_location()
 
         # Drag the caret down by 50px, and insert '!'.
         self.actions.flick(el, x, y, x, y + 50).perform()
-        self.actions.key_down(content_to_add).key_up(content_to_add).perform()
+        self.actions.send_keys(content_to_add).perform()
         self.assertNotEqual(non_target_content, sel.content)
 
     @parameterized(_input_id, el_id=_input_id)
     @parameterized(_input_padding_id, el_id=_input_padding_id)
     @parameterized(_textarea_one_line_id, el_id=_textarea_one_line_id)
     @parameterized(_contenteditable_id, el_id=_contenteditable_id)
     def test_caret_not_jump_to_front_when_dragging_up_to_editable_content_boundary(self, el_id):
         self.open_test_html(self._cursor_html)
@@ -191,19 +202,19 @@ class AccessibleCaretCursorModeTestCase(
         # Goal: the cursor position is not changed after dragging the caret down
         # on the Y-axis.
         el.tap()
         sel.move_cursor_to_end()
         sel.move_cursor_by_offset(1, backward=True)
         el.tap(*sel.cursor_location())
         x, y = sel.first_caret_location()
 
-        # Drag the caret up by 50px, and insert '!'.
-        self.actions.flick(el, x, y, x, y - 50).perform()
-        self.actions.key_down(content_to_add).key_up(content_to_add).perform()
+        # Drag the caret up by 40px, and insert '!'.
+        self.actions.flick(el, x, y, x, y - 40).perform()
+        self.actions.send_keys(content_to_add).perform()
         self.assertNotEqual(non_target_content, sel.content)
 
     def test_drag_caret_from_front_to_end_across_columns(self):
         self.open_test_html('layout/test_carets_columns.html')
         el = self.marionette.find_element(By.ID, 'columns-inner')
         sel = SelectionManager(el)
         content_to_add = '!'
         target_content = sel.content + content_to_add
@@ -219,17 +230,17 @@ class AccessibleCaretCursorModeTestCase(
         sel.move_cursor_to_front()
         el.tap(*sel.cursor_location())
         src_x, src_y = sel.first_caret_location()
         dest_x, dest_y = el.rect['width'], el.rect['height']
 
         # Drag the first caret to the bottom-right corner of the element.
         self.actions.flick(el, src_x, src_y, dest_x, dest_y).perform()
 
-        self.actions.key_down(content_to_add).key_up(content_to_add).perform()
+        self.actions.send_keys(content_to_add).perform()
         self.assertEqual(target_content, sel.content)
 
     def test_move_cursor_to_front_by_dragging_caret_to_front_br_element(self):
         self.open_test_html(self._cursor_html)
         el = self.marionette.find_element(By.ID, self._contenteditable_id)
         sel = SelectionManager(el)
         content_to_add_1 = '!'
         content_to_add_2 = '\n\n'
@@ -253,10 +264,10 @@ class AccessibleCaretCursorModeTestCase(
         sel.move_cursor_to_end()
         sel.move_cursor_by_offset(1, backward=True)
         el.tap(*sel.cursor_location())
         src_x, src_y = sel.first_caret_location()
 
         # Move first caret to the front of the input box.
         self.actions.flick(el, src_x, src_y, dest_x, dest_y).perform()
 
-        self.actions.key_down(content_to_add_1).key_up(content_to_add_1).perform()
+        self.actions.send_keys(content_to_add_1).perform()
         self.assertEqual(target_content, sel.content)
--- a/layout/base/tests/marionette/test_accessiblecaret_selection_mode.py
+++ b/layout/base/tests/marionette/test_accessiblecaret_selection_mode.py
@@ -1,33 +1,31 @@
 # -*- coding: utf-8 -*-
 # This Source Code Form is subject to the terms of the Mozilla Public
 # License, v. 2.0. If a copy of the MPL was not distributed with this
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 import re
+import sys
+import os
 
+# Add this directory to the import path.
+sys.path.append(os.path.dirname(__file__))
+
+from selection import (
+    CaretActions,
+    SelectionManager,
+)
 from marionette_driver.by import By
-from marionette_driver.legacy_actions import Actions
-from marionette_driver.selection import SelectionManager
 from marionette_harness.marionette_test import (
     MarionetteTestCase,
     SkipTest,
     parameterized
 )
 
-
-def skip_if_not_rotatable(target):
-    def wrapper(self, *args, **kwargs):
-        if not self.marionette.session_capabilities.get('rotatable'):
-            raise SkipTest('skipping due to device not rotatable')
-        return target(self, *args, **kwargs)
-    return wrapper
-
-
 class AccessibleCaretSelectionModeTestCase(MarionetteTestCase):
     '''Test cases for AccessibleCaret under selection mode.'''
     # Element IDs.
     _input_id = 'input'
     _input_padding_id = 'input-padding'
     _input_size_id = 'input-size'
     _textarea_id = 'textarea'
     _textarea2_id = 'textarea2'
@@ -52,17 +50,21 @@ class AccessibleCaretSelectionModeTestCa
         # Code to execute before every test is running.
         super(AccessibleCaretSelectionModeTestCase, self).setUp()
         self.carets_tested_pref = 'layout.accessiblecaret.enabled'
         self.prefs = {
             'layout.word_select.eat_space_to_next_word': False,
             self.carets_tested_pref: True,
         }
         self.marionette.set_prefs(self.prefs)
-        self.actions = Actions(self.marionette)
+        self.actions = CaretActions(self.marionette)
+
+    def tearDown(self):
+        self.marionette.actions.release()
+        super(AccessibleCaretSelectionModeTestCase, self).tearDown()
 
     def open_test_html(self, test_html):
         self.marionette.navigate(self.marionette.absolute_url(test_html))
 
     def word_offset(self, text, ordinal):
         'Get the character offset of the ordinal-th word in text.'
         tokens = re.split(r'(\S+)', text)         # both words and spaces
         spaces = tokens[0::2]                     # collect spaces at odd indices
@@ -580,18 +582,18 @@ class AccessibleCaretSelectionModeTestCa
 
         # Goal: the selection is not changed after dragging the caret on the
         # Y-axis.
         target_content = words[1]
 
         self.long_press_on_word(el, 1)
         (caret1_x, caret1_y), (caret2_x, caret2_y) = sel.carets_location()
 
-        # Drag the first caret up by 50px.
-        self.actions.flick(el, caret1_x, caret1_y, caret1_x, caret1_y - 50).perform()
+        # Drag the first caret up by 40px.
+        self.actions.flick(el, caret1_x, caret1_y, caret1_x, caret1_y - 40).perform()
         self.assertEqual(target_content, sel.selected_content)
 
         # Drag the second caret down by 50px.
         self.actions.flick(el, caret2_x, caret2_y, caret2_x, caret2_y + 50).perform()
         self.assertEqual(target_content, sel.selected_content)
 
     def test_carets_should_not_appear_when_long_pressing_svg_shapes(self):
         self.open_test_html(self._svg_shapes_html)
--- a/layout/generic/nsFrame.cpp
+++ b/layout/generic/nsFrame.cpp
@@ -2959,25 +2959,31 @@ void nsIFrame::BuildDisplayListForStacki
     // Restict the building area to the overflow rect for these frames, since
     // RetainedDisplayListBuilder uses it to know if the size of the stacking
     // context changed.
     visibleRect.IntersectRect(visibleRect, GetVisualOverflowRect());
     dirtyRect.IntersectRect(dirtyRect, GetVisualOverflowRect());
   }
 
   bool hasOverrideDirtyRect = false;
-  // If we have an override dirty region, and neither us nor our ancestors are
-  // modified, then use it.
-  if (HasOverrideDirtyRegion() && !aBuilder->InInvalidSubtree() &&
-      !IsFrameModified()) {
-    nsDisplayListBuilder::DisplayListBuildingData* data =
-        GetProperty(nsDisplayListBuilder::DisplayListBuildingRect());
-    if (data) {
-      dirtyRect = data->mDirtyRect.Intersect(visibleRect);
-      hasOverrideDirtyRect = true;
+  // If we're doing a partial build, we're not invalid and we're capable
+  // of having an override building rect (stacking context and fixed pos
+  // containing block), then we should assume we have one.
+  // Either we have an explicit one, or nothing in our subtree changed and
+  // we have an implicit empty rect.
+  if (aBuilder->IsPartialUpdate() && !aBuilder->InInvalidSubtree() &&
+      !IsFrameModified() && IsFixedPosContainingBlock()) {
+    dirtyRect = nsRect();
+    if (HasOverrideDirtyRegion()) {
+      nsDisplayListBuilder::DisplayListBuildingData* data =
+          GetProperty(nsDisplayListBuilder::DisplayListBuildingRect());
+      if (data) {
+        dirtyRect = data->mDirtyRect.Intersect(visibleRect);
+        hasOverrideDirtyRect = true;
+      }
     }
   }
 
   bool usingFilter = StyleEffects()->HasFilters();
   bool usingMask = nsSVGIntegrationUtils::UsingMaskOrClipPathForFrame(this);
   bool usingSVGEffects = usingFilter || usingMask;
 
   nsRect visibleRectOutsideSVGEffects = visibleRect;
@@ -7097,17 +7103,17 @@ Layer* nsIFrame::InvalidateLayer(Display
                                  const nsIntRect* aDamageRect,
                                  const nsRect* aFrameDamageRect,
                                  uint32_t aFlags /* = 0 */) {
   NS_ASSERTION(aDisplayItemKey > DisplayItemType::TYPE_ZERO, "Need a key");
 
   Layer* layer = FrameLayerBuilder::GetDedicatedLayer(this, aDisplayItemKey);
 
   nsIFrame* displayRoot = nsLayoutUtils::GetDisplayRootFrame(this);
-  InvalidateRenderingObservers(displayRoot, this);
+  InvalidateRenderingObservers(displayRoot, this, false);
 
   // Check if frame supports WebRender's async update
   if ((aFlags & UPDATE_IS_ASYNC) &&
       WebRenderUserData::SupportsAsyncUpdate(this)) {
     // WebRender does not use layer, then return nullptr.
     return nullptr;
   }
 
--- a/layout/generic/nsImageFrame.cpp
+++ b/layout/generic/nsImageFrame.cpp
@@ -2543,16 +2543,48 @@ void nsImageFrame::IconLoad::GetPrefs() 
 
   mPrefShowPlaceholders =
       Preferences::GetBool("browser.display.show_image_placeholders", true);
 
   mPrefShowLoadingPlaceholder = Preferences::GetBool(
       "browser.display.show_loading_image_placeholder", true);
 }
 
+nsresult nsImageFrame::RestartAnimation() {
+  nsCOMPtr<imgIRequest> currentRequest = GetCurrentRequest();
+  /*
+   * We cannot count on mContentURLRequestRegistered to make
+   * the deregistration work. So, we are going to force it.
+   */
+  bool deregister = true;
+
+  if (currentRequest && !mContentURLRequestRegistered) {
+    nsLayoutUtils::RegisterImageRequestIfAnimated(PresContext(), currentRequest,
+                                                  &deregister);
+    return currentRequest->StartDecoding(imgIContainer::FLAG_NONE);
+  }
+  return NS_OK;
+}
+
+nsresult nsImageFrame::StopAnimation() {
+  nsCOMPtr<imgIRequest> currentRequest = GetCurrentRequest();
+  /*
+   * We cannot count on mContentURLRequestRegistered to make
+   * the deregistration work. So, we are going to force it.
+   */
+  bool deregister = true;
+
+  if (currentRequest) {
+    nsLayoutUtils::DeregisterImageRequest(PresContext(), currentRequest,
+                                          &deregister);
+    return currentRequest->CancelAndForgetObserver(NS_BINDING_ABORTED);
+  }
+  return NS_OK;
+}
+
 NS_IMETHODIMP
 nsImageFrame::IconLoad::Notify(imgIRequest* aRequest, int32_t aType,
                                const nsIntRect* aData) {
   MOZ_ASSERT(aRequest);
 
   if (aType != imgINotificationObserver::LOAD_COMPLETE &&
       aType != imgINotificationObserver::FRAME_UPDATE) {
     return NS_OK;
--- a/layout/generic/nsImageFrame.h
+++ b/layout/generic/nsImageFrame.h
@@ -129,16 +129,19 @@ class nsImageFrame : public nsAtomicCont
   static void ReleaseGlobals() {
     if (gIconLoad) {
       gIconLoad->Shutdown();
       gIconLoad = nullptr;
     }
     NS_IF_RELEASE(sIOService);
   }
 
+  virtual nsresult RestartAnimation();
+  virtual nsresult StopAnimation();
+
   already_AddRefed<imgIRequest> GetCurrentRequest() const;
   nsresult Notify(imgIRequest* aRequest, int32_t aType, const nsIntRect* aData);
 
   /**
    * Function to test whether aContent, which has aComputedStyle as its style,
    * should get an image frame.  Note that this method is only used by the
    * frame constructor; it's only here because it uses gIconLoad for now.
    */
--- a/layout/painting/ActiveLayerTracker.cpp
+++ b/layout/painting/ActiveLayerTracker.cpp
@@ -187,17 +187,17 @@ void LayerActivityTracker::NotifyExpired
   MOZ_ASSERT((f == nullptr) != (c == nullptr),
              "A LayerActivity object should always have a reference to either "
              "its frame or its content");
 
   if (f) {
     // The pres context might have been detached during the delay -
     // that's fine, just skip the paint.
     if (f->PresContext()->GetContainerWeak()) {
-      f->SchedulePaint();
+      f->SchedulePaint(nsIFrame::PAINT_DEFAULT, false);
     }
     f->RemoveStateBits(NS_FRAME_HAS_LAYER_ACTIVITY_PROPERTY);
     f->DeleteProperty(LayerActivityProperty());
   } else {
     c->DeleteProperty(nsGkAtoms::LayerActivity);
   }
 }
 
--- a/layout/painting/nsDisplayList.cpp
+++ b/layout/painting/nsDisplayList.cpp
@@ -565,19 +565,19 @@ static void AddAnimationForProperty(nsIF
 
   animation->originTime() =
       !aAnimation->GetTimeline()
           ? TimeStamp()
           : aAnimation->GetTimeline()->ToTimeStamp(TimeDuration());
 
   Nullable<TimeDuration> startTime = aAnimation->GetCurrentOrPendingStartTime();
   if (startTime.IsNull()) {
-    animation->startTime() = null_t();
+    animation->startTime() = Nothing();
   } else {
-    animation->startTime() = startTime.Value();
+    animation->startTime() = Some(startTime.Value());
   }
 
   animation->holdTime() = aAnimation->GetCurrentTimeAsDuration().Value();
 
   const ComputedTiming computedTiming =
       aAnimation->GetEffect()->GetComputedTiming();
   animation->delay() = timing.Delay();
   animation->endDelay() = timing.EndDelay();
--- a/layout/reftests/async-scrolling/reftest.list
+++ b/layout/reftests/async-scrolling/reftest.list
@@ -7,17 +7,17 @@ skip-if(!asyncPan) == bg-fixed-child-cli
 skip-if(!asyncPan) == bg-fixed-child-clip-2.html bg-fixed-child-clip-ref.html
 fuzzy(0-1,0-246) fuzzy-if(skiaContent,0-2,0-170) fuzzy-if(browserIsRemote&&d2d,0-59,0-187) fuzzy-if(webrender,41-41,166-166) skip-if(!asyncPan) == bg-fixed-child-mask.html bg-fixed-child-mask-ref.html
 skip-if(!asyncPan) == bg-fixed-in-opacity.html bg-fixed-in-opacity-ref.html
 # Passing the test below without WebRender would require implementing CSS filters in the Gecko compositor.
 fails-if(!webrender) skip-if(!asyncPan) fuzzy-if(webrender&&gtkWidget,0-1,0-87) fuzzy-if(webrender&&!gtkWidget,0-1,0-8) == bg-fixed-in-css-filter.html bg-fixed-in-css-filter-ref.html # bug 1454794 for webrender fuzziness
 skip-if(!asyncPan) == bg-fixed-child-no-culling-1.html bg-fixed-child-no-culling-1-ref.html
 skip-if(!asyncPan) == bg-fixed-child-no-culling-2.html bg-fixed-child-no-culling-2-ref.html
 skip-if(!asyncPan) == bg-fixed-child-no-culling-3.html bg-fixed-child-no-culling-3-ref.html
-fuzzy-if(Android,0-2,0-4000) fuzzy-if(browserIsRemote&&cocoaWidget,0-2,0-179524) fuzzy-if(browserIsRemote&&winWidget,0-1,0-74590) fuzzy-if(gtkWidget&&layersGPUAccelerated,0-1,0-3528) skip-if(!asyncPan) == bg-fixed-transformed-image.html bg-fixed-transformed-image-ref.html
+fuzzy-if(Android,0-2,0-4000) fuzzy-if(browserIsRemote&&cocoaWidget,0-2,0-179524) fuzzy-if((/^Windows\x20NT\x2010\.0/.test(http.oscpu))&&(/^aarch64-msvc/.test(xulRuntime.XPCOMABI)),1-1,75800-75886) fuzzy-if(browserIsRemote&&winWidget,0-1,0-74590) fuzzy-if(gtkWidget&&layersGPUAccelerated,0-1,0-3528) skip-if(!asyncPan) == bg-fixed-transformed-image.html bg-fixed-transformed-image-ref.html
 test-pref(layout.css.contain.enabled,true) skip-if(!asyncPan) == contain-paint-scrollable-frame-1.html contain-paint-scrollable-frame-1-ref.html
 skip-if(!asyncPan) == element-1.html element-1-ref.html
 pref(layers.force-active,true) skip-if(!asyncPan) == iframe-1.html iframe-1-ref.html
 skip-if(!asyncPan) == nested-1.html nested-1-ref.html
 skip-if(!asyncPan) == nested-2.html nested-2-ref.html
 skip-if(!asyncPan) == position-fixed-1.html position-fixed-1-ref.html
 skip-if(!asyncPan) == position-fixed-2.html position-fixed-2-ref.html
 fuzzy-if(/^Windows\x20NT\x2010\.0/.test(http.oscpu),0-1,0-3120) skip-if(!asyncPan) == position-fixed-body.html position-fixed-body-ref.html
--- a/layout/reftests/display-list/reftest.list
+++ b/layout/reftests/display-list/reftest.list
@@ -1,13 +1,14 @@
 skip-if(!retainedDisplayList) == retained-dl-style-change-1.html retained-dl-style-change-1-ref.html
 skip-if(!retainedDisplayList) == retained-dl-frame-deleted-1.html retained-dl-style-change-1-ref.html
 skip-if(!retainedDisplayList) == retained-dl-frame-created-1.html retained-dl-style-change-1-ref.html
 skip-if(!retainedDisplayList) == retained-dl-style-change-stacking-context-1.html retained-dl-style-change-stacking-context-1-ref.html
 skip-if(!retainedDisplayList) == retained-dl-style-change-stacking-context-2.html retained-dl-style-change-stacking-context-2-ref.html
+skip-if(!retainedDisplayList) == retained-dl-style-change-stacking-context-3.html retained-dl-style-change-stacking-context-3-ref.html
 skip-if(!retainedDisplayList||!asyncPan) == retained-dl-async-scrolled-1.html retained-dl-async-scrolled-1-ref.html
 skip-if(!retainedDisplayList) == retained-dl-remove-for-ancestor-change-1.html retained-dl-remove-for-ancestor-change-1-ref.html
 skip-if(!retainedDisplayList) == retained-dl-scroll-out-of-view-1.html retained-dl-scroll-out-of-view-1-ref.html
 skip-if(!retainedDisplayList) == retained-dl-displayport-1.html retained-dl-displayport-1-ref.html
 skip-if(!retainedDisplayList) == retained-dl-prerender-transform-1.html retained-dl-prerender-transform-1-ref.html
 skip-if(!retainedDisplayList) == retained-dl-animation-on-pseudo.html retained-dl-animation-on-pseudo-ref.html
 skip-if(!retainedDisplayList) == retained-dl-opacity-animation-on-ib-split.html retained-dl-opacity-animation-on-ib-split-ref.html
 == retained-dl-wrap-list.html retained-dl-wrap-list-ref.html
new file mode 100644
--- /dev/null
+++ b/layout/reftests/display-list/retained-dl-style-change-stacking-context-3-ref.html
@@ -0,0 +1,20 @@
+<html>
+<head>
+<style>
+  body {
+    margin: 0px;
+  }
+  div {
+    width:100px;
+    height:100px;
+    display: inline-block;
+    position:absolute;
+    background-color: green;
+  }
+</style>
+</head>
+<body>
+  <div></div>
+  <div style="top: 110px;"></div>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/display-list/retained-dl-style-change-stacking-context-3.html
@@ -0,0 +1,29 @@
+<html class="reftest-wait">
+<head>
+<style>
+  body {
+    margin: 0px;
+  }
+  div {
+    width:100px;
+    height:100px;
+    display: inline-block;
+    position:absolute;
+  }
+</style>
+</head>
+<body>
+  <div style="position:fixed;" class="reftest-display-list">
+    <div style="background-color:green;" class="reftest-no-display-list">
+  </div>
+  <div id="second" style="background-color:red; top: 110px;"></div>
+</body>
+<script>
+function doTest() {
+  document.getElementById("second").style.backgroundColor = "green";
+  document.documentElement.removeAttribute("class");
+}
+
+window.addEventListener("MozReftestInvalidate", doTest);
+</script>
+</html>
--- a/layout/style/crashtests/crashtests.list
+++ b/layout/style/crashtests/crashtests.list
@@ -204,19 +204,19 @@ load 1383975.html
 load border-image-visited-link.html
 load content-only-on-link-before.html
 load content-only-on-visited-before.html
 load font-face-truncated-src.html
 load large_border_image_width.html
 load link-transition-before.html
 skip-if(winWidget&&isDebugBuild&&/^Windows\x20NT\x206\.1/.test(http.oscpu)) load long-url-list-stack-overflow.html #Bug 1525117
 load scale-on-block-continuation.html
-skip-if(Android&&AndroidVersion<21) load 1383981.html
-skip-if(Android&&AndroidVersion<21) load 1383981-2.html
-skip-if(Android&&AndroidVersion<21) load 1383981-3.html
+skip-if(Android&&AndroidVersion<21||(/^Windows\x20NT\x2010\.0/.test(http.oscpu),0-1,0-256)&&(/^aarch64-msvc/.test(xulRuntime.XPCOMABI))) load 1383981.html
+skip-if(Android&&AndroidVersion<21||(/^Windows\x20NT\x2010\.0/.test(http.oscpu),0-1,0-256)&&(/^aarch64-msvc/.test(xulRuntime.XPCOMABI))) load 1383981-2.html
+skip-if(Android&&AndroidVersion<21||(/^Windows\x20NT\x2010\.0/.test(http.oscpu),0-1,0-256)&&(/^aarch64-msvc/.test(xulRuntime.XPCOMABI))) load 1383981-3.html
 load 1384824-1.html
 load 1384824-2.html
 load 1386773.html
 load 1387481-1.html
 load 1387499.html
 load 1388234.html
 load 1391577.html
 load 1393189.html
--- a/layout/xul/nsDeckFrame.cpp
+++ b/layout/xul/nsDeckFrame.cpp
@@ -23,16 +23,18 @@
 #include "nsCSSRendering.h"
 #include "nsViewManager.h"
 #include "nsBoxLayoutState.h"
 #include "nsStackLayout.h"
 #include "nsDisplayList.h"
 #include "nsContainerFrame.h"
 #include "nsContentUtils.h"
 #include "nsXULPopupManager.h"
+#include "nsImageBoxFrame.h"
+#include "nsImageFrame.h"
 
 #ifdef ACCESSIBILITY
 #  include "nsAccessibilityService.h"
 #endif
 
 using namespace mozilla;
 
 nsIFrame* NS_NewDeckFrame(nsIPresShell* aPresShell, ComputedStyle* aStyle) {
@@ -67,35 +69,41 @@ nsresult nsDeckFrame::AttributeChanged(i
 
 void nsDeckFrame::Init(nsIContent* aContent, nsContainerFrame* aParent,
                        nsIFrame* aPrevInFlow) {
   nsBoxFrame::Init(aContent, aParent, aPrevInFlow);
 
   mIndex = GetSelectedIndex();
 }
 
+void nsDeckFrame::ShowBox(nsIFrame* aBox) { Animate(aBox, true); }
+
 void nsDeckFrame::HideBox(nsIFrame* aBox) {
   nsIPresShell::ClearMouseCapture(aBox);
+  Animate(aBox, false);
 }
 
 void nsDeckFrame::IndexChanged() {
   // did the index change?
   int32_t index = GetSelectedIndex();
+
   if (index == mIndex) return;
 
   // redraw
   InvalidateFrame();
 
   // hide the currently showing box
   nsIFrame* currentBox = GetSelectedBox();
   if (currentBox)  // only hide if it exists
     HideBox(currentBox);
 
   mIndex = index;
 
+  ShowBox(GetSelectedBox());
+
 #ifdef ACCESSIBILITY
   nsAccessibilityService* accService = GetAccService();
   if (accService) {
     accService->DeckPanelSwitched(PresContext()->GetPresShell(), mContent,
                                   currentBox, GetSelectedBox());
   }
 #endif
 
@@ -166,16 +174,44 @@ void nsDeckFrame::BuildDisplayListForChi
   if (!box) return;
 
   // Putting the child in the background list. This is a little weird but
   // it matches what we were doing before.
   nsDisplayListSet set(aLists, aLists.BlockBorderBackgrounds());
   BuildDisplayListForChild(aBuilder, box, set);
 }
 
+void nsDeckFrame::Animate(nsIFrame* aParentBox, bool start) {
+  if (!aParentBox) return;
+
+  nsImageBoxFrame* imgBoxFrame = do_QueryFrame(aParentBox);
+  nsImageFrame* imgFrame = do_QueryFrame(aParentBox);
+
+  if (imgBoxFrame) {
+    if (start)
+      imgBoxFrame->RestartAnimation();
+    else
+      imgBoxFrame->StopAnimation();
+  }
+
+  if (imgFrame) {
+    if (start)
+      imgFrame->RestartAnimation();
+    else
+      imgFrame->StopAnimation();
+  }
+
+  for (nsIFrame::ChildListIterator childLists(aParentBox); !childLists.IsDone();
+       childLists.Next()) {
+    for (nsIFrame* child : childLists.CurrentList()) {
+      Animate(child, start);
+    }
+  }
+}
+
 NS_IMETHODIMP
 nsDeckFrame::DoXULLayout(nsBoxLayoutState& aState) {
   // Make sure we tweak the state so it does not resize our children.
   // We will do that.
   uint32_t oldFlags = aState.LayoutFlags();
   aState.SetLayoutFlags(NS_FRAME_NO_SIZE_VIEW | NS_FRAME_NO_VISIBILITY);
 
   // do a normal layout
--- a/layout/xul/nsDeckFrame.h
+++ b/layout/xul/nsDeckFrame.h
@@ -51,15 +51,18 @@ class nsDeckFrame final : public nsBoxFr
   explicit nsDeckFrame(ComputedStyle* aStyle, nsPresContext* aPresContext);
 
   nsIFrame* GetSelectedBox();
 
  protected:
   void IndexChanged();
   int32_t GetSelectedIndex();
   void HideBox(nsIFrame* aBox);
+  void ShowBox(nsIFrame* aBox);
 
  private:
   int32_t mIndex;
 
+  void Animate(nsIFrame*, bool);
+
 };  // class nsDeckFrame
 
 #endif
--- a/layout/xul/nsImageBoxFrame.cpp
+++ b/layout/xul/nsImageBoxFrame.cpp
@@ -119,16 +119,19 @@ static void FireImageDOMEvent(nsIContent
 //
 // Creates a new image frame and returns it
 //
 nsIFrame* NS_NewImageBoxFrame(nsIPresShell* aPresShell, ComputedStyle* aStyle) {
   return new (aPresShell) nsImageBoxFrame(aStyle, aPresShell->GetPresContext());
 }
 
 NS_IMPL_FRAMEARENA_HELPERS(nsImageBoxFrame)
+NS_QUERYFRAME_HEAD(nsImageBoxFrame)
+  NS_QUERYFRAME_ENTRY(nsImageBoxFrame)
+NS_QUERYFRAME_TAIL_INHERITING(nsLeafBoxFrame)
 
 nsresult nsImageBoxFrame::AttributeChanged(int32_t aNameSpaceID,
                                            nsAtom* aAttribute,
                                            int32_t aModType) {
   nsresult rv =
       nsLeafBoxFrame::AttributeChanged(aNameSpaceID, aAttribute, aModType);
 
   if (aAttribute == nsGkAtoms::src) {
@@ -190,16 +193,26 @@ void nsImageBoxFrame::Init(nsIContent* a
   mSuppressStyleCheck = true;
   nsLeafBoxFrame::Init(aContent, aParent, aPrevInFlow);
   mSuppressStyleCheck = false;
 
   UpdateLoadFlags();
   UpdateImage();
 }
 
+void nsImageBoxFrame::RestartAnimation() { UpdateImage(); }
+
+void nsImageBoxFrame::StopAnimation() {
+  if (mImageRequest && mRequestRegistered) {
+    nsLayoutUtils::DeregisterImageRequest(PresContext(), mImageRequest,
+                                          &mRequestRegistered);
+    mImageRequest->CancelAndForgetObserver(NS_BINDING_ABORTED);
+  }
+}
+
 void nsImageBoxFrame::UpdateImage() {
   nsPresContext* presContext = PresContext();
 
   RefPtr<imgRequestProxy> oldImageRequest = mImageRequest;
 
   if (mImageRequest) {
     nsLayoutUtils::DeregisterImageRequest(presContext, mImageRequest,
                                           &mRequestRegistered);
--- a/layout/xul/nsImageBoxFrame.h
+++ b/layout/xul/nsImageBoxFrame.h
@@ -36,16 +36,17 @@ class nsImageBoxListener final : public 
 class nsImageBoxFrame final : public nsLeafBoxFrame {
  public:
   typedef mozilla::image::ImgDrawResult ImgDrawResult;
   typedef mozilla::layers::ImageContainer ImageContainer;
   typedef mozilla::layers::LayerManager LayerManager;
 
   friend class nsDisplayXULImage;
   NS_DECL_FRAMEARENA_HELPERS(nsImageBoxFrame)
+  NS_DECL_QUERYFRAME
 
   virtual nsSize GetXULPrefSize(nsBoxLayoutState& aBoxLayoutState) override;
   virtual nsSize GetXULMinSize(nsBoxLayoutState& aBoxLayoutState) override;
   virtual nscoord GetXULBoxAscent(nsBoxLayoutState& aBoxLayoutState) override;
   virtual void MarkIntrinsicISizesDirty() override;
 
   nsresult Notify(imgIRequest* aRequest, int32_t aType, const nsIntRect* aData);
 
@@ -75,16 +76,19 @@ class nsImageBoxFrame final : public nsL
   void UpdateImage();
 
   /**
    * Update mLoadFlags from content attributes. Does not attempt to reload the
    * image using the new load flags.
    */
   void UpdateLoadFlags();
 
+  void RestartAnimation();
+  void StopAnimation();
+
   virtual void BuildDisplayList(nsDisplayListBuilder* aBuilder,
                                 const nsDisplayListSet& aLists) override;
 
   virtual ~nsImageBoxFrame();
 
   already_AddRefed<imgIContainer> GetImageContainerForPainting(
       const nsPoint& aPt, ImgDrawResult& aDrawResult,
       Maybe<nsPoint>& aAnchorPoint, nsRect& aDest);
--- a/media/webrtc/signaling/src/peerconnection/PeerConnectionCtx.cpp
+++ b/media/webrtc/signaling/src/peerconnection/PeerConnectionCtx.cpp
@@ -169,113 +169,108 @@ void PeerConnectionCtx::Destroy() {
     gInstance->Cleanup();
     delete gInstance;
     gInstance = nullptr;
   }
 
   StopWebRtcLog();
 }
 
+template <typename T>
+static void RecordCommonRtpTelemetry(const T& list, const T& lastList,
+                                     const bool isRemote) {
+  using namespace Telemetry;
+  if (!list.WasPassed()) {
+    return;
+  }
+  for (const auto& s : list.Value()) {
+    const bool isAudio = s.mKind.Value().Find("audio") != -1;
+    if (s.mPacketsLost.WasPassed() && s.mPacketsReceived.WasPassed()) {
+      if (const uint64_t total =
+              s.mPacketsLost.Value() + s.mPacketsReceived.Value()) {
+        HistogramID id =
+            isRemote ? (isAudio ? WEBRTC_AUDIO_QUALITY_OUTBOUND_PACKETLOSS_RATE
+                                : WEBRTC_VIDEO_QUALITY_OUTBOUND_PACKETLOSS_RATE)
+                     : (isAudio ? WEBRTC_AUDIO_QUALITY_INBOUND_PACKETLOSS_RATE
+                                : WEBRTC_VIDEO_QUALITY_INBOUND_PACKETLOSS_RATE);
+        Accumulate(id, (s.mPacketsLost.Value() * 1000) / total);
+      }
+    }
+    if (s.mJitter.WasPassed()) {
+      HistogramID id = isRemote
+                           ? (isAudio ? WEBRTC_AUDIO_QUALITY_OUTBOUND_JITTER
+                                      : WEBRTC_VIDEO_QUALITY_OUTBOUND_JITTER)
+                           : (isAudio ? WEBRTC_AUDIO_QUALITY_INBOUND_JITTER
+                                      : WEBRTC_VIDEO_QUALITY_INBOUND_JITTER);
+      Accumulate(id, s.mJitter.Value() * 1000);
+    }
+    // Record bandwidth telemetry
+    if (s.mBytesReceived.WasPassed() && lastList.WasPassed()) {
+      for (const auto& lastS : lastList.Value()) {
+        if (lastS.mId == s.mId) {
+          int32_t deltaMs = s.mTimestamp.Value() - lastS.mTimestamp.Value();
+          // In theory we're called every second, so delta *should* be in that
+          // range. Small deltas could cause errors due to division
+          if (deltaMs < 500 || deltaMs > 60000 ||
+              !lastS.mBytesReceived.WasPassed()) {
+            break;
+          }
+          HistogramID id =
+              isRemote
+                  ? (isAudio ? WEBRTC_AUDIO_QUALITY_OUTBOUND_BANDWIDTH_KBITS
+                             : WEBRTC_VIDEO_QUALITY_OUTBOUND_BANDWIDTH_KBITS)
+                  : (isAudio ? WEBRTC_AUDIO_QUALITY_INBOUND_BANDWIDTH_KBITS
+                             : WEBRTC_VIDEO_QUALITY_INBOUND_BANDWIDTH_KBITS);
+          // We could accumulate values until enough time has passed
+          // and then Accumulate() but this isn't that important
+          Accumulate(
+              id,
+              ((s.mBytesReceived.Value() - lastS.mBytesReceived.Value()) * 8) /
+                  deltaMs);
+          break;
+        }
+      }
+    }
+  }
+}
+
 // Telemetry reporting every second after start of first call.
 // The threading model around the media pipelines is weird:
 // - The pipelines are containers,
 // - containers that are only safe on main thread, with members only safe on
 //   STS,
 // - hence the there and back again approach.
 
-static auto FindId(const Sequence<RTCInboundRTPStreamStats>& aArray,
-                   const nsString& aId) -> decltype(aArray.Length()) {
-  for (decltype(aArray.Length()) i = 0; i < aArray.Length(); i++) {
-    if (aArray[i].mId.Value() == aId) {
-      return i;
-    }
-  }
-  return aArray.NoIndex;
-}
-
 void PeerConnectionCtx::DeliverStats(RTCStatsQuery& aQuery) {
   using namespace Telemetry;
 
   std::unique_ptr<dom::RTCStatsReportInternal> report(std::move(aQuery.report));
   // First, get reports from a second ago, if any, for calculations below
   std::unique_ptr<dom::RTCStatsReportInternal> lastReport;
   {
     auto i = mLastReports.find(report->mPcid);
     if (i != mLastReports.end()) {
       lastReport = std::move(i->second);
+    } else {
+      lastReport = std::make_unique<dom::RTCStatsReportInternal>();
     }
   }
-
-  if (report->mInboundRTPStreamStats.WasPassed()) {
-    // Then, look for the things we want telemetry on
-    for (auto& s : report->mInboundRTPStreamStats.Value()) {
-      bool isRemote = s.mType.Value() == dom::RTCStatsType::Remote_inbound_rtp;
-      bool isAudio = (s.mId.Value().Find("audio") != -1);
-      if (s.mPacketsLost.WasPassed() && s.mPacketsReceived.WasPassed() &&
-          (s.mPacketsLost.Value() + s.mPacketsReceived.Value()) != 0) {
-        HistogramID id;
-        if (isRemote) {
-          id = isAudio ? WEBRTC_AUDIO_QUALITY_OUTBOUND_PACKETLOSS_RATE
-                       : WEBRTC_VIDEO_QUALITY_OUTBOUND_PACKETLOSS_RATE;
-        } else {
-          id = isAudio ? WEBRTC_AUDIO_QUALITY_INBOUND_PACKETLOSS_RATE
-                       : WEBRTC_VIDEO_QUALITY_INBOUND_PACKETLOSS_RATE;
-        }
-        // *1000 so we can read in 10's of a percent (permille)
-        Accumulate(id,
-                   (s.mPacketsLost.Value() * 1000) /
-                       (s.mPacketsLost.Value() + s.mPacketsReceived.Value()));
-      }
-      if (s.mJitter.WasPassed()) {
-        HistogramID id;
-        if (isRemote) {
-          id = isAudio ? WEBRTC_AUDIO_QUALITY_OUTBOUND_JITTER
-                       : WEBRTC_VIDEO_QUALITY_OUTBOUND_JITTER;
-        } else {
-          id = isAudio ? WEBRTC_AUDIO_QUALITY_INBOUND_JITTER
-                       : WEBRTC_VIDEO_QUALITY_INBOUND_JITTER;
-        }
-        Accumulate(id, s.mJitter.Value());
-      }
+  // Record Telemetery
+  RecordCommonRtpTelemetry(report->mInboundRtpStreamStats,
+                           lastReport->mInboundRtpStreamStats, false);
+  RecordCommonRtpTelemetry(report->mRemoteInboundRtpStreamStats,
+                           lastReport->mRemoteInboundRtpStreamStats, true);
+  if (report->mRemoteInboundRtpStreamStats.WasPassed()) {
+    for (const auto& s : report->mRemoteInboundRtpStreamStats.Value()) {
       if (s.mRoundTripTime.WasPassed()) {
-        MOZ_ASSERT(isRemote);
+        const bool isAudio = s.mKind.Value().Find("audio") != -1;
         HistogramID id = isAudio ? WEBRTC_AUDIO_QUALITY_OUTBOUND_RTT
                                  : WEBRTC_VIDEO_QUALITY_OUTBOUND_RTT;
         Accumulate(id, s.mRoundTripTime.Value() * 1000);
       }
-      if (lastReport && lastReport->mInboundRTPStreamStats.WasPassed() &&
-          s.mBytesReceived.WasPassed()) {
-        auto& laststats = lastReport->mInboundRTPStreamStats.Value();
-        auto i = FindId(laststats, s.mId.Value());
-        if (i != laststats.NoIndex) {
-          auto& lasts = laststats[i];
-          if (lasts.mBytesReceived.WasPassed()) {
-            auto delta_ms =
-                int32_t(s.mTimestamp.Value() - lasts.mTimestamp.Value());
-            // In theory we're called every second, so delta *should* be in that
-            // range. Small deltas could cause errors due to division
-            if (delta_ms > 500 && delta_ms < 60000) {
-              HistogramID id;
-              if (isRemote) {
-                id = isAudio ? WEBRTC_AUDIO_QUALITY_OUTBOUND_BANDWIDTH_KBITS
-                             : WEBRTC_VIDEO_QUALITY_OUTBOUND_BANDWIDTH_KBITS;
-              } else {
-                id = isAudio ? WEBRTC_AUDIO_QUALITY_INBOUND_BANDWIDTH_KBITS
-                             : WEBRTC_VIDEO_QUALITY_INBOUND_BANDWIDTH_KBITS;
-              }
-              Accumulate(id, ((s.mBytesReceived.Value() -
-                               lasts.mBytesReceived.Value()) *
-                              8) /
-                                 delta_ms);
-            }
-            // We could accumulate values until enough time has passed
-            // and then Accumulate() but this isn't that important.
-          }
-        }
-      }
     }
   }
 
   mLastReports[report->mPcid] = std::move(report);
 }
 
 void PeerConnectionCtx::EverySecondTelemetryCallback_m(nsITimer* timer,
                                                        void* closure) {
--- a/media/webrtc/signaling/src/peerconnection/PeerConnectionImpl.cpp
+++ b/media/webrtc/signaling/src/peerconnection/PeerConnectionImpl.cpp
@@ -1558,18 +1558,20 @@ nsresult PeerConnectionImpl::GetTimeSinc
 }
 
 class RTCStatsReportInternalConstruct : public RTCStatsReportInternal {
  public:
   RTCStatsReportInternalConstruct(const nsString& pcid,
                                   DOMHighResTimeStamp now) {
     mPcid = pcid;
     mRtpContributingSourceStats.Construct();
-    mInboundRTPStreamStats.Construct();
-    mOutboundRTPStreamStats.Construct();
+    mInboundRtpStreamStats.Construct();
+    mOutboundRtpStreamStats.Construct();
+    mRemoteInboundRtpStreamStats.Construct();
+    mRemoteOutboundRtpStreamStats.Construct();
     mMediaStreamTrackStats.Construct();
     mMediaStreamStats.Construct();
     mTransportStats.Construct();
     mIceComponentStats.Construct();
     mIceCandidatePairStats.Construct();
     mIceCandidateStats.Construct();
     mCodecStats.Construct();
     mTimestamp.Construct(now);
@@ -2822,40 +2824,40 @@ RefPtr<RTCStatsQueryPromise> PeerConnect
           uint32_t packetsReceived;
           uint64_t bytesReceived;
           uint32_t packetsLost;
           int32_t rtt;
           if (mp.Conduit()->GetRTCPReceiverReport(&jitterMs, &packetsReceived,
                                                   &bytesReceived, &packetsLost,
                                                   &rtt)) {
             remoteId = NS_LITERAL_STRING("outbound_rtcp_") + idstr;
-            RTCInboundRTPStreamStats s;
+            RTCRemoteInboundRtpStreamStats s;
             // TODO Bug 1496533 - use reception time not query time
             s.mTimestamp.Construct(query->now);
             s.mId.Construct(remoteId);
             s.mType.Construct(RTCStatsType::Remote_inbound_rtp);
             ssrc.apply([&s](uint32_t aSsrc) { s.mSsrc.Construct(aSsrc); });
             s.mMediaType.Construct(
                 kind);  // mediaType is the old name for kind.
             s.mKind.Construct(kind);
             s.mJitter.Construct(double(jitterMs) / 1000);
             s.mLocalId.Construct(localId);
             s.mPacketsReceived.Construct(packetsReceived);
             s.mBytesReceived.Construct(bytesReceived);
             s.mPacketsLost.Construct(packetsLost);
             if (rtt > 0) {  // RTT is not reported when it is zero
               s.mRoundTripTime.Construct(static_cast<double>(rtt) / 1000);
             }
-            query->report->mInboundRTPStreamStats.Value().AppendElement(
+            query->report->mRemoteInboundRtpStreamStats.Value().AppendElement(
                 s, fallible);
           }
         }
         // Then, fill in local side (with cross-link to remote only if present)
         {
-          RTCOutboundRTPStreamStats s;
+          RTCOutboundRtpStreamStats s;
           // TODO Bug 1496533 - use reception time not query time
           s.mTimestamp.Construct(query->now);
           s.mId.Construct(localId);
           s.mType.Construct(RTCStatsType::Outbound_rtp);
           ssrc.apply([&s](uint32_t aSsrc) { s.mSsrc.Construct(aSsrc); });
           s.mMediaType.Construct(kind);  // mediaType is the old name for kind.
           s.mKind.Construct(kind);
           s.mRemoteId.Construct(remoteId);
@@ -2889,17 +2891,17 @@ RefPtr<RTCStatsQueryPromise> PeerConnect
               s.mFramerateStdDev.Construct(framerateStdDev);
               s.mBitrateMean.Construct(bitrateMean);
               s.mBitrateStdDev.Construct(bitrateStdDev);
               s.mDroppedFrames.Construct(droppedFrames);
               s.mFramesEncoded.Construct(framesEncoded);
               qpSum.apply([&s](uint64_t aQp) { s.mQpSum.Construct(aQp); });
             }
           });
-          query->report->mOutboundRTPStreamStats.Value().AppendElement(
+          query->report->mOutboundRtpStreamStats.Value().AppendElement(
               s, fallible);
         }
         break;
       }
       case MediaPipeline::DirectionType::RECEIVE: {
         nsString localId = NS_LITERAL_STRING("inbound_rtp_") + idstr;
         nsString remoteId;
         Maybe<uint32_t> ssrc;
@@ -2908,34 +2910,34 @@ RefPtr<RTCStatsQueryPromise> PeerConnect
           ssrc = Some(ssrcval);
         }
         {
           // First, fill in remote stat with rtcp sender data, if present.
           uint32_t packetsSent;
           uint64_t bytesSent;
           if (mp.Conduit()->GetRTCPSenderReport(&packetsSent, &bytesSent)) {
             remoteId = NS_LITERAL_STRING("inbound_rtcp_") + idstr;
-            RTCOutboundRTPStreamStats s;
+            RTCRemoteOutboundRtpStreamStats s;
             // TODO Bug 1496533 - use reception time not query time
             s.mTimestamp.Construct(query->now);
             s.mId.Construct(remoteId);
             s.mType.Construct(RTCStatsType::Remote_outbound_rtp);
             ssrc.apply([&s](uint32_t aSsrc) { s.mSsrc.Construct(aSsrc); });
             s.mMediaType.Construct(
                 kind);  // mediaType is the old name for kind.
             s.mKind.Construct(kind);
             s.mLocalId.Construct(localId);
             s.mPacketsSent.Construct(packetsSent);
             s.mBytesSent.Construct(bytesSent);
-            query->report->mOutboundRTPStreamStats.Value().AppendElement(
+            query->report->mRemoteOutboundRtpStreamStats.Value().AppendElement(
                 s, fallible);
           }
         }
         // Then, fill in local side (with cross-link to remote only if present)
-        RTCInboundRTPStreamStats s;
+        RTCInboundRtpStreamStats s;
         s.mTimestamp.Construct(query->now);
         s.mId.Construct(localId);
         s.mType.Construct(RTCStatsType::Inbound_rtp);
         ssrc.apply([&s](uint32_t aSsrc) { s.mSsrc.Construct(aSsrc); });
         s.mMediaType.Construct(kind);  // mediaType is the old name for kind.
         s.mKind.Construct(kind);
         unsigned int jitterMs, packetsLost;
         if (mp.Conduit()->GetRTPReceiverStats(&jitterMs, &packetsLost)) {
@@ -2972,17 +2974,17 @@ RefPtr<RTCStatsQueryPromise> PeerConnect
             s.mFramerateMean.Construct(framerateMean);
             s.mFramerateStdDev.Construct(framerateStdDev);
             s.mBitrateMean.Construct(bitrateMean);
             s.mBitrateStdDev.Construct(bitrateStdDev);
             s.mDiscardedPackets.Construct(discardedPackets);
             s.mFramesDecoded.Construct(framesDecoded);
           }
         });
-        query->report->mInboundRTPStreamStats.Value().AppendElement(s,
+        query->report->mInboundRtpStreamStats.Value().AppendElement(s,
                                                                     fallible);
         // Fill in Contributing Source statistics
         mp.GetContributingSourceStats(
             localId, query->report->mRtpContributingSourceStats.Value());
         break;
       }
     }
   }
--- a/media/webrtc/signaling/src/peerconnection/WebrtcGlobalInformation.cpp
+++ b/media/webrtc/signaling/src/peerconnection/WebrtcGlobalInformation.cpp
@@ -955,23 +955,22 @@ static void StoreLongTermICEStatisticsIm
   for (auto& streamResult : streamResults) {
     Telemetry::RecordWebrtcIceCandidates(
         streamResult.second.candidateTypeBitpattern,
         streamResult.second.streamSucceeded);
   }
 
   // Beyond ICE, accumulate telemetry for various PER_CALL settings here.
 
-  if (query->report->mOutboundRTPStreamStats.WasPassed()) {
-    auto& array = query->report->mOutboundRTPStreamStats.Value();
+  if (query->report->mOutboundRtpStreamStats.WasPassed()) {
+    auto& array = query->report->mOutboundRtpStreamStats.Value();
     for (decltype(array.Length()) i = 0; i < array.Length(); i++) {
       auto& s = array[i];
       bool isVideo = (s.mId.Value().Find("video") != -1);
-      bool isRemote = s.mType.Value() == dom::RTCStatsType::Remote_outbound_rtp;
-      if (!isVideo || isRemote) {
+      if (!isVideo) {
         continue;
       }
       if (s.mBitrateMean.WasPassed()) {
         Accumulate(WEBRTC_VIDEO_ENCODER_BITRATE_AVG_PER_CALL_KBPS,
                    uint32_t(s.mBitrateMean.Value() / 1000));
       }
       if (s.mBitrateStdDev.WasPassed()) {
         Accumulate(WEBRTC_VIDEO_ENCODER_BITRATE_STD_DEV_PER_CALL_KBPS,
@@ -990,23 +989,22 @@ static void StoreLongTermICEStatisticsIm
         if (mins > 0) {
           Accumulate(WEBRTC_VIDEO_ENCODER_DROPPED_FRAMES_PER_CALL_FPM,
                      uint32_t(double(s.mDroppedFrames.Value()) / mins));
         }
       }
     }
   }
 
-  if (query->report->mInboundRTPStreamStats.WasPassed()) {
-    auto& array = query->report->mInboundRTPStreamStats.Value();
+  if (query->report->mInboundRtpStreamStats.WasPassed()) {
+    auto& array = query->report->mInboundRtpStreamStats.Value();
     for (decltype(array.Length()) i = 0; i < array.Length(); i++) {
       auto& s = array[i];
       bool isVideo = (s.mId.Value().Find("video") != -1);
-      bool isRemote = s.mType.Value() == dom::RTCStatsType::Remote_inbound_rtp;
-      if (!isVideo || isRemote) {
+      if (!isVideo) {
         continue;
       }
       if (s.mBitrateMean.WasPassed()) {
         Accumulate(WEBRTC_VIDEO_DECODER_BITRATE_AVG_PER_CALL_KBPS,
                    uint32_t(s.mBitrateMean.Value() / 1000));
       }
       if (s.mBitrateStdDev.WasPassed()) {
         Accumulate(WEBRTC_VIDEO_DECODER_BITRATE_STD_DEV_PER_CALL_KBPS,
--- a/memory/replace/dmd/DMD.cpp
+++ b/memory/replace/dmd/DMD.cpp
@@ -36,16 +36,17 @@
 #include "mozilla/HashFunctions.h"
 #include "mozilla/HashTable.h"
 #include "mozilla/IntegerPrintfMacros.h"
 #include "mozilla/JSONWriter.h"
 #include "mozilla/Likely.h"
 #include "mozilla/MemoryReporting.h"
 #include "mozilla/PodOperations.h"
 #include "mozilla/StackWalk.h"
+#include "mozilla/ThreadLocal.h"
 
 // CodeAddressService is defined entirely in the header, so this does not make
 // DMD depend on XPCOM's object file.
 #include "CodeAddressService.h"
 
 // replace_malloc.h needs to be included before replace_malloc_bridge.h,
 // which DMD.h includes, so DMD.h needs to be included after replace_malloc.h.
 #include "replace_malloc.h"
@@ -430,85 +431,78 @@ class AutoUnlockState {
   DISALLOW_COPY_AND_ASSIGN(AutoUnlockState);
 
  public:
   AutoUnlockState() { gStateLock->Unlock(); }
   ~AutoUnlockState() { gStateLock->Lock(); }
 };
 
 //---------------------------------------------------------------------------
-// Thread-local storage and blocking of intercepts
+// Per-thread blocking of intercepts
 //---------------------------------------------------------------------------
 
-#ifdef XP_WIN
-
-#  define DMD_TLS_INDEX_TYPE DWORD
-#  define DMD_CREATE_TLS_INDEX(i_) \
-    do {                           \
-      (i_) = TlsAlloc();           \
-    } while (0)
-#  define DMD_DESTROY_TLS_INDEX(i_) TlsFree((i_))
-#  define DMD_GET_TLS_DATA(i_) TlsGetValue((i_))
-#  define DMD_SET_TLS_DATA(i_, v_) TlsSetValue((i_), (v_))
-
+// On MacOS, the first __thread/thread_local access calls malloc, which leads
+// to an infinite loop. So we use pthread-based TLS instead, which somehow
+// doesn't have this problem.
+#if !defined(XP_DARWIN)
+#  define DMD_THREAD_LOCAL(T) MOZ_THREAD_LOCAL(T)
 #else
-
-#  define DMD_TLS_INDEX_TYPE pthread_key_t
-#  define DMD_CREATE_TLS_INDEX(i_) pthread_key_create(&(i_), nullptr)
-#  define DMD_DESTROY_TLS_INDEX(i_) pthread_key_delete((i_))
-#  define DMD_GET_TLS_DATA(i_) pthread_getspecific((i_))
-#  define DMD_SET_TLS_DATA(i_, v_) pthread_setspecific((i_), (v_))
-
+#  define DMD_THREAD_LOCAL(T) \
+    detail::ThreadLocal<T, detail::ThreadLocalKeyStorage>
 #endif
 
-static DMD_TLS_INDEX_TYPE gTlsIndex;
-
 class Thread {
   // Required for allocation via InfallibleAllocPolicy::new_.
   friend class InfallibleAllocPolicy;
 
   // When true, this blocks intercepts, which allows malloc interception
   // functions to themselves call malloc.  (Nb: for direct calls to malloc we
   // can just use InfallibleAllocPolicy::{malloc_,new_}, but we sometimes
   // indirectly call vanilla malloc via functions like MozStackWalk.)
   bool mBlockIntercepts;
 
   Thread() : mBlockIntercepts(false) {}
 
   DISALLOW_COPY_AND_ASSIGN(Thread);
 
+  static DMD_THREAD_LOCAL(Thread*) tlsThread;
+
  public:
-  static Thread* Fetch();
+  static void Init() {
+    if (!tlsThread.init()) {
+      MOZ_CRASH();
+    }
+  }
+
+  static Thread* Fetch() {
+    Thread* t = tlsThread.get();
+    if (MOZ_UNLIKELY(!t)) {
+      // This memory is never freed, even if the thread dies. It's a leak, but
+      // only a tiny one.
+      t = InfallibleAllocPolicy::new_<Thread>();
+      tlsThread.set(t);
+    }
+
+    return t;
+  }
 
   bool BlockIntercepts() {
     MOZ_ASSERT(!mBlockIntercepts);
     return mBlockIntercepts = true;
   }
 
   bool UnblockIntercepts() {
     MOZ_ASSERT(mBlockIntercepts);
     return mBlockIntercepts = false;
   }
 
   bool InterceptsAreBlocked() const { return mBlockIntercepts; }
 };
 
-/* static */
-Thread* Thread::Fetch() {
-  Thread* t = static_cast<Thread*>(DMD_GET_TLS_DATA(gTlsIndex));
-
-  if (MOZ_UNLIKELY(!t)) {
-    // This memory is never freed, even if the thread dies.  It's a leak, but
-    // only a tiny one.
-    t = InfallibleAllocPolicy::new_<Thread>();
-    DMD_SET_TLS_DATA(gTlsIndex, t);
-  }
-
-  return t;
-}
+DMD_THREAD_LOCAL(Thread*) Thread::tlsThread;
 
 // An object of this class must be created (on the stack) before running any
 // code that might allocate.
 class AutoBlockIntercepts {
   Thread* const mT;
 
   DISALLOW_COPY_AND_ASSIGN(AutoBlockIntercepts);
 
@@ -1405,17 +1399,17 @@ static bool Init(malloc_table_t* aMalloc
   gOptions = InfallibleAllocPolicy::new_<Options>(e);
 
   gStateLock = InfallibleAllocPolicy::new_<Mutex>();
 
   gBernoulli = (FastBernoulliTrial*)InfallibleAllocPolicy::malloc_(
       sizeof(FastBernoulliTrial));
   ResetBernoulli();
 
-  DMD_CREATE_TLS_INDEX(gTlsIndex);
+  Thread::Init();
 
   {
     AutoLockState lock;
 
     gStackTraceTable = InfallibleAllocPolicy::new_<StackTraceTable>(8192);
     gLiveBlockTable = InfallibleAllocPolicy::new_<LiveBlockTable>(8192);
 
     // Create this even if the mode isn't Cumulative (albeit with a small
--- a/mfbt/tests/TestFloatingPoint.cpp
+++ b/mfbt/tests/TestFloatingPoint.cpp
@@ -2,16 +2,17 @@
 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "mozilla/Compiler.h"
 #include "mozilla/FloatingPoint.h"
 
+#include <cmath>  // exp2
 #include <float.h>
 #include <math.h>
 
 using mozilla::ExponentComponent;
 using mozilla::FloatingPoint;
 using mozilla::FuzzyEqualsAdditive;
 using mozilla::FuzzyEqualsMultiplicative;
 using mozilla::IsFinite;
@@ -23,16 +24,18 @@ using mozilla::IsNegativeZero;
 using mozilla::IsPositiveZero;
 using mozilla::NegativeInfinity;
 using mozilla::NumberEqualsInt32;
 using mozilla::NumberIsInt32;
 using mozilla::NumbersAreIdentical;
 using mozilla::PositiveInfinity;
 using mozilla::SpecificNaN;
 using mozilla::UnspecifiedNaN;
+using std::exp2;
+using std::exp2f;
 
 #define A(a) MOZ_RELEASE_ASSERT(a)
 
 template <typename T>
 static void ShouldBeIdentical(T aD1, T aD2) {
   A(NumbersAreIdentical(aD1, aD2));
   A(NumbersAreIdentical(aD2, aD1));
 }
@@ -303,35 +306,31 @@ static void TestDoublesPredicates() {
   A(i == INT32_MIN);
   A(NumberIsInt32(double(INT32_MAX), &i));
   A(i == INT32_MAX);
   A(NumberEqualsInt32(double(INT32_MIN), &i));
   A(i == INT32_MIN);
   A(NumberEqualsInt32(double(INT32_MAX), &i));
   A(i == INT32_MAX);
 
-  // MSVC seems to compile 2**-1075, which should be half of the smallest
-  // IEEE-754 double precision value, to equal 2**-1074 right now.  This might
-  // be the result of a missing compiler flag to force more-accurate floating
-  // point calculations; bug 1440184 has been filed as a followup to fix this,
-  // so that only the first half of this condition is necessary.
-  A(pow(2.0, -1075.0) == 0.0 ||
-    (MOZ_IS_MSVC && pow(2.0, -1075.0) == pow(2.0, -1074.0)));
+  // Sanity-check that the IEEE-754 double-precision-derived literals used in
+  // testing here work as we intend them to.
+  A(exp2(-1075.0) == 0.0);
+  A(exp2(-1074.0) != 0.0);
 
-  A(pow(2.0, -1074.0) != 0.0);
-  A(!NumberIsInt32(pow(2.0, -1074.0), &i));
-  A(!NumberIsInt32(2 * pow(2.0, -1074.0), &i));
+  A(!NumberIsInt32(exp2(-1074.0), &i));
+  A(!NumberIsInt32(2 * exp2(-1074.0), &i));
   A(!NumberIsInt32(0.5, &i));
-  A(1.0 - pow(2.0, -54.0) == 1.0);
-  A(1.0 - pow(2.0, -53.0) != 1.0);
-  A(!NumberIsInt32(1.0 - pow(2.0, -53.0), &i));
-  A(!NumberIsInt32(1.0 - pow(2.0, -52.0), &i));
-  A(1.0 + pow(2.0, -53.0) == 1.0f);
-  A(1.0 + pow(2.0, -52.0) != 1.0f);
-  A(!NumberIsInt32(1.0 + pow(2.0, -52.0), &i));
+  A(1.0 - exp2(-54.0) == 1.0);
+  A(1.0 - exp2(-53.0) != 1.0);
+  A(!NumberIsInt32(1.0 - exp2(-53.0), &i));
+  A(!NumberIsInt32(1.0 - exp2(-52.0), &i));
+  A(1.0 + exp2(-53.0) == 1.0f);
+  A(1.0 + exp2(-52.0) != 1.0f);
+  A(!NumberIsInt32(1.0 + exp2(-52.0), &i));
   A(!NumberIsInt32(1.5f, &i));
   A(!NumberIsInt32(-double(2147483649), &i));
   A(!NumberIsInt32(double(2147483648), &i));
   A(!NumberIsInt32(-double(1ULL << 52) + 0.5, &i));
   A(!NumberIsInt32(double(1ULL << 52) - 0.5, &i));
   A(!NumberIsInt32(double(2147483648), &i));
   A(!NumberIsInt32(double(INT32_MAX) + 0.1, &i));
   A(!NumberIsInt32(double(INT32_MIN) - 0.1, &i));
@@ -621,55 +620,55 @@ static void TestIsFloat32Representable()
   A(IsFloat32Representable(SpecificNaN<double>(0, 71389)));
   A(IsFloat32Representable(SpecificNaN<double>(0, (uint64_t(1) << 52) - 2)));
   A(IsFloat32Representable(SpecificNaN<double>(1, 1)));
   A(IsFloat32Representable(SpecificNaN<double>(1, 71389)));
   A(IsFloat32Representable(SpecificNaN<double>(1, (uint64_t(1) << 52) - 2)));
   A(IsFloat32Representable(PositiveInfinity<double>()));
   A(IsFloat32Representable(NegativeInfinity<double>()));
 
-  // MSVC seems to compile 2**-1075, which should be half of the smallest
-  // IEEE-754 double precision value, to equal 2**-1074 right now.  This might
-  // be the result of a missing compiler flag to force more-accurate floating
-  // point calculations; bug 1440184 has been filed as a followup to fix this,
-  // so that only the first half of this condition is necessary.
-  A(pow(2.0, -1075.0) == 0.0 ||
-    (MOZ_IS_MSVC && pow(2.0, -1075.0) == pow(2.0, -1074.0)));
-
-  A(powf(2.0f, -150.0f) == 0.0);
-  A(powf(2.0f, -149.0f) != 0.0);
+  // Sanity-check that the IEEE-754 double-precision-derived literals used in
+  // testing here work as we intend them to.
+  A(exp2(-1075.0) == 0.0);
+  A(exp2(-1074.0) != 0.0);
 
   for (double littleExp = -1074.0; littleExp < -149.0; littleExp++) {
-    // Powers of two below the available range aren't representable.
-    A(!IsFloat32Representable(pow(2.0, littleExp)));
+    // Powers of two representable as doubles but not as floats aren't
+    // representable.
+    A(!IsFloat32Representable(exp2(littleExp)));
   }
 
+  // Sanity-check that the IEEE-754 single-precision-derived literals used in
+  // testing here work as we intend them to.
+  A(exp2f(-150.0f) == 0.0);
+  A(exp2f(-149.0f) != 0.0);
+
   // Exact powers of two within the available range are representable.
   for (double exponent = -149.0; exponent < 128.0; exponent++) {
-    A(IsFloat32Representable(pow(2.0, exponent)));
+    A(IsFloat32Representable(exp2(exponent)));
   }
 
   // Powers of two above the available range aren't representable.
   for (double bigExp = 128.0; bigExp < 1024.0; bigExp++) {
-    A(!IsFloat32Representable(pow(2.0, bigExp)));
+    A(!IsFloat32Representable(exp2(bigExp)));
   }
 
   // Various denormal (i.e. super-small) doubles with MSB and LSB as far apart
   // as possible are representable (but taken one bit further apart are not
   // representable).
   //
   // Note that the final iteration tests non-denormal with exponent field
   // containing (biased) 1, as |oneTooSmall| and |widestPossible| happen still
   // to be correct for that exponent due to the extra bit of precision in the
   // implicit-one bit.
-  double oneTooSmall = pow(2.0, -150.0);
+  double oneTooSmall = exp2(-150.0);
   for (double denormExp = -149.0;
        denormExp < 1 - double(FloatingPoint<double>::kExponentBias) + 1;
        denormExp++) {
-    double baseDenorm = pow(2.0, denormExp);
+    double baseDenorm = exp2(denormExp);
     double tooWide = baseDenorm + oneTooSmall;
     A(!IsFloat32Representable(tooWide));
 
     double widestPossible = baseDenorm;
     if (oneTooSmall * 2.0 != baseDenorm) {
       widestPossible += oneTooSmall * 2.0;
     }
 
--- a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/media/RemoteManager.java
+++ b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/media/RemoteManager.java
@@ -60,17 +60,17 @@ public final class RemoteManager impleme
         public void onServiceDisconnected(final ComponentName name) {
             if (DEBUG) Log.d(LOGTAG, "service disconnected");
             unlink();
         }
 
         private boolean connect() {
             Context appCtxt = GeckoAppShell.getApplicationContext();
             appCtxt.bindService(new Intent(appCtxt, MediaManager.class),
-                    mConnection, Context.BIND_AUTO_CREATE);
+                    mConnection, Context.BIND_AUTO_CREATE | Context.BIND_IMPORTANT);
             waitConnect();
             return mRemote != null;
         }
 
         // Wait up to 5s.
         private synchronized void waitConnect() {
             int waitCount = 0;
             while (mRemote == null && waitCount < 5) {
--- a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/process/GeckoProcessManager.java
+++ b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/process/GeckoProcessManager.java
@@ -81,17 +81,17 @@ public final class GeckoProcessManager e
                 return mChild;
             }
 
             final Context context = GeckoAppShell.getApplicationContext();
             final Intent intent = new Intent();
             intent.setClassName(context,
                                 GeckoServiceChildProcess.class.getName() + '$' + mType);
 
-            if (context.bindService(intent, this, Context.BIND_AUTO_CREATE)) {
+            if (context.bindService(intent, this, Context.BIND_AUTO_CREATE | Context.BIND_IMPORTANT)) {
                 waitForChildLocked();
                 if (mChild != null) {
                     return mChild;
                 }
             }
 
             Log.e(LOGTAG, "Cannot connect to process " + mType);
             unbind();
--- a/mobile/android/modules/ActionBarHandler.jsm
+++ b/mobile/android/modules/ActionBarHandler.jsm
@@ -469,17 +469,17 @@ var ActionBarHandler = {
             return false;
           }
           // Allow if selected text exists.
           return (ActionBarHandler._getSelectedText().length > 0);
         },
       },
 
       action: function(element, win) {
-        ActionBarHandler._getEditor(element, win).copy();
+        ActionBarHandler._getDocShell(win).doCommand("cmd_copy");
 
         let msg = Strings.browser.GetStringFromName("selectionHelper.textCopied");
         Snackbars.show(msg, Snackbars.LENGTH_LONG);
 
         ActionBarHandler._uninit();
         UITelemetry.addEvent("action.1", "actionbar", null, "copy");
       },
     },
@@ -728,16 +728,23 @@ var ActionBarHandler = {
   /**
    * If we have an active selection, is it part of a designMode document?
    */
   _isInDesignMode: function(win) {
     return this._selectionID && (win.document.designMode === "on");
   },
 
   /**
+   * Get current DocShell object.
+   */
+  _getDocShell: function(win) {
+    return win.docShell;
+  },
+
+  /**
    * Provides the currently selected text, for either an editable,
    * or for the default contentWindow.
    */
   _getSelectedText: function() {
     // Can be called from FindInPageBar "TextSelection:Get", when there
     // is no active selection.
     if (!this._selectionID) {
       return "";
--- a/modules/libpref/init/all.js
+++ b/modules/libpref/init/all.js
@@ -215,24 +215,24 @@ pref("dom.keyboardevent.dispatch_during_
 pref("dom.keyboardevent.keypress.dispatch_non_printable_keys_only_system_group_in_content", true);
 
 // Blacklist of domains of web apps which are not aware of strict keypress
 // dispatching behavior.  This is comma separated list.  If you need to match
 // all sub-domains, you can specify it as "*.example.com".  Additionally, you
 // can limit the path.  E.g., "example.com/foo" means "example.com/foo*".  So,
 // if you need to limit under a directory, the path should end with "/" like
 // "example.com/foo/".  Note that this cannot limit port number for now.
-pref("dom.keyboardevent.keypress.hack.dispatch_non_printable_keys", "");
+pref("dom.keyboardevent.keypress.hack.dispatch_non_printable_keys", "www.icloud.com");
 
 // Blacklist of domains of web apps which handle keyCode and charCode of
 // keypress events with a path only for Firefox (i.e., broken if we set
 // non-zero keyCode or charCode value to the other).  The format is exactly
 // same as "dom.keyboardevent.keypress.hack.dispatch_non_printable_keys". So,
 // check its explanation for the detail.
-pref("dom.keyboardevent.keypress.hack.use_legacy_keycode_and_charcode", "powerpoint.officeapps.live.com");
+pref("dom.keyboardevent.keypress.hack.use_legacy_keycode_and_charcode", "*.gov.online.office365.us,*.officeapps-df.live.com.,*.officeapps.live.com,*.online.office.de,*.partner.officewebapps.cn,mail.notes.na.collabserv.com");
 
 // Whether InputEvent.data is enabled.
 pref("dom.inputevent.data.enabled", true);
 
 // Whether InputEvent.dataTransfer is enabled.
 pref("dom.inputevent.datatransfer.enabled", true);
 
 // Whether InputEvent.inputType is enabled.
--- a/mozglue/misc/PlatformConditionVariable.h
+++ b/mozglue/misc/PlatformConditionVariable.h
@@ -31,18 +31,22 @@ class ConditionVariableImpl {
   MFBT_API ~ConditionVariableImpl();
 
   // Wake one thread that is waiting on this condition.
   MFBT_API void notify_one();
 
   // Wake all threads that are waiting on this condition.
   MFBT_API void notify_all();
 
-  // Block the current thread of execution until this condition variable is
-  // woken from another thread via notify_one or notify_all.
+  // Atomically release |lock| and sleep the current thread of execution on
+  // this condition variable.
+  // |lock| will be re-acquired before this function returns.
+  // The thread may be woken from sleep from another thread via notify_one()
+  // or notify_all(), but may also wake spuriously.  The caller should recheck
+  // its predicate after this function returns, typically in a while loop.
   MFBT_API void wait(MutexImpl& lock);
 
   MFBT_API CVStatus wait_for(MutexImpl& lock,
                              const mozilla::TimeDuration& rel_time);
 
  private:
   ConditionVariableImpl(const ConditionVariableImpl&) = delete;
   ConditionVariableImpl& operator=(const ConditionVariableImpl&) = delete;
--- a/netwerk/base/nsBaseChannel.cpp
+++ b/netwerk/base/nsBaseChannel.cpp
@@ -245,23 +245,55 @@ nsresult nsBaseChannel::BeginPumpingData
   // important that the pending flag is set when we call into the stream (the
   // call to AsyncRead results in the stream's AsyncWait method being called)
   // and especially when we call into the loadgroup.  Our caller takes care to
   // release mPump if we return an error.
 
   nsCOMPtr<nsIEventTarget> target = GetNeckoTarget();
   rv = nsInputStreamPump::Create(getter_AddRefs(mPump), stream, 0, 0, true,
                                  target);
-  if (NS_SUCCEEDED(rv)) {
-    mPumpingData = true;
-    mRequest = mPump;
-    rv = mPump->AsyncRead(this, nullptr);
+  if (NS_FAILED(rv)) {
+    return rv;
+  }
+
+  mPumpingData = true;
+  mRequest = mPump;
+  rv = mPump->AsyncRead(this, nullptr);
+  if (NS_FAILED(rv)) {
+    return rv;
+  }
+
+  RefPtr<BlockingPromise> promise;
+  rv = ListenerBlockingPromise(getter_AddRefs(promise));
+  if (NS_FAILED(rv)) {
+    return rv;
   }
 
-  return rv;
+  if (promise) {
+    mPump->Suspend();
+
+    RefPtr<nsBaseChannel> self(this);
+    nsCOMPtr<nsISerialEventTarget> serialTarget(do_QueryInterface(target));
+    MOZ_ASSERT(serialTarget);
+
+    promise->Then(serialTarget, __func__,
+                  [self, this](nsresult rv) {
+                    MOZ_ASSERT(mPump);
+                    MOZ_ASSERT(NS_SUCCEEDED(rv));
+                    mPump->Resume();
+                  },
+                  [self, this](nsresult rv) {
+                    MOZ_ASSERT(mPump);
+                    MOZ_ASSERT(NS_FAILED(rv));
+                    Cancel(rv);
+                    mPump->Resume();
+                  });
+  }
+
+  return NS_OK;
 }
 
 void nsBaseChannel::HandleAsyncRedirect(nsIChannel *newChannel) {
   NS_ASSERTION(!mPumpingData, "Shouldn't have gotten here");
 
   nsresult rv = mStatus;
   if (NS_SUCCEEDED(mStatus)) {
     rv = Redirect(newChannel, nsIChannelEventSink::REDIRECT_TEMPORARY, true);
--- a/netwerk/base/nsBaseChannel.h
+++ b/netwerk/base/nsBaseChannel.h
@@ -2,16 +2,17 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef nsBaseChannel_h__
 #define nsBaseChannel_h__
 
 #include "mozilla/net/NeckoTargetHolder.h"
+#include "mozilla/MozPromise.h"
 #include "nsString.h"
 #include "nsAutoPtr.h"
 #include "nsCOMPtr.h"
 #include "nsHashPropertyBag.h"
 #include "nsInputStreamPump.h"
 
 #include "nsIChannel.h"
 #include "nsIURI.h"
@@ -68,16 +69,18 @@ class nsBaseChannel
   nsresult Init() { return NS_OK; }
 
  protected:
   // -----------------------------------------------
   // Methods to be implemented by the derived class:
 
   virtual ~nsBaseChannel();
 
+  using BlockingPromise = mozilla::MozPromise<nsresult, nsresult, true>;
+
  private:
   // Implemented by subclass to supply data stream.  The parameter, async, is
   // true when called from nsIChannel::AsyncOpen and false otherwise.  When
   // async is true, the resulting stream will be used with a nsIInputStreamPump
   // instance.  This means that if it is a non-blocking stream that supports
   // nsIAsyncInputStream that it will be read entirely on the main application
   // thread, and its AsyncWait method will be called whenever ReadSegments
   // returns NS_BASE_STREAM_WOULD_BLOCK.  Otherwise, if the stream is blocking,
@@ -101,16 +104,30 @@ class nsBaseChannel
   // and at some point call OnStartRequest followed by OnStopRequest.
   // Additionally, it may provide a request object which may be used to
   // suspend, resume, and cancel the underlying request.
   virtual nsresult BeginAsyncRead(nsIStreamListener *listener,
                                   nsIRequest **request) {
     return NS_ERROR_NOT_IMPLEMENTED;
   }
 
+  // This method may return a promise that will keep the input stream pump
+  // suspended until the promise is resolved or rejected.  On resolution the
+  // pump is resumed.  On rejection the channel is canceled with the resulting
+  // error and then the pump is also resumed to propagate the error to the
+  // channel listener.  Use it to do any asynchronous/background tasks you need
+  // to finish prior calling OnStartRequest of the listener.  This method is
+  // called right after OpenContentStream() with async == true, after the input
+  // stream pump has already been called asyncRead().
+  virtual nsresult ListenerBlockingPromise(BlockingPromise **aPromise) {
+    NS_ENSURE_ARG(aPromise);
+    *aPromise = nullptr;
+    return NS_OK;
+  }
+
   // The basechannel calls this method from its OnTransportStatus method to
   // determine whether to call nsIProgressEventSink::OnStatus in addition to
   // nsIProgressEventSink::OnProgress.  This method may be overriden by the
   // subclass to enable nsIProgressEventSink::OnStatus events.  If this method
   // returns true, then the statusArg out param specifies the "statusArg" value
   // to pass to the OnStatus method.  By default, OnStatus messages are
   // suppressed.  The status parameter passed to this method is the status value
   // from the OnTransportStatus method.
--- a/netwerk/protocol/file/nsFileChannel.cpp
+++ b/netwerk/protocol/file/nsFileChannel.cpp
@@ -24,16 +24,17 @@
 
 #include "nsIFileURL.h"
 #include "nsIURIMutator.h"
 #include "nsIFile.h"
 #include "nsIMIMEService.h"
 #include "prio.h"
 #include <algorithm>
 
+#include "mozilla/TaskQueue.h"
 #include "mozilla/Unused.h"
 
 using namespace mozilla;
 using namespace mozilla::net;
 
 //-----------------------------------------------------------------------------
 
 class nsFileCopyEvent : public Runnable {
@@ -386,37 +387,90 @@ nsresult nsFileChannel::OpenContentStrea
   } else {
     nsAutoCString contentType;
     rv = MakeFileInputStream(file, stream, contentType, async);
     if (NS_FAILED(rv)) return rv;
 
     EnableSynthesizedProgressEvents(true);
 
     // fixup content length and type
-    if (mContentLength < 0) {
-      int64_t size;
-      rv = file->GetFileSize(&size);
+
+    // when we are called from asyncOpen, the content length fixup will be
+    // performed on a background thread and block the listener invocation via
+    // ListenerBlockingPromise method
+    if (!async && mContentLength < 0) {
+      rv = FixupContentLength(false);
       if (NS_FAILED(rv)) {
-        if (async && (NS_ERROR_FILE_NOT_FOUND == rv ||
-                      NS_ERROR_FILE_TARGET_DOES_NOT_EXIST == rv)) {
-          size = 0;
-        } else {
-          return rv;
-        }
+        return rv;
       }
-      mContentLength = size;
     }
-    if (!contentType.IsEmpty()) SetContentType(contentType);
+
+    if (!contentType.IsEmpty()) {
+      SetContentType(contentType);
+    }
   }
 
   *result = nullptr;
   stream.swap(*result);
   return NS_OK;
 }
 
+nsresult nsFileChannel::ListenerBlockingPromise(BlockingPromise **aPromise) {
+  NS_ENSURE_ARG(aPromise);
+  *aPromise = nullptr;
+
+  if (mContentLength >= 0) {
+    return NS_OK;
+  }
+
+  nsCOMPtr<nsIEventTarget> sts(
+      do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID));
+  if (!sts) {
+    return FixupContentLength(true);
+  }
+
+  RefPtr<TaskQueue> taskQueue = new TaskQueue(sts.forget());
+  RefPtr<nsFileChannel> self = this;
+  RefPtr<BlockingPromise> promise =
+      mozilla::InvokeAsync(taskQueue, __func__, [self{std::move(self)}]() {
+        nsresult rv = self->FixupContentLength(true);
+        if (NS_FAILED(rv)) {
+          return BlockingPromise::CreateAndReject(rv, __func__);
+        }
+        return BlockingPromise::CreateAndResolve(NS_OK, __func__);
+      });
+
+  promise.forget(aPromise);
+  return NS_OK;
+}
+
+nsresult nsFileChannel::FixupContentLength(bool async) {
+  MOZ_ASSERT(mContentLength < 0);
+
+  nsCOMPtr<nsIFile> file;
+  nsresult rv = GetFile(getter_AddRefs(file));
+  if (NS_FAILED(rv)) {
+    return rv;
+  }
+
+  int64_t size;
+  rv = file->GetFileSize(&size);
+  if (NS_FAILED(rv)) {
+    if (async && (NS_ERROR_FILE_NOT_FOUND == rv ||
+                  NS_ERROR_FILE_TARGET_DOES_NOT_EXIST == rv)) {
+      size = 0;
+    } else {
+      return rv;
+    }
+  }
+  mContentLength = size;
+
+  return NS_OK;
+}
+
 //-----------------------------------------------------------------------------
 // nsFileChannel::nsISupports
 
 NS_IMPL_ISUPPORTS_INHERITED(nsFileChannel, nsBaseChannel, nsIUploadChannel,
                             nsIFileChannel)
 
 //-----------------------------------------------------------------------------
 // nsFileChannel::nsIFileChannel
--- a/netwerk/protocol/file/nsFileChannel.h
+++ b/netwerk/protocol/file/nsFileChannel.h
@@ -32,15 +32,21 @@ class nsFileChannel : public nsBaseChann
   // untouched. The caller should not use it in that case.
   MOZ_MUST_USE nsresult MakeFileInputStream(nsIFile *file,
                                             nsCOMPtr<nsIInputStream> &stream,
                                             nsCString &contentType, bool async);
 
   virtual MOZ_MUST_USE nsresult OpenContentStream(
       bool async, nsIInputStream **result, nsIChannel **channel) override;
 
+  // Implementing the pump blocking promise to fixup content length on a
+  // background thread prior to calling on mListener
+  virtual nsresult ListenerBlockingPromise(BlockingPromise **promise) override;
+
  private:
+  nsresult FixupContentLength(bool async);
+
   nsCOMPtr<nsIInputStream> mUploadStream;
   int64_t mUploadLength;
   nsCOMPtr<nsIURI> mFileURI;
 };
 
 #endif  // !nsFileChannel_h__
--- a/netwerk/test/unit/test_trr.js
+++ b/netwerk/test/unit/test_trr.js
@@ -77,17 +77,17 @@ function testsDone()
 
 var test_loops;
 var test_answer="127.0.0.1";
 
 // check that we do lookup the name fine
 var listenerFine = {
   onLookupComplete: function(inRequest, inRecord, inStatus) {
     if (inRequest == listen) {
-      Assert.ok(!inStatus);
+      Assert.equal(inStatus, Cr.NS_OK);
       var answer = inRecord.getNextAddrAsString();
       Assert.equal(answer, test_answer);
       do_test_finished();
       run_dns_tests();
     }
   },
   QueryInterface: function(aIID) {
     if (aIID.equals(Ci.nsIDNSListener) ||
@@ -97,17 +97,17 @@ var listenerFine = {
     throw Cr.NS_ERROR_NO_INTERFACE;
   }
 };
 
 // check that the name lookup fails
 var listenerFails = {
   onLookupComplete: function(inRequest, inRecord, inStatus) {
     if (inRequest == listen) {
-      Assert.ok(!Components.isSuccessCode(inStatus));
+      Assert.ok(!Components.isSuccessCode(inStatus), `must be failure code: ${inStatus}`);
       do_test_finished();
       run_dns_tests();
     }
   },
   QueryInterface: function(aIID) {
     if (aIID.equals(Ci.nsIDNSListener) ||
         aIID.equals(Ci.nsISupports)) {
       return this;
@@ -145,139 +145,152 @@ var listenerUntilFine = {
   }
 };
 
 var listen;
 
 // verify basic A record
 function test1()
 {
+  dns.clearCache(true);
   prefs.setIntPref("network.trr.mode", 2); // TRR-first
   prefs.setCharPref("network.trr.uri", "https://foo.example.com:" + h2Port + "/dns");
   test_answer="127.0.0.1";
   listen = dns.asyncResolve("bar.example.com", 0, listenerFine, mainThread, defaultOriginAttributes);
 }
 
 // verify basic A record - without bootstrapping
 function test1b()
 {
+  dns.clearCache(true);
   prefs.setIntPref("network.trr.mode", 3); // TRR-only
   prefs.setCharPref("network.trr.uri", "https://foo.example.com:" + h2Port + "/dns");
   prefs.clearUserPref("network.trr.bootstrapAddress");
   prefs.setCharPref("network.dns.localDomains", "foo.example.com");
   test_answer = "127.0.0.1";
   listen = dns.asyncResolve("bar.example.com", 0, listenerFine, mainThread, defaultOriginAttributes);
 }
 
 // verify that the name was put in cache - it works with bad DNS URI
 function test2()
 {
+  // Don't clear the cache. That is what we're checking.
   prefs.setIntPref("network.trr.mode", 3); // TRR-only
   //prefs.clearUserPref("network.trr.bootstrapAddress");
   //prefs.setCharPref("network.dns.localDomains", "foo.example.com");
   prefs.setCharPref("network.trr.uri", "https://foo.example.com:" + h2Port + "/404");
   test_answer = "127.0.0.1";
   listen = dns.asyncResolve("bar.example.com", 0, listenerFine, mainThread, defaultOriginAttributes);
 }
 
 // verify working credentials in DOH request
 function test3()
 {
+  dns.clearCache(true);
   prefs.setIntPref("network.trr.mode", 3); // TRR-only
   prefs.setCharPref("network.trr.uri", "https://foo.example.com:" + h2Port + "/dns-auth");
   prefs.setCharPref("network.trr.credentials", "user:password");
   test_answer = "127.0.0.1";
   listen = dns.asyncResolve("bar.example.com", 0, listenerFine, mainThread, defaultOriginAttributes);
 }
 
 // verify failing credentials in DOH request
 function test4()
 {
+  dns.clearCache(true);
   prefs.setIntPref("network.trr.mode", 3); // TRR-only
   prefs.setCharPref("network.trr.uri", "https://foo.example.com:" + h2Port + "/dns-auth");
   prefs.setCharPref("network.trr.credentials", "evil:person");
   test_answer = "127.0.0.1";
   listen = dns.asyncResolve("wrong.example.com", 0, listenerFails, mainThread, defaultOriginAttributes);
 }
 
 // verify DOH push, part A
 function test5()
 {
+  dns.clearCache(true);
   prefs.setIntPref("network.trr.mode", 3); // TRR-only
   prefs.setCharPref("network.trr.uri", "https://foo.example.com:" + h2Port + "/dns-push");
   test_answer = "127.0.0.1";
   listen = dns.asyncResolve("first.example.com", 0, listenerFine, mainThread, defaultOriginAttributes);
 }
 
 function test5b()
 {
   // At this point the second host name should've been pushed and we can resolve it using
   // cache only. Set back the URI to a path that fails.
+  // Don't clear the cache, otherwise we lose the pushed record.
   prefs.setCharPref("network.trr.uri", "https://foo.example.com:" + h2Port + "/404");
   dump("test5b - resolve push.example.now please\n");
   test_answer = "2018::2018";
   listen = dns.asyncResolve("push.example.com", 0, listenerUntilFine, mainThread, defaultOriginAttributes);
 }
 
 // verify AAAA entry
 function test6()
 {
+  dns.clearCache(true);
   prefs.setIntPref("network.trr.mode", 3); // TRR-only
   prefs.setCharPref("network.trr.uri", "https://foo.example.com:" + h2Port + "/dns-aaaa");
   test_answer = "2020:2020::2020";
   listen = dns.asyncResolve("aaaa.example.com", 0, listenerFine, mainThread, defaultOriginAttributes);
 }
 
 // verify RFC1918 address from the server is rejected
 function test7()
 {
+  dns.clearCache(true);
   prefs.setIntPref("network.trr.mode", 3); // TRR-only
   prefs.setCharPref("network.trr.uri", "https://foo.example.com:" + h2Port + "/dns-rfc1918");
   listen = dns.asyncResolve("rfc1918.example.com", 0, listenerFails, mainThread, defaultOriginAttributes);
 }
 
 // verify RFC1918 address from the server is fine when told so
 function test8()
 {
+  dns.clearCache(true);
   prefs.setIntPref("network.trr.mode", 3); // TRR-only
   prefs.setCharPref("network.trr.uri", "https://foo.example.com:" + h2Port + "/dns-rfc1918");
   prefs.setBoolPref("network.trr.allow-rfc1918", true);
   test_answer = "192.168.0.1";
   listen = dns.asyncResolve("rfc1918.example.com", 0, listenerFine, mainThread, defaultOriginAttributes);
 }
 
 // use GET and disable ECS (makes a larger request)
 // verify URI template cutoff
 function test8b()
 {
+  dns.clearCache(true);
   prefs.setIntPref("network.trr.mode", 3); // TRR-only
   prefs.setCharPref("network.trr.uri", "https://foo.example.com:" + h2Port + "/dns-ecs{?dns}");
   prefs.clearUserPref("network.trr.allow-rfc1918");
   prefs.setBoolPref("network.trr.useGET", true);
   prefs.setBoolPref("network.trr.disable-ECS", true);
   test_answer = "5.5.5.5";
   listen = dns.asyncResolve("ecs.example.com", 0, listenerFine, mainThread, defaultOriginAttributes);
 }
 
 // use GET
 function test9()
 {
+  dns.clearCache(true);
   prefs.setIntPref("network.trr.mode", 3); // TRR-only
   prefs.setCharPref("network.trr.uri", "https://foo.example.com:" + h2Port + "/dns-get");
   prefs.clearUserPref("network.trr.allow-rfc1918");
   prefs.setBoolPref("network.trr.useGET", true);
   prefs.setBoolPref("network.trr.disable-ECS", false);
   test_answer = "1.2.3.4";
   listen = dns.asyncResolve("get.example.com", 0, listenerFine, mainThread, defaultOriginAttributes);
 }
 
 // confirmationNS set without confirmed NS yet
 // NOTE: this requires test9 to run before, as the http2 server resets state there
 function test10()
 {
+  dns.clearCache(true);
   prefs.setIntPref("network.trr.mode", 3); // TRR-only
   prefs.clearUserPref("network.trr.useGET");
   prefs.clearUserPref("network.trr.disable-ECS");
   prefs.setCharPref("network.trr.uri", "https://foo.example.com:" + h2Port + "/dns-confirm");
   prefs.setCharPref("network.trr.confirmationNS", "confirm.example.com");
   test_loops = 100; // set for test10b
   try {
     listen = dns.asyncResolve("wrong.example.com", 0, listenerFails,
@@ -288,16 +301,17 @@ function test10()
     do_timeout(200, run_dns_tests);
     //run_dns_tests();
   }
 }
 
 // confirmationNS, retry until the confirmed NS works
 function test10b()
 {
+  dns.clearCache(true);
   print("test confirmationNS, retry until the confirmed NS works");
   prefs.setIntPref("network.trr.mode", 3); // TRR-only
   // same URI as in test10
   test_answer = "1::ffff"
   // this test needs to resolve new names in every attempt since the DNS cache
   // will keep the negative resolved info
   try {
     listen = dns.asyncResolve("10b-" + test_loops + ".example.com", 0, listenerUntilFine,
@@ -306,128 +320,141 @@ function test10b()
     // wait a while and try again
     test_loops--;
     do_timeout(200, test10b);
   }
 }
 // use a slow server and short timeout!
 function test11()
 {
+  dns.clearCache(true);
   prefs.setIntPref("network.trr.mode", 3); // TRR-only
   prefs.setCharPref("network.trr.confirmationNS", "skip");
   prefs.setCharPref("network.trr.uri", "https://foo.example.com:" + h2Port + "/dns-750ms");
   prefs.setIntPref("network.trr.request-timeout", 10);
   listen = dns.asyncResolve("test11.example.com", 0, listenerFails, mainThread, defaultOriginAttributes);
 }
 
 // gets an NS back from DOH
 function test12()
 {
+  dns.clearCache(true);
   prefs.setIntPref("network.trr.mode", 2); // TRR-first
   prefs.setCharPref("network.trr.uri", "https://foo.example.com:" + h2Port + "/dns-ns");
   prefs.clearUserPref("network.trr.request-timeout");
   test_answer = "127.0.0.1";
   listen = dns.asyncResolve("test12.example.com", 0, listenerFine, mainThread, defaultOriginAttributes);
 }
 
 // TRR-first gets a 404 back from DOH
 function test13()
 {
+  dns.clearCache(true);
   prefs.setIntPref("network.trr.mode", 2); // TRR-first
   prefs.setCharPref("network.trr.uri", "https://foo.example.com:" + h2Port + "/404");
   test_answer = "127.0.0.1";
   listen = dns.asyncResolve("test13.example.com", 0, listenerFine, mainThread, defaultOriginAttributes);
 }
 
 // TRR-shadow gets a 404 back from DOH
 function test14()
 {
+  dns.clearCache(true);
   prefs.setIntPref("network.trr.mode", 4); // TRR-shadow
   prefs.setCharPref("network.trr.uri", "https://foo.example.com:" + h2Port + "/404");
   test_answer = "127.0.0.1";
   listen = dns.asyncResolve("test14.example.com", 0, listenerFine, mainThread, defaultOriginAttributes);
 }
 
 // TRR-shadow and timed out TRR
 function test15()
 {
+  dns.clearCache(true);
   prefs.setIntPref("network.trr.mode", 4); // TRR-shadow
   prefs.setCharPref("network.trr.uri", "https://foo.example.com:" + h2Port + "/dns-750ms");
   prefs.setIntPref("network.trr.request-timeout", 10);
   test_answer = "127.0.0.1";
   listen = dns.asyncResolve("test15.example.com", 0, listenerFine, mainThread, defaultOriginAttributes);
 }
 
 // TRR-first and timed out TRR
 function test16()
 {
+  dns.clearCache(true);
   prefs.setIntPref("network.trr.mode", 2); // TRR-first
   prefs.setCharPref("network.trr.uri", "https://foo.example.com:" + h2Port + "/dns-750ms");
   prefs.setIntPref("network.trr.request-timeout", 10);
   test_answer = "127.0.0.1";
   listen = dns.asyncResolve("test16.example.com", 0, listenerFine, mainThread, defaultOriginAttributes);
 }
 
 // TRR-only and chase CNAME
 function test17()
 {
+  dns.clearCache(true);
   prefs.setIntPref("network.trr.mode", 3); // TRR-only
   prefs.setCharPref("network.trr.uri", "https://foo.example.com:" + h2Port + "/dns-cname");
   prefs.clearUserPref("network.trr.request-timeout");
   test_answer = "99.88.77.66";
   listen = dns.asyncResolve("cname.example.com", 0, listenerFine, mainThread, defaultOriginAttributes);
 }
 
 // TRR-only and a CNAME loop
 function test18()
 {
+  dns.clearCache(true);
   prefs.setIntPref("network.trr.mode", 3); // TRR-only
   prefs.setCharPref("network.trr.uri", "https://foo.example.com:" + h2Port + "/dns-cname-loop");
   listen = dns.asyncResolve("test18.example.com", 0, listenerFails, mainThread, defaultOriginAttributes);
 }
 
 // TRR-race and a CNAME loop
 function test19()
 {
+  dns.clearCache(true);
   prefs.setIntPref("network.trr.mode", 1); // Race them!
   prefs.setCharPref("network.trr.uri", "https://foo.example.com:" + h2Port + "/dns-cname-loop");
   test_answer = "127.0.0.1";
   listen = dns.asyncResolve("test19.example.com", 0, listenerFine, mainThread, defaultOriginAttributes);
 }
 
 // TRR-first and a CNAME loop
 function test20()
 {
+  dns.clearCache(true);
   prefs.setIntPref("network.trr.mode", 2); // TRR-first
   prefs.setCharPref("network.trr.uri", "https://foo.example.com:" + h2Port + "/dns-cname-loop");
   test_answer = "127.0.0.1";
   listen = dns.asyncResolve("test20.example.com", 0, listenerFine, mainThread, defaultOriginAttributes);
 }
 
 // TRR-shadow and a CNAME loop
 function test21()
 {
+  dns.clearCache(true);
   prefs.setIntPref("network.trr.mode", 4); // shadow
   prefs.setCharPref("network.trr.uri", "https://foo.example.com:" + h2Port + "/dns-cname-loop");
   test_answer = "127.0.0.1";
   listen = dns.asyncResolve("test21.example.com", 0, listenerFine, mainThread, defaultOriginAttributes);
 }
 
 // verify that basic A record name mismatch gets rejected. Gets the same DOH
 // response back as test1
 function test22()
 {
+  dns.clearCache(true);
   prefs.setIntPref("network.trr.mode", 3); // TRR-only to avoid native fallback
   prefs.setCharPref("network.trr.uri", "https://foo.example.com:" + h2Port + "/dns");
   listen = dns.asyncResolve("mismatch.example.com", 0, listenerFails, mainThread, defaultOriginAttributes);
 }
 
 // TRR-only, with a CNAME response with a bundled A record for that CNAME!
 function test23()
 {
+  dns.clearCache(true);
   prefs.setIntPref("network.trr.mode", 3); // TRR-only
   prefs.setCharPref("network.trr.uri", "https://foo.example.com:" + h2Port + "/dns-cname-a");
   test_answer = "9.8.7.6";
   listen = dns.asyncResolve("cname-a.example.com", 0, listenerFine, mainThread, defaultOriginAttributes);
 }
 
 // TRR-first check that TRR result is used
 function test24()
--- a/python/mozboot/mozboot/base.py
+++ b/python/mozboot/mozboot/base.py
@@ -707,17 +707,17 @@ class BaseBootstrapper(object):
         # Support 32-bit Windows on 64-bit Windows.
         win32 = 'i686-pc-windows-msvc'
         win64 = 'x86_64-pc-windows-msvc'
         if rust.platform() == win64 and win32 not in targets:
             subprocess.check_call([rustup, 'target', 'add', win32])
 
         if 'mobile_android' in self.application:
             # Let's add the most common targets.
-            android_targets = ('armv7-linux-androideabi',
+            android_targets = ('thumbv7neon-linux-androideabi',
                                'aarch64-linux-android',
                                'i686-linux-android',
                                'x86_64-linux-android', )
             for target in android_targets:
                 if target not in targets:
                     subprocess.check_call([rustup, 'target', 'add', target])
 
     def upgrade_rust(self, rustup):
--- a/python/mozbuild/mozbuild/action/check_binary.py
+++ b/python/mozbuild/mozbuild/action/check_binary.py
@@ -17,17 +17,17 @@ from mozbuild.util import memoize
 from mozpack.executables import (
     get_type,
     ELF,
     MACHO,
     UNKNOWN,
 )
 
 
-STDCXX_MAX_VERSION = Version('3.4.16')
+STDCXX_MAX_VERSION = Version('3.4.17')
 GLIBC_MAX_VERSION = Version('2.12')
 LIBGCC_MAX_VERSION = Version('4.8')
 
 HOST = {
     'MOZ_LIBSTDCXX_VERSION':
         buildconfig.substs.get('MOZ_LIBSTDCXX_HOST_VERSION'),
     'platform': buildconfig.substs['HOST_OS_ARCH'],
     'readelf': 'readelf',
--- a/security/manager/ssl/nsNSSCallbacks.cpp
+++ b/security/manager/ssl/nsNSSCallbacks.cpp
@@ -279,17 +279,18 @@ OCSPRequest::Run() {
     nsCOMPtr<nsILoadInfo> loadInfo = channel->LoadInfo();
     rv = loadInfo->SetOriginAttributes(attrs);
     if (NS_FAILED(rv)) {
       return NotifyDone(rv, lock);
     }
   }
 
   nsCOMPtr<nsIInputStream> uploadStream;
-  rv = NS_NewByteInputStream(getter_AddRefs(uploadStream), mPOSTData);
+  rv = NS_NewByteInputStream(getter_AddRefs(uploadStream), mPOSTData,
+                             NS_ASSIGNMENT_COPY);
   if (NS_FAILED(rv)) {
     return NotifyDone(rv, lock);
   }
   nsCOMPtr<nsIUploadChannel> uploadChannel(do_QueryInterface(channel));
   if (!uploadChannel) {
     return NotifyDone(NS_ERROR_FAILURE, lock);
   }
   rv = uploadChannel->SetUploadStream(uploadStream, OCSP_REQUEST_MIME_TYPE, -1);
--- a/taskcluster/ci/fetch/toolchains.yml
+++ b/taskcluster/ci/fetch/toolchains.yml
@@ -176,19 +176,19 @@ wix-3.1.1:
     url: https://github.com/wixtoolset/wix3/releases/download/wix3111rtm/wix311-binaries.zip
     sha256: 37f0a533b0978a454efb5dc3bd3598becf9660aaf4287e55bf68ca6b527d051d
     size: 34358269
 
 grcov-osx-x86_64:
   description: grcov binary release
   fetch:
     type: static-url
-    url: https://github.com/mozilla/grcov/releases/download/v0.3.2/grcov-osx-x86_64.tar.bz2
-    sha256: 6a810f782efb207234ef418ab136edd1c0ffa61aa84d8c4545042a6fa3b9dd13
-    size: 915948
+    url: https://github.com/mozilla/grcov/releases/download/v0.4.3/grcov-osx-x86_64.tar.bz2
+    sha256: 801b8d9ea07bbb73602f762a3da20420a299f709534e469ae74f74d486e3cfa8
+    size: 885425
 
 zlib-1.2.11:
   description: zlib 1.2.11 source code
   fetch:
     type: static-url
     url: http://zlib.net/zlib-1.2.11.tar.gz
     sha256: c3e5e9fdd5004dcb542feda5ee4f0ff0744628baf8ed2dd5d66f8ca1197cb1a1
     size: 607698
--- a/taskcluster/ci/toolchain/grcov.yml
+++ b/taskcluster/ci/toolchain/grcov.yml
@@ -12,33 +12,30 @@ job-defaults:
         resources:
             - 'taskcluster/scripts/misc/tooltool-download.sh'
         toolchain-artifact: public/build/grcov.tar.xz
 
 linux64-grcov:
     treeherder:
         symbol: TL(grcov)
     toolchains:
-        - linux64-rust-1.28
-        - linux64-clang-7
+        - linux64-rust-1.32
 
 macosx64-grcov:
     treeherder:
         symbol: TM(grcov)
     run-on-projects:
         - trunk
         - try
     toolchains:
-        - linux64-rust-1.28
-        - linux64-clang-7
+        - linux64-rust-1.32
 
 win64-grcov:
     treeherder:
         symbol: TW64(grcov)
     worker-type: aws-provisioner-v1/gecko-{level}-b-win2012
     worker:
         env:
             TOOLTOOL_MANIFEST: "browser/config/tooltool-manifests/win64/sccache-build.manifest"
     run:
         toolchain-artifact: public/build/grcov.tar.bz2
     toolchains:
-        - win64-rust-1.28
-        - win64-clang-cl
+        - win64-rust-1.32
--- a/taskcluster/scripts/misc/build-grcov.sh
+++ b/taskcluster/scripts/misc/build-grcov.sh
@@ -1,25 +1,23 @@
 #!/bin/bash
 set -x -e -v
 
 # This script is for building grcov
 
 OWNER=marco-c
 PROJECT=grcov
-PROJECT_REVISION=4ad0dbc35b9614e45812e179176f48bb1f70ccab
+PROJECT_REVISION=9214a916805838265764f9c69eaed657ea3db021
 
 # This script is for building rust-size
 case "$(uname -s)" in
 Linux)
     WORKSPACE=$HOME/workspace
     UPLOAD_DIR=$HOME/artifacts
     COMPRESS_EXT=xz
-
-    export CXX=clang++
     ;;
 MINGW*)
     WORKSPACE=$PWD
     UPLOAD_DIR=$WORKSPACE/public/build
     WIN_WORKSPACE="$(pwd -W)"
     COMPRESS_EXT=bz2
 
     export INCLUDE="$WIN_WORKSPACE/build/src/vs2017_15.8.4/VC/include;$WIN_WORKSPACE/build/src/vs2017_15.8.4/VC/atlmfc/include;$WIN_WORKSPACE/build/src/vs2017_15.8.4/SDK/Include/10.0.17134.0/ucrt;$WIN_WORKSPACE/build/src/vs2017_15.8.4/SDK/Include/10.0.17134.0/shared;$WIN_WORKSPACE/build/src/vs2017_15.8.4/SDK/Include/10.0.17134.0/um;$WIN_WORKSPACE/build/src/vs2017_15.8.4/SDK/Include/10.0.17134.0/winrt;$WIN_WORKSPACE/build/src/vs2017_15.8.4/DIA SDK/include"
@@ -34,17 +32,17 @@ cd $WORKSPACE/build/src
 
 . taskcluster/scripts/misc/tooltool-download.sh
 
 # cargo gets mad if the parent directory has a Cargo.toml file in it
 if [ -e Cargo.toml ]; then
   mv Cargo.toml Cargo.toml.back
 fi
 
-PATH="$WORKSPACE/build/src/clang/bin/:$PWD/rustc/bin:$PATH"
+PATH="$PWD/rustc/bin:$PATH"
 
 git clone -n https://github.com/${OWNER}/${PROJECT} ${PROJECT}
 
 pushd $PROJECT
 
 git checkout $PROJECT_REVISION
 
 cargo build --verbose --release
--- a/testing/marionette/client/marionette_driver/__init__.py
+++ b/testing/marionette/client/marionette_driver/__init__.py
@@ -13,15 +13,14 @@ from marionette_driver import (
     decorators,
     errors,
     expected,
     geckoinstance,
     gestures,
     keys,
     localization,
     marionette,
-    selection,
     wait,
 )
 from marionette_driver.by import By
 from marionette_driver.date_time_value import DateTimeValue
 from marionette_driver.gestures import smooth_scroll, pinch
 from marionette_driver.wait import Wait
--- a/testing/marionette/modal.js
+++ b/testing/marionette/modal.js
@@ -147,15 +147,17 @@ modal.Dialog = class {
     let win = this.window;
     if (win) {
       return win.Dialog;
     }
     return this.curBrowser_.getTabModal();
   }
 
   get args() {
-    return this.tabModal.args;
+    let tm = this.tabModal;
+    return tm ? tm.args : null;
   }
 
   get ui() {
-    return this.tabModal.ui;
+    let tm = this.tabModal;
+    return tm ? tm.ui : null;
   }
 };
--- a/testing/mozbase/mozproxy/mozproxy/backends/mitm.py
+++ b/testing/mozbase/mozproxy/mozproxy/backends/mitm.py
@@ -37,16 +37,19 @@ except Exception:
         ".mitmproxy",
         "mitmproxy-ca-cert.cer",
     )
 
 # On Windows, deal with mozilla-build having forward slashes in $HOME:
 if os.name == "nt" and "/" in DEFAULT_CERT_PATH:
     DEFAULT_CERT_PATH = DEFAULT_CERT_PATH.replace("/", "\\")
 
+# maximal allowed runtime of a mitmproxy command
+MITMDUMP_COMMAND_TIMEOUT = 30
+
 # to install mitmproxy certificate into Firefox and turn on/off proxy
 POLICIES_CONTENT_ON = """{
   "policies": {
     "Certificates": {
       "Install": ["%(cert)s"]
     },
     "Proxy": {
       "Mode": "manual",
@@ -109,21 +112,16 @@ class Mitmproxy(Playback):
 
         # In case the setup fails, we want to stop the process before raising.
         try:
             self.setup()
         except Exception:
             self.stop()
             raise
 
-    @property
-    def mitmdump_sleep_seconds(self):
-        """Time to sleep, in seconds, after issuing a `mitmdump` command."""
-        return 10 if not self.config['run_local'] else 1
-
     def download(self):
         """Download and unpack mitmproxy binary and pageset using tooltool"""
         if not os.path.exists(self.mozproxy_dir):
             os.makedirs(self.mozproxy_dir)
 
         LOG.info("downloading mitmproxy binary")
         _manifest = os.path.join(here, self.config["playback_binary_manifest"])
         transformed_manifest = transform_platform(_manifest, self.config["platform"])
@@ -174,29 +172,29 @@ class Mitmproxy(Playback):
         LOG.info("Starting mitmproxy playback using command: %s" % " ".join(command))
         # to turn off mitmproxy log output, use these params for Popen:
         # Popen(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, env=env)
         self.mitmproxy_proc = ProcessHandler(command,
                                              logfile=os.path.join(self.upload_dir,
                                                                   "mitmproxy.log"),
                                              env=env)
         self.mitmproxy_proc.run()
-        max_wait = time.time() + self.mitmdump_sleep_seconds
+        end_time = time.time() + MITMDUMP_COMMAND_TIMEOUT
         ready = False
-        while time.time() < max_wait:
+        while time.time() < end_time:
             ready = self.check_proxy()
             if ready:
                 LOG.info(
                     "Mitmproxy playback successfully started as pid %d"
                     % self.mitmproxy_proc.pid
                 )
                 return
-            time.sleep(1)
+            time.sleep(0.25)
         # cannot continue as we won't be able to playback the pages
-        LOG.error("Aborting: mitmproxy playback process failed to work")
+        LOG.error("Aborting: Mitmproxy process did not startup")
         self.stop_mitmproxy_playback()
         sys.exit()  # XXX why do we need to do that? a raise is not enough?
 
     def stop_mitmproxy_playback(self):
         """Stop the mitproxy server playback"""
         if self.mitmproxy_proc is None or self.mitmproxy_proc.poll() is not None:
             return
         LOG.info(
--- a/testing/web-platform/meta/2dcontext/imagebitmap/createImageBitmap-transfer.html.ini
+++ b/testing/web-platform/meta/2dcontext/imagebitmap/createImageBitmap-transfer.html.ini
@@ -1,7 +1,4 @@
 [createImageBitmap-transfer.html]
-  disabled:
-    if debug and (os == "linux"): https://bugzilla.mozilla.org/show_bug.cgi?id=1524653
-    if debug and (os == "win") and (bits == 32): https://bugzilla.mozilla.org/show_bug.cgi?id=1524653
-    if debug and (os == "mac"): https://bugzilla.mozilla.org/show_bug.cgi?id=1524653
   [Transfer ImageBitmap created from an OffscreenCanvas]
     expected: FAIL
+
--- a/testing/web-platform/meta/css/CSS2/margin-padding-clear/margin-em-inherit-001.xht.ini
+++ b/testing/web-platform/meta/css/CSS2/margin-padding-clear/margin-em-inherit-001.xht.ini
@@ -8,9 +8,10 @@
     if debug and not webrender and e10s and (os == "linux") and (version == "Ubuntu 16.04") and (processor == "x86_64") and (bits == 64): PASS
     if not debug and not webrender and e10s and (os == "linux") and (version == "Ubuntu 16.04") and (processor == "x86") and (bits == 32): PASS
     if not debug and not webrender and e10s and (os == "win") and (version == "10.0.17134") and (processor == "x86_64") and (bits == 64): PASS
     if debug and webrender and e10s and (os == "win") and (version == "10.0.17134") and (processor == "x86_64") and (bits == 64): PASS
     if debug and not webrender and e10s and (os == "win") and (version == "6.1.7601") and (processor == "x86") and (bits == 32): PASS
     if debug and not webrender and e10s and (os == "win") and (version == "10.0.17134") and (processor == "x86_64") and (bits == 64): PASS
     if not debug and not webrender and e10s and (os == "win") and (version == "6.1.7601") and (processor == "x86") and (bits == 32): PASS
     if not debug and webrender and e10s and (os == "win") and (version == "10.0.17134") and (processor == "x86_64") and (bits == 64): PASS
+    if (os == 'win' and processor == 'aarch64'): PASS # bug 1538012
     FAIL
--- a/testing/web-platform/meta/css/CSS2/normal-flow/blocks-022.xht.ini
+++ b/testing/web-platform/meta/css/CSS2/normal-flow/blocks-022.xht.ini
@@ -11,9 +11,10 @@
     if debug and not webrender and e10s and (os == "mac") and (version == "OS X 10.10.5") and (processor == "x86_64") and (bits == 64): PASS
     if debug and webrender and e10s and (os == "win") and (version == "10.0.17134") and (processor == "x86_64") and (bits == 64): PASS
     if debug and not webrender and e10s and (os == "win") and (version == "6.1.7601") and (processor == "x86") and (bits == 32): PASS
     if debug and not webrender and e10s and (os == "win") and (version == "10.0.17134") and (processor == "x86_64") and (bits == 64): PASS
     if not debug and not webrender and e10s and (os == "win") and (version == "6.1.7601") and (processor == "x86") and (bits == 32): PASS
     if not debug and webrender and e10s and (os == "linux") and (version == "Ubuntu 16.04") and (processor == "x86_64") and (bits == 64): PASS
     if not debug and webrender and e10s and (os == "win") and (version == "10.0.17134") and (processor == "x86_64") and (bits == 64): PASS
     if not debug and not webrender and e10s and (os == "mac") and (version == "OS X 10.10.5") and (processor == "x86_64") and (bits == 64): PASS
+    if (os == 'win' and processor == 'aarch64'): PASS # bug 1537993
     FAIL
--- a/testing/web-platform/meta/css/CSS2/tables/table-anonymous-objects-061.xht.ini
+++ b/testing/web-platform/meta/css/CSS2/tables/table-anonymous-objects-061.xht.ini
@@ -11,9 +11,10 @@
     if debug and not webrender and e10s and (os == "mac") and (version == "OS X 10.10.5") and (processor == "x86_64") and (bits == 64): PASS
     if debug and webrender and e10s and (os == "win") and (version == "10.0.17134") and (processor == "x86_64") and (bits == 64): PASS
     if debug and not webrender and e10s and (os == "win") and (version == "6.1.7601") and (processor == "x86") and (bits == 32): PASS
     if debug and not webrender and e10s and (os == "win") and (version == "10.0.17134") and (processor == "x86_64") and (bits == 64): PASS
     if not debug and not webrender and e10s and (os == "win") and (version == "6.1.7601") and (processor == "x86") and (bits == 32): PASS
     if not debug and webrender and e10s and (os == "linux") and (version == "Ubuntu 16.04") and (processor == "x86_64") and (bits == 64): PASS
     if not debug and webrender and e10s and (os == "win") and (version == "10.0.17134") and (processor == "x86_64") and (bits == 64): PASS
     if not debug and not webrender and e10s and (os == "mac") and (version == "OS X 10.10.5") and (processor == "x86_64") and (bits == 64): PASS
+    if (os == 'win' and processor == 'aarch64'): PASS # bug 1537997
     FAIL
--- a/testing/web-platform/meta/css/css-writing-modes/abs-pos-non-replaced-vlr-013.xht.ini
+++ b/testing/web-platform/meta/css/css-writing-modes/abs-pos-non-replaced-vlr-013.xht.ini
@@ -11,9 +11,10 @@
     if debug and not webrender and e10s and (os == "mac") and (version == "OS X 10.10.5") and (processor == "x86_64") and (bits == 64): PASS
     if debug and webrender and e10s and (os == "win") and (version == "10.0.17134") and (processor == "x86_64") and (bits == 64): PASS
     if debug and not webrender and e10s and (os == "win") and (version == "6.1.7601") and (processor == "x86") and (bits == 32): PASS
     if debug and not webrender and e10s and (os == "win") and (version == "10.0.17134") and (processor == "x86_64") and (bits == 64): PASS
     if not debug and not webrender and e10s and (os == "win") and (version == "6.1.7601") and (processor == "x86") and (bits == 32): PASS
     if not debug and webrender and e10s and (os == "linux") and (version == "Ubuntu 16.04") and (processor == "x86_64") and (bits == 64): PASS
     if not debug and webrender and e10s and (os == "win") and (version == "10.0.17134") and (processor == "x86_64") and (bits == 64): PASS
     if not debug and not webrender and e10s and (os == "mac") and (version == "OS X 10.10.5") and (processor == "x86_64") and (bits == 64): PASS
+    if (os == 'win' and processor == 'aarch64'): PASS # bug 1538017
     FAIL
--- a/testing/web-platform/meta/css/css-writing-modes/abs-pos-non-replaced-vlr-019.xht.ini
+++ b/testing/web-platform/meta/css/css-writing-modes/abs-pos-non-replaced-vlr-019.xht.ini
@@ -11,9 +11,10 @@
     if debug and not webrender and e10s and (os == "mac") and (version == "OS X 10.10.5") and (processor == "x86_64") and (bits == 64): PASS
     if debug and webrender and e10s and (os == "win") and (version == "10.0.17134") and (processor == "x86_64") and (bits == 64): PASS
     if debug and not webrender and e10s and (os == "win") and (version == "6.1.7601") and (processor == "x86") and (bits == 32): PASS
     if debug and not webrender and e10s and (os == "win") and (version == "10.0.17134") and (processor == "x86_64") and (bits == 64): PASS
     if not debug and not webrender and e10s and (os == "win") and (version == "6.1.7601") and (processor == "x86") and (bits == 32): PASS
     if not debug and webrender and e10s and (os == "linux") and (version == "Ubuntu 16.04") and (processor == "x86_64") and (bits == 64): PASS
     if not debug and webrender and e10s and (os == "win") and (version == "10.0.17134") and (processor == "x86_64") and (bits == 64): PASS
     if not debug and not webrender and e10s and (os == "mac") and (version == "OS X 10.10.5") and (processor == "x86_64") and (bits == 64): PASS
+    if (os == 'win' and processor == 'aarch64'): PASS # bug 1538017
     FAIL
--- a/testing/web-platform/meta/css/css-writing-modes/abs-pos-non-replaced-vlr-037.xht.ini
+++ b/testing/web-platform/meta/css/css-writing-modes/abs-pos-non-replaced-vlr-037.xht.ini
@@ -11,9 +11,10 @@
     if debug and not webrender and e10s and (os == "mac") and (version == "OS X 10.10.5") and (processor == "x86_64") and (bits == 64): PASS
     if debug and webrender and e10s and (os == "win") and (version == "10.0.17134") and (processor == "x86_64") and (bits == 64): PASS
     if debug and not webrender and e10s and (os == "win") and (version == "6.1.7601") and (processor == "x86") and (bits == 32): PASS
     if debug and not webrender and e10s and (os == "win") and (version == "10.0.17134") and (processor == "x86_64") and (bits == 64): PASS
     if not debug and not webrender and e10s and (os == "win") and (version == "6.1.7601") and (processor == "x86") and (bits == 32): PASS
     if not debug and webrender and e10s and (os == "linux") and (version == "Ubuntu 16.04") and (processor == "x86_64") and (bits == 64): PASS
     if not debug and webrender and e10s and (os == "win") and (version == "10.0.17134") and (processor == "x86_64") and (bits == 64): PASS
     if not debug and not webrender and e10s and (os == "mac") and (version == "OS X 10.10.5") and (processor == "x86_64") and (bits == 64): PASS
+    if (os == 'win' and processor == 'aarch64'): PASS # bug 1538017
     FAIL
--- a/testing/web-platform/meta/css/css-writing-modes/abs-pos-non-replaced-vlr-061.xht.ini
+++ b/testing/web-platform/meta/css/css-writing-modes/abs-pos-non-replaced-vlr-061.xht.ini
@@ -11,9 +11,10 @@
     if debug and not webrender and e10s and (os == "mac") and (version == "OS X 10.10.5") and (processor == "x86_64") and (bits == 64): PASS
     if debug and webrender and e10s and (os == "win") and (version == "10.0.17134") and (processor == "x86_64") and (bits == 64): PASS
     if debug and not webrender and e10s and (os == "win") and (version == "6.1.7601") and (processor == "x86") and (bits == 32): PASS
     if debug and not webrender and e10s and (os == "win") and (version == "10.0.17134") and (processor == "x86_64") and (bits == 64): PASS
     if not debug and not webrender and e10s and (os == "win") and (version == "6.1.7601") and (processor == "x86") and (bits == 32): PASS
     if not debug and webrender and e10s and (os == "linux") and (version == "Ubuntu 16.04") and (processor == "x86_64") and (bits == 64): PASS
     if not debug and webrender and e10s and (os == "win") and (version == "10.0.17134") and (processor == "x86_64") and (bits == 64): PASS
     if not debug and not webrender and e10s and (os == "mac") and (version == "OS X 10.10.5") and (processor == "x86_64") and (bits == 64): PASS
+    if (os == 'win' and processor == 'aarch64'): PASS # bug 1538017
     FAIL
--- a/testing/web-platform/meta/css/css-writing-modes/abs-pos-non-replaced-vlr-067.xht.ini
+++ b/testing/web-platform/meta/css/css-writing-modes/abs-pos-non-replaced-vlr-067.xht.ini
@@ -11,9 +11,10 @@
     if debug and not webrender and e10s and (os == "mac") and (version == "OS X 10.10.5") and (processor == "x86_64") and (bits == 64): PASS
     if debug and webrender and e10s and (os == "win") and (version == "10.0.17134") and (processor == "x86_64") and (bits == 64): PASS
     if debug and not webrender and e10s and (os == "win") and (version == "6.1.7601") and (processor == "x86") and (bits == 32): PASS
     if debug and not webrender and e10s and (os == "win") and (version == "10.0.17134") and (processor == "x86_64") and (bits == 64): PASS
     if not debug and not webrender and e10s and (os == "win") and (version == "6.1.7601") and (processor == "x86") and (bits == 32): PASS
     if not debug and webrender and e10s and (os == "linux") and (version == "Ubuntu 16.04") and (processor == "x86_64") and (bits == 64): PASS
     if not debug and webrender and e10s and (os == "win") and (version == "10.0.17134") and (processor == "x86_64") and (bits == 64): PASS
     if not debug and not webrender and e10s and (os == "mac") and (version == "OS X 10.10.5") and (processor == "x86_64") and (bits == 64): PASS
+    if (os == 'win' and processor == 'aarch64'): PASS # bug 1538017
     FAIL
--- a/testing/web-platform/meta/css/css-writing-modes/abs-pos-non-replaced-vlr-115.xht.ini
+++ b/testing/web-platform/meta/css/css-writing-modes/abs-pos-non-replaced-vlr-115.xht.ini
@@ -11,9 +11,10 @@
     if debug and not webrender and e10s and (os == "mac") and (version == "OS X 10.10.5") and (processor == "x86_64") and (bits == 64): PASS
     if debug and webrender and e10s and (os == "win") and (version == "10.0.17134") and (processor == "x86_64") and (bits == 64): PASS
     if debug and not webrender and e10s and (os == "win") and (version == "6.1.7601") and (processor == "x86") and (bits == 32): PASS
     if debug and not webrender and e10s and (os == "win") and (version == "10.0.17134") and (processor == "x86_64") and (bits == 64): PASS
     if not debug and not webrender and e10s and (os == "win") and (version == "6.1.7601") and (processor == "x86") and (bits == 32): PASS
     if not debug and webrender and e10s and (os == "linux") and (version == "Ubuntu 16.04") and (processor == "x86_64") and (bits == 64): PASS
     if not debug and webrender and e10s and (os == "win") and (version == "10.0.17134") and (processor == "x86_64") and (bits == 64): PASS
     if not debug and not webrender and e10s and (os == "mac") and (version == "OS X 10.10.5") and (processor == "x86_64") and (bits == 64): PASS
+    if (os == 'win' and processor == 'aarch64'): PASS # bug 1538017
     FAIL
--- a/testing/web-platform/meta/css/css-writing-modes/abs-pos-non-replaced-vlr-155.xht.ini
+++ b/testing/web-platform/meta/css/css-writing-modes/abs-pos-non-replaced-vlr-155.xht.ini
@@ -11,9 +11,10 @@
     if debug and not webrender and e10s and (os == "mac") and (version == "OS X 10.10.5") and (processor == "x86_64") and (bits == 64): PASS
     if debug and webrender and e10s and (os == "win") and (version == "10.0.17134") and (processor == "x86_64") and (bits == 64): PASS
     if debug and not webrender and e10s and (os == "win") and (version == "6.1.7601") and (processor == "x86") and (bits == 32): PASS
     if debug and not webrender and e10s and (os == "win") and (version == "10.0.17134") and (processor == "x86_64") and (bits == 64): PASS
     if not debug and not webrender and e10s and (os == "win") and (version == "6.1.7601") and (processor == "x86") and (bits == 32): PASS
     if not debug and webrender and e10s and (os == "linux") and (version == "Ubuntu 16.04") and (processor == "x86_64") and (bits == 64): PASS
     if not debug and webrender and e10s and (os == "win") and (version == "10.0.17134") and (processor == "x86_64") and (bits == 64): PASS
     if not debug and not webrender and e10s and (os == "mac") and (version == "OS X 10.10.5") and (processor == "x86_64") and (bits == 64): PASS
+    if (os == 'win' and processor == 'aarch64'): PASS # bug 1538017
     FAIL
--- a/testing/web-platform/meta/css/css-writing-modes/abs-pos-non-replaced-vlr-171.xht.ini
+++ b/testing/web-platform/meta/css/css-writing-modes/abs-pos-non-replaced-vlr-171.xht.ini
@@ -11,9 +11,10 @@
     if debug and not webrender and e10s and (os == "mac") and (version == "OS X 10.10.5") and (processor == "x86_64") and (bits == 64): PASS
     if debug and webrender and e10s and (os == "win") and (version == "10.0.17134") and (processor == "x86_64") and (bits == 64): PASS
     if debug and not webrender and e10s and (os == "win") and (version == "6.1.7601") and (processor == "x86") and (bits == 32): PASS
     if debug and not webrender and e10s and (os == "win") and (version == "10.0.17134") and (processor == "x86_64") and (bits == 64): PASS
     if not debug and not webrender and e10s and (os == "win") and (version == "6.1.7601") and (processor == "x86") and (bits == 32): PASS
     if not debug and webrender and e10s and (os == "linux") and (version == "Ubuntu 16.04") and (processor == "x86_64") and (bits == 64): PASS
     if not debug and webrender and e10s and (os == "win") and (version == "10.0.17134") and (processor == "x86_64") and (bits == 64): PASS
     if not debug and not webrender and e10s and (os == "mac") and (version == "OS X 10.10.5") and (processor == "x86_64") and (bits == 64): PASS
+    if (os == 'win' and processor == 'aarch64'): PASS # bug 1538017
     FAIL
--- a/testing/web-platform/meta/css/css-writing-modes/abs-pos-non-replaced-vlr-195.xht.ini
+++ b/testing/web-platform/meta/css/css-writing-modes/abs-pos-non-replaced-vlr-195.xht.ini
@@ -11,9 +11,10 @@
     if debug and not webrender and e10s and (os == "mac") and (version == "OS X 10.10.5") and (processor == "x86_64") and (bits == 64): PASS
     if debug and webrender and e10s and (os == "win") and (version == "10.0.17134") and (processor == "x86_64") and (bits == 64): PASS
     if debug and not webrender and e10s and (os == "win") and (version == "6.1.7601") and (processor == "x86") and (bits == 32): PASS
     if debug and not webrender and e10s and (os == "win") and (version == "10.0.17134") and (processor == "x86_64") and (bits == 64): PASS
     if not debug and not webrender and e10s and (os == "win") and (version == "6.1.7601") and (processor == "x86") and (bits == 32): PASS
     if not debug and webrender and e10s and (os == "linux") and (version == "Ubuntu 16.04") and (processor == "x86_64") and (bits == 64): PASS
     if not debug and webrender and e10s and (os == "win") and (version == "10.0.17134") and (processor == "x86_64") and (bits == 64): PASS
     if not debug and not webrender and e10s and (os == "mac") and (version == "OS X 10.10.5") and (processor == "x86_64") and (bits == 64): PASS
+    if (os == 'win' and processor == 'aarch64'): PASS # bug 1538017
     FAIL
--- a/testing/web-platform/meta/css/css-writing-modes/abs-pos-non-replaced-vlr-211.xht.ini
+++ b/testing/web-platform/meta/css/css-writing-modes/abs-pos-non-replaced-vlr-211.xht.ini
@@ -11,9 +11,10 @@
     if debug and not webrender and e10s and (os == "mac") and (version == "OS X 10.10.5") and (processor == "x86_64") and (bits == 64): PASS
     if debug and webrender and e10s and (os == "win") and (version == "10.0.17134") and (processor == "x86_64") and (bits == 64): PASS
     if debug and not webrender and e10s and (os == "win") and (version == "6.1.7601") and (processor == "x86") and (bits == 32): PASS
     if debug and not webrender and e10s and (os == "win") and (version == "10.0.17134") and (processor == "x86_64") and (bits == 64): PASS
     if not debug and not webrender and e10s and (os == "win") and (version == "6.1.7601") and (processor == "x86") and (bits == 32): PASS
     if not debug and webrender and e10s and (os == "linux") and (version == "Ubuntu 16.04") and (processor == "x86_64") and (bits == 64): PASS
     if not debug and webrender and e10s and (os == "win") and (version == "10.0.17134") and (processor == "x86_64") and (bits == 64): PASS
     if not debug and not webrender and e10s and (os == "mac") and (version == "OS X 10.10.5") and (processor == "x86_64") and (bits == 64): PASS
+    if (os == 'win' and processor == 'aarch64'): PASS # bug 1538017
     FAIL
--- a/testing/web-platform/meta/css/css-writing-modes/abs-pos-non-replaced-vlr-227.xht.ini
+++ b/testing/web-platform/meta/css/css-writing-modes/abs-pos-non-replaced-vlr-227.xht.ini
@@ -11,9 +11,10 @@
     if debug and not webrender and e10s and (os == "mac") and (version == "OS X 10.10.5") and (processor == "x86_64") and (bits == 64): PASS
     if debug and webrender and e10s and (os == "win") and (version == "10.0.17134") and (processor == "x86_64") and (bits == 64): PASS
     if debug and not webrender and e10s and (os == "win") and (version == "6.1.7601") and (processor == "x86") and (bits == 32): PASS
     if debug and not webrender and e10s and (os == "win") and (version == "10.0.17134") and (processor == "x86_64") and (bits == 64): PASS
     if not debug and not webrender and e10s and (os == "win") and (version == "6.1.7601") and (processor == "x86") and (bits == 32): PASS
     if not debug and webrender and e10s and (os == "linux") and (version == "Ubuntu 16.04") and (processor == "x86_64") and (bits == 64): PASS
     if not debug and webrender and e10s and (os == "win") and (version == "10.0.17134") and (processor == "x86_64") and (bits == 64): PASS
     if not debug and not webrender and e10s and (os == "mac") and (version == "OS X 10.10.5") and (processor == "x86_64") and (bits == 64): PASS
+    if (os == 'win' and processor == 'aarch64'): PASS # bug 1538017
     FAIL
--- a/testing/web-platform/meta/css/css-writing-modes/abs-pos-non-replaced-vrl-018.xht.ini
+++ b/testing/web-platform/meta/css/css-writing-modes/abs-pos-non-replaced-vrl-018.xht.ini
@@ -11,9 +11,10 @@
     if debug and not webrender and e10s and (os == "mac") and (version == "OS X 10.10.5") and (processor == "x86_64") and (bits == 64): PASS
     if debug and webrender and e10s and (os == "win") and (version == "10.0.17134") and (processor == "x86_64") and (bits == 64): PASS
     if debug and not webrender and e10s and (os == "win") and (version == "6.1.7601") and (processor == "x86") and (bits == 32): PASS
     if debug and not webrender and e10s and (os == "win") and (version == "10.0.17134") and (processor == "x86_64") and (bits == 64): PASS
     if not debug and not webrender and e10s and (os == "win") and (version == "6.1.7601") and (processor == "x86") and (bits == 32): PASS
     if not debug and webrender and e10s and (os == "linux") and (version == "Ubuntu 16.04") and (processor == "x86_64") and (bits == 64): PASS
     if not debug and webrender and e10s and (os == "win") and (version == "10.0.17134") and (processor == "x86_64") and (bits == 64): PASS
     if not debug and not webrender and e10s and (os == "mac") and (version == "OS X 10.10.5") and (processor == "x86_64") and (bits == 64): PASS
+    if (os == 'win' and processor == 'aarch64'): PASS # bug 1538017
     FAIL
--- a/testing/web-platform/meta/css/css-writing-modes/abs-pos-non-replaced-vrl-024.xht.ini
+++ b/testing/web-platform/meta/css/css-writing-modes/abs-pos-non-replaced-vrl-024.xht.ini
@@ -11,9 +11,10 @@
     if debug and not webrender and e10s and (os == "mac") and (version == "OS X 10.10.5") and (processor == "x86_64") and (bits == 64): PASS
     if debug and webrender and e10s and (os == "win") and (version == "10.0.17134") and (processor == "x86_64") and (bits == 64): PASS
     if debug and not webrender and e10s and (os == "win") and (version == "6.1.7601") and (processor == "x86") and (bits == 32): PASS
     if debug and not webrender and e10s and (os == "win") and (version == "10.0.17134") and (processor == "x86_64") and (bits == 64): PASS
     if not debug and not webrender and e10s and (os == "win") and (version == "6.1.7601") and (processor == "x86") and (bits == 32): PASS
     if not debug and webrender and e10s and (os == "linux") and (version == "Ubuntu 16.04") and (processor == "x86_64") and (bits == 64): PASS
     if not debug and webrender and e10s and (os == "win") and (version == "10.0.17134") and (processor == "x86_64") and (bits == 64): PASS
     if not debug and not webrender and e10s and (os == "mac") and (version == "OS X 10.10.5") and (processor == "x86_64") and (bits == 64): PASS
+    if (os == 'win' and processor == 'aarch64'): PASS # bug 1538017
     FAIL
--- a/testing/web-platform/meta/css/css-writing-modes/abs-pos-non-replaced-vrl-042.xht.ini
+++ b/testing/web-platform/meta/css/css-writing-modes/abs-pos-non-replaced-vrl-042.xht.ini
@@ -11,9 +11,10 @@
     if debug and not webrender and e10s and (os == "mac") and (version == "OS X 10.10.5") and (processor == "x86_64") and (bits == 64): PASS
     if debug and webrender and e10s and (os == "win") and (version == "10.0.17134") and (processor == "x86_64") and (bits == 64): PASS
     if debug and not webrender and e10s and (os == "win") and (version == "6.1.7601") and (processor == "x86") and (bits == 32): PASS
     if debug and not webrender and e10s and (os == "win") and (version == "10.0.17134") and (processor == "x86_64") and (bits == 64): PASS
     if not debug and not webrender and e10s and (os == "win") and (version == "6.1.7601") and (processor == "x86") and (bits == 32): PASS
     if not debug and webrender and e10s and (os == "linux") and (version == "Ubuntu 16.04") and (processor == "x86_64") and (bits == 64): PASS
     if not debug and webrender and e10s and (os == "win") and (version == "10.0.17134") and (processor == "x86_64") and (bits == 64): PASS
     if not debug and not webrender and e10s and (os == "mac") and (version == "OS X 10.10.5") and (processor == "x86_64") and (bits == 64): PASS
+    if (os == 'win' and processor == 'aarch64'): PASS # bug 1538017
     FAIL
--- a/testing/web-platform/meta/css/css-writing-modes/abs-pos-non-replaced-vrl-106.xht.ini
+++ b/testing/web-platform/meta/css/css-writing-modes/abs-pos-non-replaced-vrl-106.xht.ini
@@ -11,9 +11,10 @@
     if debug and not webrender and e10s and (os == "mac") and (version == "OS X 10.10.5") and (processor == "x86_64") and (bits == 64): PASS
     if debug and webrender and e10s and (os == "win") and (version == "10.0.17134") and (processor == "x86_64") and (bits == 64): PASS
     if debug and not webrender and e10s and (os == "win") and (version == "6.1.7601") and (processor == "x86") and (bits == 32): PASS
     if debug and not webrender and e10s and (os == "win") and (version == "10.0.17134") and (processor == "x86_64") and (bits == 64): PASS
     if not debug and not webrender and e10s and (os == "win") and (version == "6.1.7601") and (processor == "x86") and (bits == 32): PASS
     if not debug and webrender and e10s and (os == "linux") and (version == "Ubuntu 16.04") and (processor == "x86_64") and (bits == 64): PASS
     if not debug and webrender and e10s and (os == "win") and (version == "10.0.17134") and (processor == "x86_64") and (bits == 64): PASS
     if not debug and not webrender and e10s and (os == "mac") and (version == "OS X 10.10.5") and (processor == "x86_64") and (bits == 64): PASS
+    if (os == 'win' and processor == 'aarch64'): PASS # bug 1538017
     FAIL
--- a/testing/web-platform/meta/css/css-writing-modes/abs-pos-non-replaced-vrl-138.xht.ini
+++ b/testing/web-platform/meta/css/css-writing-modes/abs-pos-non-replaced-vrl-138.xht.ini
@@ -11,9 +11,10 @@
     if debug and not webrender and e10s and (os == "mac") and (version == "OS X 10.10.5") and (processor == "x86_64") and (bits == 64): PASS
     if debug and webrender and e10s and (os == "win") and (version == "10.0.17134") and (processor == "x86_64") and (bits == 64): PASS
     if debug and not webrender and e10s and (os == "win") and (version == "6.1.7601") and (processor == "x86") and (bits == 32): PASS
     if debug and not webrender and e10s and (os == "win") and (version == "10.0.17134") and (processor == "x86_64") and (bits == 64): PASS
     if not debug and not webrender and e10s and (os == "win") and (version == "6.1.7601") and (processor == "x86") and (bits == 32): PASS
     if not debug and webrender and e10s and (os == "linux") and (version == "Ubuntu 16.04") and (processor == "x86_64") and (bits == 64): PASS
     if not debug and webrender and e10s and (os == "win") and (version == "10.0.17134") and (processor == "x86_64") and (bits == 64): PASS
     if not debug and not webrender and e10s and (os == "mac") and (version == "OS X 10.10.5") and (processor == "x86_64") and (bits == 64): PASS
+    if (os == 'win' and processor == 'aarch64'): PASS # bug 1538017
     FAIL
--- a/testing/web-platform/meta/css/css-writing-modes/abs-pos-non-replaced-vrl-154.xht.ini
+++ b/testing/web-platform/meta/css/css-writing-modes/abs-pos-non-replaced-vrl-154.xht.ini
@@ -11,9 +11,10 @@
     if debug and not webrender and e10s and (os == "mac") and (version == "OS X 10.10.5") and (processor == "x86_64") and (bits == 64): PASS
     if debug and webrender and e10s and (os == "win") and (version == "10.0.17134") and (processor == "x86_64") and (bits == 64): PASS
     if debug and not webrender and e10s and (os == "win") and (version == "6.1.7601") and (processor == "x86") and (bits == 32): PASS
     if debug and not webrender and e10s and (os == "win") and (version == "10.0.17134") and (processor == "x86_64") and (bits == 64): PASS
     if not debug and not webrender and e10s and (os == "win") and (version == "6.1.7601") and (processor == "x86") and (bits == 32): PASS
     if not debug and webrender and e10s and (os == "linux") and (version == "Ubuntu 16.04") and (processor == "x86_64") and (bits == 64): PASS
     if not debug and webrender and e10s and (os == "win") and (version == "10.0.17134") and (processor == "x86_64") and (bits == 64): PASS
     if not debug and not webrender and e10s and (os == "mac") and (version == "OS X 10.10.5") and (processor == "x86_64") and (bits == 64): PASS
+    if (os == 'win' and processor == 'aarch64'): PASS # bug 1538017
     FAIL
--- a/testing/web-platform/meta/css/css-writing-modes/abs-pos-non-replaced-vrl-170.xht.ini
+++ b/testing/web-platform/meta/css/css-writing-modes/abs-pos-non-replaced-vrl-170.xht.ini
@@ -11,9 +11,10 @@
     if debug and not webrender and e10s and (os == "mac") and (version == "OS X 10.10.5") and (processor == "x86_64") and (bits == 64): PASS
     if debug and webrender and e10s and (os == "win") and (version == "10.0.17134") and (processor == "x86_64") and (bits == 64): PASS
     if debug and not webrender and e10s and (os == "win") and (version == "6.1.7601") and (processor == "x86") and (bits == 32): PASS
     if debug and not webrender and e10s and (os == "win") and (version == "10.0.17134") and (processor == "x86_64") and (bits == 64): PASS
     if not debug and not webrender and e10s and (os == "win") and (version == "6.1.7601") and (processor == "x86") and (bits