Merge autoland to mozilla-central. a=merge
authorSandor Molnar <smolnar@mozilla.com>
Fri, 24 Sep 2021 00:43:42 +0300
changeset 593102 4eda9eb8926bdd50f4b80128ce3475eb7c6d9a4d
parent 593005 ba378c62cecf6a3048baddde1f9c7d61b45a2914 (current diff)
parent 593101 b42a7fdc31d2269388668e82f1cf9804b4f26018 (diff)
child 593110 0296d35ec393edb7b35bcaa075b349063165e297
push id38820
push usersmolnar@mozilla.com
push dateThu, 23 Sep 2021 21:45:25 +0000
treeherdermozilla-central@4eda9eb8926b [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone94.0a1
first release with
nightly linux32
4eda9eb8926b / 94.0a1 / 20210923214525 / files
nightly linux64
4eda9eb8926b / 94.0a1 / 20210923214525 / files
nightly mac
4eda9eb8926b / 94.0a1 / 20210923214525 / files
nightly win32
4eda9eb8926b / 94.0a1 / 20210923214525 / files
nightly win64
4eda9eb8926b / 94.0a1 / 20210923214525 / 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
third_party/jpeg-xl/lib/jxl/base/time.cc
third_party/jpeg-xl/lib/jxl/base/time.h
third_party/jpeg-xl/lib/jxl/modular/transform/near-lossless.h
third_party/jpeg-xl/lib/jxl/modular/transform/subtractgreen.h
--- a/.cargo/config.in
+++ b/.cargo/config.in
@@ -40,17 +40,17 @@ rev = "fce878ff15f4e1dba1e5b54f82460af60
 [source."https://github.com/mozilla/application-services"]
 git = "https://github.com/mozilla/application-services"
 replace-with = "vendored-sources"
 rev = "8a576fbe79199fa8664f64285524017f74ebcc5f"
 
 [source."https://github.com/mozilla-spidermonkey/jsparagus"]
 git = "https://github.com/mozilla-spidermonkey/jsparagus"
 replace-with = "vendored-sources"
-rev = "6c97434b6586fc234fdba0494f4ddd004b9578de"
+rev = "ecc433935118a4ff22d33c4fe7c625a5d0bfb571"
 
 [source."https://github.com/kvark/dummy-web"]
 git = "https://github.com/kvark/dummy-web"
 replace-with = "vendored-sources"
 
 [source."https://github.com/kinetiknz/mio-named-pipes"]
 git = "https://github.com/kinetiknz/mio-named-pipes"
 replace-with = "vendored-sources"
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -2478,90 +2478,90 @@ dependencies = [
 [[package]]
 name = "js-sys"
 version = "0.3.100"
 source = "git+https://github.com/kvark/dummy-web#5731e569d865a1ebaf116f48dad781f355a99243"
 
 [[package]]
 name = "jsparagus"
 version = "0.1.0"
-source = "git+https://github.com/mozilla-spidermonkey/jsparagus?rev=6c97434b6586fc234fdba0494f4ddd004b9578de#6c97434b6586fc234fdba0494f4ddd004b9578de"
+source = "git+https://github.com/mozilla-spidermonkey/jsparagus?rev=ecc433935118a4ff22d33c4fe7c625a5d0bfb571#ecc433935118a4ff22d33c4fe7c625a5d0bfb571"
 dependencies = [
  "jsparagus-ast",
  "jsparagus-emitter",
  "jsparagus-generated-parser",
  "jsparagus-json-log",
  "jsparagus-parser",
  "jsparagus-scope",
  "jsparagus-stencil",
 ]
 
 [[package]]
 name = "jsparagus-ast"
 version = "0.1.0"
-source = "git+https://github.com/mozilla-spidermonkey/jsparagus?rev=6c97434b6586fc234fdba0494f4ddd004b9578de#6c97434b6586fc234fdba0494f4ddd004b9578de"
+source = "git+https://github.com/mozilla-spidermonkey/jsparagus?rev=ecc433935118a4ff22d33c4fe7c625a5d0bfb571#ecc433935118a4ff22d33c4fe7c625a5d0bfb571"
 dependencies = [
  "bumpalo",
  "indexmap",
 ]
 
 [[package]]
 name = "jsparagus-emitter"
 version = "0.1.0"
-source = "git+https://github.com/mozilla-spidermonkey/jsparagus?rev=6c97434b6586fc234fdba0494f4ddd004b9578de#6c97434b6586fc234fdba0494f4ddd004b9578de"
+source = "git+https://github.com/mozilla-spidermonkey/jsparagus?rev=ecc433935118a4ff22d33c4fe7c625a5d0bfb571#ecc433935118a4ff22d33c4fe7c625a5d0bfb571"
 dependencies = [
  "bumpalo",
  "byteorder",
  "indexmap",
  "jsparagus-ast",
  "jsparagus-scope",
  "jsparagus-stencil",
 ]
 
 [[package]]
 name = "jsparagus-generated-parser"
 version = "0.1.0"
-source = "git+https://github.com/mozilla-spidermonkey/jsparagus?rev=6c97434b6586fc234fdba0494f4ddd004b9578de#6c97434b6586fc234fdba0494f4ddd004b9578de"
+source = "git+https://github.com/mozilla-spidermonkey/jsparagus?rev=ecc433935118a4ff22d33c4fe7c625a5d0bfb571#ecc433935118a4ff22d33c4fe7c625a5d0bfb571"
 dependencies = [
  "bumpalo",
  "jsparagus-ast",
  "static_assertions",
 ]
 
 [[package]]
 name = "jsparagus-json-log"
 version = "0.1.0"
-source = "git+https://github.com/mozilla-spidermonkey/jsparagus?rev=6c97434b6586fc234fdba0494f4ddd004b9578de#6c97434b6586fc234fdba0494f4ddd004b9578de"
+source = "git+https://github.com/mozilla-spidermonkey/jsparagus?rev=ecc433935118a4ff22d33c4fe7c625a5d0bfb571#ecc433935118a4ff22d33c4fe7c625a5d0bfb571"
 
 [[package]]
 name = "jsparagus-parser"
 version = "0.1.0"
-source = "git+https://github.com/mozilla-spidermonkey/jsparagus?rev=6c97434b6586fc234fdba0494f4ddd004b9578de#6c97434b6586fc234fdba0494f4ddd004b9578de"
+source = "git+https://github.com/mozilla-spidermonkey/jsparagus?rev=ecc433935118a4ff22d33c4fe7c625a5d0bfb571#ecc433935118a4ff22d33c4fe7c625a5d0bfb571"
 dependencies = [
  "arrayvec 0.5.2",
  "bumpalo",
  "jsparagus-ast",
  "jsparagus-generated-parser",
  "jsparagus-json-log",
 ]
 
 [[package]]
 name = "jsparagus-scope"
 version = "0.1.0"
-source = "git+https://github.com/mozilla-spidermonkey/jsparagus?rev=6c97434b6586fc234fdba0494f4ddd004b9578de#6c97434b6586fc234fdba0494f4ddd004b9578de"
+source = "git+https://github.com/mozilla-spidermonkey/jsparagus?rev=ecc433935118a4ff22d33c4fe7c625a5d0bfb571#ecc433935118a4ff22d33c4fe7c625a5d0bfb571"
 dependencies = [
  "indexmap",
  "jsparagus-ast",
  "jsparagus-stencil",
 ]
 
 [[package]]
 name = "jsparagus-stencil"
 version = "0.1.0"
-source = "git+https://github.com/mozilla-spidermonkey/jsparagus?rev=6c97434b6586fc234fdba0494f4ddd004b9578de#6c97434b6586fc234fdba0494f4ddd004b9578de"
+source = "git+https://github.com/mozilla-spidermonkey/jsparagus?rev=ecc433935118a4ff22d33c4fe7c625a5d0bfb571#ecc433935118a4ff22d33c4fe7c625a5d0bfb571"
 dependencies = [
  "jsparagus-ast",
 ]
 
 [[package]]
 name = "jsrust"
 version = "0.1.0"
 dependencies = [
new file mode 100644
--- /dev/null
+++ b/accessible/base/TextLeafRange.cpp
@@ -0,0 +1,689 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=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 "TextLeafRange.h"
+
+#include "mozilla/a11y/Accessible.h"
+#include "mozilla/a11y/DocAccessible.h"
+#include "mozilla/a11y/LocalAccessible.h"
+#include "mozilla/intl/WordBreaker.h"
+#include "mozilla/StaticPrefs_layout.h"
+#include "nsAccUtils.h"
+#include "nsContentUtils.h"
+#include "nsIAccessiblePivot.h"
+#include "nsILineIterator.h"
+#include "nsTArray.h"
+#include "nsTextFrame.h"
+#include "nsUnicodeProperties.h"
+#include "Pivot.h"
+
+namespace mozilla::a11y {
+
+/*** Helpers ***/
+
+/**
+ * These two functions convert between rendered and content text offsets.
+ * When text DOM nodes are rendered, the rendered text often does not contain
+ * all the whitespace from the source. For example, by default, the text
+ * "a   b" will be rendered as "a b"; i.e. multiple spaces are compressed to
+ * one. TextLeafAccessibles contain rendered text, but when we query layout, we
+ * need to provide offsets into the original content text. Similarly, layout
+ * returns content offsets, but we need to convert them to rendered offsets to
+ * map them to TextLeafAccessibles.
+ */
+
+static int32_t RenderedToContentOffset(LocalAccessible* aAcc,
+                                       uint32_t aRenderedOffset) {
+  if (aAcc->LocalParent() && aAcc->LocalParent()->IsTextField()) {
+    return static_cast<int32_t>(aRenderedOffset);
+  }
+
+  nsIFrame* frame = aAcc->GetFrame();
+  MOZ_ASSERT(frame && frame->IsTextFrame());
+
+  nsIFrame::RenderedText text =
+      frame->GetRenderedText(aRenderedOffset, aRenderedOffset + 1,
+                             nsIFrame::TextOffsetType::OffsetsInRenderedText,
+                             nsIFrame::TrailingWhitespace::DontTrim);
+  return text.mOffsetWithinNodeText;
+}
+
+static uint32_t ContentToRenderedOffset(LocalAccessible* aAcc,
+                                        int32_t aContentOffset) {
+  if (aAcc->LocalParent() && aAcc->LocalParent()->IsTextField()) {
+    return aContentOffset;
+  }
+
+  nsIFrame* frame = aAcc->GetFrame();
+  MOZ_ASSERT(frame && frame->IsTextFrame());
+
+  nsIFrame::RenderedText text =
+      frame->GetRenderedText(aContentOffset, aContentOffset + 1,
+                             nsIFrame::TextOffsetType::OffsetsInContentText,
+                             nsIFrame::TrailingWhitespace::DontTrim);
+  return text.mOffsetWithinNodeRenderedText;
+}
+
+class LeafRule : public PivotRule {
+ public:
+  virtual uint16_t Match(Accessible* aAcc) override {
+    if (aAcc->IsOuterDoc()) {
+      return nsIAccessibleTraversalRule::FILTER_IGNORE_SUBTREE;
+    }
+    // We deliberately include Accessibles such as empty input elements and
+    // empty containers, as these can be at the start of a line.
+    if (!aAcc->HasChildren()) {
+      return nsIAccessibleTraversalRule::FILTER_MATCH;
+    }
+    return nsIAccessibleTraversalRule::FILTER_IGNORE;
+  }
+};
+
+static Accessible* NextLeaf(Accessible* aOrigin) {
+  DocAccessible* doc = aOrigin->AsLocal()->Document();
+  Pivot pivot(doc);
+  auto rule = LeafRule();
+  return pivot.Next(aOrigin, rule);
+}
+
+static Accessible* PrevLeaf(Accessible* aOrigin) {
+  DocAccessible* doc = aOrigin->AsLocal()->Document();
+  Pivot pivot(doc);
+  auto rule = LeafRule();
+  return pivot.Prev(aOrigin, rule);
+}
+
+static bool IsLocalAccAtLineStart(LocalAccessible* aAcc) {
+  if (aAcc->NativeRole() == roles::LISTITEM_MARKER) {
+    // A bullet always starts a line.
+    return true;
+  }
+  // Splitting of content across lines is handled by layout.
+  // nsIFrame::IsLogicallyAtLineEdge queries whether a frame is the first frame
+  // on its line. However, we can't use that because the first frame on a line
+  // might not be included in the a11y tree; e.g. an empty span, or space
+  // in the DOM after a line break which is stripped when rendered. Instead, we
+  // get the line number for this Accessible's frame and the line number for the
+  // previous leaf Accessible's frame and compare them.
+  Accessible* prev = PrevLeaf(aAcc);
+  LocalAccessible* prevLocal = prev ? prev->AsLocal() : nullptr;
+  if (!prevLocal) {
+    // There's nothing before us, so this is the start of the first line.
+    return true;
+  }
+  if (prevLocal->NativeRole() == roles::LISTITEM_MARKER) {
+    // If there is  a bullet immediately before us and we're inside the same
+    // list item, this is not the start of a line.
+    LocalAccessible* listItem = prevLocal->LocalParent();
+    MOZ_ASSERT(listItem);
+    LocalAccessible* doc = listItem->Document();
+    MOZ_ASSERT(doc);
+    for (LocalAccessible* parent = aAcc->LocalParent(); parent && parent != doc;
+         parent = parent->LocalParent()) {
+      if (parent == listItem) {
+        return false;
+      }
+    }
+  }
+  nsIFrame* thisFrame = aAcc->GetFrame();
+  if (!thisFrame) {
+    return false;
+  }
+  // Even though we have a leaf Accessible, there might be a child frame; e.g.
+  // an empty input element is a leaf Accessible but has an empty text frame
+  // child. To get a line number, we need a leaf frame.
+  nsIFrame::GetFirstLeaf(&thisFrame);
+  nsIFrame* prevFrame = prevLocal->GetFrame();
+  if (!prevFrame) {
+    return false;
+  }
+  nsIFrame::GetLastLeaf(&prevFrame);
+  nsIFrame* thisLineFrame = nullptr;
+  nsIFrame* thisBlock = thisFrame->GetContainingBlockForLine(
+      /* aLockScroll */ false, thisLineFrame);
+  if (!thisBlock) {
+    // We couldn't get the containing block for this frame. In that case, we
+    // play it safe and assume this is the beginning of a new line.
+    return true;
+  }
+  nsIFrame* prevLineFrame = nullptr;
+  nsIFrame* prevBlock = prevFrame->GetContainingBlockForLine(
+      /* aLockScroll */ false, prevLineFrame);
+  if (thisBlock != prevBlock) {
+    // If the blocks are different, that means there's nothing before us on the
+    // same line, so we're at the start.
+    return true;
+  }
+  nsAutoLineIterator it = thisBlock->GetLineIterator();
+  if (!it) {
+    // Error; play it safe.
+    return true;
+  }
+  int32_t thisLineNum = it->FindLineContaining(thisLineFrame);
+  if (thisLineNum < 0) {
+    // Error; play it safe.
+    return true;
+  }
+  int32_t prevLineNum = it->FindLineContaining(prevLineFrame);
+  // if the blocks and line numbers are different, that means there's nothing
+  // before us on the same line, so we're at the start.
+  return thisLineNum != prevLineNum;
+}
+
+/**
+ * There are many kinds of word break, but we only need to treat punctuation and
+ * space specially.
+ */
+enum WordBreakClass { eWbcSpace = 0, eWbcPunct, eWbcOther };
+
+/**
+ * Words can cross Accessibles. To work out whether we're at the start of a
+ * word, we might have to check the previous leaf. This class handles querying
+ * the previous WordBreakClass, crossing Accessibles if necessary.
+ */
+class PrevWordBreakClassWalker {
+ public:
+  PrevWordBreakClassWalker(Accessible* aAcc, const nsAString& aText,
+                           int32_t aOffset)
+      : mAcc(aAcc), mText(aText), mOffset(aOffset) {
+    mClass = GetClass(mText.CharAt(mOffset));
+  }
+
+  WordBreakClass CurClass() { return mClass; }
+
+  Maybe<WordBreakClass> PrevClass() {
+    for (;;) {
+      if (!PrevChar()) {
+        return Nothing();
+      }
+      WordBreakClass curClass = GetClass(mText.CharAt(mOffset));
+      if (curClass != mClass) {
+        mClass = curClass;
+        return Some(curClass);
+      }
+    }
+    MOZ_ASSERT_UNREACHABLE();
+    return Nothing();
+  }
+
+  bool IsStartOfGroup() {
+    PrevChar();
+    WordBreakClass curClass = GetClass(mText.CharAt(mOffset));
+    // We wanted to peek at the previous character, not really move to it.
+    ++mOffset;
+    return curClass != mClass;
+  }
+
+ private:
+  bool PrevChar() {
+    if (mOffset > 0) {
+      --mOffset;
+      return true;
+    }
+    mAcc = PrevLeaf(mAcc);
+    if (!mAcc) {
+      return false;
+    }
+    mText.Truncate();
+    mAcc->AsLocal()->AppendTextTo(mText);
+    mOffset = static_cast<int32_t>(mText.Length()) - 1;
+    return true;
+  }
+
+  WordBreakClass GetClass(char16_t aChar) {
+    // Based on IsSelectionInlineWhitespace and IsSelectionNewline in
+    // layout/generic/nsTextFrame.cpp.
+    const char16_t kCharNbsp = 0xA0;
+    switch (aChar) {
+      case ' ':
+      case kCharNbsp:
+      case '\t':
+      case '\f':
+      case '\n':
+      case '\r':
+        return eWbcSpace;
+      default:
+        break;
+    }
+    // Based on ClusterIterator::IsPunctuation in
+    // layout/generic/nsTextFrame.cpp.
+    uint8_t cat = unicode::GetGeneralCategory(aChar);
+    switch (cat) {
+      case HB_UNICODE_GENERAL_CATEGORY_CONNECT_PUNCTUATION: /* Pc */
+        if (aChar == '_' &&
+            !StaticPrefs::layout_word_select_stop_at_underscore()) {
+          return eWbcOther;
+        }
+        [[fallthrough]];
+      case HB_UNICODE_GENERAL_CATEGORY_DASH_PUNCTUATION:    /* Pd */
+      case HB_UNICODE_GENERAL_CATEGORY_CLOSE_PUNCTUATION:   /* Pe */
+      case HB_UNICODE_GENERAL_CATEGORY_FINAL_PUNCTUATION:   /* Pf */
+      case HB_UNICODE_GENERAL_CATEGORY_INITIAL_PUNCTUATION: /* Pi */
+      case HB_UNICODE_GENERAL_CATEGORY_OTHER_PUNCTUATION:   /* Po */
+      case HB_UNICODE_GENERAL_CATEGORY_OPEN_PUNCTUATION:    /* Ps */
+      case HB_UNICODE_GENERAL_CATEGORY_CURRENCY_SYMBOL:     /* Sc */
+      case HB_UNICODE_GENERAL_CATEGORY_MATH_SYMBOL:         /* Sm */
+      case HB_UNICODE_GENERAL_CATEGORY_OTHER_SYMBOL:        /* So */
+        return eWbcPunct;
+      default:
+        break;
+    }
+    return eWbcOther;
+  }
+
+  Accessible* mAcc;
+  nsAutoString mText;
+  int32_t mOffset;
+  WordBreakClass mClass;
+};
+
+/**
+ * WordBreaker breaks at all space, punctuation, etc. We want to emulate
+ * layout, so that's not what we want. This function determines whether this
+ * is acceptable as the start of a word for our purposes.
+ */
+static bool IsAcceptableWordStart(intl::WordBreaker* aBreaker, Accessible* aAcc,
+                                  const nsAutoString& aText, int32_t aOffset) {
+  PrevWordBreakClassWalker walker(aAcc, aText, aOffset);
+  if (!walker.IsStartOfGroup()) {
+    // If we're not at the start of a WordBreaker group, this can't be the
+    // start of a word.
+    return false;
+  }
+  WordBreakClass curClass = walker.CurClass();
+  if (curClass == eWbcSpace) {
+    // Space isn't the start of a word.
+    return false;
+  }
+  Maybe<WordBreakClass> prevClass = walker.PrevClass();
+  if (curClass == eWbcPunct && (!prevClass || prevClass.value() != eWbcSpace)) {
+    // Punctuation isn't the start of a word (unless it is after space).
+    return false;
+  }
+  if (!prevClass || prevClass.value() != eWbcPunct) {
+    // If there's nothing before this or the group before this isn't
+    // punctuation, this is the start of a word.
+    return true;
+  }
+  // At this point, we know the group before this is punctuation.
+  if (!StaticPrefs::layout_word_select_stop_at_punctuation()) {
+    // When layout.word_select.stop_at_punctuation is false (defaults to true),
+    // if there is punctuation before this, this is not the start of a word.
+    return false;
+  }
+  Maybe<WordBreakClass> prevPrevClass = walker.PrevClass();
+  if (!prevPrevClass || prevPrevClass.value() == eWbcSpace) {
+    // If there is punctuation before this and space (or nothing) before the
+    // punctuation, this is not the start of a word.
+    return false;
+  }
+  return true;
+}
+
+/*** TextLeafPoint ***/
+
+TextLeafPoint::TextLeafPoint(Accessible* aAcc, int32_t aOffset) {
+  if (aAcc->HasChildren()) {
+    // Find a leaf. This might not necessarily be a TextLeafAccessible; it
+    // could be an empty container.
+    for (Accessible* acc = aAcc->FirstChild(); acc; acc = acc->FirstChild()) {
+      mAcc = acc;
+    }
+    mOffset = 0;
+    return;
+  }
+  mAcc = aAcc;
+  mOffset = aOffset;
+}
+
+bool TextLeafPoint::operator<(const TextLeafPoint& aPoint) const {
+  if (mAcc == aPoint.mAcc) {
+    return mOffset < aPoint.mOffset;
+  }
+
+  // Build the chain of parents.
+  Accessible* thisP = mAcc;
+  Accessible* otherP = aPoint.mAcc;
+  AutoTArray<Accessible*, 30> thisParents, otherParents;
+  do {
+    thisParents.AppendElement(thisP);
+    thisP = thisP->Parent();
+  } while (thisP);
+  do {
+    otherParents.AppendElement(otherP);
+    otherP = otherP->Parent();
+  } while (otherP);
+
+  // Find where the parent chain differs.
+  uint32_t thisPos = thisParents.Length(), otherPos = otherParents.Length();
+  for (uint32_t len = std::min(thisPos, otherPos); len > 0; --len) {
+    Accessible* thisChild = thisParents.ElementAt(--thisPos);
+    Accessible* otherChild = otherParents.ElementAt(--otherPos);
+    if (thisChild != otherChild) {
+      return thisChild->IndexInParent() < otherChild->IndexInParent();
+    }
+  }
+
+  // If the ancestries are the same length (both thisPos and otherPos are 0),
+  // we should have returned by now.
+  MOZ_ASSERT(thisPos != 0 || otherPos != 0);
+  // At this point, one of the ancestries is a superset of the other, so one of
+  // thisPos or otherPos should be 0.
+  MOZ_ASSERT(thisPos != otherPos);
+  // If the other Accessible is deeper than this one (otherPos > 0), this
+  // Accessible comes before the other.
+  return otherPos > 0;
+}
+
+bool TextLeafPoint::IsEmptyLastLine() const {
+  if (mAcc->IsHTMLBr() && mOffset == 1) {
+    return true;
+  }
+  if (!mAcc->IsTextLeaf()) {
+    return false;
+  }
+  LocalAccessible* localAcc = mAcc->AsLocal();
+  MOZ_ASSERT(localAcc);
+  if (mOffset < static_cast<int32_t>(nsAccUtils::TextLength(localAcc))) {
+    return false;
+  }
+  nsAutoString text;
+  mAcc->AsLocal()->AppendTextTo(text, mOffset - 1, 1);
+  return text.CharAt(0) == '\n';
+}
+
+TextLeafPoint TextLeafPoint::FindPrevLineStartSameLocalAcc(
+    bool aIncludeOrigin) const {
+  LocalAccessible* acc = mAcc->AsLocal();
+  MOZ_ASSERT(acc);
+  if (mOffset == 0) {
+    if (aIncludeOrigin && IsLocalAccAtLineStart(acc)) {
+      return *this;
+    }
+    return TextLeafPoint();
+  }
+  nsIFrame* frame = acc->GetFrame();
+  if (!frame) {
+    MOZ_ASSERT_UNREACHABLE("No frame");
+    return TextLeafPoint();
+  }
+  if (!frame->IsTextFrame()) {
+    if (IsLocalAccAtLineStart(acc)) {
+      return TextLeafPoint(acc, 0);
+    }
+    return TextLeafPoint();
+  }
+  // Each line of a text node is rendered as a continuation frame. Get the
+  // continuation containing the origin.
+  int32_t origOffset = mOffset;
+  origOffset = RenderedToContentOffset(acc, origOffset);
+  nsTextFrame* continuation = nullptr;
+  int32_t unusedOffsetInContinuation = 0;
+  frame->GetChildFrameContainingOffset(
+      origOffset, true, &unusedOffsetInContinuation, (nsIFrame**)&continuation);
+  MOZ_ASSERT(continuation);
+  int32_t lineStart = continuation->GetContentOffset();
+  if (!aIncludeOrigin && lineStart > 0 && lineStart == origOffset) {
+    // A line starts at the origin, but the caller doesn't want this included.
+    // Go back one more.
+    continuation = continuation->GetPrevContinuation();
+    MOZ_ASSERT(continuation);
+    lineStart = continuation->GetContentOffset();
+  }
+  MOZ_ASSERT(lineStart >= 0);
+  if (lineStart == 0 && !IsLocalAccAtLineStart(acc)) {
+    // This is the first line of this text node, but there is something else
+    // on the same line before this text node, so don't return this as a line
+    // start.
+    return TextLeafPoint();
+  }
+  lineStart = static_cast<int32_t>(ContentToRenderedOffset(acc, lineStart));
+  return TextLeafPoint(acc, lineStart);
+}
+
+TextLeafPoint TextLeafPoint::FindNextLineStartSameLocalAcc(
+    bool aIncludeOrigin) const {
+  LocalAccessible* acc = mAcc->AsLocal();
+  MOZ_ASSERT(acc);
+  if (aIncludeOrigin && mOffset == 0 && IsLocalAccAtLineStart(acc)) {
+    return *this;
+  }
+  nsIFrame* frame = acc->GetFrame();
+  if (!frame) {
+    MOZ_ASSERT_UNREACHABLE("No frame");
+    return TextLeafPoint();
+  }
+  if (!frame->IsTextFrame()) {
+    // There can't be multiple lines in a non-text leaf.
+    return TextLeafPoint();
+  }
+  // Each line of a text node is rendered as a continuation frame. Get the
+  // continuation containing the origin.
+  int32_t origOffset = mOffset;
+  origOffset = RenderedToContentOffset(acc, origOffset);
+  nsTextFrame* continuation = nullptr;
+  int32_t unusedOffsetInContinuation = 0;
+  frame->GetChildFrameContainingOffset(
+      origOffset, true, &unusedOffsetInContinuation, (nsIFrame**)&continuation);
+  MOZ_ASSERT(continuation);
+  if (
+      // A line starts at the origin and the caller wants this included.
+      aIncludeOrigin && continuation->GetContentOffset() == origOffset &&
+      // If this is the first line of this text node (offset 0), don't treat it
+      // as a line start if there's something else on the line before this text
+      // node.
+      !(origOffset == 0 && !IsLocalAccAtLineStart(acc))) {
+    return *this;
+  }
+  continuation = continuation->GetNextContinuation();
+  if (!continuation) {
+    return TextLeafPoint();
+  }
+  int32_t lineStart = continuation->GetContentOffset();
+  lineStart = static_cast<int32_t>(ContentToRenderedOffset(acc, lineStart));
+  return TextLeafPoint(acc, lineStart);
+}
+
+TextLeafPoint TextLeafPoint::FindPrevWordStartSameAcc(
+    bool aIncludeOrigin) const {
+  if (mOffset == 0 && !aIncludeOrigin) {
+    // We can't go back any further and the caller doesn't want the origin
+    // included, so there's nothing more to do.
+    return TextLeafPoint();
+  }
+  nsAutoString text;
+  MOZ_ASSERT(mAcc->IsLocal());
+  mAcc->AsLocal()->AppendTextTo(text);
+  intl::WordBreaker* breaker = nsContentUtils::WordBreaker();
+  TextLeafPoint lineStart = *this;
+  // A word never starts with a line feed character. If there are multiple
+  // consecutive line feed characters and we're after the first of them, the
+  // previous line start will be a line feed character. Skip this and any prior
+  // consecutive line feed first.
+  for (; lineStart.mOffset >= 0 && text.CharAt(lineStart.mOffset) == '\n';
+       --lineStart.mOffset) {
+  }
+  if (lineStart.mOffset < 0) {
+    // There's no line start for our purposes.
+    lineStart = TextLeafPoint();
+  } else {
+    lineStart = lineStart.FindPrevLineStartSameLocalAcc(aIncludeOrigin);
+  }
+  // Keep walking backward until we find an acceptable word start.
+  intl::WordRange word;
+  if (mOffset == 0) {
+    word.mBegin = 0;
+  } else if (mOffset == static_cast<int32_t>(text.Length())) {
+    word = breaker->FindWord(text.get(), text.Length(), mOffset - 1);
+  } else {
+    word = breaker->FindWord(text.get(), text.Length(), mOffset);
+  }
+  for (;;
+       word = breaker->FindWord(text.get(), text.Length(), word.mBegin - 1)) {
+    if (!aIncludeOrigin && static_cast<int32_t>(word.mBegin) == mOffset) {
+      // A word possibly starts at the origin, but the caller doesn't want this
+      // included.
+      MOZ_ASSERT(word.mBegin != 0);
+      continue;
+    }
+    if (lineStart && static_cast<int32_t>(word.mBegin) < lineStart.mOffset) {
+      // A line start always starts a new word.
+      return lineStart;
+    }
+    if (IsAcceptableWordStart(breaker, mAcc, text,
+                              static_cast<int32_t>(word.mBegin))) {
+      break;
+    }
+    if (word.mBegin == 0) {
+      // We can't go back any further.
+      if (lineStart) {
+        // A line start always starts a new word.
+        return lineStart;
+      }
+      return TextLeafPoint();
+    }
+  }
+  return TextLeafPoint(mAcc, static_cast<int32_t>(word.mBegin));
+}
+
+TextLeafPoint TextLeafPoint::FindNextWordStartSameAcc(
+    bool aIncludeOrigin) const {
+  nsAutoString text;
+  MOZ_ASSERT(mAcc->IsLocal());
+  mAcc->AsLocal()->AppendTextTo(text);
+  int32_t wordStart = mOffset;
+  intl::WordBreaker* breaker = nsContentUtils::WordBreaker();
+  if (aIncludeOrigin) {
+    if (wordStart == 0) {
+      if (IsAcceptableWordStart(breaker, mAcc, text, 0)) {
+        return *this;
+      }
+    } else {
+      // The origin might start a word, so search from just before it.
+      --wordStart;
+    }
+  }
+  TextLeafPoint lineStart = FindNextLineStartSameLocalAcc(aIncludeOrigin);
+  if (lineStart) {
+    // A word never starts with a line feed character. If there are multiple
+    // consecutive line feed characters, lineStart will point at the second of
+    // them. Skip this and any subsequent consecutive line feed.
+    for (; lineStart.mOffset < static_cast<int32_t>(text.Length()) &&
+           text.CharAt(lineStart.mOffset) == '\n';
+         ++lineStart.mOffset) {
+    }
+    if (lineStart.mOffset == static_cast<int32_t>(text.Length())) {
+      // There's no line start for our purposes.
+      lineStart = TextLeafPoint();
+    }
+  }
+  // Keep walking forward until we find an acceptable word start.
+  for (;;) {
+    wordStart = breaker->Next(text.get(), text.Length(), wordStart);
+    if (wordStart == NS_WORDBREAKER_NEED_MORE_TEXT ||
+        wordStart == static_cast<int32_t>(text.Length())) {
+      if (lineStart) {
+        // A line start always starts a new word.
+        return lineStart;
+      }
+      return TextLeafPoint();
+    }
+    if (lineStart && wordStart > lineStart.mOffset) {
+      // A line start always starts a new word.
+      return lineStart;
+    }
+    if (IsAcceptableWordStart(breaker, mAcc, text, wordStart)) {
+      break;
+    }
+  }
+  return TextLeafPoint(mAcc, wordStart);
+}
+
+TextLeafPoint TextLeafPoint::FindBoundary(AccessibleTextBoundary aBoundaryType,
+                                          nsDirection aDirection,
+                                          bool aIncludeOrigin) const {
+  // XXX Add handling for caret at end of wrapped line.
+  if (aBoundaryType == nsIAccessibleText::BOUNDARY_LINE_START &&
+      aIncludeOrigin && aDirection == eDirPrevious && IsEmptyLastLine()) {
+    // If we're at an empty line at the end of an Accessible,  we don't want to
+    // walk into the previous line. For example, this can happen if the caret
+    // is positioned on an empty line at the end of a textarea.
+    return *this;
+  }
+  if (aBoundaryType == nsIAccessibleText::BOUNDARY_CHAR && aIncludeOrigin) {
+    return *this;
+  }
+  TextLeafPoint searchFrom = *this;
+  bool includeOrigin = aIncludeOrigin;
+  for (;;) {
+    TextLeafPoint boundary;
+    // Search for the boundary within the current Accessible.
+    switch (aBoundaryType) {
+      case nsIAccessibleText::BOUNDARY_CHAR:
+        if (aDirection == eDirPrevious && searchFrom.mOffset > 0) {
+          boundary.mAcc = searchFrom.mAcc;
+          boundary.mOffset = searchFrom.mOffset - 1;
+        } else if (aDirection == eDirNext) {
+          if (includeOrigin) {
+            // We've moved to the next leaf. That means we've set the offset
+            // to 0, so we're already at the next character.
+            boundary = searchFrom;
+          } else if (searchFrom.mOffset <
+                     static_cast<int32_t>(
+                         nsAccUtils::TextLength(searchFrom.mAcc->AsLocal()))) {
+            boundary.mAcc = searchFrom.mAcc;
+            boundary.mOffset = searchFrom.mOffset + 1;
+          }
+        }
+        break;
+      case nsIAccessibleText::BOUNDARY_WORD_START:
+        if (aDirection == eDirPrevious) {
+          boundary = searchFrom.FindPrevWordStartSameAcc(includeOrigin);
+        } else {
+          boundary = searchFrom.FindNextWordStartSameAcc(includeOrigin);
+        }
+        break;
+      case nsIAccessibleText::BOUNDARY_LINE_START:
+        if (aDirection == eDirPrevious) {
+          boundary = searchFrom.FindPrevLineStartSameLocalAcc(includeOrigin);
+        } else {
+          boundary = searchFrom.FindNextLineStartSameLocalAcc(includeOrigin);
+        }
+        break;
+      default:
+        MOZ_ASSERT_UNREACHABLE();
+        break;
+    }
+    if (boundary) {
+      return boundary;
+    }
+    // We didn't find it in this Accessible, so try the previous/next leaf.
+    Accessible* acc = aDirection == eDirPrevious ? PrevLeaf(searchFrom.mAcc)
+                                                 : NextLeaf(searchFrom.mAcc);
+    if (!acc) {
+      // No further leaf was found. Use the start/end of the first/last leaf.
+      return TextLeafPoint(searchFrom.mAcc,
+                           aDirection == eDirPrevious
+                               ? 0
+                               : static_cast<int32_t>(nsAccUtils::TextLength(
+                                     searchFrom.mAcc->AsLocal())));
+    }
+    searchFrom.mAcc = acc;
+    // When searching backward, search from the end of the text in the
+    // Accessible. When searching forward, search from the start of the text.
+    searchFrom.mOffset =
+        aDirection == eDirPrevious
+            ? static_cast<int32_t>(nsAccUtils::TextLength(acc->AsLocal()))
+            : 0;
+    // The start/end of the Accessible might be a boundary. If so, we must stop
+    // on it.
+    includeOrigin = true;
+  }
+  MOZ_ASSERT_UNREACHABLE();
+  return TextLeafPoint();
+}
+
+}  // namespace mozilla::a11y
new file mode 100644
--- /dev/null
+++ b/accessible/base/TextLeafRange.h
@@ -0,0 +1,114 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=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 mozilla_a11y_TextLeafRange_h__
+#define mozilla_a11y_TextLeafRange_h__
+
+#include <stdint.h>
+
+#include "nsDirection.h"
+#include "nsIAccessibleText.h"
+
+namespace mozilla::a11y {
+class Accessible;
+
+/**
+ * Represents a point within accessible text.
+ * This is stored as a leaf Accessible and an offset into that Accessible.
+ * For an empty Accessible, the offset will always be 0.
+ * This will eventually replace TextPoint. Unlike TextPoint, this does not
+ * use HyperTextAccessible offsets.
+ */
+class TextLeafPoint final {
+ public:
+  TextLeafPoint(Accessible* aAcc, int32_t aOffset);
+
+  /**
+   * Constructs an invalid TextPoint (mAcc is null).
+   * A TextLeafPoint in this state will evaluate to false.
+   * mAcc can be set later. Alternatively, this can be used to indicate an error
+   * (e.g. if a requested point couldn't be found).
+   */
+  TextLeafPoint() : mAcc(nullptr), mOffset(0) {}
+
+  Accessible* mAcc;
+  int32_t mOffset;
+
+  bool operator==(const TextLeafPoint& aPoint) const {
+    return mAcc == aPoint.mAcc && mOffset == aPoint.mOffset;
+  }
+
+  bool operator!=(const TextLeafPoint& aPoint) const {
+    return !(*this == aPoint);
+  }
+
+  bool operator<(const TextLeafPoint& aPoint) const;
+
+  /**
+   * A valid TextLeafPoint evaluates to true. An invalid TextLeafPoint
+   * evaluates to false.
+   */
+  explicit operator bool() const { return !!mAcc; }
+
+  /**
+   * Find a boundary (word start, line start, etc.) in a specific direction.
+   * If no boundary is found, the start/end of the document is returned
+   * (depending on the direction).
+   * If aIncludeorigin is true and this is at a boundary, this will be
+   * returned unchanged.
+   */
+  TextLeafPoint FindBoundary(AccessibleTextBoundary aBoundaryType,
+                             nsDirection aDirection,
+                             bool aIncludeOrigin = false) const;
+
+  /**
+   * These two functions find a line start boundary within the same
+   * LocalAccessible as this. That is, they do not cross Accessibles. If no
+   * boundary is found, an invalid TextLeafPoint is returned.
+   * These are used by FindBoundary. Most callers will want FindBoundary
+   * instead.
+   */
+  TextLeafPoint FindPrevLineStartSameLocalAcc(bool aIncludeOrigin) const;
+  TextLeafPoint FindNextLineStartSameLocalAcc(bool aIncludeOrigin) const;
+
+  /**
+   * These two functions find a word start boundary within the same
+   * Accessible as this. That is, they do not cross Accessibles. If no
+   * boundary is found, an invalid TextLeafPoint is returned.
+   * These are used by FindBoundary. Most callers will want FindBoundary
+   * instead.
+   */
+  TextLeafPoint FindPrevWordStartSameAcc(bool aIncludeOrigin) const;
+  TextLeafPoint FindNextWordStartSameAcc(bool aIncludeOrigin) const;
+
+ private:
+  bool IsEmptyLastLine() const;
+};
+
+/**
+ * Represents a range of accessible text.
+ * This will eventually replace TextRange.
+ */
+class TextLeafRange final {
+ public:
+  TextLeafRange(const TextLeafPoint& aStart, const TextLeafPoint& aEnd)
+      : mStart(aStart), mEnd(aEnd) {}
+  explicit TextLeafRange(const TextLeafPoint& aStart)
+      : mStart(aStart), mEnd(aStart) {}
+
+  TextLeafPoint Start() { return mStart; }
+  void SetStart(const TextLeafPoint& aStart) { mStart = aStart; }
+  TextLeafPoint End() { return mEnd; }
+  void SetEnd(const TextLeafPoint& aEnd) { mEnd = aEnd; }
+
+ private:
+  TextLeafPoint mStart;
+  TextLeafPoint mEnd;
+};
+
+}  // namespace mozilla::a11y
+
+#endif
--- a/accessible/base/moz.build
+++ b/accessible/base/moz.build
@@ -45,16 +45,17 @@ UNIFIED_SOURCES += [
     "nsAccUtils.cpp",
     "nsCoreUtils.cpp",
     "nsEventShell.cpp",
     "nsTextEquivUtils.cpp",
     "Pivot.cpp",
     "SelectionManager.cpp",
     "StyleInfo.cpp",
     "TextAttrs.cpp",
+    "TextLeafRange.cpp",
     "TextRange.cpp",
     "TextUpdater.cpp",
     "TreeWalker.cpp",
 ]
 
 if CONFIG["A11Y_LOG"]:
     UNIFIED_SOURCES += [
         "Logging.cpp",
--- a/accessible/generic/HyperTextAccessible.cpp
+++ b/accessible/generic/HyperTextAccessible.cpp
@@ -13,16 +13,17 @@
 #include "DocAccessible.h"
 #include "HTMLListAccessible.h"
 #include "LocalAccessible-inl.h"
 #include "Pivot.h"
 #include "Relation.h"
 #include "Role.h"
 #include "States.h"
 #include "TextAttrs.h"
+#include "TextLeafRange.h"
 #include "TextRange.h"
 #include "TreeWalker.h"
 
 #include "nsCaret.h"
 #include "nsContentUtils.h"
 #include "nsDebug.h"
 #include "nsFocusManager.h"
 #include "nsIEditingSession.h"
@@ -36,16 +37,17 @@
 #include "nsTextFragment.h"
 #include "mozilla/Assertions.h"
 #include "mozilla/BinarySearch.h"
 #include "mozilla/EditorBase.h"
 #include "mozilla/EventStates.h"
 #include "mozilla/HTMLEditor.h"
 #include "mozilla/MathAlgorithms.h"
 #include "mozilla/PresShell.h"
+#include "mozilla/StaticPrefs_accessibility.h"
 #include "mozilla/StaticPrefs_layout.h"
 #include "mozilla/dom/Element.h"
 #include "mozilla/dom/HTMLBRElement.h"
 #include "mozilla/dom/HTMLHeadingElement.h"
 #include "mozilla/dom/Selection.h"
 #include "gfxSkipChars.h"
 #include <algorithm>
 
@@ -1045,16 +1047,62 @@ void HyperTextAccessible::TextAtOffset(i
   aText.Truncate();
 
   uint32_t adjustedOffset = ConvertMagicOffset(aOffset);
   if (adjustedOffset == std::numeric_limits<uint32_t>::max()) {
     NS_ERROR("Wrong given offset!");
     return;
   }
 
+  if (StaticPrefs::accessibility_cache_enabled_AtStartup()) {
+    // This isn't strictly related to caching, but this new text implementation
+    // is being developed to make caching feasible. We put it behind this pref
+    // to make it easy to test while it's still under development.
+    switch (aBoundaryType) {
+      case nsIAccessibleText::BOUNDARY_WORD_START:
+      case nsIAccessibleText::BOUNDARY_LINE_START:
+        TextLeafPoint origStart =
+            ToTextLeafPoint(static_cast<int32_t>(adjustedOffset));
+        TextLeafPoint end;
+        LocalAccessible* childAcc = GetChildAtOffset(adjustedOffset);
+        if (childAcc && childAcc->IsHyperText()) {
+          // We're searching for boundaries enclosing an embedded object.
+          // An embedded object might contain several boundaries itself.
+          // Thus, we must ensure we search for the end boundary from the last
+          // text in the subtree, not just the first.
+          // For example, if the embedded object is a link and it contains two
+          // words, but the second word expands beyond the link, we want to
+          // include the part of the second word which is outside of the link.
+          end = ToTextLeafPoint(static_cast<int32_t>(adjustedOffset),
+                                /* aDescendToEnd */ true);
+        } else {
+          end = origStart;
+        }
+        TextLeafPoint start = origStart.FindBoundary(
+            aBoundaryType, eDirPrevious, /* aIncludeOrigin */ true);
+        *aStartOffset = static_cast<int32_t>(
+            TransformOffset(start.mAcc->AsLocal(), start.mOffset,
+                            /* aIsEndOffset */ false));
+        if (*aStartOffset == static_cast<int32_t>(CharacterCount()) &&
+            (*aStartOffset > static_cast<int32_t>(adjustedOffset) ||
+             start != origStart)) {
+          // start is before this HyperTextAccessible. In that case,
+          // Transformoffset will return CharacterCount(), but we want to
+          // clip to the start of this HyperTextAccessible, not the end.
+          *aStartOffset = 0;
+        }
+        end = end.FindBoundary(aBoundaryType, eDirNext);
+        *aEndOffset = static_cast<int32_t>(
+            TransformOffset(end.mAcc->AsLocal(), end.mOffset,
+                            /* aIsEndOffset */ true));
+        TextSubstring(*aStartOffset, *aEndOffset, aText);
+        return;
+    }
+  }
+
   switch (aBoundaryType) {
     case nsIAccessibleText::BOUNDARY_CHAR:
       // Return no char if caret is at the end of wrapped line (case of no line
       // end character). Returning a next line char is confusing for AT.
       if (aOffset == nsIAccessibleText::TEXT_OFFSET_CARET &&
           IsCaretAtEndOfLine()) {
         *aStartOffset = *aEndOffset = adjustedOffset;
       } else {
@@ -2081,16 +2129,34 @@ void HyperTextAccessible::RangeAtPoint(i
   // Return collapsed text range for the point.
   if (parent) {
     HyperTextAccessible* ht = parent->AsHyperText();
     int32_t offset = ht->GetChildOffset(child);
     aRange.Set(mDoc, ht, offset, ht, offset);
   }
 }
 
+TextLeafPoint HyperTextAccessible::ToTextLeafPoint(int32_t aOffset,
+                                                   bool aDescendToEnd) {
+  if (!HasChildren()) {
+    return TextLeafPoint(this, 0);
+  }
+  LocalAccessible* child = GetChildAtOffset(aOffset);
+  if (!child) {
+    return TextLeafPoint();
+  }
+  if (HyperTextAccessible* childHt = child->AsHyperText()) {
+    return childHt->ToTextLeafPoint(
+        aDescendToEnd ? static_cast<int32_t>(childHt->CharacterCount()) : 0,
+        aDescendToEnd);
+  }
+  int32_t offset = aOffset - GetChildOffset(child);
+  return TextLeafPoint(child, offset);
+}
+
 ////////////////////////////////////////////////////////////////////////////////
 // LocalAccessible public
 
 // LocalAccessible protected
 ENameValueFlag HyperTextAccessible::NativeName(nsString& aName) const {
   // Check @alt attribute for invalid img elements.
   bool hasImgAlt = false;
   if (mContent->IsHTMLElement(nsGkAtoms::img)) {
--- a/accessible/generic/HyperTextAccessible.h
+++ b/accessible/generic/HyperTextAccessible.h
@@ -22,16 +22,17 @@ class nsIWidget;
 namespace mozilla {
 class EditorBase;
 namespace dom {
 class Selection;
 }
 
 namespace a11y {
 
+class TextLeafPoint;
 class TextRange;
 
 struct DOMPoint {
   DOMPoint() : node(nullptr), idx(0) {}
   DOMPoint(nsINode* aNode, int32_t aIdx) : node(aNode), idx(aIdx) {}
 
   nsINode* node;
   int32_t idx;
@@ -379,16 +380,24 @@ class HyperTextAccessible : public Acces
    */
   void RangeByChild(LocalAccessible* aChild, TextRange& aRange) const;
 
   /**
    * Return a range containing an accessible at the given point.
    */
   void RangeAtPoint(int32_t aX, int32_t aY, TextRange& aRange) const;
 
+  /**
+   * Get a TextLeafPoint for a given offset in this HyperTextAccessible.
+   * If the offset points to an embedded object and aDescendToEnd is true,
+   * the point right at the end of this subtree will be returned instead of the
+   * start.
+   */
+  TextLeafPoint ToTextLeafPoint(int32_t aOffset, bool aDescendToEnd = false);
+
   //////////////////////////////////////////////////////////////////////////////
   // EditableTextAccessible
 
   MOZ_CAN_RUN_SCRIPT_BOUNDARY void ReplaceText(const nsAString& aText);
   MOZ_CAN_RUN_SCRIPT_BOUNDARY void InsertText(const nsAString& aText,
                                               int32_t aPosition);
   void CopyText(int32_t aStartPos, int32_t aEndPos);
   MOZ_CAN_RUN_SCRIPT_BOUNDARY void CutText(int32_t aStartPos, int32_t aEndPos);
--- a/accessible/tests/mochitest/text/test_lineboundary.html
+++ b/accessible/tests/mochitest/text/test_lineboundary.html
@@ -8,18 +8,24 @@
   <script type="application/javascript"
           src="../common.js"></script>
   <script type="application/javascript"
           src="../text.js"></script>
   <script type="application/javascript">
     function doTest() {
       testTextAtOffset("line_test_1", BOUNDARY_LINE_START,
                        [[0, 6, "Line 1 ", 0, 7],
-                        [7, 7, "", 7, 7],
+                        // See the todo test below.
+                        // [7, 7, kEmbedChar, 7, 8],
                         [8, 15, "Line 3 ", 8, 15]]);
+      testTextAtOffset(/* aOffset */ 7, BOUNDARY_LINE_START,
+        kEmbedChar, /* aStartOffset */ 7, /* aEndOffset */ 8,
+        "line_test_1",
+        /* returned text */ kTodo,
+        /* returned start offset */ kOk, /* returned end offset */ kTodo);
 
       // ////////////////////////////////////////////////////////////////////////
       // __h__e__l__l__o__ __m__y__ __f__r__i__e__n__d__
       //  0  1  2  3  4  5  6  7  8  9 10 11 12 13 14 15
 
       var IDs = [ "input", "div", "editable", "textarea",
                   getNode("ta", getNode("ta_cntr").contentDocument) ];
 
--- a/browser/base/content/browser.css
+++ b/browser/base/content/browser.css
@@ -1231,16 +1231,17 @@ toolbarpaletteitem[dragover] {
 
 #customization-footer-spacer,
 #customization-spacer {
   flex: 1 1 auto;
 }
 
 #customization-footer {
   display: flex;
+  align-items: center;
   flex-shrink: 0;
   flex-wrap: wrap;
 }
 
 #customization-toolbar-visibility-button > .box-inherit > .button-menu-dropmarker {
   display: -moz-box;
 }
 
--- a/browser/base/content/test/fullscreen/browser.ini
+++ b/browser/base/content/test/fullscreen/browser.ini
@@ -16,8 +16,10 @@ skip-if = (os == 'mac') || (os == 'linux
 [browser_fullscreen_window_open.js]
 skip-if = debug && os == 'mac' # Bug 1568570
 [browser_fullscreen_window_focus.js]
 skip-if =
   os == 'mac' # Bug 1568570
 [browser_fullscreen_api_fission.js]
 https_first_disabled = true
 support-files = fullscreen.html FullscreenFrame.jsm
+[browser_fullscreen_warning.js]
+support-files = fullscreen.html
new file mode 100644
--- /dev/null
+++ b/browser/base/content/test/fullscreen/browser_fullscreen_warning.js
@@ -0,0 +1,59 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+add_task(async function test_fullscreen_display_none() {
+  await SpecialPowers.pushPrefEnv({
+    set: [
+      ["full-screen-api.enabled", true],
+      ["full-screen-api.allow-trusted-requests-only", false],
+    ],
+  });
+
+  await BrowserTestUtils.withNewTab(
+    {
+      gBrowser,
+      url: `data:text/html,
+      <html>
+        <head>
+          <meta charset="utf-8"/>
+          <title>Fullscreen Test</title>
+        </head>
+        <body id="body">
+        <iframe
+         src="https://example.org/browser/browser/base/content/test/fullscreen/fullscreen.html"
+         hidden
+         allowfullscreen></iframe>
+        </body>
+      </html>`,
+    },
+    async function(browser) {
+      let warning = document.getElementById("fullscreen-warning");
+      let warningShownPromise = BrowserTestUtils.waitForAttribute(
+        "onscreen",
+        warning,
+        "true"
+      );
+      // Enter fullscreen
+      await SpecialPowers.spawn(browser, [], async () => {
+        let frame = content.document.querySelector("iframe");
+        frame.focus();
+        await SpecialPowers.spawn(frame, [], () => {
+          content.document.getElementById("request").click();
+        });
+      });
+      await warningShownPromise;
+      ok(true, "Fullscreen warning shown");
+      // Exit fullscreen
+      let exitFullscreenPromise = BrowserTestUtils.waitForEvent(
+        document,
+        "fullscreenchange",
+        false,
+        () => !document.fullscreenElement
+      );
+      document.getElementById("fullscreen-exit-button").click();
+      await exitFullscreenPromise;
+    }
+  );
+});
--- a/browser/components/customizableui/CustomizeMode.jsm
+++ b/browser/components/customizableui/CustomizeMode.jsm
@@ -1,15 +1,15 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
-var EXPORTED_SYMBOLS = ["CustomizeMode", "_defaultImportantThemes"];
+var EXPORTED_SYMBOLS = ["CustomizeMode"];
 
 const kPrefCustomizationDebug = "browser.uiCustomization.debug";
 const kPaletteId = "customization-palette";
 const kDragDataTypePrefix = "text/toolbarwrapper-id/";
 const kSkipSourceNodePref = "browser.uiCustomization.skipSourceNodeCheck";
 const kDrawInTitlebarPref = "browser.tabs.drawInTitlebar";
 const kCompactModeShowPref = "browser.compactmode.show";
 const kBookmarksToolbarPref = "browser.toolbars.bookmarks.visibility";
@@ -79,28 +79,16 @@ XPCOMUtils.defineLazyGetter(this, "log",
   gDebug = Services.prefs.getBoolPref(kPrefCustomizationDebug, false);
   let consoleOptions = {
     maxLogLevel: gDebug ? "all" : "log",
     prefix: "CustomizeMode",
   };
   return new scope.ConsoleAPI(consoleOptions);
 });
 
-const DEFAULT_THEME_ID = "default-theme@mozilla.org";
-const LIGHT_THEME_ID = "firefox-compact-light@mozilla.org";
-const DARK_THEME_ID = "firefox-compact-dark@mozilla.org";
-const ALPENGLOW_THEME_ID = "firefox-alpenglow@mozilla.org";
-
-const _defaultImportantThemes = [
-  DEFAULT_THEME_ID,
-  LIGHT_THEME_ID,
-  DARK_THEME_ID,
-  ALPENGLOW_THEME_ID,
-];
-
 var gDraggingInToolbars;
 
 var gTab;
 
 function closeGlobalTab() {
   let win = gTab.ownerGlobal;
   if (win.gBrowser.browsers.length == 1) {
     win.BrowserOpenTab();
@@ -1406,18 +1394,17 @@ CustomizeMode.prototype = {
       aReason == CustomizableUI.REASON_AREA_UNREGISTERED
     ) {
       this._unwrapItemsInArea(aContainer);
       this._removeDragHandlers(aContainer);
       this.areas.delete(aContainer);
     }
   },
 
-  openAddonsManagerThemes(aEvent) {
-    aEvent.target.parentNode.parentNode.hidePopup();
+  openAddonsManagerThemes() {
     AMTelemetry.recordLinkEvent({ object: "customize", value: "manageThemes" });
     this.window.BrowserOpenAddonsMgr("addons://list/theme");
   },
 
   getMoreThemes(aEvent) {
     aEvent.target.parentNode.parentNode.hidePopup();
     AMTelemetry.recordLinkEvent({ object: "customize", value: "getThemes" });
     let getMoreURL = Services.urlFormatter.formatURLPref(
@@ -1537,110 +1524,16 @@ CustomizeMode.prototype = {
   updateAutoTouchMode(checked) {
     Services.prefs.setBoolPref("browser.touchmode.auto", checked);
     // Re-render the menu items since the active mode might have
     // change because of this.
     this.onUIDensityMenuShowing();
     this._onUIChange();
   },
 
-  async onThemesMenuShowing(aEvent) {
-    const MAX_THEME_COUNT = 6;
-
-    this._clearThemesMenu(aEvent.target);
-
-    let onThemeSelected = panel => {
-      // This causes us to call _onUIChange when the LWT actually changes,
-      // so the restore defaults / undo reset button is updated correctly.
-      this._nextThemeChangeUserTriggered = true;
-      panel.hidePopup();
-    };
-
-    let doc = this.document;
-
-    function buildToolbarButton(aTheme) {
-      let tbb = doc.createXULElement("toolbarbutton");
-      tbb.theme = aTheme;
-      tbb.setAttribute("label", aTheme.name);
-      tbb.setAttribute(
-        "image",
-        aTheme.iconURL || "chrome://mozapps/skin/extensions/themeGeneric.svg"
-      );
-      if (aTheme.description) {
-        tbb.setAttribute("tooltiptext", aTheme.description);
-      }
-      tbb.setAttribute("tabindex", "0");
-      tbb.classList.add("customization-lwtheme-menu-theme");
-      let isActive = aTheme.isActive;
-      tbb.setAttribute("aria-checked", isActive);
-      tbb.setAttribute("role", "menuitemradio");
-      if (isActive) {
-        tbb.setAttribute("active", "true");
-      }
-
-      return tbb;
-    }
-
-    let themes = await AddonManager.getAddonsByTypes(["theme"]);
-    let currentTheme = themes.find(theme => theme.isActive);
-
-    // Move the current theme (if any) and the default themes to the start:
-    let importantThemes = new Set(_defaultImportantThemes);
-    if (currentTheme) {
-      importantThemes.add(currentTheme.id);
-    }
-    let importantList = [];
-    for (let importantTheme of importantThemes) {
-      importantList.push(
-        ...themes.splice(
-          themes.findIndex(theme => theme.id == importantTheme),
-          1
-        )
-      );
-    }
-
-    // Sort the remainder alphabetically:
-    themes.sort((a, b) => a.name.localeCompare(b.name));
-    themes = importantList.concat(themes);
-
-    if (themes.length > MAX_THEME_COUNT) {
-      themes.length = MAX_THEME_COUNT;
-    }
-
-    let footer = doc.getElementById("customization-lwtheme-menu-footer");
-    let panel = footer.parentNode;
-    for (let theme of themes) {
-      let button = buildToolbarButton(theme);
-      button.addEventListener("command", async () => {
-        onThemeSelected(panel);
-        await button.theme.enable();
-        AMTelemetry.recordActionEvent({
-          object: "customize",
-          action: "enable",
-          extra: { type: "theme", addonId: theme.id },
-        });
-      });
-      panel.insertBefore(button, footer);
-    }
-  },
-
-  _clearThemesMenu(panel) {
-    let footer = this.$("customization-lwtheme-menu-footer");
-    let element = footer;
-    while (
-      element.previousElementSibling &&
-      element.previousElementSibling.localName == "toolbarbutton"
-    ) {
-      element.previousElementSibling.remove();
-    }
-
-    // Workaround for bug 1059934
-    panel.removeAttribute("height");
-  },
-
   _onUIChange() {
     this._changed = true;
     if (!this.resetting) {
       this._updateResetButton();
       this._updateUndoResetButton();
       this._updateEmptyPaletteNotice();
     }
     CustomizableUI.dispatchToolboxEvent("customizationchange");
--- a/browser/components/customizableui/content/customizeMode.inc.xhtml
+++ b/browser/components/customizableui/content/customizeMode.inc.xhtml
@@ -27,36 +27,16 @@
 <hbox id="customization-footer">
 <checkbox id="customization-titlebar-visibility-checkbox" class="customizationmode-checkbox"
 # NB: because oncommand fires after click, by the time we've fired, the checkbox binding
 #    will already have switched the button's state, so this is correct:
           oncommand="gCustomizeMode.toggleTitlebar(this.checked)" data-l10n-id="customize-mode-titlebar"/>
 <button id="customization-toolbar-visibility-button" class="customizationmode-button" type="menu" data-l10n-id="customize-mode-toolbars">
   <menupopup id="customization-toolbar-menu" onpopupshowing="onViewToolbarsPopupShowing(event)"/>
 </button>
-<button id="customization-lwtheme-button" data-l10n-id="customize-mode-lwthemes" class="customizationmode-button" type="menu">
-  <panel type="arrow" id="customization-lwtheme-menu"
-         orient="vertical"
-         onpopupshowing="gCustomizeMode.onThemesMenuShowing(event);"
-         position="topleft bottomleft"
-         flip="none"
-         role="menu">
-    <label id="customization-lwtheme-menu-header" data-l10n-id="customize-mode-lwthemes-my-themes"/>
-    <hbox id="customization-lwtheme-menu-footer">
-      <toolbarbutton class="customization-lwtheme-menu-footeritem"
-                     data-l10n-id="customize-mode-lwthemes-menu-manage"
-                     tabindex="0"
-                     oncommand="gCustomizeMode.openAddonsManagerThemes(event);"/>
-      <toolbarbutton class="customization-lwtheme-menu-footeritem"
-                     data-l10n-id="customize-mode-lwthemes-menu-get-more"
-                     tabindex="0"
-                     oncommand="gCustomizeMode.getMoreThemes(event);"/>
-    </hbox>
-  </panel>
-</button>
 <button id="customization-uidensity-button"
         data-l10n-id="customize-mode-uidensity"
         class="customizationmode-button"
         type="menu"
         hidden="true">
   <panel type="arrow" id="customization-uidensity-menu"
          orient="vertical"
          onpopupshowing="gCustomizeMode.onUIDensityMenuShowing();"
@@ -98,16 +78,20 @@
     <spacer hidden="true" id="customization-uidensity-touch-spacer"/>
     <checkbox id="customization-uidensity-autotouchmode-checkbox"
               hidden="true"
               data-l10n-id="customize-mode-uidensity-auto-touch-mode-checkbox"
               oncommand="gCustomizeMode.updateAutoTouchMode(this.checked)"/>
 #endif
   </panel>
 </button>
+<label is="text-link"
+       id="customization-lwtheme-link"
+       data-l10n-id="customize-mode-lwthemes-link"
+       onclick="gCustomizeMode.openAddonsManagerThemes();" />
 
 <button id="whimsy-button"
         type="checkbox"
         class="customizationmode-button"
         oncommand="gCustomizeMode.togglePong(this.checked);"
         hidden="true"/>
 
 <spacer id="customization-footer-spacer"/>
--- a/browser/components/customizableui/test/browser.ini
+++ b/browser/components/customizableui/test/browser.ini
@@ -116,18 +116,16 @@ skip-if = verify
 [browser_989751_subviewbutton_class.js]
 [browser_992747_toggle_noncustomizable_toolbar.js]
 [browser_993322_widget_notoolbar.js]
 skip-if = verify
 [browser_995164_registerArea_during_customize_mode.js]
 [browser_996364_registerArea_different_properties.js]
 [browser_996635_remove_non_widgets.js]
 [browser_1003588_no_specials_in_panel.js]
-[browser_1007336_lwthemes_in_customize_mode.js]
-skip-if = os == "linux" # crashing on Linux due to bug 1271683
 [browser_1008559_anchor_undo_restore.js]
 [browser_1042100_default_placements_update.js]
 [browser_1058573_showToolbarsDropdown.js]
 [browser_1087303_button_fullscreen.js]
 tags = fullscreen
 skip-if = os == "mac"
 [browser_1087303_button_preferences.js]
 [browser_1089591_still_customizable_after_reset.js]
@@ -140,16 +138,17 @@ skip-if = verify
 [browser_allow_dragging_removable_false.js]
 [browser_bookmarks_toolbar_collapsed_restore_default.js]
 [browser_bookmarks_toolbar_shown_newtab.js]
 [browser_bootstrapped_custom_toolbar.js]
 [browser_ctrl_click_panel_opening.js]
 [browser_currentset_post_reset.js]
 [browser_customizemode_contextmenu_menubuttonstate.js]
 skip-if = os == "win" && bits == 64 # 1526429
+[browser_customizemode_lwthemes.js]
 [browser_customizemode_uidensity.js]
 [browser_disable_commands_customize.js]
 [browser_drag_outside_palette.js]
 [browser_exit_background_customize_mode.js]
 https_first_disabled = true
 [browser_flexible_space_area.js]
 [browser_help_panel_cloning.js]
 [browser_history_after_appMenu.js]
--- a/browser/components/customizableui/test/browser_970511_undo_restore_default.js
+++ b/browser/components/customizableui/test/browser_970511_undo_restore_default.js
@@ -1,80 +1,95 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
 requestLongerTimeout(2);
 
-// Restoring default should reset theme and show an "undo" option which undoes the restoring operation.
+// Restoring default should reset density and show an "undo" option which undoes
+// the restoring operation.
 add_task(async function() {
+  await SpecialPowers.pushPrefEnv({
+    set: [["browser.compactmode.show", true]],
+  });
   let stopReloadButtonId = "stop-reload-button";
   CustomizableUI.removeWidgetFromArea(stopReloadButtonId);
   await startCustomizing();
   ok(!CustomizableUI.inDefaultState, "Not in default state to begin with");
   is(
     CustomizableUI.getPlacementOfWidget(stopReloadButtonId),
     null,
     "Stop/reload button is in palette"
   );
   let undoResetButton = document.getElementById(
     "customization-undo-reset-button"
   );
   is(undoResetButton.hidden, true, "The undo button is hidden before reset");
 
-  let themesButton = document.getElementById("customization-lwtheme-button");
-  let popup = document.getElementById("customization-lwtheme-menu");
+  let densityButton = document.getElementById("customization-uidensity-button");
+  let popup = document.getElementById("customization-uidensity-menu");
   let popupShownPromise = popupShown(popup);
-  EventUtils.synthesizeMouseAtCenter(themesButton, {});
-  info("Clicked on themes button");
+  EventUtils.synthesizeMouseAtCenter(densityButton, {});
+  info("Clicked on density button");
   await popupShownPromise;
 
-  let header = document.getElementById("customization-lwtheme-menu-header");
-  let firstLWTheme = header.nextElementSibling.nextElementSibling;
-  let firstLWThemeId = firstLWTheme.theme.id;
-  let themeChangedPromise = promiseObserverNotified(
-    "lightweight-theme-styling-update"
+  let compactModeItem = document.getElementById(
+    "customization-uidensity-menuitem-compact"
   );
-  firstLWTheme.doCommand();
-  info("Clicked on first theme");
-  await themeChangedPromise;
+  let win = document.getElementById("main-window");
+  let densityChangedPromise = new Promise(resolve => {
+    let observer = new MutationObserver(() => {
+      if (win.getAttribute("uidensity") == "compact") {
+        resolve();
+        observer.disconnect();
+      }
+    });
+    observer.observe(win, {
+      attributes: true,
+      attributeFilter: ["uidensity"],
+    });
+  });
 
-  let theme = await AddonManager.getAddonByID(firstLWThemeId);
-  is(theme.isActive, true, "Theme changed to first option");
+  compactModeItem.doCommand();
+  info("Clicked on compact density");
+  await densityChangedPromise;
 
   await gCustomizeMode.reset();
 
   ok(CustomizableUI.inDefaultState, "In default state after reset");
   is(undoResetButton.hidden, false, "The undo button is visible after reset");
-  theme = await AddonManager.getAddonByID("default-theme@mozilla.org");
-  is(theme.isActive, true, "Theme reset to default");
+  is(
+    win.hasAttribute("uidensity"),
+    false,
+    "The window has been restored to normal density."
+  );
 
   await gCustomizeMode.undoReset();
 
-  theme = await AddonManager.getAddonByID(firstLWThemeId);
   is(
-    theme.isActive,
-    true,
-    "Theme has been reset from default to original choice"
+    win.getAttribute("uidensity"),
+    "compact",
+    "Density has been reset to compact."
   );
   ok(!CustomizableUI.inDefaultState, "Not in default state after undo-reset");
   is(
     undoResetButton.hidden,
     true,
     "The undo button is hidden after clicking on the undo button"
   );
   is(
     CustomizableUI.getPlacementOfWidget(stopReloadButtonId),
     null,
     "Stop/reload button is in palette"
   );
 
   await gCustomizeMode.reset();
+  await SpecialPowers.popPrefEnv();
 });
 
 // Performing an action after a reset will hide the undo button.
 add_task(async function action_after_reset_hides_undo() {
   let stopReloadButtonId = "stop-reload-button";
   CustomizableUI.removeWidgetFromArea(stopReloadButtonId);
   ok(!CustomizableUI.inDefaultState, "Not in default state to begin with");
   is(
rename from browser/components/customizableui/test/browser_1007336_lwthemes_in_customize_mode.js
rename to browser/components/customizableui/test/browser_customizemode_lwthemes.js
--- a/browser/components/customizableui/test/browser_1007336_lwthemes_in_customize_mode.js
+++ b/browser/components/customizableui/test/browser_customizemode_lwthemes.js
@@ -1,278 +1,25 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
-const DEFAULT_THEME_ID = "default-theme@mozilla.org";
-const LIGHT_THEME_ID = "firefox-compact-light@mozilla.org";
-const DARK_THEME_ID = "firefox-compact-dark@mozilla.org";
-const ALPENGLOW_THEME_ID = "firefox-alpenglow@mozilla.org";
-
-const MAX_THEME_COUNT = 6; // Not exposed from CustomizeMode.jsm
-
-const { _defaultImportantThemes } = ChromeUtils.import(
-  "resource:///modules/CustomizeMode.jsm"
-);
-
-async function installTheme(id) {
-  let extension = ExtensionTestUtils.loadExtension({
-    manifest: {
-      applications: { gecko: { id } },
-      manifest_version: 2,
-      name: "Theme " + id,
-      description: "wow. such theme.",
-      author: "Pixel Pusher",
-      version: "1",
-      theme: {},
-    },
-    useAddonManager: "temporary",
-  });
-  await extension.startup();
-  return extension;
-}
-
 add_task(async function() {
   await startCustomizing();
-  // Check restore defaults button is disabled.
-  ok(
-    document.getElementById("customization-reset-button").disabled,
-    "Reset button should start out disabled"
-  );
-
-  let themesButton = document.getElementById("customization-lwtheme-button");
-  let themesButtonIcon = themesButton.icon;
-  let iconURL = themesButtonIcon.style.backgroundImage;
-  // If we've run other tests before, we might have set the image to the
-  // default theme's icon explicitly, otherwise it might be empty, in which
-  // case the icon is determined by CSS (which will be the default
-  // theme's icon).
-  if (iconURL) {
-    ok(
-      /default/i.test(themesButtonIcon.style.backgroundImage),
-      `Button should show default theme thumbnail - was: "${iconURL}"`
-    );
-  } else {
-    is(
-      iconURL,
-      "",
-      `Button should show default theme thumbnail (empty string) - was: "${iconURL}"`
-    );
-  }
-  let popup = document.getElementById("customization-lwtheme-menu");
-
-  let popupShownPromise = popupShown(popup);
-  EventUtils.synthesizeMouseAtCenter(themesButton, {});
-  info("Clicked on themes button");
-  await popupShownPromise;
-
-  // close current tab and re-open Customize menu to confirm correct number of Themes
-  await endCustomizing();
-  info("Exited customize mode");
-  await startCustomizing();
-  info("Started customizing a second time");
-  popupShownPromise = popupShown(popup);
-  EventUtils.synthesizeMouseAtCenter(themesButton, {});
-  info("Clicked on themes button a second time");
-  await popupShownPromise;
-
-  let header = document.getElementById("customization-lwtheme-menu-header");
-  let footer = document.getElementById("customization-lwtheme-menu-footer");
-  let menu = document.getElementById("customization-lwtheme-menu");
-  let themeMenuItems = menu.querySelectorAll(
-    "toolbarbutton.customization-lwtheme-menu-theme"
-  );
+  // Find the footer buttons to test.
+  let manageLink = document.querySelector("#customization-lwtheme-link");
 
-  is(
-    themeMenuItems.length,
-    _defaultImportantThemes.length,
-    "There should only be four themes (default, light, dark, alpenglow) in the 'My Themes' section by default"
-  );
-  // Note that we use our own theme ID constants in the
-  // following tests because we want to test things are
-  // displayed in the proper order, not just in the order
-  // that constants in the code happens to be in.
-  is(
-    themeMenuItems[0].theme.id,
-    DEFAULT_THEME_ID,
-    "The first theme should be the default theme"
-  );
-  is(
-    themeMenuItems[1].theme.id,
-    LIGHT_THEME_ID,
-    "The second theme should be the light theme"
-  );
-  is(
-    themeMenuItems[2].theme.id,
-    DARK_THEME_ID,
-    "The third theme should be the dark theme"
-  );
-  is(
-    themeMenuItems[3].theme.id,
-    ALPENGLOW_THEME_ID,
-    "The fourth theme should be the alpenglow theme"
-  );
-
-  let themeChangedPromise = promiseObserverNotified(
-    "lightweight-theme-styling-update"
-  );
-  header.nextElementSibling.nextElementSibling.doCommand(); // Select light theme
-  info("Clicked on light theme");
-  await themeChangedPromise;
-
-  let button = document.getElementById("customization-reset-button");
-  await TestUtils.waitForCondition(() => !button.disabled);
-
-  // Check restore defaults button is enabled.
-  ok(!button.disabled, "Reset button should not be disabled anymore");
-  ok(
-    themesButtonIcon.style.backgroundImage === "",
-    `Button should still show no theme - was: "${themesButtonIcon.style.backgroundImage}"`
-  );
-
-  popupShownPromise = popupShown(popup);
-  EventUtils.synthesizeMouseAtCenter(themesButton, {});
-  info("Clicked on themes button a third time");
-  await popupShownPromise;
-
-  let activeThemes = popup.querySelectorAll(
-    "toolbarbutton.customization-lwtheme-menu-theme[active]"
-  );
-  is(activeThemes.length, 1, "Exactly 1 theme should be selected");
-  if (activeThemes.length) {
-    is(
-      activeThemes[0].theme.id,
-      LIGHT_THEME_ID,
-      "Light theme should be selected"
-    );
-  }
-  popup.hidePopup();
+  let waitForNewTab = BrowserTestUtils.waitForNewTab(gBrowser, "about:addons");
+  manageLink.click();
+  let addonsTab = await waitForNewTab;
 
-  // Install 5 themes:
-  let addons = [];
-  for (let n = 1; n <= 5; n++) {
-    addons.push(await installTheme("my-theme-" + n + "@example.com"));
-  }
-  addons = await Promise.all(addons);
-
-  ok(
-    !themesButtonIcon.style.backgroundImage,
-    `Button should show fallback theme thumbnail - was: "${themesButtonIcon.style.backgroundImage}"`
-  );
-
-  popupShownPromise = popupShown(popup);
-  EventUtils.synthesizeMouseAtCenter(themesButton, {});
-  info("Clicked on themes button a fourth time");
-  await popupShownPromise;
-
-  activeThemes = popup.querySelectorAll(
-    "toolbarbutton.customization-lwtheme-menu-theme[active]"
-  );
-  is(activeThemes.length, 1, "Exactly 1 theme should be selected");
-  if (activeThemes.length) {
-    is(
-      activeThemes[0].theme.id,
-      "my-theme-5@example.com",
-      "Last installed theme should be selected"
-    );
-  }
+  is(gBrowser.currentURI.spec, "about:addons", "Manage opened about:addons");
+  BrowserTestUtils.removeTab(addonsTab);
 
-  let firstLWTheme = footer.previousElementSibling;
-  let firstLWThemeId = firstLWTheme.theme.id;
-  themeChangedPromise = promiseObserverNotified(
-    "lightweight-theme-styling-update"
-  );
-  firstLWTheme.doCommand();
-  info("Clicked on first theme");
-  await themeChangedPromise;
-
-  await new Promise(executeSoon);
-
-  popupShownPromise = popupShown(popup);
-  EventUtils.synthesizeMouseAtCenter(themesButton, {});
-  info("Clicked on themes button");
-  await popupShownPromise;
-
-  activeThemes = popup.querySelectorAll(
-    "toolbarbutton.customization-lwtheme-menu-theme[active]"
-  );
-  is(activeThemes.length, 1, "Exactly 1 theme should be selected");
-  if (activeThemes.length) {
-    is(
-      activeThemes[0].theme.id,
-      firstLWThemeId,
-      "First theme should be selected"
-    );
-  }
-
-  is(
-    header.nextElementSibling.theme.id,
-    DEFAULT_THEME_ID,
-    "The first theme should be the Default theme"
+  // Wait for customize mode to be re-entered now that the customize tab is
+  // active. This is needed for endCustomizing() to work properly.
+  await TestUtils.waitForCondition(
+    () => document.documentElement.getAttribute("customizing") == "true"
   );
-  let themeCount = 0;
-  let iterNode = header;
-  while (iterNode.nextElementSibling && iterNode.nextElementSibling.theme) {
-    themeCount++;
-    iterNode = iterNode.nextElementSibling;
-  }
-  is(
-    themeCount,
-    MAX_THEME_COUNT,
-    "There should be the max number of themes in the 'My Themes' section"
-  );
-
-  let defaultTheme = header.nextElementSibling;
-  defaultTheme.doCommand();
-  await new Promise(SimpleTest.executeSoon);
-
-  // ensure current theme isn't set to "Default"
-  popupShownPromise = popupShown(popup);
-  EventUtils.synthesizeMouseAtCenter(themesButton, {});
-  info("Clicked on themes button a sixth time");
-  await popupShownPromise;
-
-  // check that "Restore Defaults" button resets theme
-  await gCustomizeMode.reset();
-
-  defaultTheme = await AddonManager.getAddonByID(DEFAULT_THEME_ID);
-  is(defaultTheme.isActive, true, "Current theme reset to default");
-
-  await endCustomizing();
-  await startCustomizing();
-  popupShownPromise = popupShown(popup);
-  EventUtils.synthesizeMouseAtCenter(themesButton, {});
-  info("Clicked on themes button a seventh time");
-  await popupShownPromise;
-  header = document.getElementById("customization-lwtheme-menu-header");
-  is(header.hidden, false, "Header should never be hidden");
-  let themeNode = header.nextElementSibling;
-  is(
-    themeNode.theme.id,
-    DEFAULT_THEME_ID,
-    "The first theme should be the Default theme"
-  );
-  is(themeNode.hidden, false, "The default theme should never be hidden");
-
-  themeNode = themeNode.nextElementSibling;
-  is(
-    themeNode.theme.id,
-    LIGHT_THEME_ID,
-    "The second theme should be the Light theme"
-  );
-  is(themeNode.hidden, false, "The light theme should never be hidden");
-
-  themeNode = themeNode.nextElementSibling;
-  is(
-    themeNode.theme.id,
-    DARK_THEME_ID,
-    "The third theme should be the Dark theme"
-  );
-  is(themeNode.hidden, false, "The dark theme should never be hidden");
-
-  await Promise.all(addons.map(a => a.unload()));
-});
-
-add_task(async function asyncCleanup() {
   await endCustomizing();
 });
--- a/browser/components/customizableui/test/browser_lwt_telemetry.js
+++ b/browser/components/customizableui/test/browser_lwt_telemetry.js
@@ -10,35 +10,26 @@ add_task(async function testCustomize() 
   // Reset the theme prefs to ensure they haven't been messed with.
   await SpecialPowers.pushPrefEnv({
     set: [["lightweightThemes.getMoreURL", getMoreURL]],
   });
 
   await startCustomizing();
 
   // Find the footer buttons to test.
-  let footerRow = document.getElementById("customization-lwtheme-menu-footer");
-  let [manageButton, getMoreButton] = footerRow.childNodes;
+  let manageLink = document.querySelector("#customization-lwtheme-link");
 
-  // Check the manage button, it should open about:addons.
+  // Check the manage themes button, it should open about:addons.
   let waitForNewTab = BrowserTestUtils.waitForNewTab(gBrowser, "about:addons");
-  manageButton.click();
+  manageLink.click();
   let addonsTab = await waitForNewTab;
 
   is(gBrowser.currentURI.spec, "about:addons", "Manage opened about:addons");
   BrowserTestUtils.removeTab(addonsTab);
 
-  // Check the get more button, we mocked it to open getMoreURL.
-  waitForNewTab = BrowserTestUtils.waitForNewTab(gBrowser, getMoreURL);
-  getMoreButton.click();
-  addonsTab = await waitForNewTab;
-
-  is(gBrowser.currentURI.spec, getMoreURL, "Get more opened AMO");
-  BrowserTestUtils.removeTab(addonsTab);
-
   let snapshot = Services.telemetry.snapshotEvents(
     Ci.nsITelemetry.DATASET_PRERELEASE_CHANNELS,
     true
   );
 
   // Make sure we got some data.
   ok(
     snapshot.parent && !!snapshot.parent.length,
@@ -51,20 +42,17 @@ add_task(async function testCustomize() 
       ([timestamp, category, method, object]) =>
         category == "addonsManager" && object == "customize"
     )
     .map(relatedEvent => relatedEvent.slice(2, 6));
 
   // Events are now [method, object, value, extra] as expected.
   Assert.deepEqual(
     relatedEvents,
-    [
-      ["link", "customize", "manageThemes"],
-      ["link", "customize", "getThemes"],
-    ],
+    [["link", "customize", "manageThemes"]],
     "The events are recorded correctly"
   );
 
   // Wait for customize mode to be re-entered now that the customize tab is
   // active. This is needed for endCustomizing() to work properly.
   await TestUtils.waitForCondition(
     () => document.documentElement.getAttribute("customizing") == "true"
   );
--- a/browser/extensions/webcompat/data/injections.js
+++ b/browser/extensions/webcompat/data/injections.js
@@ -371,16 +371,36 @@ const AVAILABLE_INJECTIONS = [
       css: [
         {
           file: "injections/css/bug1704653-tsky.in-clear-float.css",
         },
       ],
     },
   },
   {
+    id: "bug1731825",
+    platform: "desktop",
+    domain: "Office 365 email handling prompt",
+    bug: "1731825",
+    contentScripts: {
+      matches: [
+        "*://*.live.com/*",
+        "*://*.office.com/*",
+        "*://*.sharepoint.com/*",
+      ],
+      js: [
+        {
+          file:
+            "injections/js/bug1731825-office365-email-handling-prompt-autohide.js",
+        },
+      ],
+      allFrames: true,
+    },
+  },
+  {
     id: "bug1707795",
     platform: "desktop",
     domain: "Office Excel spreadsheets",
     bug: "1707795",
     contentScripts: {
       matches: [
         "*://*.live.com/*",
         "*://*.office.com/*",
new file mode 100644
--- /dev/null
+++ b/browser/extensions/webcompat/injections/js/bug1731825-office365-email-handling-prompt-autohide.js
@@ -0,0 +1,32 @@
+"use strict";
+
+/**
+ * Bug 1731825 - Office 365 email handling prompt autohide
+ *
+ * This site patch prevents the notification bar on Office 365
+ * apps from popping up on each page-load, offering to handle
+ * email with Outlook.
+ */
+
+/* globals exportFunction */
+
+const warning =
+  "Office 365 Outlook email handling prompt has been hidden. See https://bugzilla.mozilla.org/show_bug.cgi?id=1731825 for details.";
+
+const localStorageKey = "mailProtocolHandlerAlreadyOffered";
+
+const nav = navigator.wrappedJSObject;
+const { registerProtocolHandler } = nav;
+const { localStorage } = window.wrappedJSObject;
+
+Object.defineProperty(navigator.wrappedJSObject, "registerProtocolHandler", {
+  value: exportFunction(function(scheme, url, title) {
+    if (localStorage.getItem(localStorageKey)) {
+      console.info(warning);
+      return undefined;
+    }
+    registerProtocolHandler.call(nav, scheme, url, title);
+    localStorage.setItem(localStorageKey, true);
+    return undefined;
+  }, window),
+});
--- a/browser/extensions/webcompat/manifest.json
+++ b/browser/extensions/webcompat/manifest.json
@@ -1,13 +1,13 @@
 {
   "manifest_version": 2,
   "name": "Web Compatibility Interventions",
   "description": "Urgent post-release fixes for web compatibility.",
-  "version": "26.2.0",
+  "version": "26.3.0",
   "applications": {
     "gecko": {
       "id": "webcompat@mozilla.org",
       "strict_min_version": "59.0b5"
     }
   },
 
   "experiment_apis": {
--- a/browser/extensions/webcompat/moz.build
+++ b/browser/extensions/webcompat/moz.build
@@ -73,16 +73,17 @@ FINAL_TARGET_FILES.features["webcompat@m
     "injections/js/bug1579159-m.tailieu.vn-pdfjs-worker-disable.js",
     "injections/js/bug1605611-maps.google.com-directions-time.js",
     "injections/js/bug1610358-pcloud.com-appVersion-change.js",
     "injections/js/bug1631811-datastudio.google.com-indexedDB.js",
     "injections/js/bug1711082-m.aliexpress.com-undisable-search.js",
     "injections/js/bug1714612-www.rfi.it-outertext.js",
     "injections/js/bug1724764-amextravel.com-window-print.js",
     "injections/js/bug1724868-news.yahoo.co.jp-ua-override.js",
+    "injections/js/bug1731825-office365-email-handling-prompt-autohide.js",
 ]
 
 FINAL_TARGET_FILES.features["webcompat@mozilla.org"]["shims"] += [
     "shims/addthis-angular.js",
     "shims/adform.js",
     "shims/adnexus-prebid.js",
     "shims/adsafeprotected-ima.js",
     "shims/apstag.js",
--- a/browser/locales/en-US/browser/customizeMode.ftl
+++ b/browser/locales/en-US/browser/customizeMode.ftl
@@ -5,43 +5,34 @@
 customize-mode-restore-defaults =
     .label = Restore Defaults
 customize-mode-menu-and-toolbars-header = Drag your favorite items into the toolbar or overflow menu.
 customize-mode-overflow-list-title = Overflow Menu
 customize-mode-uidensity =
     .label = Density
 customize-mode-done =
     .label = Done
-customize-mode-lwthemes-menu-manage =
-    .label = Manage
-    .accesskey = M
 customize-mode-toolbars =
     .label = Toolbars
 customize-mode-titlebar =
     .label = Title Bar
 customize-mode-uidensity-menu-touch =
     .label = Touch
     .accesskey = T
     .tooltiptext = Touch
 customize-mode-uidensity-auto-touch-mode-checkbox =
     .label = Use Touch for Tablet Mode
-customize-mode-lwthemes =
-    .label = Themes
 customize-mode-overflow-list-description = Drag and drop items here to keep them within reach but out of your toolbar…
 customize-mode-uidensity-menu-normal =
     .label = Normal
     .accesskey = N
     .tooltiptext = Normal
 customize-mode-uidensity-menu-compact-unsupported =
     .label = Compact (not supported)
     .accesskey = C
     .tooltiptext = Compact (not supported)
-customize-mode-lwthemes-menu-get-more =
-    .label = Get More Themes
-    .accesskey = G
 customize-mode-undo-cmd =
     .label = Undo
-customize-mode-lwthemes-my-themes =
-    .value = My Themes
+customize-mode-lwthemes-link = Manage Themes
 customize-mode-touchbar-cmd =
     .label = Customize Touch Bar…
 customize-mode-downloads-button-autohide =
     .label = Hide button when empty
--- a/browser/modules/PermissionUI.jsm
+++ b/browser/modules/PermissionUI.jsm
@@ -161,19 +161,19 @@ var PermissionPromptPrototype = {
    * infrastructure such as temporary permissions. permissionKey should
    * still return a valid name in those cases for that integration to work.
    */
   get usePermissionManager() {
     return true;
   },
 
   /**
-   * These are the options that will be passed to the
-   * PopupNotification when it is shown. See the documentation
-   * for PopupNotification for more details.
+   * These are the options that will be passed to the PopupNotification when it
+   * is shown. See the documentation of `PopupNotifications_show` in
+   * PopupNotifications.jsm for details.
    *
    * Note that prompt() will automatically set displayURI to
    * be the URI of the requesting pricipal, unless the displayURI is exactly
    * set to false.
    */
   get popupOptions() {
     return {};
   },
@@ -204,17 +204,17 @@ var PermissionPromptPrototype = {
    * You must return a unique ID string here, for which PopupNotification
    * will then create a <xul:popupnotification> node with the ID
    * "<notificationID>-notification".
    *
    * If there's a custom <xul:popupnotification> you're hoping to show,
    * then you need to make sure its ID has the "-notification" suffix,
    * and then return the prefix here.
    *
-   * See PopupNotification.jsm for more details.
+   * See PopupNotifications.jsm for more details.
    *
    * @return {string}
    *         The unique ID that will be used to as the
    *         "<unique ID>-notification" ID for the <xul:popupnotification>
    *         to use or create.
    */
   get notificationID() {
     throw new Error("Not implemented.");
@@ -225,19 +225,18 @@ var PermissionPromptPrototype = {
    *
    * @return {string}
    */
   get anchorID() {
     return "default-notification-icon";
   },
 
   /**
-   * The message to show to the user in the PopupNotification. A string
-   * with "<>" as a placeholder that is usually replaced by an addon name
-   * or a host name, formatted in bold, to describe the permission that is being requested.
+   * The message to show to the user in the PopupNotification, see
+   * `PopupNotifications_show` in PopupNotifications.jsm.
    *
    * Subclasses must override this.
    *
    * @return {string}
    */
   get message() {
     throw new Error("Not implemented.");
   },
--- a/browser/themes/shared/customizableui/customizeMode.inc.css
+++ b/browser/themes/shared/customizableui/customizeMode.inc.css
@@ -138,16 +138,21 @@
 }
 
 .customizationmode-button[disabled="true"] {
   opacity: .5;
 }
 }
 %endif /* defined(XP_MACOSX) || defined(XP_WIN) */
 
+#customization-lwtheme-link {
+  display: flex;
+  align-items: center;
+}
+
 #widget-overflow-fixed-list > toolbarpaletteitem:not([notransition])[place="menu-panel"],
 toolbarpaletteitem:not([notransition])[place="toolbar"] {
   transition: border-width var(--drag-drop-transition-duration) ease-in-out;
 }
 
 toolbarpaletteitem[mousedown] {
   cursor: -moz-grabbing;
 }
--- a/devtools/client/debugger/src/client/firefox/commands.js
+++ b/devtools/client/debugger/src/client/firefox/commands.js
@@ -329,20 +329,28 @@ async function blackBox(sourceActor, isB
 
 async function setSkipPausing(shouldSkip) {
   await commands.threadConfigurationCommand.updateConfiguration({
     skipBreakpoints: shouldSkip,
   });
 }
 
 async function setEventListenerBreakpoints(ids) {
-  return forEachThread(thread => thread.setActiveEventBreakpoints(ids));
+  // @backward-compat { version 94 } The `event-breakpoints` trait check is no longer needed, but
+  // keep the check for target watcher support to fallback for unsupported targets (e.g webextensions)
+  const hasWatcherSupport = commands.targetCommand.hasTargetWatcherSupport(
+    "event-breakpoints"
+  );
+  if (!hasWatcherSupport) {
+    return forEachThread(thread => thread.setActiveEventBreakpoints(ids));
+  }
+  const breakpointListFront = await commands.targetCommand.watcherFront.getBreakpointListActor();
+  await breakpointListFront.setActiveEventBreakpoints(ids);
 }
 
-// eslint-disable-next-line
 async function getEventListenerBreakpointTypes() {
   let categories;
   try {
     categories = await currentThreadFront().getAvailableEventBreakpoints();
 
     if (!Array.isArray(categories)) {
       // When connecting to older browser that had our placeholder
       // implementation of the 'getAvailableEventBreakpoints' endpoint, we
--- a/devtools/client/debugger/test/mochitest/browser.ini
+++ b/devtools/client/debugger/test/mochitest/browser.ini
@@ -213,16 +213,17 @@ skip-if =
 [browser_dbg-worker-scopes.js]
 skip-if =
   os == 'linux' && debug # Bug 1456013
   ccov # Bug 1456013
   os == 'linux' && asan # Bug 1559547
   os == 'mac' # Bug 1607321
   os == 'win' && os_version == '10.0' && bits == 64 # Bug 1607321
 [browser_dbg-event-handler.js]
+[browser_dbg-event-breakpoints-fission.js]
 [browser_dbg-event-breakpoints.js]
 [browser_dbg-eval-throw.js]
 [browser_dbg-sourceURL-breakpoint.js]
 [browser_dbg-old-breakpoint.js]
 skip-if = os == "linux" || os == "mac" #Bug 1644044
 [browser_dbg-idb-run-to-completion.js]
 [browser_dbg-inline-script-offset.js]
 [browser_dbg-scopes-duplicated.js]
new file mode 100644
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/browser_dbg-event-breakpoints-fission.js
@@ -0,0 +1,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/>. */
+
+ // Tests early event breakpoints and event breakpoints in a remote frame.
+
+add_task(async function () {
+    await pushPref(
+    "devtools.debugger.features.event-listeners-breakpoints",
+    true
+  );
+
+  const dbg = await initDebugger(
+    "doc-event-breakpoints-fission.html",
+    "event-breakpoints"
+  );
+
+  await selectSource(dbg, "event-breakpoints");
+  await waitForSelectedSource(dbg, "event-breakpoints");
+
+  await dbg.actions.addEventListenerBreakpoints([
+    "event.mouse.click",
+    "event.xhr.load",
+    "timer.timeout.set"
+  ]);
+
+  info("Assert early timeout event breakpoint gets hit");
+  const waitForReload = reloadBrowser();
+
+  await waitForPaused(dbg);
+  assertPauseLocation(dbg, 17, "doc-event-breakpoints-fission.html");
+  await resume(dbg);
+
+  await waitForReload;
+
+  info("Assert event breakpoints work in remote frame");
+  await invokeAndAssertBreakpoints(dbg);
+
+  info("reload the iframe")
+  await SpecialPowers.spawn(gBrowser.selectedBrowser, [], () =>
+    content.wrappedJSObject.reloadIframe()
+  );
+  info("Assert event breakpoints work in remote frame after reload");
+  await invokeAndAssertBreakpoints(dbg);
+});
+
+async function invokeAndAssertBreakpoints(dbg) {
+  invokeInTabRemoteFrame("clickHandler");
+  await waitForPaused(dbg);
+  assertPauseLocation(dbg, 12);
+  await resume(dbg);
+
+  invokeInTabRemoteFrame("xhrHandler");
+  await waitForPaused(dbg);
+  assertPauseLocation(dbg, 20);
+  await resume(dbg);
+}
+
+function assertPauseLocation(dbg, line, url = "event-breakpoints.js") {
+  const { location } = dbg.selectors.getVisibleSelectedFrame();
+  const selectedSource = dbg.selectors.getSelectedSourceWithContent();
+
+  is(location.sourceId, selectedSource.id, `Correct selected sourceId`);
+  ok(selectedSource.url.includes(url), "Correct url");
+  is(location.line, line, "Correct paused line");
+
+  assertPausedLocation(dbg);
+}
+
+async function invokeInTabRemoteFrame(fnc, ...args) {
+  info(`Invoking in tab remote frame: ${fnc}(${args.map(uneval).join(",")})`);
+  await SpecialPowers.spawn(gBrowser.selectedBrowser, [fnc, args], function (_fnc, _args) {
+    return SpecialPowers.spawn(
+      content.document.querySelector("iframe"),
+      [_fnc, _args],
+      (__fnc, __args) => content.wrappedJSObject[__fnc](...__args)
+    );
+  });
+}
+
new file mode 100644
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/examples/doc-event-breakpoints-fission.html
@@ -0,0 +1,23 @@
+<!-- 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/. -->
+<!DOCTYPE html>
+<html>
+  <head>
+    <meta charset="utf-8"/>
+    <title>Debugger event bp fission test page</title>
+    <script>
+      function reloadIframe() {
+        return new Promise(resolve => {
+          const iframe = document.querySelector("iframe");
+          iframe.addEventListener("load", () => resolve(), { once: true });
+          iframe.setAttribute("src", iframe.getAttribute("src"));
+        });
+      }
+        setTimeout(() => console.log("timeout fired"), 0)
+    </script>
+  </head>
+  <body>
+    <iframe src="http://example.org/browser/devtools/client/debugger/test/mochitest/examples/doc-event-breakpoints.html"></iframe>
+  </body>
+</html>
--- a/devtools/client/framework/moz.build
+++ b/devtools/client/framework/moz.build
@@ -1,19 +1,15 @@
 # -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
 # vim: set filetype=python:
 # 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/.
 
 BROWSER_CHROME_MANIFESTS += [
-    "test/allocations/browser_allocations_browser_console.ini",
-    "test/allocations/browser_allocations_reload.ini",
-    "test/allocations/browser_allocations_target.ini",
-    "test/allocations/browser_allocations_toolbox.ini",
     "test/browser-enable-popup-devtools-user.ini",
     "test/browser-enable-popup-new-user.ini",
     "test/browser-telemetry-startup.ini",
     "test/browser.ini",
     "test/metrics/browser_metrics.ini",
     "test/metrics/browser_metrics_debugger.ini",
     "test/metrics/browser_metrics_inspector.ini",
     "test/metrics/browser_metrics_netmonitor.ini",
@@ -21,16 +17,17 @@ BROWSER_CHROME_MANIFESTS += [
 ]
 XPCSHELL_TESTS_MANIFESTS += ["test/xpcshell/xpcshell.ini"]
 
 DIRS += [
     "actions",
     "browser-toolbox",
     "components",
     "reducers",
+    "test/allocations",
 ]
 
 DevToolsModules(
     "browser-menus.js",
     "descriptor-from-url.js",
     "devtools-browser.js",
     "devtools.js",
     "enable-devtools-popup.js",
--- a/devtools/client/framework/test/allocations/README.md
+++ b/devtools/client/framework/test/allocations/README.md
@@ -160,18 +160,16 @@ This doesn't tell us why the object is b
 
 ## Debug leaks via dominators
 
 This last feature might be the most powerful and isn't bound to DEBUG_DEVTOOLS_ALLOCATIONS.
 This is always enabled.
 Also, it requires to know which particular object is being leaked and also require to hack
 the codebase in order to pass a reference of the suspicious object to the test helper.
 
-/!\ For now, this API only works from the parent process.
-
 You can instruct the test helper to track a given object by doing this:
 ```
  1: // Let's say it is some code running from "my-module.js"
  2:
  3: // From a DevTools CommonJS module:
  4: const { track } = require("devtools/shared/test-helpers/tracked-objects.jsm");
  5: // From anything else, JSM, XPCOM module,...:
  6: const { track } = ChromeUtils.import("resource://devtools/shared/test-helpers/tracked-objects.jsm");
rename from devtools/client/framework/test/allocations/browser_allocations_reload.ini
rename to devtools/client/framework/test/allocations/browser_allocations_reload_debugger.ini
--- a/devtools/client/framework/test/allocations/browser_allocations_reload.ini
+++ b/devtools/client/framework/test/allocations/browser_allocations_reload_debugger.ini
@@ -4,10 +4,10 @@ subsuite = devtools
 support-files =
   !/devtools/shared/test-helpers/allocation-tracker.js
   head.js
   reloaded-page.html
   reloaded.png
 
 # Each metrics tests is loaded in a separate .ini file. This way the test is executed
 # individually, without any other test being executed before or after.
-[browser_allocations_reload.js]
+[browser_allocations_reload_debugger.js]
 skip-if = os != 'linux' || debug || asan # Results should be platform agnostic - only run on linux64-opt
rename from devtools/client/framework/test/allocations/browser_allocations_reload.js
rename to devtools/client/framework/test/allocations/browser_allocations_reload_debugger.js
--- a/devtools/client/framework/test/allocations/browser_allocations_reload.js
+++ b/devtools/client/framework/test/allocations/browser_allocations_reload_debugger.js
@@ -32,17 +32,17 @@ async function testScript(toolbox) {
 
   // eslint-disable-next-line mozilla/no-arbitrary-setTimeout
   await new Promise(resolve => setTimeout(resolve, 1000));
 }
 
 add_task(async function() {
   const tab = await addTab(TEST_URL);
   const toolbox = await gDevTools.showToolboxForTab(tab, {
-    toolId: "inspector",
+    toolId: "jsdebugger",
   });
 
   // Run the test scenario first before recording in order to load all the
   // modules. Otherwise they get reported as "still allocated" objects,
   // whereas we do expect them to be kept in memory as they are loaded via
   // the main DevTools loader, which keeps the module loaded until the
   // shutdown of Firefox
   await testScript(toolbox);
@@ -51,15 +51,15 @@ add_task(async function() {
     alsoRecordContentProcess: true,
   });
 
   // Now, run the test script. This time, we record this run.
   for (let i = 0; i < 10; i++) {
     await testScript(toolbox);
   }
 
-  await stopRecordingAllocations("reload", {
+  await stopRecordingAllocations("reload-debugger", {
     alsoRecordContentProcess: true,
   });
 
   await toolbox.destroy();
   gBrowser.removeTab(tab);
 });
copy from devtools/client/framework/test/allocations/browser_allocations_reload.ini
copy to devtools/client/framework/test/allocations/browser_allocations_reload_inspector.ini
--- a/devtools/client/framework/test/allocations/browser_allocations_reload.ini
+++ b/devtools/client/framework/test/allocations/browser_allocations_reload_inspector.ini
@@ -4,10 +4,10 @@ subsuite = devtools
 support-files =
   !/devtools/shared/test-helpers/allocation-tracker.js
   head.js
   reloaded-page.html
   reloaded.png
 
 # Each metrics tests is loaded in a separate .ini file. This way the test is executed
 # individually, without any other test being executed before or after.
-[browser_allocations_reload.js]
+[browser_allocations_reload_inspector.js]
 skip-if = os != 'linux' || debug || asan # Results should be platform agnostic - only run on linux64-opt
copy from devtools/client/framework/test/allocations/browser_allocations_reload.js
copy to devtools/client/framework/test/allocations/browser_allocations_reload_inspector.js
--- a/devtools/client/framework/test/allocations/browser_allocations_reload.js
+++ b/devtools/client/framework/test/allocations/browser_allocations_reload_inspector.js
@@ -51,15 +51,15 @@ add_task(async function() {
     alsoRecordContentProcess: true,
   });
 
   // Now, run the test script. This time, we record this run.
   for (let i = 0; i < 10; i++) {
     await testScript(toolbox);
   }
 
-  await stopRecordingAllocations("reload", {
+  await stopRecordingAllocations("reload-inspector", {
     alsoRecordContentProcess: true,
   });
 
   await toolbox.destroy();
   gBrowser.removeTab(tab);
 });
copy from devtools/client/framework/test/allocations/browser_allocations_reload.ini
copy to devtools/client/framework/test/allocations/browser_allocations_reload_netmonitor.ini
--- a/devtools/client/framework/test/allocations/browser_allocations_reload.ini
+++ b/devtools/client/framework/test/allocations/browser_allocations_reload_netmonitor.ini
@@ -4,10 +4,10 @@ subsuite = devtools
 support-files =
   !/devtools/shared/test-helpers/allocation-tracker.js
   head.js
   reloaded-page.html
   reloaded.png
 
 # Each metrics tests is loaded in a separate .ini file. This way the test is executed
 # individually, without any other test being executed before or after.
-[browser_allocations_reload.js]
+[browser_allocations_reload_netmonitor.js]
 skip-if = os != 'linux' || debug || asan # Results should be platform agnostic - only run on linux64-opt
copy from devtools/client/framework/test/allocations/browser_allocations_reload.js
copy to devtools/client/framework/test/allocations/browser_allocations_reload_netmonitor.js
--- a/devtools/client/framework/test/allocations/browser_allocations_reload.js
+++ b/devtools/client/framework/test/allocations/browser_allocations_reload_netmonitor.js
@@ -32,17 +32,17 @@ async function testScript(toolbox) {
 
   // eslint-disable-next-line mozilla/no-arbitrary-setTimeout
   await new Promise(resolve => setTimeout(resolve, 1000));
 }
 
 add_task(async function() {
   const tab = await addTab(TEST_URL);
   const toolbox = await gDevTools.showToolboxForTab(tab, {
-    toolId: "inspector",
+    toolId: "netmonitor",
   });
 
   // Run the test scenario first before recording in order to load all the
   // modules. Otherwise they get reported as "still allocated" objects,
   // whereas we do expect them to be kept in memory as they are loaded via
   // the main DevTools loader, which keeps the module loaded until the
   // shutdown of Firefox
   await testScript(toolbox);
@@ -51,15 +51,15 @@ add_task(async function() {
     alsoRecordContentProcess: true,
   });
 
   // Now, run the test script. This time, we record this run.
   for (let i = 0; i < 10; i++) {
     await testScript(toolbox);
   }
 
-  await stopRecordingAllocations("reload", {
+  await stopRecordingAllocations("reload-netmonitor", {
     alsoRecordContentProcess: true,
   });
 
   await toolbox.destroy();
   gBrowser.removeTab(tab);
 });
copy from devtools/client/framework/test/allocations/browser_allocations_reload.ini
copy to devtools/client/framework/test/allocations/browser_allocations_reload_webconsole.ini
--- a/devtools/client/framework/test/allocations/browser_allocations_reload.ini
+++ b/devtools/client/framework/test/allocations/browser_allocations_reload_webconsole.ini
@@ -4,10 +4,10 @@ subsuite = devtools
 support-files =
   !/devtools/shared/test-helpers/allocation-tracker.js
   head.js
   reloaded-page.html
   reloaded.png
 
 # Each metrics tests is loaded in a separate .ini file. This way the test is executed
 # individually, without any other test being executed before or after.
-[browser_allocations_reload.js]
+[browser_allocations_reload_webconsole.js]
 skip-if = os != 'linux' || debug || asan # Results should be platform agnostic - only run on linux64-opt
copy from devtools/client/framework/test/allocations/browser_allocations_reload.js
copy to devtools/client/framework/test/allocations/browser_allocations_reload_webconsole.js
--- a/devtools/client/framework/test/allocations/browser_allocations_reload.js
+++ b/devtools/client/framework/test/allocations/browser_allocations_reload_webconsole.js
@@ -32,17 +32,17 @@ async function testScript(toolbox) {
 
   // eslint-disable-next-line mozilla/no-arbitrary-setTimeout
   await new Promise(resolve => setTimeout(resolve, 1000));
 }
 
 add_task(async function() {
   const tab = await addTab(TEST_URL);
   const toolbox = await gDevTools.showToolboxForTab(tab, {
-    toolId: "inspector",
+    toolId: "webconsole",
   });
 
   // Run the test scenario first before recording in order to load all the
   // modules. Otherwise they get reported as "still allocated" objects,
   // whereas we do expect them to be kept in memory as they are loaded via
   // the main DevTools loader, which keeps the module loaded until the
   // shutdown of Firefox
   await testScript(toolbox);
@@ -51,15 +51,15 @@ add_task(async function() {
     alsoRecordContentProcess: true,
   });
 
   // Now, run the test script. This time, we record this run.
   for (let i = 0; i < 10; i++) {
     await testScript(toolbox);
   }
 
-  await stopRecordingAllocations("reload", {
+  await stopRecordingAllocations("reload-webconsole", {
     alsoRecordContentProcess: true,
   });
 
   await toolbox.destroy();
   gBrowser.removeTab(tab);
 });
--- a/devtools/client/framework/test/allocations/head.js
+++ b/devtools/client/framework/test/allocations/head.js
@@ -106,19 +106,16 @@ async function stopRecordingAllocations(
   // Ensure that Memory API didn't ran out of buffers
   ok(!tracker.overflowed, "Allocation were all recorded in the parent process");
 
   // And finally, retrieve the record *after* having ran the test
   const parentProcessData = await tracker.stopRecordingAllocations(
     DEBUG_ALLOCATIONS
   );
 
-  // Only do that from the parent process for now,
-  // as traceObjects doesn't work from the content process.
-  // It throws because the content process isn't allowed to read the snapshot files.
   const objectNodeIds = TrackedObjects.getAllNodeIds();
   if (objectNodeIds.length > 0) {
     tracker.traceObjects(objectNodeIds);
   }
 
   let contentProcessData = null;
   if (alsoRecordContentProcess) {
     contentProcessData = await SpecialPowers.spawn(
@@ -133,16 +130,46 @@ async function stopRecordingAllocations(
           !tracker.overflowed,
           "Allocation were all recorded in the content process"
         );
         return tracker.stopRecordingAllocations(debug_allocations);
       }
     );
   }
 
+  const trackedObjectsInContent = await SpecialPowers.spawn(
+    gBrowser.selectedBrowser,
+    [],
+    () => {
+      const TrackedObjects = ChromeUtils.import(
+        "resource://devtools/shared/test-helpers/tracked-objects.jsm"
+      );
+      const objectNodeIds = TrackedObjects.getAllNodeIds();
+      if (objectNodeIds.length > 0) {
+        const { DevToolsLoader } = ChromeUtils.import(
+          "resource://devtools/shared/Loader.jsm"
+        );
+        const { tracker } = DevToolsLoader;
+        // Record the heap snapshot from the content process,
+        // and pass the record's filepath to the parent process
+        // As only the parent process can read the file because
+        // of sandbox restrictions made to content processes regarding file I/O.
+        const snapshotFile = tracker.getSnapshotFile();
+        return { snapshotFile, objectNodeIds };
+      }
+      return null;
+    }
+  );
+  if (trackedObjectsInContent) {
+    tracker.traceObjects(
+      trackedObjectsInContent.objectNodeIds,
+      trackedObjectsInContent.snapshotFile
+    );
+  }
+
   // Craft the JSON object required to save data in talos database
   info(
     `The ${recordName} test leaked ${parentProcessData.objectsWithStack} objects (${parentProcessData.objectsWithoutStack} with missing allocation site) in the parent process`
   );
   const PERFHERDER_DATA = {
     framework: {
       name: "devtools",
     },
new file mode 100644
--- /dev/null
+++ b/devtools/client/framework/test/allocations/moz.build
@@ -0,0 +1,15 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# 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/.
+
+BROWSER_CHROME_MANIFESTS += [
+    "browser_allocations_browser_console.ini",
+    "browser_allocations_reload_debugger.ini",
+    "browser_allocations_reload_inspector.ini",
+    "browser_allocations_reload_netmonitor.ini",
+    "browser_allocations_reload_webconsole.ini",
+    "browser_allocations_target.ini",
+    "browser_allocations_toolbox.ini",
+]
--- a/devtools/server/actors/breakpoint-list.js
+++ b/devtools/server/actors/breakpoint-list.js
@@ -5,17 +5,17 @@
 "use strict";
 
 const { ActorClassWithSpec, Actor } = require("devtools/shared/protocol");
 const { breakpointListSpec } = require("devtools/shared/specs/breakpoint-list");
 const {
   WatchedDataHelpers,
 } = require("devtools/server/actors/watcher/WatchedDataHelpers.jsm");
 const { SUPPORTED_DATA } = WatchedDataHelpers;
-const { BREAKPOINTS, XHR_BREAKPOINTS } = SUPPORTED_DATA;
+const { BREAKPOINTS, XHR_BREAKPOINTS, EVENT_BREAKPOINTS } = SUPPORTED_DATA;
 
 /**
  * This actor manages the breakpoints list.
  *
  * Breakpoints should be available as early as possible to new targets and
  * will be forwarded to the WatcherActor to populate the shared watcher data available to
  * all DevTools targets.
  *
@@ -62,11 +62,33 @@ const BreakpointListActor = ActorClassWi
    *
    * See setXHRBreakpoint for arguments definition.
    */
   removeXHRBreakpoint(path, method) {
     return this.watcherActor.removeDataEntry(XHR_BREAKPOINTS, [
       { path, method },
     ]);
   },
+
+  /**
+   * Set the active breakpoints
+   *
+   * @param {Array<String>} ids
+   *                        An array of eventlistener breakpoint ids. These
+   *                        are unique identifiers for event breakpoints.
+   *                        See devtools/server/actors/utils/event-breakpoints.js
+   *                        for details.
+   */
+  setActiveEventBreakpoints(ids) {
+    const existingIds = this.watcherActor.getWatchedData(EVENT_BREAKPOINTS);
+    const addIds = ids.filter(id => !existingIds.includes(id));
+    const removeIds = existingIds.filter(id => !ids.includes(id));
+
+    if (addIds.length) {
+      this.watcherActor.addDataEntry(EVENT_BREAKPOINTS, addIds);
+    }
+    if (removeIds.length) {
+      this.watcherActor.removeDataEntry(EVENT_BREAKPOINTS, removeIds);
+    }
+  },
 });
 
 exports.BreakpointListActor = BreakpointListActor;
--- a/devtools/server/actors/targets/target-actor-mixin.js
+++ b/devtools/server/actors/targets/target-actor-mixin.js
@@ -12,16 +12,17 @@ const {
 } = require("devtools/server/actors/watcher/WatchedDataHelpers.jsm");
 const { STATES: THREAD_STATES } = require("devtools/server/actors/thread");
 const {
   RESOURCES,
   BREAKPOINTS,
   TARGET_CONFIGURATION,
   THREAD_CONFIGURATION,
   XHR_BREAKPOINTS,
+  EVENT_BREAKPOINTS,
 } = WatchedDataHelpers.SUPPORTED_DATA;
 
 loader.lazyRequireGetter(
   this,
   "StyleSheetsManager",
   "devtools/server/actors/utils/stylesheets-manager",
   true
 );
@@ -40,16 +41,17 @@ module.exports = function(targetType, ta
      * @param string type
      *        The type of data to be added
      * @param Array<Object> entries
      *        The values to be added to this type of data
      * @param Boolean isDocumentCreation
      *        Set to true if this function is called just after a new document (and its
      *        associated target) is created.
      */
+    // eslint-disable-next-line complexity
     async addWatcherDataEntry(type, entries, isDocumentCreation = false) {
       if (type == RESOURCES) {
         await this._watchTargetResources(entries);
       } else if (type == BREAKPOINTS) {
         // Breakpoints require the target to be attached,
         // mostly to have the thread actor instantiated
         // (content process targets don't have attach method,
         //  instead they instantiate their ThreadActor immediately)
@@ -121,16 +123,30 @@ module.exports = function(targetType, ta
           await this.threadActor.attach();
         }
 
         await Promise.all(
           entries.map(({ path, method }) =>
             this.threadActor.setXHRBreakpoint(path, method)
           )
         );
+      } else if (type == EVENT_BREAKPOINTS) {
+        // Same as comments for XHR breakpoints. See lines 109-112
+        if (typeof this.attach == "function") {
+          this.attach();
+        }
+
+        // Same as comments for XHR breakpoints. See lines 117-118
+        if (
+          this.threadActor.state == THREAD_STATES.DETACHED &&
+          !this.targetType.endsWith("worker")
+        ) {
+          this.threadActor.attach();
+        }
+        await this.threadActor.setActiveEventBreakpoints(entries);
       }
     },
 
     removeWatcherDataEntry(type, entries) {
       if (type == RESOURCES) {
         return this._unwatchTargetResources(entries);
       } else if (type == BREAKPOINTS) {
         for (const { location } of entries) {
--- a/devtools/server/actors/utils/event-breakpoints.js
+++ b/devtools/server/actors/utils/event-breakpoints.js
@@ -468,8 +468,12 @@ function getAvailableEventBreakpoints() 
       events: items.map(item => ({
         id: item.id,
         name: item.name,
       })),
     });
   }
   return available;
 }
+exports.validateEventBreakpoint = validateEventBreakpoint;
+function validateEventBreakpoint(id) {
+  return !!EVENTS_BY_ID[id];
+}
--- a/devtools/server/actors/watcher.js
+++ b/devtools/server/actors/watcher.js
@@ -168,16 +168,18 @@ exports.WatcherActor = protocol.ActorCla
           [Resources.TYPES.SERVER_SENT_EVENT]: hasBrowserElement,
           [Resources.TYPES.WEBSOCKET]: hasBrowserElement,
         },
 
         // @backward-compat { version 93 } Starts supporting setSaveRequestAndResponseBodies on the NetworkParent actor
         saveRequestAndResponseBodies: true,
         // @backward-compat { version 93 } The network parent actor started exposing setPersist method.
         "network-persist": true,
+        // @backward-compat { version 94 } Full support for event breakpoints via the watcher actor
+        "event-breakpoints": true,
       },
     };
   },
 
   /**
    * Start watching for a new target type.
    *
    * This will instantiate Target Actors for existing debugging context of this type,
--- a/devtools/server/actors/watcher/WatchedDataHelpers.jsm
+++ b/devtools/server/actors/watcher/WatchedDataHelpers.jsm
@@ -6,28 +6,56 @@
 
 /**
  * Helper module alongside WatcherRegistry, which focus on updating the "watchedData" object.
  * This object is shared across processes and threads and have to be maintained in all these runtimes.
  */
 
 var EXPORTED_SYMBOLS = ["WatchedDataHelpers"];
 
-// Allow this JSM to also be loaded as a CommonJS module
-// Because this module is used from the worker thread,
-// (via target-actor-mixin), and workers can't load JSMs via ChromeUtils.import.
-const { validateBreakpointLocation } =
-  typeof module == "object"
-    ? require("devtools/shared/validate-breakpoint.jsm")
-    : ChromeUtils.import("resource://devtools/shared/validate-breakpoint.jsm");
+if (typeof module == "object") {
+  // Allow this JSM to also be loaded as a CommonJS module
+  // Because this module is used from the worker thread,
+  // (via target-actor-mixin), and workers can't load JSMs via ChromeUtils.import.
+  loader.lazyRequireGetter(
+    this,
+    "validateBreakpointLocation",
+    "devtools/shared/validate-breakpoint.jsm",
+    true
+  );
+
+  loader.lazyRequireGetter(
+    this,
+    "validateEventBreakpoint",
+    "devtools/server/actors/utils/event-breakpoints",
+    true
+  );
+} else {
+  const { XPCOMUtils } = ChromeUtils.import(
+    "resource://gre/modules/XPCOMUtils.jsm"
+  );
+  XPCOMUtils.defineLazyGetter(this, "validateBreakpointLocation", () => {
+    return ChromeUtils.import(
+      "resource://devtools/shared/validate-breakpoint.jsm"
+    ).validateBreakpointLocation;
+  });
+  XPCOMUtils.defineLazyGetter(this, "validateEventBreakpoint", () => {
+    const { loader } = ChromeUtils.import(
+      "resource://devtools/shared/Loader.jsm"
+    );
+    return loader.require("devtools/server/actors/utils/event-breakpoints")
+      .validateEventBreakpoint;
+  });
+}
 
 // List of all arrays stored in `watchedData`, which are replicated across processes and threads
 const SUPPORTED_DATA = {
   BREAKPOINTS: "breakpoints",
   XHR_BREAKPOINTS: "xhr-breakpoints",
+  EVENT_BREAKPOINTS: "event-breakpoints",
   RESOURCES: "resources",
   TARGET_CONFIGURATION: "target-configuration",
   THREAD_CONFIGURATION: "thread-configuration",
   TARGETS: "targets",
 };
 
 // Optional function, if data isn't a primitive data type in order to produce a key
 // for the given data entry
@@ -54,16 +82,29 @@ const DATA_KEY_FUNCTION = {
     }
     if (typeof method != "string") {
       throw new Error(
         `XHR Breakpoints expect to have method string, got ${typeof method} instead.`
       );
     }
     return `${path}:${method}`;
   },
+  [SUPPORTED_DATA.EVENT_BREAKPOINTS]: function(id) {
+    if (typeof id != "string") {
+      throw new Error(
+        `Event Breakpoints expect the id to be a string , got ${typeof id} instead.`
+      );
+    }
+    if (!validateEventBreakpoint(id)) {
+      throw new Error(
+        `The id string should be a valid event breakpoint id, ${id} is not.`
+      );
+    }
+    return id;
+  },
 };
 
 function idFunction(v) {
   if (typeof v != "string") {
     throw new Error(
       `Expect data entry values to be string, or be using custom data key functions. Got ${typeof v} type instead.`
     );
   }
--- a/devtools/shared/commands/resource/legacy-listeners/reflow.js
+++ b/devtools/shared/commands/resource/legacy-listeners/reflow.js
@@ -2,16 +2,20 @@
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
 const ResourceCommand = require("devtools/shared/commands/resource/resource-command");
 
 module.exports = async function({ targetFront, onAvailable }) {
+  if (!targetFront.getTrait("isBrowsingContext")) {
+    // The reflows only work with BrowsingContext targets
+    return;
+  }
   const reflowFront = await targetFront.getFront("reflow");
   reflowFront.on("reflows", reflows =>
     onAvailable([
       {
         resourceType: ResourceCommand.TYPES.REFLOW,
         reflows,
       },
     ])
--- a/devtools/shared/specs/breakpoint-list.js
+++ b/devtools/shared/specs/breakpoint-list.js
@@ -34,12 +34,17 @@ const breakpointListSpec = generateActor
       },
     },
     removeXHRBreakpoint: {
       request: {
         path: Arg(0, "string"),
         method: Arg(1, "string"),
       },
     },
+    setActiveEventBreakpoints: {
+      request: {
+        ids: Arg(0, "array:string"),
+      },
+    },
   },
 });
 
 exports.breakpointListSpec = breakpointListSpec;
--- a/devtools/shared/test-helpers/allocation-tracker.js
+++ b/devtools/shared/test-helpers/allocation-tracker.js
@@ -433,21 +433,43 @@ exports.allocationTracker = function({
         Cu.forceCC();
         await new Promise(resolve => Cu.schedulePreciseShrinkingGC(resolve));
 
         // eslint-disable-next-line mozilla/no-arbitrary-setTimeout
         await new Promise(resolve => setTimeout(resolve, 1000));
       }
     },
 
-    traceObjects(objects) {
+    /**
+     * Return the absolute file path to a memory snapshot.
+     * This is used to compute dominator trees in `traceObjects`.
+     */
+    getSnapshotFile() {
+      return ChromeUtils.saveHeapSnapshot({ debugger: dbg });
+    },
+
+    /**
+     * Print information about why a list of objects are being held in memory.
+     *
+     * @param Array<NodeId> objects
+     *        List of NodeId's of objects to debug. NodeIds can be retrieved
+     *        via ChromeUtils.getObjectNodeId.
+     * @param String snapshotFile
+     *        Absolute path to a Heap snapshot file retrieved via this.getSnapshotFile.
+     *        This is used to trace content process objects. We have to record the snapshot
+     *        from the content process, but can only read it from the parent process because
+     *        of I/O restrictions in content processes.
+     */
+    traceObjects(objects, snapshotFile) {
       // There is no API to get the heap snapshot at runtime,
       // the only way is to save it to disk and then load it from disk
-      const filePath = ChromeUtils.saveHeapSnapshot({ debugger: dbg });
-      const snapshot = ChromeUtils.readHeapSnapshot(filePath);
+      if (!snapshotFile) {
+        snapshotFile = this.getSnapshotFile();
+      }
+      const snapshot = ChromeUtils.readHeapSnapshot(snapshotFile);
 
       function getObjectClass(id) {
         if (!id) {
           return "<null>";
         }
         try {
           let stack = [...snapshot.describeNode({ by: "allocationStack" }, id)];
           let line;
@@ -474,16 +496,19 @@ exports.allocationTracker = function({
             stack = "no-desc";
           }
           return (
             Object.entries(
               snapshot.describeNode({ by: "objectClass" }, id)
             )[0][0] + (stack ? "@" + stack + ":" + line : "")
           );
         } catch (e) {
+          if (e.name == "NS_ERROR_ILLEGAL_VALUE") {
+            return "<not-in-memory-snapshot:is-from-untracked-global?>";
+          }
           return "<invalid:" + id + ":" + e + ">";
         }
       }
       function printPath(src, dst) {
         let paths;
         try {
           paths = snapshot.computeShortestPaths(src, [dst], 10);
         } catch (e) {}
--- a/devtools/shared/test-helpers/tracked-objects.jsm
+++ b/devtools/shared/test-helpers/tracked-objects.jsm
@@ -9,28 +9,23 @@
 //
 // We are going to store a weak reference to the passed objects,
 // in order to prevent holding them in memory.
 // Allocation tracker will then print detailed information
 // about why these objects are still allocated.
 
 var EXPORTED_SYMBOLS = ["track", "getAllNodeIds", "clear"];
 
-const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
-
 const objects = [];
 
 /**
  * Request to track why the given object is kept in memory,
  * later on, when retrieving all the watched object via getAllNodeIds.
  */
 function track(obj) {
-  if (Services.appinfo.processType != Ci.nsIXULRuntime.PROCESS_TYPE_DEFAULT) {
-    throw new Error("For now, this API only works from the parent process");
-  }
   // We store a weak reference, so that we do force keeping the object in memory!!
   objects.push(Cu.getWeakReference(obj));
 }
 
 /**
  * Return the NodeId's of all the objects passed via `track()` method.
  *
  * NodeId's are used by spidermonkey memory API to designates JS objects in head snapshots.
--- a/dom/base/nsContentPermissionHelper.cpp
+++ b/dom/base/nsContentPermissionHelper.cpp
@@ -470,34 +470,34 @@ ContentPermissionRequestBase::GetIsHandl
 NS_IMETHODIMP
 ContentPermissionRequestBase::GetTypes(nsIArray** aTypes) {
   nsTArray<nsString> emptyOptions;
   return nsContentPermissionUtils::CreatePermissionArray(mType, emptyOptions,
                                                          aTypes);
 }
 
 ContentPermissionRequestBase::PromptResult
-ContentPermissionRequestBase::CheckPromptPrefs() {
+ContentPermissionRequestBase::CheckPromptPrefs() const {
   MOZ_ASSERT(!mPrefName.IsEmpty(),
              "This derived class must support checking pref types");
 
   nsAutoCString prefName(mPrefName);
   prefName.AppendLiteral(".prompt.testing");
   if (Preferences::GetBool(PromiseFlatCString(prefName).get(), false)) {
     prefName.AppendLiteral(".allow");
     if (Preferences::GetBool(PromiseFlatCString(prefName).get(), true)) {
       return PromptResult::Granted;
     }
     return PromptResult::Denied;
   }
 
   return PromptResult::Pending;
 }
 
-bool ContentPermissionRequestBase::CheckPermissionDelegate() {
+bool ContentPermissionRequestBase::CheckPermissionDelegate() const {
   // There is case that ContentPermissionRequestBase is constructed without
   // window, then mPermissionHandler will be null. So we only check permission
   // delegate if we have non-null mPermissionHandler
   if (mPermissionHandler &&
       !mPermissionHandler->HasPermissionDelegated(mType)) {
     return false;
   }
 
--- a/dom/base/nsContentPermissionHelper.h
+++ b/dom/base/nsContentPermissionHelper.h
@@ -118,41 +118,53 @@ class ContentPermissionRequestBase : pub
 
   enum class PromptResult {
     Granted,
     Denied,
     Pending,
   };
   nsresult ShowPrompt(PromptResult& aResult);
 
-  PromptResult CheckPromptPrefs();
+  PromptResult CheckPromptPrefs() const;
 
   // Check if the permission has an opportunity to request.
-  bool CheckPermissionDelegate();
+  bool CheckPermissionDelegate() const;
 
   enum class DelayedTaskType {
     Allow,
     Deny,
     Request,
   };
   void RequestDelayedTask(nsIEventTarget* aTarget, DelayedTaskType aType);
 
  protected:
+  // @param aPrefName see `mPrefName`.
+  // @param aType see `mType`.
   ContentPermissionRequestBase(nsIPrincipal* aPrincipal,
                                nsPIDOMWindowInner* aWindow,
                                const nsACString& aPrefName,
                                const nsACString& aType);
   virtual ~ContentPermissionRequestBase() = default;
 
   nsCOMPtr<nsIPrincipal> mPrincipal;
   nsCOMPtr<nsIPrincipal> mTopLevelPrincipal;
   nsCOMPtr<nsPIDOMWindowInner> mWindow;
   RefPtr<PermissionDelegateHandler> mPermissionHandler;
-  nsCString mPrefName;
-  nsCString mType;
+
+  // The prefix of a pref which allows tests to bypass showing the prompt.
+  // Tests will have to set both of
+  // ${mPrefName}.prompt.testing and
+  // ${mPrefName}.prompt.testing.allow
+  // to either true or false. If no such testing is required, mPrefName may be
+  // empty.
+  const nsCString mPrefName;
+
+  // The type of the request, such as "autoplay-media-audible".
+  const nsCString mType;
+
   bool mIsHandlingUserInput;
   bool mMaybeUnsafePermissionDelegate;
 };
 
 }  // namespace dom
 }  // namespace mozilla
 
 using mozilla::dom::ContentPermissionRequestParent;
--- a/dom/cache/Cache.cpp
+++ b/dom/cache/Cache.cpp
@@ -143,18 +143,18 @@ class Cache::FetchHandler final : public
 
       QM_TRY(OkIf(JS_GetElement(aCx, obj, i, &value)), QM_VOID, failOnErr);
 
       QM_TRY(OkIf(value.isObject()), QM_VOID, failOnErr);
 
       JS::Rooted<JSObject*> responseObj(aCx, &value.toObject());
 
       RefPtr<Response> response;
-      QM_TRY((UNWRAP_OBJECT(Response, responseObj, response)), QM_VOID,
-             failOnErr);
+      QM_TRY(MOZ_TO_RESULT(UNWRAP_OBJECT(Response, responseObj, response)),
+             QM_VOID, failOnErr);
 
       QM_TRY(OkIf(response->Type() != ResponseType::Error), QM_VOID, failOnErr);
 
       // Do not allow the convenience methods .add()/.addAll() to store failed
       // or invalid responses.  A consequence of this is that these methods
       // cannot be used to store opaque or opaqueredirect responses since they
       // always expose a 0 status value.
       ErrorResult errorResult;
--- a/dom/cache/CacheStorage.cpp
+++ b/dom/cache/CacheStorage.cpp
@@ -101,35 +101,36 @@ bool IsTrusted(const PrincipalInfo& aPri
 
   // off the main thread URL parsing using nsStdURLParser.
   const nsCOMPtr<nsIURLParser> urlParser = new nsStdURLParser();
 
   uint32_t schemePos;
   int32_t schemeLen;
   uint32_t authPos;
   int32_t authLen;
-  QM_TRY(
-      urlParser->ParseURL(url, flatURL.Length(), &schemePos, &schemeLen,
-                          &authPos, &authLen, nullptr, nullptr),  // ignore path
-      false);
+  QM_TRY(MOZ_TO_RESULT(urlParser->ParseURL(url, flatURL.Length(), &schemePos,
+                                           &schemeLen, &authPos, &authLen,
+                                           nullptr, nullptr)),  // ignore path
+         false);
 
   const nsAutoCString scheme(Substring(flatURL, schemePos, schemeLen));
   if (scheme.LowerCaseEqualsLiteral("https") ||
       scheme.LowerCaseEqualsLiteral("file") ||
       scheme.LowerCaseEqualsLiteral("moz-extension")) {
     return true;
   }
 
   uint32_t hostPos;
   int32_t hostLen;
-  QM_TRY(urlParser->ParseAuthority(url + authPos, authLen, nullptr,
-                                   nullptr,           // ignore username
-                                   nullptr, nullptr,  // ignore password
-                                   &hostPos, &hostLen,
-                                   nullptr),  // ignore port
+  QM_TRY(MOZ_TO_RESULT(
+             urlParser->ParseAuthority(url + authPos, authLen, nullptr,
+                                       nullptr,           // ignore username
+                                       nullptr, nullptr,  // ignore password
+                                       &hostPos, &hostLen,
+                                       nullptr)),  // ignore port
          false);
 
   return nsMixedContentBlocker::IsPotentiallyTrustworthyLoopbackHost(
       nsDependentCSubstring(url + authPos + hostPos, hostLen));
 }
 
 }  // namespace
 
@@ -137,18 +138,18 @@ bool IsTrusted(const PrincipalInfo& aPri
 already_AddRefed<CacheStorage> CacheStorage::CreateOnMainThread(
     Namespace aNamespace, nsIGlobalObject* aGlobal, nsIPrincipal* aPrincipal,
     bool aForceTrustedOrigin, ErrorResult& aRv) {
   MOZ_DIAGNOSTIC_ASSERT(aGlobal);
   MOZ_DIAGNOSTIC_ASSERT(aPrincipal);
   MOZ_ASSERT(NS_IsMainThread());
 
   PrincipalInfo principalInfo;
-  QM_TRY(PrincipalToPrincipalInfo(aPrincipal, &principalInfo), nullptr,
-         [&aRv](const nsresult rv) { aRv.Throw(rv); });
+  QM_TRY(MOZ_TO_RESULT(PrincipalToPrincipalInfo(aPrincipal, &principalInfo)),
+         nullptr, [&aRv](const nsresult rv) { aRv.Throw(rv); });
 
   QM_TRY(OkIf(QuotaManager::IsPrincipalInfoValid(principalInfo)),
          RefPtr{new CacheStorage(NS_ERROR_DOM_SECURITY_ERR)}.forget(),
          [](const auto) {
            NS_WARNING("CacheStorage not supported on invalid origins.");
          });
 
   const bool testingEnabled =
--- a/dom/cache/DBSchema.cpp
+++ b/dom/cache/DBSchema.cpp
@@ -6,16 +6,17 @@
 
 #include "mozilla/dom/cache/DBSchema.h"
 
 #include "ipc/IPCMessageUtils.h"
 #include "mozilla/BasePrincipal.h"
 #include "mozilla/dom/HeadersBinding.h"
 #include "mozilla/dom/InternalHeaders.h"
 #include "mozilla/dom/InternalResponse.h"
+#include "mozilla/dom/QMResultInlines.h"
 #include "mozilla/dom/RequestBinding.h"
 #include "mozilla/dom/ResponseBinding.h"
 #include "mozilla/dom/cache/CacheCommon.h"
 #include "mozilla/dom/cache/CacheTypes.h"
 #include "mozilla/dom/cache/SavedTypes.h"
 #include "mozilla/dom/cache/Types.h"
 #include "mozilla/dom/cache/TypeUtils.h"
 #include "mozilla/net/MozURL.h"
@@ -428,17 +429,18 @@ class MOZ_RAII AutoDisableForeignKeyChec
                 mForeignKeyCheckingDisabled = true;
                 return Ok{};
               }));
     }
   }
 
   ~AutoDisableForeignKeyChecking() {
     if (mForeignKeyCheckingDisabled) {
-      QM_WARNONLY_TRY(mConn->ExecuteSimpleSQL("PRAGMA foreign_keys = ON;"_ns));
+      QM_WARNONLY_TRY(QM_TO_RESULT(
+          mConn->ExecuteSimpleSQL("PRAGMA foreign_keys = ON;"_ns)));
     }
   }
 
  private:
   nsCOMPtr<mozIStorageConnection> mConn;
   bool mForeignKeyCheckingDisabled;
 };
 
@@ -516,20 +518,20 @@ nsresult CreateOrMigrateSchema(mozIStora
   QM_TRY(trans.Commit());
 
   if (migrating) {
     // Migrations happen infrequently and reflect a chance in DB structure.
     // This is a good time to rebuild the database.  It also helps catch
     // if a new migration is incorrect by fast failing on the corruption.
     // Unfortunately, this must be performed outside of the transaction.
 
-    QM_TRY(aConn.ExecuteSimpleSQL("VACUUM"_ns), QM_PROPAGATE,
+    QM_TRY(MOZ_TO_RESULT(aConn.ExecuteSimpleSQL("VACUUM"_ns)), QM_PROPAGATE,
            ([&aConn](const nsresult rv) {
              if (rv == NS_ERROR_STORAGE_CONSTRAINT) {
-               QM_WARNONLY_TRY(IntegrityCheck(aConn));
+               QM_WARNONLY_TRY(QM_TO_RESULT(IntegrityCheck(aConn)));
              }
            }));
   }
 
   return NS_OK;
 }
 
 nsresult InitializeConnection(mozIStorageConnection& aConn) {
--- a/dom/cache/FileUtils.cpp
+++ b/dom/cache/FileUtils.cpp
@@ -3,16 +3,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/. */
 
 #include "FileUtilsImpl.h"
 
 #include "DBSchema.h"
 #include "mozilla/dom/InternalResponse.h"
+#include "mozilla/dom/QMResultInlines.h"
 #include "mozilla/dom/quota/FileStreams.h"
 #include "mozilla/dom/quota/QuotaManager.h"
 #include "mozilla/dom/quota/QuotaObject.h"
 #include "mozilla/ScopeExit.h"
 #include "mozilla/SnappyCompressOutputStream.h"
 #include "mozilla/Unused.h"
 #include "nsIObjectInputStream.h"
 #include "nsIObjectOutputStream.h"
@@ -166,17 +167,18 @@ Result<std::pair<nsID, nsCOMPtr<nsISuppo
                       aCallback, aClosure, true,
                       true,  // close streams
                       getter_AddRefs(copyContext)));
 
   return std::make_pair(id, std::move(copyContext));
 }
 
 void BodyCancelWrite(nsISupports& aCopyContext) {
-  QM_WARNONLY_TRY(NS_CancelAsyncCopy(&aCopyContext, NS_ERROR_ABORT));
+  QM_WARNONLY_TRY(
+      QM_TO_RESULT(NS_CancelAsyncCopy(&aCopyContext, NS_ERROR_ABORT)));
 
   // TODO The partially written file must be cleaned up after the async copy
   // makes its callback.
 }
 
 nsresult BodyFinalizeWrite(nsIFile& aBaseDir, const nsID& aId) {
   QM_TRY_INSPECT(const auto& tmpFile,
                  BodyIdToFile(aBaseDir, aId, BODY_FILE_TMP));
@@ -717,17 +719,18 @@ Result<int64_t, nsresult> DirectoryPaddi
                                                   mozIStorageConnection& aConn,
                                                   const bool aMustRestore) {
   // The content of padding file is untrusted, so remove it here.
   QM_TRY(DirectoryPaddingDeleteFile(aBaseDir, DirPaddingFile::FILE));
 
   QM_TRY_INSPECT(const int64_t& paddingSize, db::FindOverallPaddingSize(aConn));
   MOZ_DIAGNOSTIC_ASSERT(paddingSize >= 0);
 
-  QM_TRY(DirectoryPaddingWrite(aBaseDir, DirPaddingFile::FILE, paddingSize),
+  QM_TRY(MOZ_TO_RESULT(DirectoryPaddingWrite(aBaseDir, DirPaddingFile::FILE,
+                                             paddingSize)),
          (aMustRestore ? Err(tryTempError)
                        : Result<int64_t, nsresult>{paddingSize}));
 
   QM_TRY(DirectoryPaddingDeleteFile(aBaseDir, DirPaddingFile::TMP_FILE));
 
   return paddingSize;
 }
 
--- a/dom/cache/Manager.cpp
+++ b/dom/cache/Manager.cpp
@@ -135,20 +135,20 @@ class SetupAction final : public SyncDBA
       // has the correct content.
       // We'll restore padding file below, so just warn here if failure happens.
       //
       // XXX Before, if MaybeUpdatePaddingFile failed but we didn't enter the if
       // body below, we would have propagated the MaybeUpdatePaddingFile
       // failure, but if we entered it and RestorePaddingFile succeeded, we
       // would have returned NS_OK. Now, we will never propagate a
       // MaybeUpdatePaddingFile failure.
-      QM_WARNONLY_TRY(
+      QM_WARNONLY_TRY(QM_TO_RESULT(
           MaybeUpdatePaddingFile(aDBDir, aConn, /* aIncreaceSize */ 0,
                                  overallDeletedPaddingSize.value(),
-                                 [&trans]() { return trans.Commit(); }));
+                                 [&trans]() { return trans.Commit(); })));
     }
 
     if (DirectoryPaddingFileExists(*aDBDir, DirPaddingFile::TMP_FILE) ||
         !DirectoryPaddingFileExists(*aDBDir, DirPaddingFile::FILE)) {
       QM_TRY(RestorePaddingFile(aDBDir, aConn));
     }
 
     return NS_OK;
@@ -180,18 +180,19 @@ class DeleteOrphanedBodyAction final : p
     const auto resolve = [&aResolver](const nsresult rv) {
       aResolver->Resolve(rv);
     };
 
     QM_TRY_INSPECT(const auto& dbDir,
                    CloneFileAndAppend(*aQuotaInfo.mDir, u"cache"_ns), QM_VOID,
                    resolve);
 
-    QM_TRY(BodyDeleteFiles(aQuotaInfo, *dbDir, mDeletedBodyIdList), QM_VOID,
-           resolve);
+    QM_TRY(
+        MOZ_TO_RESULT(BodyDeleteFiles(aQuotaInfo, *dbDir, mDeletedBodyIdList)),
+        QM_VOID, resolve);
 
     aResolver->Resolve(NS_OK);
   }
 
  private:
   DeletedBodyIdList mDeletedBodyIdList;
 };
 
@@ -378,27 +379,16 @@ class Manager::Factory {
     MOZ_DIAGNOSTIC_ASSERT(mManagerList.IsEmpty());
     MOZ_DIAGNOSTIC_ASSERT(!mInSyncAbortOrShutdown);
   }
 
   static nsresult MaybeCreateInstance() {
     mozilla::ipc::AssertIsOnBackgroundThread();
 
     if (!sFactory) {
-      // Be clear about what we are locking.  sFactory is bg thread only, so
-      // we don't need to lock it here.  Just protect sFactoryShutdown and
-      // sBackgroundThread.
-      {
-        StaticMutexAutoLock lock(sMutex);
-
-        if (sFactoryShutdown) {
-          return NS_ERROR_ILLEGAL_DURING_SHUTDOWN;
-        }
-      }
-
       // We cannot use ClearOnShutdown() here because we're not on the main
       // thread.  Instead, we delete sFactory in Factory::Remove() after the
       // last manager is removed.  ShutdownObserver ensures this happens
       // before shutdown.
       sFactory = new Factory();
     }
 
     // Never return sFactory to code outside Factory.  We need to delete it
@@ -423,17 +413,17 @@ class Manager::Factory {
 
     sFactory = nullptr;
   }
 
   static SafeRefPtr<Manager> Acquire(const ManagerId& aManagerId,
                                      State aState = Open) {
     mozilla::ipc::AssertIsOnBackgroundThread();
 
-    QM_TRY(MaybeCreateInstance(), nullptr);
+    QM_TRY(MOZ_TO_RESULT(MaybeCreateInstance()), nullptr);
 
     // Iterate in reverse to find the most recent, matching Manager.  This
     // is important when looking for a Closing Manager.  If a new Manager
     // chains to an old Manager we want it to be the most recent one.
     const auto range = Reversed(sFactory->mManagerList.NonObservingRange());
     const auto foundIt = std::find_if(
         range.begin(), range.end(), [aState, &aManagerId](const auto& manager) {
           return aState == manager->GetState() &&
@@ -473,43 +463,30 @@ class Manager::Factory {
     MaybeDestroyInstance();
   }
 
   // Singleton created on demand and deleted when last Manager is cleared
   // in Remove().
   // PBackground thread only.
   static StaticAutoPtr<Factory> sFactory;
 
-  // protects following static attribute
-  static StaticMutex sMutex;
-
-  // Indicate if shutdown has occurred to block re-creation of sFactory.
-  // Must hold sMutex to access.
-  static bool sFactoryShutdown;
-
   // Weak references as we don't want to keep Manager objects alive forever.
   // When a Manager is destroyed it calls Factory::Remove() to clear itself.
   // PBackground thread only.
   nsTObserverArray<NotNull<Manager*>> mManagerList;
 
   // This flag is set when we are looping through the list and calling Abort()
   // or Shutdown() on each Manager.  We need to be careful not to synchronously
   // trigger the deletion of the factory while still executing this loop.
   bool mInSyncAbortOrShutdown;
 };
 
 // static
 StaticAutoPtr<Manager::Factory> Manager::Factory::sFactory;
 
-// static
-StaticMutex Manager::Factory::sMutex;
-
-// static
-bool Manager::Factory::sFactoryShutdown = false;
-
 // ----------------------------------------------------------------------------
 
 // Abstract class to help implement the various Actions.  The vast majority
 // of Actions are synchronous and need to report back to a Listener on the
 // Manager.
 class Manager::BaseAction : public SyncDBAction {
  protected:
   BaseAction(SafeRefPtr<Manager> aManager, ListenerId aListenerId)
@@ -856,17 +833,17 @@ class Manager::CachePutAllAction final :
     if (NS_FAILED(mAsyncResult)) {
       DoResolve(mAsyncResult);
       return;
     }
 
     mozStorageTransaction trans(mConn, false,
                                 mozIStorageConnection::TRANSACTION_IMMEDIATE);
 
-    QM_TRY(trans.Start(), QM_VOID);
+    QM_TRY(MOZ_TO_RESULT(trans.Start()), QM_VOID);
 
     const nsresult rv = [this, &trans]() -> nsresult {
       QM_TRY(CollectEachInRange(mList, [this](Entry& e) -> nsresult {
         if (e.mRequestStream) {
           QM_TRY(BodyFinalizeWrite(*mDBDir, e.mRequestBodyId));
         }
         if (e.mResponseStream) {
           // Gerenate padding size for opaque response if needed.
@@ -1127,21 +1104,21 @@ class Manager::CacheDeleteAction final :
         auto maybeDeletionInfo,
         db::CacheDelete(*aConn, mCacheId, mArgs.request(), mArgs.params()));
 
     mSuccess = maybeDeletionInfo.isSome();
     if (mSuccess) {
       mDeletionInfo = std::move(maybeDeletionInfo.ref());
     }
 
-    QM_TRY(
-        MaybeUpdatePaddingFile(aDBDir, aConn, /* aIncreaceSize */ 0,
-                               mDeletionInfo.mDeletedPaddingSize,
-                               [&trans]() mutable { return trans.Commit(); }),
-        QM_PROPAGATE, [this](const nsresult) { mSuccess = false; });
+    QM_TRY(MOZ_TO_RESULT(MaybeUpdatePaddingFile(
+               aDBDir, aConn, /* aIncreaceSize */ 0,
+               mDeletionInfo.mDeletedPaddingSize,
+               [&trans]() mutable { return trans.Commit(); })),
+           QM_PROPAGATE, [this](const nsresult) { mSuccess = false; });
 
     return NS_OK;
   }
 
   virtual void Complete(Listener* aListener, ErrorResult&& aRv) override {
     // If the transaction fails, we shouldn't delete the body files and decrease
     // their padding size.
     if (aRv.Failed()) {
--- a/dom/cache/PrincipalVerifier.cpp
+++ b/dom/cache/PrincipalVerifier.cpp
@@ -3,16 +3,18 @@
 /* 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/dom/cache/PrincipalVerifier.h"
 
 #include "ErrorList.h"
 #include "mozilla/dom/ContentParent.h"
+#include "mozilla/dom/QMResult.h"
+#include "mozilla/dom/QMResultInlines.h"
 #include "mozilla/dom/cache/ManagerId.h"
 #include "mozilla/ipc/BackgroundParent.h"
 #include "mozilla/ipc/PBackgroundParent.h"
 #include "mozilla/ipc/BackgroundUtils.h"
 #include "mozilla/BasePrincipal.h"
 #include "CacheCommon.h"
 #include "nsCOMPtr.h"
 #include "nsContentUtils.h"
@@ -172,13 +174,13 @@ void PrincipalVerifier::DispatchToInitia
 
   mResult = aRv;
 
   // The Cache ShutdownObserver does not track all principal verifiers, so we
   // cannot ensure this always succeeds.  Instead, simply warn on failures.
   // This will result in a new CacheStorage object delaying operations until
   // shutdown completes and the browser goes away.  This is as graceful as
   // we can get here.
-  QM_WARNONLY_TRY(
-      mInitiatingEventTarget->Dispatch(this, nsIThread::DISPATCH_NORMAL));
+  QM_WARNONLY_TRY(QM_TO_RESULT(
+      mInitiatingEventTarget->Dispatch(this, nsIThread::DISPATCH_NORMAL)));
 }
 
 }  // namespace mozilla::dom::cache
--- a/dom/cache/QuotaClientImpl.h
+++ b/dom/cache/QuotaClientImpl.h
@@ -2,16 +2,18 @@
 /* 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 mozilla_dom_cache_QuotaClientImpl_h
 #define mozilla_dom_cache_QuotaClientImpl_h
 
+#include "mozilla/dom/QMResult.h"
+#include "mozilla/dom/QMResultInlines.h"
 #include "mozilla/dom/cache/QuotaClient.h"
 #include "mozilla/dom/cache/FileUtils.h"
 
 namespace mozilla {
 namespace dom {
 namespace cache {
 
 class CacheQuotaClient final : public quota::Client {
@@ -89,18 +91,18 @@ class CacheQuotaClient final : public qu
 
     // Don't delete the temporary padding file in case of an error to force the
     // next action recalculate the padding size.
     QM_TRY(aCommitHook());
 
     QM_WARNONLY_TRY(ToResult(DirectoryPaddingFinalizeWrite(aBaseDir)),
                     ([&aBaseDir](const nsresult) {
                       // Force restore file next time.
-                      QM_WARNONLY_TRY(DirectoryPaddingDeleteFile(
-                          aBaseDir, DirPaddingFile::FILE));
+                      QM_WARNONLY_TRY(QM_TO_RESULT(DirectoryPaddingDeleteFile(
+                          aBaseDir, DirPaddingFile::FILE)));
 
                       // Ensure that we are able to force the padding file to
                       // be restored.
                       MOZ_ASSERT(DirectoryPaddingFileExists(
                           aBaseDir, DirPaddingFile::TMP_FILE));
 
                       // Since both the body file and header have been stored
                       // in the file-system, just make the action be resolve
--- a/dom/indexedDB/ActorsParent.cpp
+++ b/dom/indexedDB/ActorsParent.cpp
@@ -97,16 +97,17 @@
 #include "mozilla/dom/FlippedOnce.h"
 #include "mozilla/dom/IDBCursorBinding.h"
 #include "mozilla/dom/IPCBlob.h"
 #include "mozilla/dom/IPCBlobUtils.h"
 #include "mozilla/dom/IndexedDatabase.h"
 #include "mozilla/dom/Nullable.h"
 #include "mozilla/dom/PBackgroundMutableFileParent.h"
 #include "mozilla/dom/PContentParent.h"
+#include "mozilla/dom/QMResultInlines.h"
 #include "mozilla/dom/ScriptSettings.h"
 #include "mozilla/dom/filehandle/ActorsParent.h"
 #include "mozilla/dom/indexedDB/IDBResult.h"
 #include "mozilla/dom/indexedDB/Key.h"
 #include "mozilla/dom/indexedDB/PBackgroundIDBCursor.h"
 #include "mozilla/dom/indexedDB/PBackgroundIDBCursorParent.h"
 #include "mozilla/dom/indexedDB/PBackgroundIDBDatabase.h"
 #include "mozilla/dom/indexedDB/PBackgroundIDBDatabaseFileParent.h"
@@ -5647,17 +5648,18 @@ SerializeStructuredCloneFiles(PBackgroun
 
           case StructuredCloneFileBase::eBlob: {
             const auto impl = CreateFileBlobImpl(*aDatabase, nativeFile,
                                                  file.FileInfo().Id());
 
             IPCBlob ipcBlob;
 
             // This can only fail if the child has crashed.
-            QM_TRY(IPCBlobUtils::Serialize(impl, aBackgroundActor, ipcBlob),
+            QM_TRY(MOZ_TO_RESULT(IPCBlobUtils::Serialize(impl, aBackgroundActor,
+                                                         ipcBlob)),
                    Err(NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR),
                    IDB_REPORT_INTERNAL_ERR_LAMBDA);
 
             aDatabase->MapBlob(ipcBlob, file.FileInfoPtr());
 
             return SerializedStructuredCloneFile{ipcBlob, file.Type()};
           }
 
@@ -5915,35 +5917,36 @@ Result<Ok, nsresult> DeleteFileManagerDi
   // because we need to delete entire .files directory in the end anyway.
   QM_TRY(DatabaseFileManager::TraverseFiles(
       aFileManagerDirectory,
       // KnownDirEntryOp
       [&aQuotaManager, aPersistenceType, &aOriginMetadata](
           nsIFile& file, const bool isDirectory) -> Result<Ok, nsresult> {
         if (isDirectory) {
           // The journal directory doesn't count towards quota.
-          QM_TRY_RETURN(DeleteFilesNoQuota(file));
+          QM_TRY_RETURN(MOZ_TO_RESULT(DeleteFilesNoQuota(file)));
         }
 
         // Stored files do count towards quota.
-        QM_TRY_RETURN(DeleteFile(file, aQuotaManager, aPersistenceType,
-                                 aOriginMetadata, Idempotency::Yes));
+        QM_TRY_RETURN(
+            MOZ_TO_RESULT(DeleteFile(file, aQuotaManager, aPersistenceType,
+                                     aOriginMetadata, Idempotency::Yes)));
       },
       // UnknownDirEntryOp
       [aPersistenceType, &aOriginMetadata](
           nsIFile& file, const bool isDirectory) -> Result<Ok, nsresult> {
         // Unknown files and directories don't count towards quota.
 
         if (isDirectory) {
-          QM_TRY_RETURN(DeleteFilesNoQuota(file));
-        }
-
-        QM_TRY_RETURN(DeleteFile(file, /* doesn't count */ nullptr,
-                                 aPersistenceType, aOriginMetadata,
-                                 Idempotency::Yes));
+          QM_TRY_RETURN(MOZ_TO_RESULT(DeleteFilesNoQuota(file)));
+        }
+
+        QM_TRY_RETURN(MOZ_TO_RESULT(
+            DeleteFile(file, /* doesn't count */ nullptr, aPersistenceType,
+                       aOriginMetadata, Idempotency::Yes)));
       }));
 
   QM_TRY_RETURN(MOZ_TO_RESULT_INVOKE(aFileManagerDirectory, Remove, false));
 }
 
 // Idempotently delete all the parts of an IndexedDB database including its
 // SQLite database file, its WAL journal, it's shared-memory file, and its
 // Blob/Files sub-directory. A marker file is created prior to performing the
@@ -6689,17 +6692,17 @@ class DeserializeIndexValueHelper final 
     JS::Rooted<JSObject*> global(cx, GetSandbox(cx));
 
     QM_TRY(OkIf(global), NS_OK,
            [this](const NotOk) { OperationCompleted(NS_ERROR_FAILURE); });
 
     const JSAutoRealm ar(cx, global);
 
     JS::Rooted<JS::Value> value(cx);
-    QM_TRY(DeserializeIndexValue(cx, &value), NS_OK,
+    QM_TRY(MOZ_TO_RESULT(DeserializeIndexValue(cx, &value)), NS_OK,
            [this](const nsresult rv) { OperationCompleted(rv); });
 
     ErrorResult errorResult;
     IDBObjectStore::AppendIndexUpdateInfo(mIndexID, mKeyPath, mMultiEntry,
                                           mLocale, cx, value, &mUpdateInfoArray,
                                           &errorResult);
     QM_TRY(OkIf(!errorResult.Failed()), NS_OK,
            ([this, &errorResult](const NotOk) {
@@ -6825,17 +6828,17 @@ bool DeallocPBackgroundIndexedDBUtilsPar
   RefPtr<Utils> actor = dont_AddRef(static_cast<Utils*>(aActor));
   return true;
 }
 
 bool RecvFlushPendingFileDeletions() {
   AssertIsOnBackgroundThread();
 
   if (QuotaClient* quotaClient = QuotaClient::GetInstance()) {
-    QM_WARNONLY_TRY(quotaClient->FlushPendingFileDeletions());
+    QM_WARNONLY_TRY(QM_TO_RESULT(quotaClient->FlushPendingFileDeletions()));
   }
 
   return true;
 }
 
 RefPtr<mozilla::dom::quota::Client> CreateQuotaClient() {
   AssertIsOnBackgroundThread();
 
@@ -7179,17 +7182,17 @@ void DatabaseConnection::DoIdleProcessin
     MOZ_ASSERT(!mInReadTransaction);
     MOZ_ASSERT(!mInWriteTransaction);
 
     return res;
   }();
 
   // Truncate the WAL if we were asked to or if we managed to free some space.
   if (aNeedsCheckpoint || freedSomePages) {
-    QM_WARNONLY_TRY(CheckpointInternal(CheckpointMode::Truncate));
+    QM_WARNONLY_TRY(QM_TO_RESULT(CheckpointInternal(CheckpointMode::Truncate)));
   }
 
   // Finally try to restart the read transaction if we rolled it back earlier.
   if (beginStmt) {
     QM_WARNONLY_TRY(
         ToResult(beginStmt.Borrow()->Execute())
             .andThen([&self = *this](const Ok) -> Result<Ok, nsresult> {
               self.mInReadTransaction = true;
@@ -7287,17 +7290,18 @@ Result<bool, nsresult> DatabaseConnectio
                        this](Ok) -> Result<Ok, nsresult> {
                if (interrupted) {
                  rollback(Ok{});
                  freedSomePages = false;
                }
 
                if (freedSomePages) {
                  // Commit the write transaction.
-                 QM_TRY(commitStmt.Borrow()->Execute(), QM_PROPAGATE,
+                 QM_TRY(MOZ_TO_RESULT(commitStmt.Borrow()->Execute()),
+                        QM_PROPAGATE,
                         [](const auto&) { NS_WARNING("Failed to commit!"); });
 
                  mInWriteTransaction = false;
                }
 
                return Ok{};
              }),
          QM_PROPAGATE, rollback);
@@ -7431,17 +7435,17 @@ DatabaseConnection::AutoSavepoint::~Auto
     mConnection->AssertIsOnConnectionThread();
     MOZ_ASSERT(mDEBUGTransaction);
     MOZ_ASSERT(
         mDEBUGTransaction->GetMode() == IDBTransaction::Mode::ReadWrite ||
         mDEBUGTransaction->GetMode() == IDBTransaction::Mode::ReadWriteFlush ||
         mDEBUGTransaction->GetMode() == IDBTransaction::Mode::Cleanup ||
         mDEBUGTransaction->GetMode() == IDBTransaction::Mode::VersionChange);
 
-    QM_WARNONLY_TRY(mConnection->RollbackSavepoint());
+    QM_WARNONLY_TRY(QM_TO_RESULT(mConnection->RollbackSavepoint()));
   }
 }
 
 nsresult DatabaseConnection::AutoSavepoint::Start(
     const TransactionBase& aTransaction) {
   MOZ_ASSERT(aTransaction.GetMode() == IDBTransaction::Mode::ReadWrite ||
              aTransaction.GetMode() == IDBTransaction::Mode::ReadWriteFlush ||
              aTransaction.GetMode() == IDBTransaction::Mode::Cleanup ||
@@ -7589,27 +7593,27 @@ void DatabaseConnection::UpdateRefcountF
 
   AUTO_PROFILER_LABEL("DatabaseConnection::UpdateRefcountFunction::DidCommit",
                       DOM);
 
   for (const auto& entry : mFileInfoEntries.Values()) {
     entry->MaybeUpdateDBRefs();
   }
 
-  QM_WARNONLY_TRY(RemoveJournals(mJournalsToRemoveAfterCommit));
+  QM_WARNONLY_TRY(QM_TO_RESULT(RemoveJournals(mJournalsToRemoveAfterCommit)));
 }
 
 void DatabaseConnection::UpdateRefcountFunction::DidAbort() {
   MOZ_ASSERT(mConnection);
   mConnection->AssertIsOnConnectionThread();
 
   AUTO_PROFILER_LABEL("DatabaseConnection::UpdateRefcountFunction::DidAbort",
                       DOM);
 
-  QM_WARNONLY_TRY(RemoveJournals(mJournalsToRemoveAfterAbort));
+  QM_WARNONLY_TRY(QM_TO_RESULT(RemoveJournals(mJournalsToRemoveAfterAbort)));
 }
 
 void DatabaseConnection::UpdateRefcountFunction::StartSavepoint() {
   MOZ_ASSERT(mConnection);
   mConnection->AssertIsOnConnectionThread();
   MOZ_ASSERT(!mInSavepoint);
   MOZ_ASSERT(!mSavepointEntriesIndex.Count());
 
@@ -7746,17 +7750,17 @@ nsresult DatabaseConnection::UpdateRefco
   nsCOMPtr<nsIFile> journalDirectory = mFileManager.GetJournalDirectory();
   QM_TRY(OkIf(journalDirectory), NS_ERROR_FAILURE);
 
   for (const auto& journal : aJournals) {
     nsCOMPtr<nsIFile> file =
         DatabaseFileManager::GetFileForId(journalDirectory, journal);
     QM_TRY(OkIf(file), NS_ERROR_FAILURE);
 
-    QM_WARNONLY_TRY(file->Remove(false));
+    QM_WARNONLY_TRY(QM_TO_RESULT(file->Remove(false)));
   }
 
   return NS_OK;
 }
 
 NS_IMPL_ISUPPORTS(DatabaseConnection::UpdateRefcountFunction,
                   mozIStorageFunction)
 
@@ -12310,17 +12314,19 @@ nsCOMPtr<nsIFile> DatabaseFileManager::E
 
   if (exists) {
     QM_TRY_INSPECT(const bool& isDirectory,
                    MOZ_TO_RESULT_INVOKE(journalDirectory, IsDirectory),
                    nullptr);
 
     QM_TRY(OkIf(isDirectory), nullptr);
   } else {
-    QM_TRY(journalDirectory->Create(nsIFile::DIRECTORY_TYPE, 0755), nullptr);
+    QM_TRY(
+        MOZ_TO_RESULT(journalDirectory->Create(nsIFile::DIRECTORY_TYPE, 0755)),
+        nullptr);
   }
 
   return journalDirectory;
 }
 
 // static
 nsCOMPtr<nsIFile> DatabaseFileManager::GetFileForId(nsIFile* aDirectory,
                                                     int64_t aId) {
@@ -12838,29 +12844,30 @@ nsresult QuotaClient::GetUsageForOriginI
                            subdirNameBase)));
                      }()),
                      // Fallback.
                      ([&directory,
                        &subdirName](const NotOk) -> Result<Ok, nsresult> {
                        // If there is an unexpected directory in the idb
                        // directory, trying to delete at first instead of
                        // breaking the whole initialization.
-                       QM_TRY(DeleteFilesNoQuota(directory, subdirName),
+                       QM_TRY(MOZ_TO_RESULT(
+                                  DeleteFilesNoQuota(directory, subdirName)),
                               Err(NS_ERROR_UNEXPECTED));
 
                        return Ok{};
                      })),
                  Ok{});
 
           if (obsoleteFilenames.Contains(subdirNameBase)) {
             // If this fails, it probably means we are in a serious situation.
             // e.g. Filesystem corruption. Will handle this in bug 1521541.
-            QM_TRY(RemoveDatabaseFilesAndDirectory(*directory, subdirNameBase,
-                                                   nullptr, aPersistenceType,
-                                                   aOriginMetadata, u""_ns),
+            QM_TRY(MOZ_TO_RESULT(RemoveDatabaseFilesAndDirectory(
+                       *directory, subdirNameBase, nullptr, aPersistenceType,
+                       aOriginMetadata, u""_ns)),
                    Err(NS_ERROR_UNEXPECTED));
 
             databaseFilenames.Remove(subdirNameBase);
             return Ok{};
           }
 
           // The directory base must exist in databaseFilenames.
           // If there is an unexpected directory in the idb directory, trying to
@@ -12874,17 +12881,17 @@ nsresult QuotaClient::GetUsageForOriginI
               OkIf(databaseFilenames.Contains(subdirNameBase))
                   .mapErr([](const NotOk) { return NS_ERROR_FAILURE; }),
               // Fallback.
               ([&directory,
                 &subdirName](const nsresult) -> Result<Ok, nsresult> {
                 // XXX It seems if we really got here, we can fail the
                 // MOZ_ASSERT(!quotaManager->IsTemporaryStorageInitialized());
                 // assertion in DeleteFilesNoQuota.
-                QM_TRY(DeleteFilesNoQuota(directory, subdirName),
+                QM_TRY(MOZ_TO_RESULT(DeleteFilesNoQuota(directory, subdirName)),
                        Err(NS_ERROR_UNEXPECTED));
 
                 return Ok{};
               })));
 
           return Ok{};
         }));
   }
@@ -13361,18 +13368,19 @@ void DeleteFilesRunnable::DirectoryLockA
   mDirectoryLock = aLock;
 
   QuotaManager* const quotaManager = QuotaManager::Get();
   MOZ_ASSERT(quotaManager);
 
   // Must set this before dispatching otherwise we will race with the IO thread
   mState = State_DatabaseWorkOpen;
 
-  QM_TRY(quotaManager->IOThread()->Dispatch(this, NS_DISPATCH_NORMAL), QM_VOID,
-         [this](const nsresult) { Finish(); });
+  QM_TRY(MOZ_TO_RESULT(
+             quotaManager->IOThread()->Dispatch(this, NS_DISPATCH_NORMAL)),
+         QM_VOID, [this](const nsresult) { Finish(); });
 }
 
 void DeleteFilesRunnable::DirectoryLockFailed() {
   AssertIsOnBackgroundThread();
   MOZ_ASSERT(mState == State_DirectoryOpenPending);
   MOZ_ASSERT(!mDirectoryLock);
 
   Finish();
@@ -13517,17 +13525,18 @@ nsresult Maintenance::DirectoryOpen() {
     return NS_ERROR_ABORT;
   }
 
   QuotaManager* const quotaManager = QuotaManager::Get();
   MOZ_ASSERT(quotaManager);
 
   mState = State::DirectoryWorkOpen;
 
-  QM_TRY(quotaManager->IOThread()->Dispatch(this, NS_DISPATCH_NORMAL),
+  QM_TRY(MOZ_TO_RESULT(
+             quotaManager->IOThread()->Dispatch(this, NS_DISPATCH_NORMAL)),
          NS_ERROR_FAILURE);
 
   return NS_OK;
 }
 
 nsresult Maintenance::DirectoryWork() {
   AssertIsOnIOThread();
   MOZ_ASSERT(mState == State::DirectoryWorkOpen);
@@ -13552,17 +13561,18 @@ nsresult Maintenance::DirectoryWork() {
   // Since idle maintenance may occur before temporary storage is initialized,
   // make sure it's initialized here (all non-persistent origins need to be
   // cleaned up and quota info needs to be loaded for them).
 
   // Don't fail whole idle maintenance in case of an error, the persistent
   // repository can still
   // be processed.
   const bool initTemporaryStorageFailed = [&quotaManager] {
-    QM_TRY(quotaManager->EnsureTemporaryStorageIsInitialized(), true);
+    QM_TRY(MOZ_TO_RESULT(quotaManager->EnsureTemporaryStorageIsInitialized()),
+           true);
     return false;
   }();
 
   const nsCOMPtr<nsIFile> storageDir =
       GetFileForPath(quotaManager->GetStoragePath());
   QM_TRY(OkIf(storageDir), NS_ERROR_FAILURE);
 
   {
@@ -13909,33 +13919,33 @@ Maintenance::Run() {
         MOZ_ALWAYS_SUCCEEDS(mQuotaClient->BackgroundThread()->Dispatch(
             this, NS_DISPATCH_NORMAL));
       }
     }
   };
 
   switch (mState) {
     case State::Initial:
-      QM_TRY(Start(), NS_OK, handleError);
+      QM_TRY(MOZ_TO_RESULT(Start()), NS_OK, handleError);
       break;
 
     case State::CreateIndexedDatabaseManager:
-      QM_TRY(CreateIndexedDatabaseManager(), NS_OK, handleError);
+      QM_TRY(MOZ_TO_RESULT(CreateIndexedDatabaseManager()), NS_OK, handleError);
       break;
 
     case State::IndexedDatabaseManagerOpen:
-      QM_TRY(OpenDirectory(), NS_OK, handleError);
+      QM_TRY(MOZ_TO_RESULT(OpenDirectory()), NS_OK, handleError);
       break;
 
     case State::DirectoryWorkOpen:
-      QM_TRY(DirectoryWork(), NS_OK, handleError);
+      QM_TRY(MOZ_TO_RESULT(DirectoryWork()), NS_OK, handleError);
       break;
 
     case State::BeginDatabaseMaintenance:
-      QM_TRY(BeginDatabaseMaintenance(), NS_OK, handleError);
+      QM_TRY(MOZ_TO_RESULT(BeginDatabaseMaintenance()), NS_OK, handleError);
       break;
 
     case State::Finishing:
       Finish();
       break;
 
     default:
       MOZ_CRASH("Bad state!");
@@ -15503,17 +15513,18 @@ nsresult FactoryOp::SendToIOThread() {
   }
 
   QuotaManager* const quotaManager = QuotaManager::Get();
   MOZ_ASSERT(quotaManager);
 
   // Must set this before dispatching otherwise we will race with the IO thread.
   mState = State::DatabaseWorkOpen;
 
-  QM_TRY(quotaManager->IOThread()->Dispatch(this, NS_DISPATCH_NORMAL),
+  QM_TRY(MOZ_TO_RESULT(
+             quotaManager->IOThread()->Dispatch(this, NS_DISPATCH_NORMAL)),
          NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR, IDB_REPORT_INTERNAL_ERR_LAMBDA);
 
   return NS_OK;
 }
 
 void FactoryOp::WaitForTransactions() {
   AssertIsOnOwningThread();
   MOZ_ASSERT(mState == State::BeginVersionChange ||
@@ -15851,49 +15862,49 @@ FactoryOp::Run() {
         MOZ_ALWAYS_SUCCEEDS(
             mOwningEventTarget->Dispatch(this, NS_DISPATCH_NORMAL));
       }
     }
   };
 
   switch (mState) {
     case State::Initial:
-      QM_TRY(Open(), NS_OK, handleError);
+      QM_TRY(MOZ_TO_RESULT(Open()), NS_OK, handleError);
       break;
 
     case State::PermissionChallenge:
-      QM_TRY(ChallengePermission(), NS_OK, handleError);
+      QM_TRY(MOZ_TO_RESULT(ChallengePermission()), NS_OK, handleError);
       break;
 
     case State::PermissionRetry:
-      QM_TRY(RetryCheckPermission(), NS_OK, handleError);
+      QM_TRY(MOZ_TO_RESULT(RetryCheckPermission()), NS_OK, handleError);
       break;
 
     case State::FinishOpen:
-      QM_TRY(FinishOpen(), NS_OK, handleError);
+      QM_TRY(MOZ_TO_RESULT(FinishOpen()), NS_OK, handleError);
       break;
 
     case State::QuotaManagerPending:
-      QM_TRY(QuotaManagerOpen(), NS_OK, handleError);
+      QM_TRY(MOZ_TO_RESULT(QuotaManagerOpen()), NS_OK, handleError);
       break;
 
     case State::DatabaseOpenPending:
-      QM_TRY(DatabaseOpen(), NS_OK, handleError);
+      QM_TRY(MOZ_TO_RESULT(DatabaseOpen()), NS_OK, handleError);
       break;
 
     case State::DatabaseWorkOpen:
-      QM_TRY(DoDatabaseWork(), NS_OK, handleError);
+      QM_TRY(MOZ_TO_RESULT(DoDatabaseWork()), NS_OK, handleError);
       break;
 
     case State::BeginVersionChange:
-      QM_TRY(BeginVersionChange(), NS_OK, handleError);
+      QM_TRY(MOZ_TO_RESULT(BeginVersionChange()), NS_OK, handleError);
       break;
 
     case State::WaitingForTransactionsToComplete:
-      QM_TRY(DispatchToWorkThread(), NS_OK, handleError);
+      QM_TRY(MOZ_TO_RESULT(DispatchToWorkThread()), NS_OK, handleError);
       break;
 
     case State::SendingResults:
       SendResults();
       break;
 
     default:
       MOZ_CRASH("Bad state!");
@@ -15908,17 +15919,17 @@ void FactoryOp::DirectoryLockAcquired(Di
   MOZ_ASSERT(mState == State::DirectoryOpenPending);
   MOZ_ASSERT(!mDirectoryLock);
 
   mDirectoryLock = aLock;
 
   MOZ_ASSERT(mDirectoryLock->Id() >= 0);
   mDirectoryLockId = mDirectoryLock->Id();
 
-  QM_TRY(DirectoryOpen(), QM_VOID, [this](const nsresult rv) {
+  QM_TRY(MOZ_TO_RESULT(DirectoryOpen()), QM_VOID, [this](const nsresult rv) {
     SetFailureCodeIfUnset(rv);
 
     // The caller holds a strong reference to us, no need for a self reference
     // before calling Run().
 
     mState = State::SendingResults;
     MOZ_ALWAYS_SUCCEEDS(Run());
   });
@@ -18067,21 +18078,21 @@ DatabaseOp::Run() {
 
       MOZ_ALWAYS_SUCCEEDS(
           mOwningEventTarget->Dispatch(this, NS_DISPATCH_NORMAL));
     }
   };
 
   switch (mState) {
     case State::Initial:
-      QM_TRY(SendToIOThread(), NS_OK, handleError);
+      QM_TRY(MOZ_TO_RESULT(SendToIOThread()), NS_OK, handleError);
       break;
 
     case State::DatabaseWork:
-      QM_TRY(DoDatabaseWork(), NS_OK, handleError);
+      QM_TRY(MOZ_TO_RESULT(DoDatabaseWork()), NS_OK, handleError);
       break;
 
     case State::SendingResults:
       SendResults();
       break;
 
     default:
       MOZ_CRASH("Bad state!");
@@ -18287,17 +18298,17 @@ nsresult CreateObjectStoreOp::DoDatabase
             .map(IsSome),
         QM_ASSERT_UNREACHABLE);
 
     MOZ_ASSERT(!hasResult);
   }
 #endif
 
   DatabaseConnection::AutoSavepoint autoSave;
-  QM_TRY(autoSave.Start(Transaction())
+  QM_TRY(MOZ_TO_RESULT(autoSave.Start(Transaction()))
 #ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
              ,
          QM_PROPAGATE, MakeAutoSavepointCleanupHandler(*aConnection)
 #endif
   );
 
   // The parameter names are not used, parameters are bound by index only
   // locally in the same function.
@@ -18374,17 +18385,17 @@ nsresult DeleteObjectStoreOp::DoDatabase
     MOZ_ASSERT_IF(mIsLastObjectStore,
                   foundThisObjectStore && !foundOtherObjectStore);
     MOZ_ASSERT_IF(!mIsLastObjectStore,
                   foundThisObjectStore && foundOtherObjectStore);
   }
 #endif
 
   DatabaseConnection::AutoSavepoint autoSave;
-  QM_TRY(autoSave.Start(Transaction())
+  QM_TRY(MOZ_TO_RESULT(autoSave.Start(Transaction()))
 #ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
              ,
          QM_PROPAGATE, MakeAutoSavepointCleanupHandler(*aConnection)
 #endif
   );
 
   if (mIsLastObjectStore) {
     // We can just delete everything if this is the last object store.
@@ -18485,17 +18496,17 @@ nsresult RenameObjectStoreOp::DoDatabase
                        .map(IsSome),
                    QM_ASSERT_UNREACHABLE);
 
     MOZ_ASSERT(!hasResult);
   }
 #endif
 
   DatabaseConnection::AutoSavepoint autoSave;
-  QM_TRY(autoSave.Start(Transaction())
+  QM_TRY(MOZ_TO_RESULT(autoSave.Start(Transaction()))
 #ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
              ,
          QM_PROPAGATE, MakeAutoSavepointCleanupHandler(*aConnection)
 #endif
   );
 
   // The parameter names are not used, parameters are bound by index only
   // locally in the same function.
@@ -18651,17 +18662,17 @@ nsresult CreateIndexOp::DoDatabaseWork(D
             .map(IsSome),
         QM_ASSERT_UNREACHABLE);
 
     MOZ_ASSERT(!hasResult);
   }
 #endif
 
   DatabaseConnection::AutoSavepoint autoSave;
-  QM_TRY(autoSave.Start(Transaction())
+  QM_TRY(MOZ_TO_RESULT(autoSave.Start(Transaction()))
 #ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
              ,
          QM_PROPAGATE, MakeAutoSavepointCleanupHandler(*aConnection)
 #endif
   );
 
   // The parameter names are not used, parameters are bound by index only
   // locally in the same function.
@@ -18982,17 +18993,17 @@ nsresult DeleteIndexOp::DoDatabaseWork(D
     MOZ_ASSERT_IF(mIsLastIndex, foundThisIndex && !foundOtherIndex);
     MOZ_ASSERT_IF(!mIsLastIndex, foundThisIndex && foundOtherIndex);
   }
 #endif
 
   AUTO_PROFILER_LABEL("DeleteIndexOp::DoDatabaseWork", DOM);
 
   DatabaseConnection::AutoSavepoint autoSave;
-  QM_TRY(autoSave.Start(Transaction())
+  QM_TRY(MOZ_TO_RESULT(autoSave.Start(Transaction()))
 #ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
              ,
          QM_PROPAGATE, MakeAutoSavepointCleanupHandler(*aConnection)
 #endif
   );
 
   // mozStorage warns that these statements trigger a sort operation but we
   // don't care because this is a very rare call and we expect it to be slow.
@@ -19200,17 +19211,17 @@ nsresult RenameIndexOp::DoDatabaseWork(D
 
     MOZ_ASSERT(!hasResult);
   }
 #else
   Unused << mObjectStoreId;
 #endif
 
   DatabaseConnection::AutoSavepoint autoSave;
-  QM_TRY(autoSave.Start(Transaction())
+  QM_TRY(MOZ_TO_RESULT(autoSave.Start(Transaction()))
 #ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
              ,
          QM_PROPAGATE, MakeAutoSavepointCleanupHandler(*aConnection)
 #endif
   );
 
   // The parameter names are not used, parameters are bound by index only
   // locally in the same function.
@@ -19566,17 +19577,17 @@ nsresult ObjectStoreAddOrPutRequestOp::D
     DatabaseConnection* aConnection) {
   MOZ_ASSERT(aConnection);
   aConnection->AssertIsOnConnectionThread();
   MOZ_ASSERT(aConnection->HasStorageConnection());
 
   AUTO_PROFILER_LABEL("ObjectStoreAddOrPutRequestOp::DoDatabaseWork", DOM);
 
   DatabaseConnection::AutoSavepoint autoSave;
-  QM_TRY(autoSave.Start(Transaction())
+  QM_TRY(MOZ_TO_RESULT(autoSave.Start(Transaction()))
 #ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
              ,
          QM_PROPAGATE, MakeAutoSavepointCleanupHandler(*aConnection)
 #endif
   );
 
   QM_TRY_INSPECT(const bool& objectStoreHasIndexes,
                  ObjectStoreHasIndexes(*aConnection, mParams.objectStoreId(),
@@ -19739,17 +19750,18 @@ nsresult ObjectStoreAddOrPutRequestOp::D
         MOZ_ASSERT(storedFileInfo.IsValid());
 
         QM_TRY_INSPECT(const auto& inputStream,
                        storedFileInfo.GetInputStream());
 
         if (inputStream) {
           if (fileHelper.isNothing()) {
             fileHelper.emplace(Transaction().GetDatabase().GetFileManagerPtr());
-            QM_TRY(fileHelper->Init(), NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR,
+            QM_TRY(MOZ_TO_RESULT(fileHelper->Init()),
+                   NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR,
                    IDB_REPORT_INTERNAL_ERR_LAMBDA);
           }
 
           const DatabaseFileInfo& fileInfo = storedFileInfo.GetFileInfo();
 
           const auto file = fileHelper->GetFile(fileInfo);
           QM_TRY(OkIf(file), NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR,
                  IDB_REPORT_INTERNAL_ERR_LAMBDA);
@@ -19769,37 +19781,38 @@ nsresult ObjectStoreAddOrPutRequestOp::D
                       IDB_REPORT_INTERNAL_ERR();
                       return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
                     }
                     return rv;
                   }),
               QM_PROPAGATE,
               ([this, &file = *file, &journalFile = *journalFile](const auto) {
                 // Try to remove the file if the copy failed.
-                QM_TRY(
-                    Transaction().GetDatabase().GetFileManager().SyncDeleteFile(
-                        file, journalFile),
-                    QM_VOID);
+                QM_TRY(MOZ_TO_RESULT(Transaction()
+                                         .GetDatabase()
+                                         .GetFileManager()
+                                         .SyncDeleteFile(file, journalFile)),
+                       QM_VOID);
               }));
 
           storedFileInfo.NotifyWriteSucceeded();
         }
 
         if (!fileIds.IsEmpty()) {
           fileIds.Append(' ');
         }
         storedFileInfo.Serialize(fileIds);
       }
 
       QM_TRY(stmt->BindStringByName(kStmtParamNameFileIds, fileIds));
     } else {
       QM_TRY(stmt->BindNullByName(kStmtParamNameFileIds));
     }
 
-    QM_TRY(stmt->Execute(), QM_PROPAGATE,
+    QM_TRY(MOZ_TO_RESULT(stmt->Execute()), QM_PROPAGATE,
            [keyUnset = DebugOnly{keyUnset}](const nsresult rv) {
              if (rv == NS_ERROR_STORAGE_CONSTRAINT) {
                MOZ_ASSERT(!keyUnset, "Generated key had a collision!");
              }
            });
   }
 
   // Update our indexes if needed.
@@ -20186,17 +20199,17 @@ ObjectStoreDeleteRequestOp::ObjectStoreD
 
 nsresult ObjectStoreDeleteRequestOp::DoDatabaseWork(
     DatabaseConnection* aConnection) {
   MOZ_ASSERT(aConnection);
   aConnection->AssertIsOnConnectionThread();
   AUTO_PROFILER_LABEL("ObjectStoreDeleteRequestOp::DoDatabaseWork", DOM);
 
   DatabaseConnection::AutoSavepoint autoSave;
-  QM_TRY(autoSave.Start(Transaction())
+  QM_TRY(MOZ_TO_RESULT(autoSave.Start(Transaction()))
 #ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
              ,
          QM_PROPAGATE, MakeAutoSavepointCleanupHandler(*aConnection)
 #endif
   );
 
   QM_TRY_INSPECT(const bool& objectStoreHasIndexes,
                  ObjectStoreHasIndexes(*aConnection, mParams.objectStoreId(),
@@ -20246,17 +20259,17 @@ ObjectStoreClearRequestOp::ObjectStoreCl
 nsresult ObjectStoreClearRequestOp::DoDatabaseWork(
     DatabaseConnection* aConnection) {
   MOZ_ASSERT(aConnection);
   aConnection->AssertIsOnConnectionThread();
 
   AUTO_PROFILER_LABEL("ObjectStoreClearRequestOp::DoDatabaseWork", DOM);
 
   DatabaseConnection::AutoSavepoint autoSave;
-  QM_TRY(autoSave.Start(Transaction())
+  QM_TRY(MOZ_TO_RESULT(autoSave.Start(Transaction()))
 #ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
              ,
          QM_PROPAGATE, MakeAutoSavepointCleanupHandler(*aConnection)
 #endif
   );
 
   QM_TRY_INSPECT(const bool& objectStoreHasIndexes,
                  ObjectStoreHasIndexes(*aConnection, mParams.objectStoreId(),
--- a/dom/indexedDB/ActorsParentCommon.cpp
+++ b/dom/indexedDB/ActorsParentCommon.cpp
@@ -154,17 +154,19 @@ class SandboxHolder final {
       nsIXPConnect* const xpc = nsContentUtils::XPConnect();
       MOZ_ASSERT(xpc, "This should never be null!");
 
       // Let's use a null principal.
       const nsCOMPtr<nsIPrincipal> principal =
           NullPrincipal::CreateWithoutOriginAttributes();
 
       JS::Rooted<JSObject*> sandbox(aCx);
-      QM_TRY(xpc->CreateSandbox(aCx, principal, sandbox.address()), nullptr);
+      QM_TRY(
+          MOZ_TO_RESULT(xpc->CreateSandbox(aCx, principal, sandbox.address())),
+          nullptr);
 
       mSandbox = new JSObjectHolder(aCx, sandbox);
     }
 
     return mSandbox->GetJSObject();
   }
 
   RefPtr<JSObjectHolder> mSandbox;
--- a/dom/indexedDB/FileInfoImpl.h
+++ b/dom/indexedDB/FileInfoImpl.h
@@ -4,16 +4,18 @@
  * 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 DOM_INDEXEDDB_FILEINFOIMPL_H_
 #define DOM_INDEXEDDB_FILEINFOIMPL_H_
 
 #include "FileInfo.h"
 
+#include "mozilla/dom/QMResult.h"
+#include "mozilla/dom/QMResultInlines.h"
 #include "mozilla/dom/quota/QuotaCommon.h"
 #include "mozilla/Mutex.h"
 #include "nsIFile.h"
 
 namespace mozilla {
 namespace dom {
 namespace indexedDB {
 
@@ -84,17 +86,17 @@ void FileInfo<FileManager>::UpdateRefere
     // cleanup anymore. In that case, the entire origin directory has already
     // been deleted by the quota manager, and we don't need to delete individual
     // files.
     needsCleanup = !mFileManager->Invalidated();
   }
 
   if (needsCleanup) {
     if (aSyncDeleteFile) {
-      QM_WARNONLY_TRY(mFileManager->SyncDeleteFile(Id()));
+      QM_WARNONLY_TRY(QM_TO_RESULT(mFileManager->SyncDeleteFile(Id())));
     } else {
       Cleanup();
     }
   }
 
   delete this;
 }
 
@@ -123,17 +125,17 @@ bool FileInfo<FileManager>::LockedClearD
 
   delete this;
 
   return false;
 }
 
 template <typename FileManager>
 void FileInfo<FileManager>::Cleanup() {
-  QM_WARNONLY_TRY(mFileManager->AsyncDeleteFile(Id()));
+  QM_WARNONLY_TRY(QM_TO_RESULT(mFileManager->AsyncDeleteFile(Id())));
 }
 
 template <typename FileManager>
 nsCOMPtr<nsIFile> FileInfo<FileManager>::GetFileForFileInfo() const {
   const nsCOMPtr<nsIFile> directory = Manager().GetDirectory();
   if (NS_WARN_IF(!directory)) {
     return nullptr;
   }
--- a/dom/indexedDB/IDBDatabase.cpp
+++ b/dom/indexedDB/IDBDatabase.cpp
@@ -28,16 +28,17 @@
 #include "mozilla/dom/DOMStringListBinding.h"
 #include "mozilla/dom/Exceptions.h"
 #include "mozilla/dom/File.h"
 #include "mozilla/dom/IDBDatabaseBinding.h"
 #include "mozilla/dom/IDBObjectStoreBinding.h"
 #include "mozilla/dom/indexedDB/PBackgroundIDBDatabaseFileChild.h"
 #include "mozilla/dom/indexedDB/PBackgroundIDBSharedTypes.h"
 #include "mozilla/dom/IPCBlobUtils.h"
+#include "mozilla/dom/QMResultInlines.h"
 #include "mozilla/dom/quota/QuotaManager.h"
 #include "mozilla/ipc/BackgroundChild.h"
 #include "mozilla/ipc/BackgroundUtils.h"
 #include "mozilla/ipc/FileDescriptor.h"
 #include "mozilla/ipc/InputStreamParams.h"
 #include "mozilla/ipc/InputStreamUtils.h"
 #include "nsCOMPtr.h"
 #include "mozilla/dom/Document.h"
@@ -199,20 +200,20 @@ RefPtr<IDBDatabase> IDBDatabase::Create(
       nsCOMPtr<nsIObserverService> obsSvc = GetObserverService();
       MOZ_ASSERT(obsSvc);
 
       // This topic must be successfully registered.
       MOZ_ALWAYS_SUCCEEDS(
           obsSvc->AddObserver(observer, kWindowObserverTopic, false));
 
       // These topics are not crucial.
-      QM_WARNONLY_TRY(
-          obsSvc->AddObserver(observer, kCycleCollectionObserverTopic, false));
-      QM_WARNONLY_TRY(
-          obsSvc->AddObserver(observer, kMemoryPressureObserverTopic, false));
+      QM_WARNONLY_TRY(QM_TO_RESULT(
+          obsSvc->AddObserver(observer, kCycleCollectionObserverTopic, false)));
+      QM_WARNONLY_TRY(QM_TO_RESULT(
+          obsSvc->AddObserver(observer, kMemoryPressureObserverTopic, false)));
 
       db->mObserver = std::move(observer);
     }
   }
 
   db->IncreaseActiveDatabaseCount();
 
   return db;
--- a/dom/indexedDB/IndexedDatabaseManager.cpp
+++ b/dom/indexedDB/IndexedDatabaseManager.cpp
@@ -249,17 +249,17 @@ IndexedDatabaseManager* IndexedDatabaseM
     return nullptr;
   }
 
   if (!gDBManager) {
     sIsMainProcess = XRE_IsParentProcess();
 
     RefPtr<IndexedDatabaseManager> instance(new IndexedDatabaseManager());
 
-    QM_TRY(instance->Init(), nullptr);
+    QM_TRY(MOZ_TO_RESULT(instance->Init()), nullptr);
 
     if (gInitialized.exchange(true)) {
       NS_ERROR("Initialized more than once?!");
     }
 
     gDBManager = instance;
 
     ClearOnShutdown(&gDBManager);
--- a/dom/indexedDB/PermissionRequestBase.cpp
+++ b/dom/indexedDB/PermissionRequestBase.cpp
@@ -113,23 +113,24 @@ PermissionRequestBase::PromptIfNeeded() 
   if (currentValue == kPermissionPrompt) {
     nsCOMPtr<nsIObserverService> obsSvc = GetObserverService();
     QM_TRY(OkIf(obsSvc), Err(NS_ERROR_FAILURE));
 
     // We're about to prompt so move the members back.
     mOwnerElement = std::move(element);
     mPrincipal = std::move(principal);
 
-    QM_TRY(obsSvc->NotifyObservers(static_cast<nsIObserver*>(this),
-                                   kPermissionPromptTopic, nullptr),
-           QM_PROPAGATE, [this](const auto&) {
-             // Finally release if we failed the prompt.
-             mOwnerElement = nullptr;
-             mPrincipal = nullptr;
-           });
+    QM_TRY(
+        MOZ_TO_RESULT(obsSvc->NotifyObservers(static_cast<nsIObserver*>(this),
+                                              kPermissionPromptTopic, nullptr)),
+        QM_PROPAGATE, [this](const auto&) {
+          // Finally release if we failed the prompt.
+          mOwnerElement = nullptr;
+          mPrincipal = nullptr;
+        });
   }
 
   return currentValue;
 }
 
 void PermissionRequestBase::SetExplicitPermission(nsIPrincipal* aPrincipal,
                                                   uint32_t aIntPermission) {
   AssertSanity();
--- a/dom/localstorage/ActorsParent.cpp
+++ b/dom/localstorage/ActorsParent.cpp
@@ -62,16 +62,17 @@
 #include "mozilla/dom/Nullable.h"
 #include "mozilla/dom/PBackgroundLSDatabase.h"
 #include "mozilla/dom/PBackgroundLSDatabaseParent.h"
 #include "mozilla/dom/PBackgroundLSObserverParent.h"
 #include "mozilla/dom/PBackgroundLSRequestParent.h"
 #include "mozilla/dom/PBackgroundLSSharedTypes.h"
 #include "mozilla/dom/PBackgroundLSSimpleRequestParent.h"
 #include "mozilla/dom/PBackgroundLSSnapshotParent.h"
+#include "mozilla/dom/QMResultInlines.h"
 #include "mozilla/dom/SnappyUtils.h"
 #include "mozilla/dom/StorageDBUpdater.h"
 #include "mozilla/dom/StorageUtils.h"
 #include "mozilla/dom/ipc/IdType.h"
 #include "mozilla/dom/quota/CachingDatabaseConnection.h"
 #include "mozilla/dom/quota/CheckedUnsafePtr.h"
 #include "mozilla/dom/quota/Client.h"
 #include "mozilla/dom/quota/ClientImpl.h"
@@ -3129,17 +3130,17 @@ void InitializeLocalStorage() {
   if (!QuotaManager::IsRunningGTests()) {
     // This service has to be started on the main thread currently.
     const nsCOMPtr<mozIStorageService> ss =
         do_GetService(MOZ_STORAGE_SERVICE_CONTRACTID);
 
     QM_WARNONLY_TRY(OkIf(ss));
   }
 
-  QM_WARNONLY_TRY(QuotaClient::Initialize());
+  QM_WARNONLY_TRY(QM_TO_RESULT(QuotaClient::Initialize()));
 
   Preferences::RegisterCallbackAndCall(ShadowWritesPrefChangedCallback,
                                        kShadowWritesPref);
 
   Preferences::RegisterCallbackAndCall(SnapshotPrefillPrefChangedCallback,
                                        kSnapshotPrefillPref);
 
   Preferences::RegisterCallbackAndCall(
@@ -8806,20 +8807,20 @@ AutoWriteTransaction::AutoWriteTransacti
 }
 
 AutoWriteTransaction::~AutoWriteTransaction() {
   AssertIsOnGlobalConnectionThread();
 
   MOZ_COUNT_DTOR(mozilla::dom::AutoWriteTransaction);
 
   if (mConnection) {
-    QM_WARNONLY_TRY(mConnection->RollbackWriteTransaction());
+    QM_WARNONLY_TRY(QM_TO_RESULT(mConnection->RollbackWriteTransaction()));
 
     if (mShadowWrites) {
-      QM_WARNONLY_TRY(DetachShadowDatabaseAndUnlock());
+      QM_WARNONLY_TRY(QM_TO_RESULT(DetachShadowDatabaseAndUnlock()));
     }
   }
 }
 
 nsresult AutoWriteTransaction::Start(Connection* aConnection) {
   AssertIsOnGlobalConnectionThread();
   MOZ_ASSERT(aConnection);
   MOZ_ASSERT(!mConnection);
--- a/dom/media/webrtc/tests/mochitests/mochitest.ini
+++ b/dom/media/webrtc/tests/mochitests/mochitest.ini
@@ -180,22 +180,21 @@ scheme=http
 skip-if =
     toolkit == 'android' # websockets don't work on android (bug 1266217)
 scheme=http
 [test_peerConnection_basicAudioNoisyUDPBlock.html]
 skip-if =
     toolkit == 'android' # websockets don't work on android (bug 1266217)
 scheme=http
 [test_peerConnection_basicAudioNATRelayTLS.html]
-skip-if = true # need pyopenssl on builders, see bug 1323439 # toolkit == 'android' # websockets don't work on android (bug 1266217)
+skip-if = toolkit == 'android' # websockets don't work on android (bug 1266217)
 scheme=http
 [test_peerConnection_basicAudioRelayPolicy.html]
 skip-if =
     toolkit == 'android' # websockets don't work on android (bug 1266217)
-    win10_2004 # Bug 1718297
 scheme=http
 [test_peerConnection_basicAudioRequireEOC.html]
 [test_peerConnection_basicAudioPcmaPcmuOnly.html]
 [test_peerConnection_basicAudioDynamicPtMissingRtpmap.html]
 [test_peerConnection_basicAudioVerifyRtpHeaderExtensions.html]
 [test_peerConnection_basicAudioVideo.html]
 [test_peerConnection_basicAudioVideoCombined.html]
 [test_peerConnection_basicAudioVideoVerifyExtmap.html]
@@ -211,17 +210,16 @@ scheme=http
 # frequent timeouts/crashes on e10s (bug 1048455)
 skip-if = toolkit == 'android' # no screenshare on android
 [test_peerConnection_basicWindowshare.html]
 # frequent timeouts/crashes on e10s (bug 1048455)
 skip-if = toolkit == 'android' # no screenshare on android
 [test_peerConnection_basicH264Video.html]
 skip-if =
     toolkit == 'android' && is_emulator  # Bug 1355786, No h264 support on android emulator
-    fission && debug && os == 'windows'  # Bug 1694828, Initial triage in new task
 [test_peerConnection_bug822674.html]
 scheme=http
 [test_peerConnection_bug825703.html]
 scheme=http
 [test_peerConnection_bug827843.html]
 [test_peerConnection_bug834153.html]
 scheme=http
 [test_peerConnection_bug1013809.html]
@@ -313,19 +311,18 @@ skip-if = (os == 'win' && processor == '
 [test_peerConnection_toJSON.html]
 scheme=http
 [test_peerConnection_trackDisabling_clones.html]
 [test_peerConnection_trackDisabling.html]
 skip-if = toolkit == 'android' # Bug 1614460
 [test_peerConnection_twoAudioStreams.html]
 [test_peerConnection_twoAudioTracksInOneStream.html]
 [test_peerConnection_twoAudioVideoStreams.html]
-skip-if = (os == 'linux' && debug && e10s) # Bug 1171255 for Linux debug e10s
 [test_peerConnection_twoAudioVideoStreamsCombined.html]
-skip-if = (os == 'linux' && debug && e10s) || (toolkit == 'android') || (os == 'linux' && asan) # Bug 1127828 for Linux debug e10s, android(Bug 1189784), Bug 1480942 for Linux asan
+skip-if = (toolkit == 'android') || (os == 'linux' && asan) # android(Bug 1189784), Bug 1480942 for Linux asan
 [test_peerConnection_twoVideoStreams.html]
 [test_peerConnection_twoVideoTracksInOneStream.html]
 [test_peerConnection_addAudioTrackToExistingVideoStream.html]
 [test_peerConnection_addSecondAudioStream.html]
 [test_peerConnection_answererAddSecondAudioStream.html]
 [test_peerConnection_removeAudioTrack.html]
 [test_peerConnection_removeThenAddAudioTrack.html]
 [test_peerConnection_addSecondVideoStream.html]
--- a/dom/media/webrtc/tests/mochitests/pc.js
+++ b/dom/media/webrtc/tests/mochitests/pc.js
@@ -2275,16 +2275,20 @@ PeerConnectionWrapper.prototype = {
         JSON.stringify(rCand)
     );
     expectedLocalCandidateType = expectedLocalCandidateType || "host";
     var candidateType = lCand.candidateType;
     if (lCand.relayProtocol === "tcp" && candidateType === "relay") {
       candidateType = "relay-tcp";
     }
 
+    if (lCand.relayProtocol === "tls" && candidateType === "relay") {
+      candidateType = "relay-tls";
+    }
+
     if (expectedLocalCandidateType === "srflx" && candidateType === "prflx") {
       // Be forgiving of prflx when expecting srflx, since that can happen due
       // to timing.
       candidateType = "srflx";
     }
 
     is(
       candidateType,
--- a/dom/media/webrtc/tests/mochitests/test_peerConnection_basicAudioNATRelayTCP.html
+++ b/dom/media/webrtc/tests/mochitests/test_peerConnection_basicAudioNATRelayTCP.html
@@ -19,16 +19,17 @@ if (!("mediaDevices" in navigator)) {
 } else {
   runNetworkTest(async (options = {}) => {
     await pushPrefs(
         ['media.peerconnection.ice.obfuscate_host_addresses', false],
         ['media.peerconnection.nat_simulator.filtering_type', 'PORT_DEPENDENT'],
         ['media.peerconnection.nat_simulator.mapping_type', 'PORT_DEPENDENT'],
         ['media.peerconnection.nat_simulator.block_udp', true],
         ['media.peerconnection.nat_simulator.block_tcp', false],
+        ['media.peerconnection.nat_simulator.block_tls', true],
         ['media.peerconnection.ice.loopback', true],
         ['media.getusermedia.insecure.enabled', true]);
     options.expectedLocalCandidateType = "relay-tcp";
     options.expectedRemoteCandidateType = "relay-tcp";
     // No reason to wait for gathering to complete like the other NAT tests,
     // since relayed-tcp is the only thing that can work.
     const test = new PeerConnectionTest(options);
     test.setMediaConstraints([{audio: true}], [{audio: true}]);
--- a/dom/media/webrtc/tests/mochitests/test_peerConnection_basicAudioNATRelayTLS.html
+++ b/dom/media/webrtc/tests/mochitests/test_peerConnection_basicAudioNATRelayTLS.html
@@ -19,19 +19,20 @@ if (!("mediaDevices" in navigator)) {
 } else {
   runNetworkTest(async (options = {}) => {
     await pushPrefs(
         ['media.peerconnection.ice.obfuscate_host_addresses', false],
         ['media.peerconnection.nat_simulator.filtering_type', 'PORT_DEPENDENT'],
         ['media.peerconnection.nat_simulator.mapping_type', 'PORT_DEPENDENT'],
         ['media.peerconnection.nat_simulator.block_udp', true],
         ['media.peerconnection.nat_simulator.block_tcp', true],
+        ['media.peerconnection.ice.loopback', true],
         ['media.getusermedia.insecure.enabled', true]);
-    options.expectedLocalCandidateType = "relay-tcp";
-    options.expectedRemoteCandidateType = "relay-tcp";
+    options.expectedLocalCandidateType = "relay-tls";
+    options.expectedRemoteCandidateType = "relay-tls";
     // No reason to wait for gathering to complete like the other NAT tests,
     // since relayed-tcp is the only thing that can work.
     const test = new PeerConnectionTest(options);
     test.setMediaConstraints([{audio: true}], [{audio: true}]);
     await test.run();
   }, { useIceServer: true });
 }
 </script>
--- a/dom/media/webrtc/tests/mochitests/test_peerConnection_basicAudioNoisyUDPBlock.html
+++ b/dom/media/webrtc/tests/mochitests/test_peerConnection_basicAudioNoisyUDPBlock.html
@@ -19,16 +19,17 @@ if (!("mediaDevices" in navigator)) {
 } else {
   runNetworkTest(async (options = {}) => {
     await pushPrefs(
         ['media.peerconnection.ice.obfuscate_host_addresses', false],
         ['media.peerconnection.nat_simulator.filtering_type', 'PORT_DEPENDENT'],
         ['media.peerconnection.nat_simulator.mapping_type', 'PORT_DEPENDENT'],
         ['media.peerconnection.nat_simulator.block_udp', true],
         ['media.peerconnection.nat_simulator.error_code_for_drop', 3 /*R_INTERNAL*/],
+        ['media.peerconnection.nat_simulator.block_tls', true],
         ['media.getusermedia.insecure.enabled', true]);
     options.expectedLocalCandidateType = "relay-tcp";
     options.expectedRemoteCandidateType = "relay-tcp";
     // No reason to wait for gathering to complete like the other NAT tests,
     // since relayed-tcp is the only thing that can work.
     const test = new PeerConnectionTest(options);
     test.setMediaConstraints([{audio: true}], [{audio: true}]);
     await test.run();
--- a/dom/media/webrtc/tests/mochitests/test_peerConnection_stats_relayProtocol.html
+++ b/dom/media/webrtc/tests/mochitests/test_peerConnection_stats_relayProtocol.html
@@ -43,18 +43,16 @@ const PC_LOCAL_TEST_LOCAL_STATS_RELAYCAN
       if (v.type == "local-candidate") {
         haveRelayProtocol[v.candidateType + "-" + v.relayProtocol] = v.relayProtocol;
       }
     }
     is(haveRelayProtocol["host-undefined"], undefined, "relayProtocol not set for host candidates");
     is(haveRelayProtocol["srflx-undefined"], undefined, "relayProtocol not set for server reflexive candidates");
     ok(haveRelayProtocol["relay-udp"], "Has UDP relay candidate");
     ok(haveRelayProtocol["relay-tcp"], "Has TCP relay candidate");
-    // TURN/TLS does not work, see https://bugzilla.mozilla.org/show_bug.cgi?id=1323439
-    // With TURN/TLS working, we should have exactly five entries in haveRelayProtocol.
-    todo(haveRelayProtocol["relay-tls"], "Has TLS relay candidate. See https://bugzilla.mozilla.org/show_bug.cgi?id=1323439");
-    is(Object.keys(haveRelayProtocol).length, 4, "All candidate types are accounted for");
+    ok(haveRelayProtocol["relay-tls"], "Has TLS relay candidate");
+    is(Object.keys(haveRelayProtocol).length, 5, "All candidate types are accounted for");
   });
 }
 </script>
 </pre>
 </body>
 </html>
--- a/dom/quota/ActorsParent.cpp
+++ b/dom/quota/ActorsParent.cpp
@@ -585,17 +585,17 @@ Result<nsCOMPtr<mozIStorageConnection>, 
                      IsDatabaseCorruptionError,
                      // Fallback. Don't throw an error, leave a corrupted
                      // webappsstore database as it is.
                      ErrToDefaultOk<nsCOMPtr<mozIStorageConnection>>));
 
   if (connection) {
     // Don't propagate an error, leave a non-updateable webappsstore database as
     // it is.
-    QM_TRY(StorageDBUpdater::Update(connection),
+    QM_TRY(MOZ_TO_RESULT(StorageDBUpdater::Update(connection)),
            nsCOMPtr<mozIStorageConnection>{});
   }
 
   return connection;
 }
 
 Result<nsCOMPtr<nsIFile>, nsresult> GetLocalStorageArchiveFile(
     const nsAString& aDirectoryPath) {
@@ -1837,18 +1837,18 @@ Result<bool, nsresult> MaybeUpdateGroupF
   } else {
     OriginAttributes originAttributes;
     nsCString originNoSuffix;
     QM_TRY(OkIf(originAttributes.PopulateFromOrigin(aOriginMetadata.mOrigin,
                                                     originNoSuffix)),
            Err(NS_ERROR_FAILURE));
 
     RefPtr<MozURL> url;
-    QM_TRY(MozURL::Init(getter_AddRefs(url), originNoSuffix), QM_PROPAGATE,
-           [&originNoSuffix](const nsresult) {
+    QM_TRY(MOZ_TO_RESULT(MozURL::Init(getter_AddRefs(url), originNoSuffix)),
+           QM_PROPAGATE, [&originNoSuffix](const nsresult) {
              QM_WARNING("A URL %s is not recognized by MozURL",
                         originNoSuffix.get());
            });
 
     QM_TRY_INSPECT(const auto& baseDomain,
                    MOZ_TO_RESULT_INVOKE_TYPED(nsAutoCString, *url, BaseDomain));
 
     const nsCString upToDateGroup = baseDomain + aOriginMetadata.mSuffix;
@@ -2713,17 +2713,17 @@ void InitializeQuotaManager() {
   if (!QuotaManager::IsRunningGTests()) {
     // This service has to be started on the main thread currently.
     const nsCOMPtr<mozIStorageService> ss =
         do_GetService(MOZ_STORAGE_SERVICE_CONTRACTID);
 
     QM_WARNONLY_TRY(OkIf(ss));
   }
 
-  QM_WARNONLY_TRY(QuotaManager::Initialize());
+  QM_WARNONLY_TRY(QM_TO_RESULT(QuotaManager::Initialize()));
 
 #ifdef DEBUG
   gQuotaManagerInitialized = true;
 #endif
 }
 
 PQuotaParent* AllocPQuotaParent() {
   AssertIsOnBackgroundThread();
@@ -3884,32 +3884,33 @@ void QuotaManager::Shutdown() {
     }));
   }
 
   for (Client::Type type : allClientTypes) {
     (*mClients)[type]->FinalizeShutdownWorkThreads();
   }
 
   // Cancel the timer regardless of whether it actually fired.
-  QM_WARNONLY_TRY((*mShutdownTimer)->Cancel());
+  QM_WARNONLY_TRY(QM_TO_RESULT((*mShutdownTimer)->Cancel()));
 
   // NB: It's very important that runnable is destroyed on this thread
   // (i.e. after we join the IO thread) because we can't release the
   // QuotaManager on the IO thread. This should probably use
   // NewNonOwningRunnableMethod ...
   RefPtr<Runnable> runnable =
       NewRunnableMethod("dom::quota::QuotaManager::ShutdownStorage", this,
                         &QuotaManager::ShutdownStorage);
   MOZ_ASSERT(runnable);
 
   // Give clients a chance to cleanup IO thread only objects.
-  QM_WARNONLY_TRY((*mIOThread)->Dispatch(runnable, NS_DISPATCH_NORMAL));
+  QM_WARNONLY_TRY(
+      QM_TO_RESULT((*mIOThread)->Dispatch(runnable, NS_DISPATCH_NORMAL)));
 
   // Make sure to join with our IO thread.
-  QM_WARNONLY_TRY((*mIOThread)->Shutdown());
+  QM_WARNONLY_TRY(QM_TO_RESULT((*mIOThread)->Shutdown()));
 
   for (RefPtr<DirectoryLockImpl>& lock : mPendingDirectoryLocks) {
     lock->Invalidate();
   }
 }
 
 void QuotaManager::InitQuotaForOrigin(
     const FullOriginMetadata& aFullOriginMetadata,
@@ -4327,28 +4328,28 @@ nsresult QuotaManager::LoadQuota() {
     };
 
     for (const PersistenceType type : kBestEffortPersistenceTypes) {
       if (NS_WARN_IF(IsShuttingDown())) {
         RETURN_STATUS_OR_RESULT(statusKeeper, NS_ERROR_ABORT);
       }
 
       QM_TRY(([&]() -> Result<Ok, nsresult> {
-        QM_TRY(([this, type, &MaybeCollectUnaccessedOrigin] {
+        QM_TRY(MOZ_TO_RESULT(([this, type, &MaybeCollectUnaccessedOrigin] {
                  const auto innerFunc = [&](const auto&) -> nsresult {
                    return InitializeRepository(type,
                                                MaybeCollectUnaccessedOrigin);
                  };
 
                  return ExecuteInitialization(
                      type == PERSISTENCE_TYPE_DEFAULT
                          ? Initialization::DefaultRepository
                          : Initialization::TemporaryRepository,
                      innerFunc);
-               }()),
+               }())),
                OK_IN_NIGHTLY_PROPAGATE_IN_OTHERS, statusKeeperFunc);
 
         return Ok{};
       }()));
     }
 
 #ifdef NIGHTLY_BUILD
     if (NS_FAILED(statusKeeper)) {
@@ -4377,19 +4378,20 @@ void QuotaManager::UnloadQuota() {
   MOZ_ASSERT(mTemporaryStorageInitialized);
   MOZ_ASSERT(mCacheUsable);
 
   auto autoRemoveQuota = MakeScopeExit([&] { RemoveQuota(); });
 
   mozStorageTransaction transaction(
       mStorageConnection, false, mozIStorageConnection::TRANSACTION_IMMEDIATE);
 
-  QM_TRY(transaction.Start(), QM_VOID);
-
-  QM_TRY(mStorageConnection->ExecuteSimpleSQL("DELETE FROM origin;"_ns),
+  QM_TRY(MOZ_TO_RESULT(transaction.Start()), QM_VOID);
+
+  QM_TRY(MOZ_TO_RESULT(
+             mStorageConnection->ExecuteSimpleSQL("DELETE FROM origin;"_ns)),
          QM_VOID);
 
   nsCOMPtr<mozIStorageStatement> insertStmt;
 
   {
     MutexAutoLock lock(mQuotaMutex);
 
     for (auto iter = mGroupInfoPairs.Iter(); !iter.Done(); iter.Next()) {
@@ -4423,39 +4425,41 @@ void QuotaManager::UnloadQuota() {
                     "origin, client_usages, usage, last_access_time, "
                     "accessed, persisted) "
                     "VALUES (:repository_id, :suffix, :group_, :origin, "
                     ":client_usages, :usage, :last_access_time, :accessed, "
                     ":persisted)"_ns),
                 QM_VOID);
           }
 
-          QM_TRY(originInfo->LockedBindToStatement(insertStmt), QM_VOID);
-
-          QM_TRY(insertStmt->Execute(), QM_VOID);
+          QM_TRY(MOZ_TO_RESULT(originInfo->LockedBindToStatement(insertStmt)),
+                 QM_VOID);
+
+          QM_TRY(MOZ_TO_RESULT(insertStmt->Execute()), QM_VOID);
         }
 
         groupInfo->LockedRemoveOriginInfos();
       }
 
       iter.Remove();
     }
   }
 
   QM_TRY_INSPECT(
       const auto& stmt,
       MOZ_TO_RESULT_INVOKE_TYPED(
           nsCOMPtr<mozIStorageStatement>, mStorageConnection, CreateStatement,
           "UPDATE cache SET valid = :valid, build_id = :buildId;"_ns),
       QM_VOID);
 
-  QM_TRY(stmt->BindInt32ByName("valid"_ns, 1), QM_VOID);
-  QM_TRY(stmt->BindUTF8StringByName("buildId"_ns, *gBuildId), QM_VOID);
-  QM_TRY(stmt->Execute(), QM_VOID);
-  QM_TRY(transaction.Commit(), QM_VOID);
+  QM_TRY(MOZ_TO_RESULT(stmt->BindInt32ByName("valid"_ns, 1)), QM_VOID);
+  QM_TRY(MOZ_TO_RESULT(stmt->BindUTF8StringByName("buildId"_ns, *gBuildId)),
+         QM_VOID);
+  QM_TRY(MOZ_TO_RESULT(stmt->Execute()), QM_VOID);
+  QM_TRY(MOZ_TO_RESULT(transaction.Commit()), QM_VOID);
 }
 
 already_AddRefed<QuotaObject> QuotaManager::GetQuotaObject(
     PersistenceType aPersistenceType, const OriginMetadata& aOriginMetadata,
     Client::Type aClientType, nsIFile* aFile, int64_t aFileSize,
     int64_t* aFileSizeOut /* = nullptr */) {
   NS_ASSERTION(!NS_IsMainThread(), "Wrong thread!");
 
@@ -4476,17 +4480,17 @@ already_AddRefed<QuotaObject> QuotaManag
         const auto& directory,
         GetDirectoryForOrigin(aPersistenceType, aOriginMetadata.mOrigin),
         nullptr);
 
     nsAutoString clientType;
     QM_TRY(OkIf(Client::TypeToText(aClientType, clientType, fallible)),
            nullptr);
 
-    QM_TRY(directory->Append(clientType), nullptr);
+    QM_TRY(MOZ_TO_RESULT(directory->Append(clientType)), nullptr);
 
     QM_TRY_INSPECT(const auto& directoryPath,
                    MOZ_TO_RESULT_INVOKE_TYPED(nsString, directory, GetPath),
                    nullptr);
 
     MOZ_ASSERT(StringBeginsWith(path, directoryPath));
   }
 #endif
@@ -5857,17 +5861,18 @@ nsresult QuotaManager::MaybeCreateOrUpgr
 
   QM_TRY_UNWRAP(auto storageVersion,
                 MOZ_TO_RESULT_INVOKE(aConnection, GetSchemaVersion));
 
   // Hacky downgrade logic!
   // If we see major.minor of 3.0, downgrade it to be 2.1.
   if (storageVersion == kHackyPreDowngradeStorageVersion) {
     storageVersion = kHackyPostDowngradeStorageVersion;
-    QM_TRY(aConnection.SetSchemaVersion(storageVersion), QM_PROPAGATE,
+    QM_TRY(MOZ_TO_RESULT(aConnection.SetSchemaVersion(storageVersion)),
+           QM_PROPAGATE,
            [](const auto&) { MOZ_ASSERT(false, "Downgrade didn't take."); });
   }
 
   QM_TRY(OkIf(GetMajorStorageVersion(storageVersion) <= kMajorStorageVersion),
          NS_ERROR_FAILURE, [](const auto&) {
            NS_WARNING("Unable to initialize storage, version is too high!");
          });
 
@@ -7665,17 +7670,18 @@ nsresult OriginOperationBase::DirectoryO
   MOZ_ASSERT(mState == State_DirectoryOpenPending);
 
   QuotaManager* const quotaManager = QuotaManager::Get();
   QM_TRY(OkIf(quotaManager), NS_ERROR_FAILURE);
 
   // Must set this before dispatching otherwise we will race with the IO thread.
   AdvanceState();
 
-  QM_TRY(quotaManager->IOThread()->Dispatch(this, NS_DISPATCH_NORMAL),
+  QM_TRY(MOZ_TO_RESULT(
+             quotaManager->IOThread()->Dispatch(this, NS_DISPATCH_NORMAL)),
          NS_ERROR_FAILURE);
 
   return NS_OK;
 }
 
 void OriginOperationBase::Finish(nsresult aResult) {
   if (NS_SUCCEEDED(mResultCode)) {
     mResultCode = aResult;
@@ -7800,17 +7806,18 @@ void NormalOriginOperationBase::Open() {
 
   if (mNeedsDirectoryLocking) {
     RefPtr<DirectoryLock> directoryLock =
         QuotaManager::Get()->CreateDirectoryLockInternal(
             mPersistenceType, mOriginScope, mClientType, mExclusive);
 
     directoryLock->Acquire(this);
   } else {
-    QM_TRY(DirectoryOpen(), QM_VOID, [this](const nsresult rv) { Finish(rv); });
+    QM_TRY(MOZ_TO_RESULT(DirectoryOpen()), QM_VOID,
+           [this](const nsresult rv) { Finish(rv); });
   }
 }
 
 void NormalOriginOperationBase::UnblockOpen() {
   AssertIsOnOwningThread();
   MOZ_ASSERT(GetState() == State_UnblockingOpen);
 
   SendResults();
@@ -7827,17 +7834,18 @@ void NormalOriginOperationBase::UnblockO
 void NormalOriginOperationBase::DirectoryLockAcquired(DirectoryLock* aLock) {
   AssertIsOnOwningThread();
   MOZ_ASSERT(aLock);
   MOZ_ASSERT(GetState() == State_DirectoryOpenPending);
   MOZ_ASSERT(!mDirectoryLock);
 
   mDirectoryLock = aLock;
 
-  QM_TRY(DirectoryOpen(), QM_VOID, [this](const nsresult rv) { Finish(rv); });
+  QM_TRY(MOZ_TO_RESULT(DirectoryOpen()), QM_VOID,
+         [this](const nsresult rv) { Finish(rv); });
 }
 
 void NormalOriginOperationBase::DirectoryLockFailed() {
   AssertIsOnOwningThread();
   MOZ_ASSERT(GetState() == State_DirectoryOpenPending);
   MOZ_ASSERT(!mDirectoryLock);
 
   Finish(NS_ERROR_FAILURE);
@@ -9201,17 +9209,18 @@ void ResetOrClearOp::DeleteFiles(QuotaMa
 }
 
 void ResetOrClearOp::DeleteStorageFile(QuotaManager& aQuotaManager) {
   AssertIsOnIOThread();
 
   QM_TRY_INSPECT(const auto& storageFile,
                  QM_NewLocalFile(aQuotaManager.GetBasePath()), QM_VOID);
 
-  QM_TRY(storageFile->Append(aQuotaManager.GetStorageName() + kSQLiteSuffix),
+  QM_TRY(MOZ_TO_RESULT(storageFile->Append(aQuotaManager.GetStorageName() +
+                                           kSQLiteSuffix)),
          QM_VOID);
 
   const nsresult rv = storageFile->Remove(true);
   if (rv != NS_ERROR_FILE_TARGET_DOES_NOT_EXIST &&
       rv != NS_ERROR_FILE_NOT_FOUND && NS_FAILED(rv)) {
     // This should never fail if we've closed the storage connection
     // correctly...
     MOZ_ASSERT(false, "Failed to remove storage file!");
@@ -9246,133 +9255,135 @@ void ResetOrClearOp::GetResponse(Request
     aResponse = ResetAllResponse();
   }
 }
 
 void ClearRequestBase::DeleteFiles(QuotaManager& aQuotaManager,
                                    PersistenceType aPersistenceType) {
   AssertIsOnIOThread();
 
-  QM_TRY(aQuotaManager.AboutToClearOrigins(
+  QM_TRY(MOZ_TO_RESULT(aQuotaManager.AboutToClearOrigins(
              Nullable<PersistenceType>(aPersistenceType), mOriginScope,
-             mClientType),
+             mClientType)),
          QM_VOID);
 
   QM_TRY_INSPECT(
       const auto& directory,
       QM_NewLocalFile(aQuotaManager.GetStoragePath(aPersistenceType)), QM_VOID);
 
   nsTArray<nsCOMPtr<nsIFile>> directoriesForRemovalRetry;
 
   aQuotaManager.MaybeRecordQuotaManagerShutdownStep(
       "ClearRequestBase: Starting deleting files"_ns);
 
-  QM_TRY(CollectEachFile(
-             *directory,
-             [originScope =
-                  [this] {
-                    OriginScope originScope = mOriginScope.Clone();
-                    if (originScope.IsOrigin()) {
-                      originScope.SetOrigin(
-                          MakeSanitizedOriginCString(originScope.GetOrigin()));
-                    } else if (originScope.IsPrefix()) {
-                      originScope.SetOriginNoSuffix(MakeSanitizedOriginCString(
-                          originScope.GetOriginNoSuffix()));
-                    }
-                    return originScope;
-                  }(),
-              aPersistenceType, &aQuotaManager, &directoriesForRemovalRetry,
-              this](nsCOMPtr<nsIFile>&& file) -> mozilla::Result<Ok, nsresult> {
-               QM_TRY_INSPECT(
-                   const auto& leafName,
-                   MOZ_TO_RESULT_INVOKE_TYPED(nsAutoString, file, GetLeafName));
-
-               QM_TRY_INSPECT(const auto& dirEntryKind, GetDirEntryKind(*file));
-
-               switch (dirEntryKind) {
-                 case nsIFileKind::ExistsAsDirectory: {
-                   // Skip the origin directory if it doesn't match the pattern.
-                   if (!originScope.Matches(OriginScope::FromOrigin(
-                           NS_ConvertUTF16toUTF8(leafName)))) {
-                     break;
-                   }
-
-                   QM_TRY_INSPECT(
-                       const auto& metadata,
-                       aQuotaManager.LoadFullOriginMetadataWithRestore(file));
-
-                   MOZ_ASSERT(metadata.mPersistenceType == aPersistenceType);
-
-                   if (!mClientType.IsNull()) {
-                     nsAutoString clientDirectoryName;
-                     QM_TRY(OkIf(Client::TypeToText(mClientType.Value(),
-                                                    clientDirectoryName,
-                                                    fallible)),
-                            Err(NS_ERROR_FAILURE));
-
-                     QM_TRY(file->Append(clientDirectoryName));
-
-                     QM_TRY_INSPECT(const bool& exists,
-                                    MOZ_TO_RESULT_INVOKE(file, Exists));
-
-                     if (!exists) {
-                       break;
-                     }
-                   }
-
-                   // We can't guarantee that this will always succeed on
-                   // Windows...
-                   QM_WARNONLY_TRY(file->Remove(true), [&](const auto&) {
-                     directoriesForRemovalRetry.AppendElement(std::move(file));
-                   });
-
-                   const bool initialized =
-                       aPersistenceType == PERSISTENCE_TYPE_PERSISTENT
-                           ? aQuotaManager.IsOriginInitialized(metadata.mOrigin)
-                           : aQuotaManager.IsTemporaryStorageInitialized();
-
-                   // If it hasn't been initialized, we don't need to update the
-                   // quota and notify the removing client.
-                   if (!initialized) {
-                     break;
-                   }
-
-                   if (aPersistenceType != PERSISTENCE_TYPE_PERSISTENT) {
-                     if (mClientType.IsNull()) {
-                       aQuotaManager.RemoveQuotaForOrigin(aPersistenceType,
-                                                          metadata);
-                     } else {
-                       aQuotaManager.ResetUsageForClient(
-                           ClientMetadata{metadata, mClientType.Value()});
-                     }
-                   }
-
-                   aQuotaManager.OriginClearCompleted(
-                       aPersistenceType, metadata.mOrigin, mClientType);
-
-                   break;
+  QM_TRY(
+      CollectEachFile(
+          *directory,
+          [originScope =
+               [this] {
+                 OriginScope originScope = mOriginScope.Clone();
+                 if (originScope.IsOrigin()) {
+                   originScope.SetOrigin(
+                       MakeSanitizedOriginCString(originScope.GetOrigin()));
+                 } else if (originScope.IsPrefix()) {
+                   originScope.SetOriginNoSuffix(MakeSanitizedOriginCString(
+                       originScope.GetOriginNoSuffix()));
                  }
-
-                 case nsIFileKind::ExistsAsFile:
-                   // Unknown files during clearing are allowed. Just warn if we
-                   // find them.
-                   if (!IsOSMetadata(leafName)) {
-                     UNKNOWN_FILE_WARNING(leafName);
-                   }
-
-                   break;
-
-                 case nsIFileKind::DoesNotExist:
-                   // Ignore files that got removed externally while iterating.
-                   break;
-               }
-
-               return Ok{};
-             }),
-         QM_VOID);
+                 return originScope;
+               }(),
+           aPersistenceType, &aQuotaManager, &directoriesForRemovalRetry,
+           this](nsCOMPtr<nsIFile>&& file) -> mozilla::Result<Ok, nsresult> {
+            QM_TRY_INSPECT(
+                const auto& leafName,
+                MOZ_TO_RESULT_INVOKE_TYPED(nsAutoString, file, GetLeafName));
+
+            QM_TRY_INSPECT(const auto& dirEntryKind, GetDirEntryKind(*file));
+
+            switch (dirEntryKind) {
+              case nsIFileKind::ExistsAsDirectory: {
+                // Skip the origin directory if it doesn't match the pattern.
+                if (!originScope.Matches(OriginScope::FromOrigin(
+                        NS_ConvertUTF16toUTF8(leafName)))) {
+                  break;
+                }
+
+                QM_TRY_INSPECT(
+                    const auto& metadata,
+                    aQuotaManager.LoadFullOriginMetadataWithRestore(file));
+
+                MOZ_ASSERT(metadata.mPersistenceType == aPersistenceType);
+
+                if (!mClientType.IsNull()) {
+                  nsAutoString clientDirectoryName;
+                  QM_TRY(
+                      OkIf(Client::TypeToText(mClientType.Value(),
+                                              clientDirectoryName, fallible)),
+                      Err(NS_ERROR_FAILURE));
+
+                  QM_TRY(file->Append(clientDirectoryName));
+
+                  QM_TRY_INSPECT(const bool& exists,
+                                 MOZ_TO_RESULT_INVOKE(file, Exists));
+
+                  if (!exists) {
+                    break;
+                  }
+                }
+
+                // We can't guarantee that this will always succeed on
+                // Windows...
+                QM_WARNONLY_TRY(
+                    QM_TO_RESULT(file->Remove(true)), [&](const auto&) {
+                      directoriesForRemovalRetry.AppendElement(std::move(file));
+                    });
+
+                const bool initialized =
+                    aPersistenceType == PERSISTENCE_TYPE_PERSISTENT
+                        ? aQuotaManager.IsOriginInitialized(metadata.mOrigin)
+                        : aQuotaManager.IsTemporaryStorageInitialized();
+
+                // If it hasn't been initialized, we don't need to update the
+                // quota and notify the removing client.
+                if (!initialized) {
+                  break;
+                }
+
+                if (aPersistenceType != PERSISTENCE_TYPE_PERSISTENT) {
+                  if (mClientType.IsNull()) {
+                    aQuotaManager.RemoveQuotaForOrigin(aPersistenceType,
+                                                       metadata);
+                  } else {
+                    aQuotaManager.ResetUsageForClient(
+                        ClientMetadata{metadata, mClientType.Value()});
+                  }
+                }
+
+                aQuotaManager.OriginClearCompleted(
+                    aPersistenceType, metadata.mOrigin, mClientType);
+
+                break;
+              }
+
+              case nsIFileKind::ExistsAsFile:
+                // Unknown files during clearing are allowed. Just warn if we
+                // find them.
+                if (!IsOSMetadata(leafName)) {
+                  UNKNOWN_FILE_WARNING(leafName);
+                }
+
+                break;
+
+              case nsIFileKind::DoesNotExist:
+                // Ignore files that got removed externally while iterating.
+                break;
+            }
+
+            return Ok{};
+          }),
+      QM_VOID);
 
   // Retry removing any directories that failed to be removed earlier now.
   //
   // XXX This will still block this operation. We might instead dispatch a
   // runnable to our own thread for each retry round with a timer. We must
   // ensure that the directory lock is upheld until we complete or give up
   // though.
   for (uint32_t index = 0; index < 10; index++) {
@@ -10055,19 +10066,21 @@ Result<bool, nsresult> StorageOperationB
         "instead of renaming!",
         NS_ConvertUTF16toUTF8(oldLeafName).get(),
         NS_ConvertUTF16toUTF8(newLeafName).get());
   }
 
   QM_TRY(CallWithDelayedRetriesIfAccessDenied(
       [&exists, &aOriginProps, &newLeafName] {
         if (exists) {
-          QM_TRY_RETURN(aOriginProps.mDirectory->Remove(/* recursive */ true));
+          QM_TRY_RETURN(MOZ_TO_RESULT(
+              aOriginProps.mDirectory->Remove(/* recursive */ true)));
         }
-        QM_TRY_RETURN(aOriginProps.mDirectory->RenameTo(nullptr, newLeafName));
+        QM_TRY_RETURN(MOZ_TO_RESULT(
+            aOriginProps.mDirectory->RenameTo(nullptr, newLeafName)));
       },
       StaticPrefs::dom_quotaManager_directoryRemovalOrRenaming_maxRetries(),
       StaticPrefs::dom_quotaManager_directoryRemovalOrRenaming_delayMs()));
 
   return true;
 }
 
 nsresult StorageOperationBase::ProcessOriginDirectories() {
--- a/dom/quota/QMResult.h
+++ b/dom/quota/QMResult.h
@@ -44,18 +44,16 @@ class QMResult {
    *
    * This is used by GenericErrorResult<QMResult> to create a propagated
    * result.
    */
   QMResult Propagate() const {
     return QMResult{mStackId, mFrameId + 1, mNSResult};
   }
 
-  operator nsresult() const { return mNSResult; }
-
  private:
   QMResult(uint64_t aStackId, uint32_t aFrameId, nsresult aNSResult)
       : mStackId(aStackId), mFrameId(aFrameId), mNSResult(aNSResult) {}
 };
 #else
 using QMResult = nsresult;
 #endif
 
@@ -66,11 +64,14 @@ using OkOrErr = Result<Ok, QMResult>;
 #ifdef QM_ERROR_STACKS_ENABLED
 inline OkOrErr ToResult(const QMResult& aValue);
 
 inline OkOrErr ToResult(QMResult&& aValue);
 #endif
 
 }  // namespace mozilla
 
+// TODO: Maybe move this to mfbt/ResultExtensions.h
+#define MOZ_TO_RESULT(expr) ToResult(expr)
+
 #define QM_TO_RESULT(expr) ToResult(ToQMResult(expr))
 
 #endif
--- a/dom/quota/QuotaCommon.h
+++ b/dom/quota/QuotaCommon.h
@@ -492,30 +492,30 @@ class NotNull;
     mozilla::dom::quota::QM_HANDLE_ERROR(                                    \
         expr, tryResult.inspectErr(), mozilla::dom::quota::Severity::Error); \
     return tryResult.propagateErr();                                         \
   }
 
 // Handles the three arguments case when a custom return value needs to be
 // returned
 #define QM_TRY_CUSTOM_RET_VAL(tryResult, expr, customRetVal)             \
-  auto tryResult = ::mozilla::ToResult(expr);                            \
+  auto tryResult = (expr);                                               \
   static_assert(std::is_empty_v<typename decltype(tryResult)::ok_type>); \
   if (MOZ_UNLIKELY(tryResult.isErr())) {                                 \
     auto tryTempError MOZ_MAYBE_UNUSED = tryResult.unwrapErr();          \
     mozilla::dom::quota::QM_HANDLE_ERROR(                                \
         expr, tryTempError, mozilla::dom::quota::Severity::Error);       \
     return customRetVal;                                                 \
   }
 
 // Handles the four arguments case when a cleanup function needs to be called
 // before a custom return value is returned
 #define QM_TRY_CUSTOM_RET_VAL_WITH_CLEANUP(tryResult, expr, customRetVal, \
                                            cleanup)                       \
-  auto tryResult = ::mozilla::ToResult(expr);                             \
+  auto tryResult = (expr);                                                \
   static_assert(std::is_empty_v<typename decltype(tryResult)::ok_type>);  \
   if (MOZ_UNLIKELY(tryResult.isErr())) {                                  \
     auto tryTempError = tryResult.unwrapErr();                            \
     mozilla::dom::quota::QM_HANDLE_ERROR(                                 \
         expr, tryTempError, mozilla::dom::quota::Severity::Error);        \
     cleanup(tryTempError);                                                \
     return customRetVal;                                                  \
   }
@@ -634,40 +634,40 @@ class NotNull;
 // QM_TRY_RETURN_CUSTOM_RET_VAL_WITH_CLEANUP and QM_TRY_RETURN_GLUE macros are
 // implementation details of QM_TRY_RETURN and shouldn't be used directly.
 
 // Handles the two arguments case when the error is (also) propagated.
 // Note that this deliberately uses a single return statement without going
 // through unwrap/unwrapErr/propagateErr, so that this does not prevent NRVO or
 // tail call optimizations when possible.
 #define QM_TRY_RETURN_PROPAGATE_ERR(tryResult, expr)                         \
-  auto tryResult = ::mozilla::ToResult(expr);                                \
+  auto tryResult = (expr);                                                   \
   if (MOZ_UNLIKELY(tryResult.isErr())) {                                     \
     mozilla::dom::quota::QM_HANDLE_ERROR(                                    \
         expr, tryResult.inspectErr(), mozilla::dom::quota::Severity::Error); \
   }                                                                          \
   return tryResult;
 
 // Handles the three arguments case when a custom return value needs to be
 // returned
 #define QM_TRY_RETURN_CUSTOM_RET_VAL(tryResult, expr, customRetVal)          \
-  auto tryResult = ::mozilla::ToResult(expr);                                \
+  auto tryResult = (expr);                                                   \
   if (MOZ_UNLIKELY(tryResult.isErr())) {                                     \
     auto tryTempError MOZ_MAYBE_UNUSED = tryResult.unwrapErr();              \
     mozilla::dom::quota::QM_HANDLE_ERROR(                                    \
         expr, tryResult.inspectErr(), mozilla::dom::quota::Severity::Error); \
     return customRetVal;                                                     \
   }                                                                          \
   return tryResult.unwrap();
 
 // Handles the four arguments case when a cleanup function needs to be called
 // before a custom return value is returned
 #define QM_TRY_RETURN_CUSTOM_RET_VAL_WITH_CLEANUP(tryResult, expr,       \
                                                   customRetVal, cleanup) \
-  auto tryResult = ::mozilla::ToResult(expr);                            \
+  auto tryResult = (expr);                                               \
   if (MOZ_UNLIKELY(tryResult.isErr())) {                                 \
     auto tryTempError = tryResult.unwrapErr();                           \
     mozilla::dom::quota::QM_HANDLE_ERROR(                                \
         expr, tryTempError, mozilla::dom::quota::Severity::Error);       \
     cleanup(tryTempError);                                               \
     return customRetVal;                                                 \
   }                                                                      \
   return tryResult.unwrap();
@@ -730,26 +730,26 @@ class NotNull;
 #define QM_FAIL(...) QM_FAIL_GLUE(__VA_ARGS__)
 
 // QM_REPORTONLY_TRY, QM_REPORTONLY_TRY_WITH_CLEANUP, QM_REPORTONLY_TRY_GLUE
 // macros are implementation details of QM_WARNONLY_TRY/QM_INFOONLY_TRY and
 // shouldn't be used directly.
 
 // Handles the three arguments case when only a warning/info is reported.
 #define QM_REPORTONLY_TRY(tryResult, severity, expr)                           \
-  auto tryResult = ::mozilla::ToResult(expr);                                  \
+  auto tryResult = (expr);                                                     \
   static_assert(std::is_empty_v<typename decltype(tryResult)::ok_type>);       \
   if (MOZ_UNLIKELY(tryResult.isErr())) {                                       \
     mozilla::dom::quota::QM_HANDLE_ERROR(                                      \
         expr, tryResult.unwrapErr(), mozilla::dom::quota::Severity::severity); \
   }
 
 // Handles the four arguments case when a cleanup function needs to be called
 #define QM_REPORTONLY_TRY_WITH_CLEANUP(tryResult, severity, expr, cleanup) \
-  auto tryResult = ::mozilla::ToResult(expr);                              \
+  auto tryResult = (expr);                                                 \
   static_assert(std::is_empty_v<typename decltype(tryResult)::ok_type>);   \
   if (MOZ_UNLIKELY(tryResult.isErr())) {                                   \
     auto tryTempError = tryResult.unwrapErr();                             \
     mozilla::dom::quota::QM_HANDLE_ERROR(                                  \
         expr, tryTempError, mozilla::dom::quota::Severity::severity);      \
     cleanup(tryTempError);                                                 \
   }
 
--- a/dom/quota/test/gtest/TestQMResult.cpp
+++ b/dom/quota/test/gtest/TestQMResult.cpp
@@ -9,144 +9,144 @@
 #include "gtest/gtest.h"
 
 #include "mozilla/dom/QMResultInlines.h"
 
 using namespace mozilla;
 
 static_assert(std::is_same_v<OkOrErr, Result<Ok, QMResult>>);
 
+uint64_t gBaseStackId;
+
+class DOM_Quota_QMResult : public testing::Test {
+ public:
+  static void SetUpTestCase() { gBaseStackId = QMResult().StackId(); }
+};
+
 #ifdef QM_ERROR_STACKS_ENABLED
-TEST(DOM_Quota_QMResult, Construct_Default)
-{
+TEST_F(DOM_Quota_QMResult, Construct_Default) {
   QMResult res;
 
-  ASSERT_EQ(res.StackId(), 1u);
+  ASSERT_EQ(res.StackId(), gBaseStackId + 1);
   ASSERT_EQ(res.FrameId(), 1u);
   ASSERT_EQ(res.NSResult(), NS_OK);
 }
 #endif
 
-TEST(DOM_Quota_QMResult, Construct_FromNSResult)
-{
+TEST_F(DOM_Quota_QMResult, Construct_FromNSResult) {
   QMResult res(NS_ERROR_FAILURE);
 
 #ifdef QM_ERROR_STACKS_ENABLED
-  ASSERT_EQ(res.StackId(), 2u);
+  ASSERT_EQ(res.StackId(), gBaseStackId + 2);
   ASSERT_EQ(res.FrameId(), 1u);
   ASSERT_EQ(res.NSResult(), NS_ERROR_FAILURE);
 #else
   ASSERT_EQ(res, NS_ERROR_FAILURE);
 #endif
 }
 
 #ifdef QM_ERROR_STACKS_ENABLED
-TEST(DOM_Quota_QMResult, Propagate)
-{
+TEST_F(DOM_Quota_QMResult, Propagate) {
   QMResult res1(NS_ERROR_FAILURE);
 
-  ASSERT_EQ(res1.StackId(), 3u);
+  ASSERT_EQ(res1.StackId(), gBaseStackId + 3);
   ASSERT_EQ(res1.FrameId(), 1u);
   ASSERT_EQ(res1.NSResult(), NS_ERROR_FAILURE);
 
   QMResult res2 = res1.Propagate();
 
-  ASSERT_EQ(res2.StackId(), 3u);
+  ASSERT_EQ(res2.StackId(), gBaseStackId + 3);
   ASSERT_EQ(res2.FrameId(), 2u);
   ASSERT_EQ(res2.NSResult(), NS_ERROR_FAILURE);
 }
 #endif
 
-TEST(DOM_Quota_QMResult, ToQMResult)
-{
+TEST_F(DOM_Quota_QMResult, ToQMResult) {
   auto res = ToQMResult(NS_ERROR_FAILURE);
 
 #ifdef QM_ERROR_STACKS_ENABLED
-  ASSERT_EQ(res.StackId(), 4u);
+  ASSERT_EQ(res.StackId(), gBaseStackId + 4);
   ASSERT_EQ(res.FrameId(), 1u);
   ASSERT_EQ(res.NSResult(), NS_ERROR_FAILURE);
 #else
   ASSERT_EQ(res, NS_ERROR_FAILURE);
 #endif
 }
 
-TEST(DOM_Quota_QMResult, ToResult)
-{
+TEST_F(DOM_Quota_QMResult, ToResult) {
   // copy
   {
     const auto res = ToQMResult(NS_ERROR_FAILURE);
     auto okOrErr = ToResult(res);
     static_assert(std::is_same_v<decltype(okOrErr), OkOrErr>);
     ASSERT_TRUE(okOrErr.isErr());
     auto err = okOrErr.unwrapErr();
 #ifdef QM_ERROR_STACKS_ENABLED
-    ASSERT_EQ(err.StackId(), 5u);
+    ASSERT_EQ(err.StackId(), gBaseStackId + 5);
     ASSERT_EQ(err.FrameId(), 1u);
     ASSERT_EQ(err.NSResult(), NS_ERROR_FAILURE);
 #else
     ASSERT_EQ(err, NS_ERROR_FAILURE);
 #endif
   }
 
   // move
   {
     auto res = ToQMResult(NS_ERROR_FAILURE);
     auto okOrErr = ToResult(std::move(res));
     static_assert(std::is_same_v<decltype(okOrErr), OkOrErr>);
     ASSERT_TRUE(okOrErr.isErr());
     auto err = okOrErr.unwrapErr();
 #ifdef QM_ERROR_STACKS_ENABLED
-    ASSERT_EQ(err.StackId(), 6u);
+    ASSERT_EQ(err.StackId(), gBaseStackId + 6);
     ASSERT_EQ(err.FrameId(), 1u);
     ASSERT_EQ(err.NSResult(), NS_ERROR_FAILURE);
 #else
     ASSERT_EQ(err, NS_ERROR_FAILURE);
 #endif
   }
 }
 
-TEST(DOM_Quota_QMResult, ToResult_Macro)
-{
+TEST_F(DOM_Quota_QMResult, ToResult_Macro) {
   auto okOrErr = QM_TO_RESULT(NS_ERROR_FAILURE);
   static_assert(std::is_same_v<decltype(okOrErr), OkOrErr>);
   ASSERT_TRUE(okOrErr.isErr());
   auto err = okOrErr.unwrapErr();
 #ifdef QM_ERROR_STACKS_ENABLED
-  ASSERT_EQ(err.StackId(), 7u);
+  ASSERT_EQ(err.StackId(), gBaseStackId + 7);
   ASSERT_EQ(err.FrameId(), 1u);
   ASSERT_EQ(err.NSResult(), NS_ERROR_FAILURE);
 #else
   ASSERT_EQ(err, NS_ERROR_FAILURE);
 #endif
 }
 
-TEST(DOM_Quota_QMResult, ErrorPropagation)
-{
+TEST_F(DOM_Quota_QMResult, ErrorPropagation) {
   OkOrErr okOrErr1 = ToResult(ToQMResult(NS_ERROR_FAILURE));
   const auto& err1 = okOrErr1.inspectErr();
 #ifdef QM_ERROR_STACKS_ENABLED
-  ASSERT_EQ(err1.StackId(), 8u);
+  ASSERT_EQ(err1.StackId(), gBaseStackId + 8);
   ASSERT_EQ(err1.FrameId(), 1u);
   ASSERT_EQ(err1.NSResult(), NS_ERROR_FAILURE);
 #else
   ASSERT_EQ(err1, NS_ERROR_FAILURE);
 #endif
 
   OkOrErr okOrErr2 = okOrErr1.propagateErr();
   const auto& err2 = okOrErr2.inspectErr();
 #ifdef QM_ERROR_STACKS_ENABLED
-  ASSERT_EQ(err2.StackId(), 8u);
+  ASSERT_EQ(err2.StackId(), gBaseStackId + 8);
   ASSERT_EQ(err2.FrameId(), 2u);
   ASSERT_EQ(err2.NSResult(), NS_ERROR_FAILURE);
 #else
   ASSERT_EQ(err2, NS_ERROR_FAILURE);
 #endif
 
   OkOrErr okOrErr3 = okOrErr2.propagateErr();
   const auto& err3 = okOrErr3.inspectErr();
 #ifdef QM_ERROR_STACKS_ENABLED
-  ASSERT_EQ(err3.StackId(), 8u);
+  ASSERT_EQ(err3.StackId(), gBaseStackId + 8);
   ASSERT_EQ(err3.FrameId(), 3u);
   ASSERT_EQ(err3.NSResult(), NS_ERROR_FAILURE);
 #else
   ASSERT_EQ(err3, NS_ERROR_FAILURE);
 #endif
 }
--- a/dom/quota/test/gtest/TestQuotaCommon.cpp
+++ b/dom/quota/test/gtest/TestQuotaCommon.cpp
@@ -58,33 +58,33 @@ TEST(QuotaCommon_Try, Success)
 }
 
 #ifdef DEBUG
 TEST(QuotaCommon_Try, Success_CustomErr_AssertUnreachable)
 {
   bool tryDidNotReturn = false;
 
   nsresult rv = [&tryDidNotReturn]() -> nsresult {
-    QM_TRY(NS_OK, QM_ASSERT_UNREACHABLE);
+    QM_TRY(MOZ_TO_RESULT(NS_OK), QM_ASSERT_UNREACHABLE);
 
     tryDidNotReturn = true;
 
     return NS_OK;
   }();
 
   EXPECT_TRUE(tryDidNotReturn);
   EXPECT_EQ(rv, NS_OK);
 }
 
 TEST(QuotaCommon_Try, Success_NoErr_AssertUnreachable)
 {
   bool tryDidNotReturn = false;
 
   [&tryDidNotReturn]() -> void {
-    QM_TRY(NS_OK, QM_ASSERT_UNREACHABLE_VOID);
+    QM_TRY(MOZ_TO_RESULT(NS_OK), QM_ASSERT_UNREACHABLE_VOID);
 
     tryDidNotReturn = true;
   }();
 
   EXPECT_TRUE(tryDidNotReturn);
 }
 #else
 #  if defined(QM_ASSERT_UNREACHABLE) || defined(QM_ASSERT_UNREACHABLE_VOID)
@@ -93,33 +93,33 @@ TEST(QuotaCommon_Try, Success_NoErr_Asse
 #endif
 
 #ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
 TEST(QuotaCommon_Try, Success_CustomErr_DiagnosticAssertUnreachable)
 {
   bool tryDidNotReturn = false;
 
   nsresult rv = [&tryDidNotReturn]() -> nsresult {
-    QM_TRY(NS_OK, QM_DIAGNOSTIC_ASSERT_UNREACHABLE);
+    QM_TRY(MOZ_TO_RESULT(NS_OK), QM_DIAGNOSTIC_ASSERT_UNREACHABLE);
 
     tryDidNotReturn = true;
 
     return NS_OK;
   }();
 
   EXPECT_TRUE(tryDidNotReturn);
   EXPECT_EQ(rv, NS_OK);
 }
 
 TEST(QuotaCommon_Try, Success_NoErr_DiagnosticAssertUnreachable)
 {
   bool tryDidNotReturn = false;
 
   [&tryDidNotReturn]() -> void {
-    QM_TRY(NS_OK, QM_DIAGNOSTIC_ASSERT_UNREACHABLE_VOID);
+    QM_TRY(MOZ_TO_RESULT(NS_OK), QM_DIAGNOSTIC_ASSERT_UNREACHABLE_VOID);
 
     tryDidNotReturn = true;
   }();
 
   EXPECT_TRUE(tryDidNotReturn);
 }
 #else
 #  if defined(QM_DIAGNOSTIC_ASSERT_UNREACHABLE) || \
@@ -129,17 +129,17 @@ TEST(QuotaCommon_Try, Success_NoErr_Diag
 #endif
 
 TEST(QuotaCommon_Try, Success_WithCleanup)
 {
   bool tryCleanupRan = false;
   bool tryDidNotReturn = false;
 
   nsresult rv = [&tryCleanupRan, &tryDidNotReturn]() -> nsresult {
-    QM_TRY(NS_OK, QM_PROPAGATE,
+    QM_TRY(MOZ_TO_RESULT(NS_OK), QM_PROPAGATE,
            [&tryCleanupRan](const auto&) { tryCleanupRan = true; });
 
     tryDidNotReturn = true;
 
     return NS_OK;
   }();
 
   EXPECT_FALSE(tryCleanupRan);
@@ -163,47 +163,47 @@ TEST(QuotaCommon_Try, Failure_PropagateE
   EXPECT_EQ(rv, NS_ERROR_FAILURE);
 }
 
 TEST(QuotaCommon_Try, Failure_CustomErr)
 {
   bool tryDidNotReturn = false;
 
   nsresult rv = [&tryDidNotReturn]() -> nsresult {
-    QM_TRY(NS_ERROR_FAILURE, NS_ERROR_UNEXPECTED);
+    QM_TRY(MOZ_TO_RESULT(NS_ERROR_FAILURE), NS_ERROR_UNEXPECTED);
 
     tryDidNotReturn = true;
 
     return NS_OK;
   }();
 
   EXPECT_FALSE(tryDidNotReturn);
   EXPECT_EQ(rv, NS_ERROR_UNEXPECTED);
 }
 
 TEST(QuotaCommon_Try, Failure_NoErr)
 {
   bool tryDidNotReturn = false;
 
   [&tryDidNotReturn]() -> void {
-    QM_TRY(NS_ERROR_FAILURE, QM_VOID);
+    QM_TRY(MOZ_TO_RESULT(NS_ERROR_FAILURE), QM_VOID);
 
     tryDidNotReturn = true;
   }();
 
   EXPECT_FALSE(tryDidNotReturn);
 }
 
 TEST(QuotaCommon_Try, Failure_WithCleanup)
 {
   bool tryCleanupRan = false;
   bool tryDidNotReturn = false;
 
   nsresult rv = [&tryCleanupRan, &tryDidNotReturn]() -> nsresult {
-    QM_TRY(NS_ERROR_FAILURE, QM_PROPAGATE,
+    QM_TRY(MOZ_TO_RESULT(NS_ERROR_FAILURE), QM_PROPAGATE,
            [&tryCleanupRan](const auto& result) {
              EXPECT_EQ(result, NS_ERROR_FAILURE);
 
              tryCleanupRan = true;
            });
 
     tryDidNotReturn = true;
 
@@ -218,17 +218,18 @@ TEST(QuotaCommon_Try, Failure_WithCleanu
 TEST(QuotaCommon_Try, Failure_WithCleanup_UnwrapErr)
 {
   bool tryCleanupRan = false;
   bool tryDidNotReturn = false;
 
   nsresult rv;
 
   [&tryCleanupRan, &tryDidNotReturn](nsresult& aRv) -> void {
-    QM_TRY(NS_ERROR_FAILURE, QM_VOID, ([&tryCleanupRan, &aRv](auto& result) {
+    QM_TRY(MOZ_TO_RESULT(NS_ERROR_FAILURE), QM_VOID,
+           ([&tryCleanupRan, &aRv](auto& result) {
              EXPECT_EQ(result, NS_ERROR_FAILURE);
 
              aRv = result;
 
              tryCleanupRan = true;
            }));
 
     tryDidNotReturn = true;
@@ -239,17 +240,17 @@ TEST(QuotaCommon_Try, Failure_WithCleanu
   EXPECT_TRUE(tryCleanupRan);
   EXPECT_FALSE(tryDidNotReturn);
   EXPECT_EQ(rv, NS_ERROR_FAILURE);
 }
 
 TEST(QuotaCommon_Try, SameLine)
 {
   // clang-format off
-  QM_TRY(NS_OK, QM_VOID); QM_TRY(NS_OK, QM_VOID);
+  QM_TRY(MOZ_TO_RESULT(NS_OK), QM_VOID); QM_TRY(MOZ_TO_RESULT(NS_OK), QM_VOID);
   // clang-format on
 }
 
 TEST(QuotaCommon_Try, NestingMadness_Success)
 {
   bool nestedTryDidNotReturn = false;
   bool tryDidNotReturn = false;
 
@@ -814,17 +815,17 @@ TEST(QuotaCommon_TryReturn, Success)
   EXPECT_EQ(res.unwrap(), 42);
 }
 
 TEST(QuotaCommon_TryReturn, Success_nsresult)
 {
   bool tryReturnDidNotReturn = false;
 
   auto res = [&tryReturnDidNotReturn] {
-    QM_TRY_RETURN(NS_OK);
+    QM_TRY_RETURN(MOZ_TO_RESULT(NS_OK));
 
     tryReturnDidNotReturn = true;
   }();
 
   EXPECT_FALSE(tryReturnDidNotReturn);
   EXPECT_TRUE(res.isOk());
 }
 
@@ -880,17 +881,17 @@ TEST(QuotaCommon_TryReturn, Failure_Prop
   EXPECT_EQ(res.unwrapErr(), NS_ERROR_FAILURE);
 }
 
 TEST(QuotaCommon_TryReturn, Failure_PropagateErr_nsresult)
 {
   bool tryReturnDidNotReturn = false;
 
   auto res = [&tryReturnDidNotReturn] {
-    QM_TRY_RETURN(NS_ERROR_FAILURE);
+    QM_TRY_RETURN(MOZ_TO_RESULT(NS_ERROR_FAILURE));
 
     tryReturnDidNotReturn = true;
   }();
 
   EXPECT_FALSE(tryReturnDidNotReturn);
   EXPECT_TRUE(res.isErr());
   EXPECT_EQ(res.unwrapErr(), NS_ERROR_FAILURE);
 }
--- a/dom/security/test/https-first/file_referrer_policy.sjs
+++ b/dom/security/test/https-first/file_referrer_policy.sjs
@@ -23,51 +23,81 @@ const expectedQueries = [
   "no-referrer-when-downgrade",
   "origin",
   "origin-when-cross-origin",
   "same-origin",
   "strict-origin",
   "strict-origin-when-cross-origin",
   "unsafe-url",
 ];
+function readQuery(testCase) {
+  let twoValues = testCase.split("-");
+  let upgradeRequest = twoValues[0] === "https" ? 1 : 0;
+  let httpsResponse = twoValues[1] === "https" ? 1 : 0;
+  return [upgradeRequest, httpsResponse];
+}
 
 function handleRequest(request, response) {
   response.setHeader("Cache-Control", "no-cache", false);
   // eslint-disable-next-line mozilla/reject-importGlobalProperties
   Cu.importGlobalProperties(["URLSearchParams"]);
   let query = new URLSearchParams(request.queryString);
-  if (query.has("sendMe")) {
+  // Downgrade to test http/https -> HTTP referrer policy
+  if (query.has("sendMe2") && request.scheme === "https") {
+    // Simulating a timeout by processing the https request
+    response.processAsync();
+    return;
+  }
+  if (query.has("sendMe") || query.has("sendMe2")) {
     response.write(RESPONSE_POLICY);
     return;
   }
   // Get the referrer policy that we want to set
   let referrerPolicy = query.get("rp");
   //If the query contained one of the expected referrer policies send a request with the given policy,
   // else send error
   if (expectedQueries.includes(referrerPolicy)) {
+    // Determine the test case, e.g. don't upgrade request but send response in https
+    let testCase = readQuery(query.get("upgrade"));
+    let httpsRequest = testCase[0];
+    let httpsResponse = testCase[1];
     // Downgrade to http if upgrade equals 0
-    if (query.get("upgrade") === "0" && request.scheme === "https") {
+    if (httpsRequest === 0 && request.scheme === "https") {
       // Simulating a timeout by processing the https request
       response.processAsync();
       return;
     }
     // create js redirection that request with the given (related to the query) referrer policy
-    const SEND_REQUEST = `
+    const SEND_REQUEST_HTTPS = `
       <html>
       <head>
         <meta name="referrer" content=${referrerPolicy}>
       </head>
       <body>
       JS REDIRECT
       <script>
         let url = 'https://example.com/tests/dom/security/test/https-first/file_referrer_policy.sjs?sendMe';
         window.location = url;
       </script>
       </body>
       </html>`;
-    response.write(SEND_REQUEST);
+    const SEND_REQUEST_HTTP = `
+      <html>
+      <head>
+        <meta name="referrer" content=${referrerPolicy}>
+      </head>
+      <body>
+      JS REDIRECT
+      <script>
+        let url = 'http://example.com/tests/dom/security/test/https-first/file_referrer_policy.sjs?sendMe2';
+        window.location = url;
+      </script>
+      </body>
+      </html>`;
+    let respond = httpsResponse === 1 ? SEND_REQUEST_HTTPS : SEND_REQUEST_HTTP;
+    response.write(respond);
     return;
   }
 
   // We should never get here but in case we send an error
   response.setStatusLine(request.httpVersion, 500, "OK");
   response.write(RESPONSE_ERROR);
 }
--- a/dom/security/test/https-first/test_referrer_policy.html
+++ b/dom/security/test/https-first/test_referrer_policy.html
@@ -6,21 +6,25 @@
 <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
 </head>
 <body>
 
 <script class="testbody" type="text/javascript">
 "use strict";
 /*
  * Description of the test:
- * We perform each test with 4 different settings.
+ * We perform each test with 8 different settings.
  * The first is a same origin request from an http site to an https site.
  * The second is a same origin request from an https -> https.
  * The third is a cross-origin request from an http -> https.
- * The last is a cross-origin request from an https -> https.
+ * The fourth is a cross-origin request from an https -> https.
+ * The fifth is a same origin request from an http -> http site.
+ * The sixth is a same origin request from an https -> http.
+ * The seventh is a cross-origin request from an http -> http.
+ * The last is a cross-origin request from an https -> http.
  */
 
 SimpleTest.waitForExplicitFinish();
 
 const SAME_ORIGIN =
   "http://example.com/tests/dom/security/test/https-first/file_referrer_policy.sjs?";
 // SAME ORIGIN with "https" instead of "http"
 const SAME_ORIGIN_HTTPS = SAME_ORIGIN.replace("http", "https");
@@ -33,131 +37,184 @@ const CROSS_ORIGIN_HTTPS = CROSS_ORIGIN.
 // Define test cases. Query equals the test case referrer policy.
 // We will set in the final request the url parameters such that 'rp=' equals the referrer policy
 //and 'upgrade=' equals '1' if the request should be https.
 // For a 'upgrade=0' url parameter the server lead to a timeout such that https-first downgrades
 // the request to http.
 const testCases = [
   {
     query: "no-referrer",
-    expectedResultSameOriginDown: "",
-    expectedResultSameOriginUp: "",
-    expectedResultCrossOriginDown:"",
-    expectedResultCrossOriginUp:""
+    expectedResultSameOriginDownUp: "",
+    expectedResultSameOriginUpUp: "",
+    expectedResultCrossOriginDownUp:"",
+    expectedResultCrossOriginUpUp:"",
+    expectedResultSameOriginDownDown: "",
+    expectedResultSameOriginUpDown: "",
+    expectedResultCrossOriginDownDown:"",
+    expectedResultCrossOriginUpDown: "",
   },
   {
     query: "no-referrer-when-downgrade",
-    expectedResultSameOriginDown: SAME_ORIGIN + "rp=no-referrer-when-downgrade&upgrade=0",
-    expectedResultSameOriginUp: SAME_ORIGIN_HTTPS + "rp=no-referrer-when-downgrade&upgrade=1",
-    expectedResultCrossOriginDown: CROSS_ORIGIN + "rp=no-referrer-when-downgrade&upgrade=0",
-    expectedResultCrossOriginUp: CROSS_ORIGIN_HTTPS + "rp=no-referrer-when-downgrade&upgrade=1"
+    expectedResultSameOriginDownUp: SAME_ORIGIN + "rp=no-referrer-when-downgrade&upgrade=http-https",
+    expectedResultSameOriginUpUp: SAME_ORIGIN_HTTPS + "rp=no-referrer-when-downgrade&upgrade=https-https",
+    expectedResultCrossOriginDownUp: CROSS_ORIGIN + "rp=no-referrer-when-downgrade&upgrade=http-https",
+    expectedResultCrossOriginUpUp: CROSS_ORIGIN_HTTPS + "rp=no-referrer-when-downgrade&upgrade=https-https",
+    expectedResultSameOriginDownDown: SAME_ORIGIN + "rp=no-referrer-when-downgrade&upgrade=http-http",
+    expectedResultSameOriginUpDown: "",
+    expectedResultCrossOriginDownDown: CROSS_ORIGIN + "rp=no-referrer-when-downgrade&upgrade=http-http",
+    expectedResultCrossOriginUpDown:"",
   },
   {
     query: "origin",
-    expectedResultSameOriginDown: "http://example.com/",
-    expectedResultSameOriginUp: "https://example.com/",
-    expectedResultCrossOriginDown:"http://example.org/",
-    expectedResultCrossOriginUp:"https://example.org/"
+    expectedResultSameOriginDownUp: "http://example.com/",
+    expectedResultSameOriginUpUp: "https://example.com/",
+    expectedResultCrossOriginDownUp:"http://example.org/",
+    expectedResultCrossOriginUpUp:"https://example.org/",
+    expectedResultSameOriginDownDown: "http://example.com/",
+    expectedResultSameOriginUpDown: "https://example.com/",
+    expectedResultCrossOriginDownDown:"http://example.org/",
+    expectedResultCrossOriginUpDown:"https://example.org/",
   },
   {
     query: "origin-when-cross-origin",
-    expectedResultSameOriginDown: "http://example.com/",
-    expectedResultSameOriginUp: SAME_ORIGIN_HTTPS + "rp=origin-when-cross-origin&upgrade=1",
-    expectedResultCrossOriginDown:"http://example.org/",
-    expectedResultCrossOriginUp:"https://example.org/"
+    expectedResultSameOriginDownUp: "http://example.com/",
+    expectedResultSameOriginUpUp: SAME_ORIGIN_HTTPS + "rp=origin-when-cross-origin&upgrade=https-https",
+    expectedResultCrossOriginDownUp:"http://example.org/",
+    expectedResultCrossOriginUpUp:"https://example.org/",
+    expectedResultSameOriginDownDown: SAME_ORIGIN + "rp=origin-when-cross-origin&upgrade=http-http",
+    expectedResultSameOriginUpDown: "https://example.com/",
+    expectedResultCrossOriginDownDown:"http://example.org/",
+    expectedResultCrossOriginUpDown:"https://example.org/",
   },
   {
     query: "same-origin",
-    expectedResultSameOriginDown: "",
-    expectedResultSameOriginUp: SAME_ORIGIN_HTTPS + "rp=same-origin&upgrade=1",
-    expectedResultCrossOriginDown:"",
-    expectedResultCrossOriginUp:""
+    expectedResultSameOriginDownUp: "",
+    expectedResultSameOriginUpUp: SAME_ORIGIN_HTTPS + "rp=same-origin&upgrade=https-https",
+    expectedResultCrossOriginDownUp:"",
+    expectedResultCrossOriginUpUp:"",
+    expectedResultSameOriginDownDown: SAME_ORIGIN + "rp=same-origin&upgrade=http-http",
+    expectedResultSameOriginUpDown: "",
+    expectedResultCrossOriginDownDown: "",
+    expectedResultCrossOriginUpDown:"",
   },
   {
     query: "strict-origin",
-    expectedResultSameOriginDown: "http://example.com/",
-    expectedResultSameOriginUp: "https://example.com/",
-    expectedResultCrossOriginDown:"http://example.org/",
-    expectedResultCrossOriginUp:"https://example.org/"
+    expectedResultSameOriginDownUp: "http://example.com/",
+    expectedResultSameOriginUpUp: "https://example.com/",
+    expectedResultCrossOriginDownUp:"http://example.org/",
+    expectedResultCrossOriginUpUp:"https://example.org/",
+    expectedResultSameOriginDownDown: "http://example.com/",
+    expectedResultSameOriginUpDown: "",
+    expectedResultCrossOriginDownDown:"http://example.org/",
+    expectedResultCrossOriginUpDown:"",
   },
   {
     query: "strict-origin-when-cross-origin",
-    expectedResultSameOriginDown: "http://example.com/",
-    expectedResultSameOriginUp: SAME_ORIGIN_HTTPS + "rp=strict-origin-when-cross-origin&upgrade=1",
-    expectedResultCrossOriginDown:"http://example.org/",
-    expectedResultCrossOriginUp:"https://example.org/"
+    expectedResultSameOriginDownUp: "http://example.com/",
+    expectedResultSameOriginUpUp: SAME_ORIGIN_HTTPS + "rp=strict-origin-when-cross-origin&upgrade=https-https",
+    expectedResultCrossOriginDownUp:"http://example.org/",
+    expectedResultCrossOriginUpUp:"https://example.org/",
+    expectedResultSameOriginDownDown: SAME_ORIGIN + "rp=strict-origin-when-cross-origin&upgrade=http-http",
+    expectedResultSameOriginUpDown: "",
+    expectedResultCrossOriginDownDown:"http://example.org/",
+    expectedResultCrossOriginUpDown:"",
   },
   {
     query: "unsafe-url",
-    expectedResultSameOriginDown: SAME_ORIGIN + "rp=unsafe-url&upgrade=0",
-    expectedResultSameOriginUp:  SAME_ORIGIN_HTTPS + "rp=unsafe-url&upgrade=1",
-    expectedResultCrossOriginDown: CROSS_ORIGIN + "rp=unsafe-url&upgrade=0",
-    expectedResultCrossOriginUp: CROSS_ORIGIN_HTTPS + "rp=unsafe-url&upgrade=1"
+    expectedResultSameOriginDownUp: SAME_ORIGIN + "rp=unsafe-url&upgrade=http-https",
+    expectedResultSameOriginUpUp:  SAME_ORIGIN_HTTPS + "rp=unsafe-url&upgrade=https-https",
+    expectedResultCrossOriginDownUp: CROSS_ORIGIN + "rp=unsafe-url&upgrade=http-https",
+    expectedResultCrossOriginUpUp: CROSS_ORIGIN_HTTPS + "rp=unsafe-url&upgrade=https-https",
+    expectedResultSameOriginDownDown: SAME_ORIGIN + "rp=unsafe-url&upgrade=http-http",
+    expectedResultSameOriginUpDown: SAME_ORIGIN_HTTPS + "rp=unsafe-url&upgrade=https-http",
+    expectedResultCrossOriginDownDown:CROSS_ORIGIN + "rp=unsafe-url&upgrade=http-http",
+    expectedResultCrossOriginUpDown:CROSS_ORIGIN_HTTPS + "rp=unsafe-url&upgrade=https-http",
   },
 ];
 
 
 let currentTest = 0;
 let sameOriginRequest = true;
 let testWin;
 let currentQuery;
 window.addEventListener("message", receiveMessage);
 let currentRun = 0;
-// All combinations, downgraded: HTTP -> HTTPS, upgraded: HTTPS -> HTTPS
-const ALL_COMB = ["downgraded", "upgraded"]
+// All combinations, HTTP -> HTTPS, HTTPS -> HTTPS, HTTP -> HTTP, HTTPS -> HTTP
+const ALL_COMB = ["http-https", "https-https" ,"http-http", "https-http"];
 
 // Receive message and verify that we receive the expected referrer header
 async function receiveMessage(event) {
   let data = event.data;
   currentQuery = testCases[currentTest].query;
+  let currentComb = ALL_COMB[currentRun];
   // if request was http -> https
-  if (currentRun === 0) {
+  if (currentComb === "http-https") {
     if (sameOriginRequest){
-      is(data.result, testCases[currentTest].expectedResultSameOriginDown ,
+      is(data.result, testCases[currentTest].expectedResultSameOriginDownUp ,
       "We received for the downgraded same site request with referrer policy: " + currentQuery + " the correct referrer");
-      is(data.location, SAME_ORIGIN_HTTPS + "sendMe");
+      is(data.location, SAME_ORIGIN_HTTPS + "sendMe","Opened correct location");
     } else {
-      is(data.result, testCases[currentTest].expectedResultCrossOriginDown ,
+      is(data.result, testCases[currentTest].expectedResultCrossOriginDownUp ,
       "We received for the downgraded cross site request with referrer policy: " + currentQuery + " the correct referrer");
       is(data.location, SAME_ORIGIN_HTTPS + "sendMe", "Opened correct location");
     }
   // if request was https -> https
-  } else if (currentRun === 1) {
+  } else if (currentComb === "https-https") {
     if (sameOriginRequest){
-      is(data.result, testCases[currentTest].expectedResultSameOriginUp ,
+      is(data.result, testCases[currentTest].expectedResultSameOriginUpUp ,
       "We received for the upgraded same site request with referrer policy: " + currentQuery + " the correct referrer");
-      is(data.location, SAME_ORIGIN_HTTPS + "sendMe");
+      is(data.location, SAME_ORIGIN_HTTPS + "sendMe", "Opened correct location");
     } else {
-      is(data.result, testCases[currentTest].expectedResultCrossOriginUp ,
+      is(data.result, testCases[currentTest].expectedResultCrossOriginUpUp,
       "We received for the upgraded cross site request with referrer policy: " + currentQuery + " the correct referrer");
       is(data.location, SAME_ORIGIN_HTTPS + "sendMe", "Opened correct location");
     }
+  } else if (currentComb === "http-http") {
+    if (sameOriginRequest){
+      is(data.result, testCases[currentTest].expectedResultSameOriginDownDown ,
+      "We received for the upgraded same site request with referrer policy: " + currentQuery + " the correct referrer");
+      is(data.location, SAME_ORIGIN + "sendMe2","Opened correct location for" + currentQuery + currentComb);
+    } else {
+      is(data.result, testCases[currentTest].expectedResultCrossOriginDownDown,
+      "We received for the upgraded cross site request with referrer policy: " + currentQuery + " the correct referrer");
+      is(data.location, SAME_ORIGIN + "sendMe2", "Opened correct location " + currentQuery + currentComb);
+    }
+  } else if (currentComb === "https-http") {
+    if (sameOriginRequest){
+      is(data.result, testCases[currentTest].expectedResultSameOriginUpDown ,
+      "We received for the upgraded same site request with referrer policy: " + currentQuery + " the correct referrer");
+      is(data.location, SAME_ORIGIN + "sendMe2","Opened correct location " + currentQuery + currentComb);
+    } else {
+      is(data.result, testCases[currentTest].expectedResultCrossOriginUpDown,
+      "We received for the upgraded cross site request with referrer policy: " + currentQuery + " the correct referrer");
+      is(data.location, SAME_ORIGIN + "sendMe2", "Opened correct location " + currentQuery + currentComb);
+    }
   }
   testWin.close();
   currentRun++;
   if (currentTest >= testCases.length -1  && currentRun === ALL_COMB.length && !sameOriginRequest) {
     window.removeEventListener("message", receiveMessage);
     SimpleTest.finish();
     return;
   }
   runTest();
 }
 
 async function runTest() {
   currentQuery = testCases[currentTest].query;
   // send same origin request
   if (sameOriginRequest && currentRun < ALL_COMB.length) {
     // if upgrade = 0 downgrade request, else upgrade
-    testWin = window.open(SAME_ORIGIN + "rp=" +currentQuery + "&upgrade=" + currentRun, "_blank");
+    testWin = window.open(SAME_ORIGIN + "rp=" +currentQuery + "&upgrade=" + ALL_COMB[currentRun], "_blank");
   } else {
     // if same origin isn't set, check if we need to send cross origin requests
     // eslint-disable-next-line no-lonely-if
     if (!sameOriginRequest && currentRun < ALL_COMB.length ) {
     // if upgrade = 0 downgrade request, else upgrade
-    testWin = window.open(CROSS_ORIGIN + "rp=" +currentQuery + "&upgrade=" + currentRun, "_blank");
+    testWin = window.open(CROSS_ORIGIN + "rp=" +currentQuery + "&upgrade=" + ALL_COMB[currentRun], "_blank");
     } // else we completed all test case of the current query for the current origin. Prepare and call next test
     else {
       // reset currentRun and go to next query
       currentRun = 0;
       if(!sameOriginRequest){
         currentTest++;
       }
       // run same test again for crossOrigin or start new test with sameOrigin
--- a/extensions/permissions/PermissionDelegateHandler.cpp
+++ b/extensions/permissions/PermissionDelegateHandler.cpp
@@ -182,17 +182,17 @@ bool PermissionDelegateHandler::HasFeatu
     return true;
   }
 
   nsAutoString featureName(info->mFeatureName);
   return FeaturePolicyUtils::IsFeatureAllowed(mDocument, featureName);
 }
 
 bool PermissionDelegateHandler::HasPermissionDelegated(
-    const nsACString& aType) {
+    const nsACString& aType) const {
   MOZ_ASSERT(mDocument);
 
   // System principal should have right to make permission request
   if (mPrincipal->IsSystemPrincipal()) {
     return true;
   }
 
   const DelegateInfo* info =
--- a/extensions/permissions/PermissionDelegateHandler.h
+++ b/extensions/permissions/PermissionDelegateHandler.h
@@ -62,17 +62,17 @@ class PermissionDelegateHandler final : 
     }
   } DelegatedPermissionList;
 
   bool Initialize();
 
   /*
    * Indicates if we has the right to make permission request with aType
    */
-  bool HasPermissionDelegated(const nsACString& aType);
+  bool HasPermissionDelegated(const nsACString& aType) const;
 
   /*
    * Get permission state, which applied permission delegate policy.
    *
    * @param aType the permission type to get
    * @param aPermission out argument which will be a permission type that we
    *                    will return from this function.
    * @param aExactHostMatch whether to look for the exact host name or also for
--- a/gfx/2d/ScaleFactors2D.h
+++ b/gfx/2d/ScaleFactors2D.h
@@ -48,16 +48,24 @@ struct ScaleFactors2D {
   }
 
   // Convert to a ScaleFactor. Asserts that the scales are, in fact, equal.
   ScaleFactor<src, dst> ToScaleFactor() const {
     MOZ_ASSERT(AreScalesSame());
     return ScaleFactor<src, dst>(xScale);
   }
 
+  /**
+   * Convert between typed and untyped ScaleFactors2D.
+   */
+  static ScaleFactors2D FromUnknownScaleFactors2D(
+      const ScaleFactors2D<UnknownUnits, UnknownUnits>& aUnknown) {
+    return ScaleFactors2D{aUnknown.xScale, aUnknown.yScale};
+  }
+
   ScaleFactors2D<src, dst>& operator=(const ScaleFactors2D<src, dst>&) =
       default;
 
   bool operator==(const ScaleFactors2D<src, dst>& aOther) const {
     return xScale == aOther.xScale && yScale == aOther.yScale;
   }
 
   bool operator!=(const ScaleFactors2D<src, dst>& aOther) const {
--- a/gfx/config/gfxVars.h
+++ b/gfx/config/gfxVars.h
@@ -43,23 +43,23 @@ class gfxVarReceiver;
   _(UseWebRenderANGLE, bool, false)                                \
   _(UseWebRenderFlipSequentialWin, bool, false)                    \
   _(UseWebRenderDCompWin, bool, false)                             \
   _(UseWebRenderDCompVideoOverlayWin, bool, false)                 \
   _(UseWebRenderTripleBufferingWin, bool, false)                   \
   _(UseWebRenderCompositor, bool, false)                           \
   _(UseWebRenderProgramBinaryDisk, bool, false)                    \
   _(UseWebRenderOptimizedShaders, bool, false)                     \
-  _(UseWebRenderMultithreading, bool, false)                       \
   _(UseWebRenderScissoredCacheClears, bool, true)                  \
   _(WebRenderProfilerUI, nsCString, nsCString())                   \
   _(WebglAllowCoreProfile, bool, true)                             \
   _(WebglAllowWindowsNativeGl, bool, false)                        \
   _(WebRenderMaxPartialPresentRects, int32_t, 0)                   \
   _(WebRenderDebugFlags, int32_t, 0)                               \
+  _(WebRenderBoolParameters, int32_t, 0)                           \
   _(WebRenderBatchingLookback, int32_t, 10)                        \
   _(WebRenderBlobTileSize, int32_t, 256)                           \
   _(UseSoftwareWebRender, bool, false)                             \
   _(AllowSoftwareWebRenderD3D11, bool, false)                      \
   _(ScreenDepth, int32_t, 0)                                       \
   _(GREDirectory, nsString, nsString())                            \
   _(ProfDirectory, nsString, nsString())                           \
   _(AllowD3D11KeyedMutex, bool, false)                             \
--- a/gfx/layers/Compositor.h
+++ b/gfx/layers/Compositor.h
@@ -261,27 +261,16 @@ class Compositor : public TextureSourceP
    * chain. aVisibleRect is used to determine which edges should be antialiased,
    * without applying the effect to the inner edges of a tiled layer.
    */
   virtual void DrawQuad(const gfx::Rect& aRect, const gfx::IntRect& aClipRect,
                         const EffectChain& aEffectChain, gfx::Float aOpacity,
                         const gfx::Matrix4x4& aTransform,
                         const gfx::Rect& aVisibleRect) = 0;
 
-  /**
-   * Overload of DrawQuad, with aVisibleRect defaulted to the value of aRect.
-   * Use this when you are drawing a single quad that is not part of a tiled
-   * layer.
-   */
-  void DrawQuad(const gfx::Rect& aRect, const gfx::IntRect& aClipRect,
-                const EffectChain& aEffectChain, gfx::Float aOpacity,
-                const gfx::Matrix4x4& aTransform) {
-    DrawQuad(aRect, aClipRect, aEffectChain, aOpacity, aTransform, aRect);
-  }
-
   void SetClearColor(const gfx::DeviceColor& aColor) { mClearColor = aColor; }
 
   /**
    * Start a new frame for rendering to the window.
    * Needs to be paired with a call to EndFrame() if the return value is not
    * Nothing().
    *
    * aInvalidRegion is the invalid region of the window.
--- a/gfx/layers/FrameMetrics.cpp
+++ b/gfx/layers/FrameMetrics.cpp
@@ -31,16 +31,17 @@ std::ostream& operator<<(std::ostream& a
           << "] [rcs=" << aMetrics.GetBoundingCompositionSize()
           << "] [v=" << aMetrics.GetLayoutViewport()
           << nsPrintfCString("] [z=(ld=%.3f r=%.3f",
                              aMetrics.GetDevPixelsPerCSSPixel().scale,
                              aMetrics.GetPresShellResolution())
                  .get()
           << " cr=" << aMetrics.GetCumulativeResolution()
           << " z=" << aMetrics.GetZoom()
+          << " t=" << aMetrics.GetTransformToAncestorScale()
           << " er=" << aMetrics.GetExtraResolution() << " )] [u=("
           << (int)aMetrics.GetVisualScrollUpdateType() << " "
           << aMetrics.GetScrollGeneration()
           << ")] scrollId=" << aMetrics.GetScrollId();
   if (aMetrics.IsRootContent()) {
     aStream << " [rcd]";
   }
   aStream << " }";
--- a/gfx/layers/FrameMetrics.h
+++ b/gfx/layers/FrameMetrics.h
@@ -6,16 +6,17 @@
 
 #ifndef GFX_FRAMEMETRICS_H
 #define GFX_FRAMEMETRICS_H
 
 #include <stdint.h>  // for uint8_t, uint32_t, uint64_t
 #include <iosfwd>
 
 #include "Units.h"                  // for CSSRect, CSSPixel, etc
+#include "UnitTransforms.h"         // for ViewAs
 #include "mozilla/DefineEnum.h"     // for MOZ_DEFINE_ENUM
 #include "mozilla/HashFunctions.h"  // for HashGeneric
 #include "mozilla/Maybe.h"
 #include "mozilla/gfx/BasePoint.h"               // for BasePoint
 #include "mozilla/gfx/Rect.h"                    // for RoundedIn
 #include "mozilla/gfx/ScaleFactor.h"             // for ScaleFactor
 #include "mozilla/gfx/Logging.h"                 // for Log
 #include "mozilla/layers/LayersTypes.h"          // for ScrollDirection
@@ -91,16 +92,17 @@ struct FrameMetrics {
         mCumulativeResolution(),
         mDevPixelsPerCSSPixel(1),
         mScrollOffset(0, 0),
         mZoom(),
         mBoundingCompositionSize(0, 0),
         mPresShellId(-1),
         mLayoutViewport(0, 0, 0, 0),
         mExtraResolution(),
+        mTransformToAncestorScale(),
         mPaintRequestTime(),
         mVisualDestination(0, 0),
         mVisualScrollUpdateType(eNone),
         mCompositionSizeWithoutDynamicToolbar(),
         mIsRootContent(false),
         mIsScrollInfoLayer(false),
         mHasNonZeroDisplayPortMargins(false),
         mMinimalDisplayPort(false) {}
@@ -121,16 +123,17 @@ struct FrameMetrics {
            mDevPixelsPerCSSPixel == aOther.mDevPixelsPerCSSPixel &&
            mScrollOffset == aOther.mScrollOffset &&
            // don't compare mZoom
            mScrollGeneration == aOther.mScrollGeneration &&
            mBoundingCompositionSize == aOther.mBoundingCompositionSize &&
            mPresShellId == aOther.mPresShellId &&
            mLayoutViewport.IsEqualEdges(aOther.mLayoutViewport) &&
            mExtraResolution == aOther.mExtraResolution &&
+           mTransformToAncestorScale == aOther.mTransformToAncestorScale &&
            mPaintRequestTime == aOther.mPaintRequestTime &&
            mVisualDestination == aOther.mVisualDestination &&
            mVisualScrollUpdateType == aOther.mVisualScrollUpdateType &&
            mIsRootContent == aOther.mIsRootContent &&
            mIsScrollInfoLayer == aOther.mIsScrollInfoLayer &&
            mHasNonZeroDisplayPortMargins ==
                aOther.mHasNonZeroDisplayPortMargins &&
            mMinimalDisplayPort == aOther.mMinimalDisplayPort &&
@@ -150,17 +153,26 @@ struct FrameMetrics {
   CSSToScreenScale2D DisplayportPixelsPerCSSPixel() const {
     // Note: use 'mZoom * ParentLayerToLayerScale(1.0f)' as the CSS-to-Layer
     // scale instead of LayersPixelsPerCSSPixel(), because displayport
     // calculations are done in the context of a repaint request, where we ask
     // Layout to repaint at a new resolution that includes any async zoom. Until
     // this repaint request is processed, LayersPixelsPerCSSPixel() does not yet
     // include the async zoom, but it will when the displayport is interpreted
     // for the repaint.
-    return mZoom * ParentLayerToLayerScale(1.0f) / mExtraResolution;
+    // Note 2: we include the transform to ancestor scale because this function
+    // (as the name implies) is used only in various displayport calculation
+    // related places, and those calculations want the transform to ancestor
+    // scale to be included becaese they want to reason about pixels which are
+    // the same size as screen pixels (so displayport sizes are e.g. limited to
+    // a multiple of the screen size). Whereas mZoom and mCumulativeResolution
+    // do not include it because of expectations of the code where they are
+    // used.
+    return mZoom * ParentLayerToLayerScale(1.0f) *
+           ViewAs<LayerToScreenScale2D>(mTransformToAncestorScale);
   }
 
   CSSToLayerScale2D LayersPixelsPerCSSPixel() const {
     return mDevPixelsPerCSSPixel * mCumulativeResolution;
   }
 
   // Get the amount by which this frame has been zoomed since the last repaint.
   LayerToParentLayerScale GetAsyncZoom() const {
@@ -392,16 +404,24 @@ struct FrameMetrics {
   void SetExtraResolution(const ScreenToLayerScale2D& aExtraResolution) {
     mExtraResolution = aExtraResolution;
   }
 
   const ScreenToLayerScale2D& GetExtraResolution() const {
     return mExtraResolution;
   }
 
+  void SetTransformToAncestorScale(const Scale2D& aTransformToAncestorScale) {
+    mTransformToAncestorScale = aTransformToAncestorScale;
+  }
+
+  const Scale2D& GetTransformToAncestorScale() const {
+    return mTransformToAncestorScale;
+  }
+
   const CSSRect& GetScrollableRect() const { return mScrollableRect; }
 
   void SetScrollableRect(const CSSRect& aScrollableRect) {
     mScrollableRect = aScrollableRect;
   }
 
   // If the frame is in vertical-RTL writing mode(E.g. "writing-mode:
   // vertical-rl" in CSS), or if it's in horizontal-RTL writing-mode(E.g.
@@ -624,16 +644,19 @@ struct FrameMetrics {
   // For a scroll frame that is not an RSF, this metric is meaningless and
   // invalid.
   CSSRect mLayoutViewport;
 
   // The extra resolution at which content in this scroll frame is drawn beyond
   // that necessary to draw one Layer pixel per Screen pixel.
   ScreenToLayerScale2D mExtraResolution;
 
+  // The scale on this scroll frame induced by enclosing CSS transforms.
+  Scale2D mTransformToAncestorScale;
+
   // The time at which the APZC last requested a repaint for this scroll frame.
   TimeStamp mPaintRequestTime;
 
   // These fields are used when the main thread wants to set a visual viewport
   // offset that's distinct from the layout viewport offset.
   // In this case, mVisualScrollUpdateType is set to eMainThread, and
   // mVisualDestination is set to desired visual destination (relative
   // to the document, like mScrollOffset).
--- a/gfx/layers/LayerManager.cpp
+++ b/gfx/layers/LayerManager.cpp
@@ -91,31 +91,16 @@ already_AddRefed<DrawTarget> LayerManage
 }
 
 already_AddRefed<ImageContainer> LayerManager::CreateImageContainer(
     ImageContainer::Mode flag) {
   RefPtr<ImageContainer> container = new ImageContainer(flag);
   return container.forget();
 }
 
-bool LayerManager::LayersComponentAlphaEnabled() {
-  // If MOZ_GFX_OPTIMIZE_MOBILE is defined, we force component alpha off
-  // and ignore the preference.
-#ifdef MOZ_GFX_OPTIMIZE_MOBILE
-  return false;
-#else
-  return StaticPrefs::
-      layers_componentalpha_enabled_AtStartup_DoNotUseDirectly();
-#endif
-}
-
-bool LayerManager::AreComponentAlphaLayersEnabled() {
-  return LayerManager::LayersComponentAlphaEnabled();
-}
-
 /*static*/
 void LayerManager::LayerUserDataDestroy(void* data) {
   delete static_cast<LayerUserData*>(data);
 }
 
 UniquePtr<LayerUserData> LayerManager::RemoveUserData(void* aKey) {
   UniquePtr<LayerUserData> d(static_cast<LayerUserData*>(
       mUserData.Remove(static_cast<gfx::UserDataKey*>(aKey))));
--- a/gfx/layers/LayerManager.h
+++ b/gfx/layers/LayerManager.h
@@ -235,29 +235,16 @@ class LayerManager : public WindowRender
 
   virtual bool HasShadowManagerInternal() const { return false; }
   bool HasShadowManager() const { return HasShadowManagerInternal(); }
   virtual void StorePluginWidgetConfigurations(
       const nsTArray<nsIWidget::Configuration>& aConfigurations) {}
   bool IsSnappingEffectiveTransforms() { return mSnapEffectiveTransforms; }
 
   /**
-   * Returns true if the underlying platform can properly support layers with
-   * SurfaceMode::SURFACE_COMPONENT_ALPHA.
-   */
-  static bool LayersComponentAlphaEnabled();
-
-  /**
-   * Returns true if this LayerManager can properly support layers with
-   * SurfaceMode::SURFACE_COMPONENT_ALPHA. LayerManagers that can't will use
-   * transparent surfaces (and lose subpixel-AA for text).
-   */
-  virtual bool AreComponentAlphaLayersEnabled();
-
-  /**
    * Returns true if this LayerManager always requires an intermediate surface
    * to render blend operations.
    */
   virtual bool BlendingRequiresIntermediateSurface() { return false; }
 
   /**
    * CONSTRUCTION PHASE ONLY
    * Set the root layer. The root layer is initially null. If there is
--- a/gfx/layers/Layers.cpp
+++ b/gfx/layers/Layers.cpp
@@ -535,17 +535,16 @@ ContainerLayer::ContainerLayer(LayerMana
       mFirstChild(nullptr),
       mLastChild(nullptr),
       mPreXScale(1.0f),
       mPreYScale(1.0f),
       mInheritedXScale(1.0f),
       mInheritedYScale(1.0f),
       mPresShellResolution(1.0f),
       mUseIntermediateSurface(false),
-      mSupportsComponentAlphaChildren(false),
       mMayHaveReadbackChild(false),
       mChildrenChanged(false) {}
 
 ContainerLayer::~ContainerLayer() = default;
 
 bool ContainerLayer::InsertAfter(Layer* aChild, Layer* aAfter) {
   if (aChild->Manager() != Manager()) {
     NS_ERROR("Child has wrong manager");
@@ -988,70 +987,23 @@ void ContainerLayer::DefaultComputeEffec
     ComputeEffectiveTransformsForChildren(Matrix4x4::From2D(residual));
   } else {
     ComputeEffectiveTransformsForChildren(idealTransform);
   }
 
   ComputeEffectiveTransformForMaskLayers(aTransformToSurface);
 }
 
-void ContainerLayer::DefaultComputeSupportsComponentAlphaChildren(
-    bool* aNeedsSurfaceCopy) {
-  if (!(GetContentFlags() & Layer::CONTENT_COMPONENT_ALPHA_DESCENDANT) ||
-      !Manager()->AreComponentAlphaLayersEnabled()) {
-    mSupportsComponentAlphaChildren = false;
-    if (aNeedsSurfaceCopy) {
-      *aNeedsSurfaceCopy = false;
-    }
-    return;
-  }
-
-  mSupportsComponentAlphaChildren = false;
-  bool needsSurfaceCopy = false;
-  CompositionOp blendMode = GetEffectiveMixBlendMode();
-  if (UseIntermediateSurface()) {
-    if (GetLocalVisibleRegion().GetNumRects() == 1 &&
-        (GetContentFlags() & Layer::CONTENT_OPAQUE)) {
-      mSupportsComponentAlphaChildren = true;
-    } else {
-      gfx::Matrix transform;
-      if (HasOpaqueAncestorLayer(this) &&
-          GetEffectiveTransform().Is2D(&transform) &&
-          !gfx::ThebesMatrix(transform).HasNonIntegerTranslation() &&
-          blendMode == gfx::CompositionOp::OP_OVER) {
-        mSupportsComponentAlphaChildren = true;
-        needsSurfaceCopy = true;
-      }
-    }
-  } else if (blendMode == gfx::CompositionOp::OP_OVER) {
-    mSupportsComponentAlphaChildren =
-        (GetContentFlags() & Layer::CONTENT_OPAQUE) ||
-        (GetParent() && GetParent()->SupportsComponentAlphaChildren());
-  }
-
-  if (aNeedsSurfaceCopy) {
-    *aNeedsSurfaceCopy = mSupportsComponentAlphaChildren && needsSurfaceCopy;
-  }
-}
-
 void ContainerLayer::ComputeEffectiveTransformsForChildren(
     const Matrix4x4& aTransformToSurface) {
   for (Layer* l = mFirstChild; l; l = l->GetNextSibling()) {
     l->ComputeEffectiveTransforms(aTransformToSurface);
   }
 }
 
-/* static */
-bool ContainerLayer::HasOpaqueAncestorLayer(Layer* aLayer) {
-  for (Layer* l = aLayer->GetParent(); l; l = l->GetParent()) {
-    if (l->GetContentFlags() & Layer::CONTENT_OPAQUE) return true;
-  }
-  return false;
-}
-
 // Note that ContainerLayer::RemoveAllChildren contains an optimized
 // version of this code; if you make changes to ContainerLayer::DidRemoveChild
 // consider whether the matching changes need to be made to
 // ContainerLayer::RemoveAllChildren
 void ContainerLayer::DidRemoveChild(Layer* aLayer) {
   PaintedLayer* tl = aLayer->AsPaintedLayer();
   if (tl && tl->UsedForReadback()) {
     for (Layer* l = mFirstChild; l; l = l->GetNextSibling()) {
--- a/gfx/layers/Layers.h
+++ b/gfx/layers/Layers.h
@@ -147,22 +147,16 @@ class Layer {
      * require per-component alpha for optimal fidelity. However, there is no
      * guarantee that component alpha will be supported for this layer at
      * paint time.
      * This should never be set at the same time as CONTENT_OPAQUE.
      */
     CONTENT_COMPONENT_ALPHA = 0x02,
 
     /**
-     * If this is set then one of the descendant layers of this one has
-     * CONTENT_COMPONENT_ALPHA set.
-     */
-    CONTENT_COMPONENT_ALPHA_DESCENDANT = 0x04,
-
-    /**
      * If this is set then this layer is part of a preserve-3d group, and should
      * be sorted with sibling layers that are also part of the same group.
      */
     CONTENT_EXTEND_3D_CONTEXT = 0x08,
     /**
      * This indicates that the transform may be changed on during an empty
      * transaction where there is no possibility of redrawing the content, so
      * the implementation should be ready for that.
@@ -1477,30 +1471,16 @@ class ContainerLayer : public Layer {
    */
   RenderTargetIntRect GetIntermediateSurfaceRect();
 
   /**
    * Returns true if this container has more than one non-empty child
    */
   bool HasMultipleChildren();
 
-  /**
-   * Returns true if this container supports children with component alpha.
-   * Should only be called while painting a child of this layer.
-   */
-  bool SupportsComponentAlphaChildren() {
-    return mSupportsComponentAlphaChildren;
-  }
-
-  /**
-   * Returns true if aLayer or any layer in its parent chain has the opaque
-   * content flag set.
-   */
-  static bool HasOpaqueAncestorLayer(Layer* aLayer);
-
   void SetChildrenChanged(bool aVal) { mChildrenChanged = aVal; }
 
   // If |aRect| is null, the entire layer should be considered invalid for
   // compositing.
   virtual void SetInvalidCompositeRect(const gfx::IntRect* aRect) {}
 
  protected:
   friend class ReadbackProcessor;
@@ -1543,27 +1523,16 @@ class ContainerLayer : public Layer {
   /**
    * A default implementation of ComputeEffectiveTransforms for use by OpenGL
    * and D3D.
    */
   void DefaultComputeEffectiveTransforms(
       const gfx::Matrix4x4& aTransformToSurface);
 
   /**
-   * A default implementation to compute and set the value for
-   * SupportsComponentAlphaChildren().
-   *
-   * If aNeedsSurfaceCopy is provided, then it is set to true if the caller
-   * needs to copy the background up into the intermediate surface created,
-   * false otherwise.
-   */
-  void DefaultComputeSupportsComponentAlphaChildren(
-      bool* aNeedsSurfaceCopy = nullptr);
-
-  /**
    * Loops over the children calling ComputeEffectiveTransforms on them.
    */
   void ComputeEffectiveTransformsForChildren(
       const gfx::Matrix4x4& aTransformToSurface);
 
   virtual void PrintInfo(std::stringstream& aStream,
                          const char* aPrefix) override;
 
@@ -1580,17 +1549,16 @@ class ContainerLayer : public Layer {
   // The resolution scale inherited from the parent layer. This will already
   // be part of mTransform.
   float mInheritedXScale;
   float mInheritedYScale;
   // For layers corresponding to an nsDisplayAsyncZoom, the resolution of the
   // associated pres shell; for other layers, 1.0.
   float mPresShellResolution;
   bool mUseIntermediateSurface;
-  bool mSupportsComponentAlphaChildren;
   bool mMayHaveReadbackChild;
   // This is updated by ComputeDifferences. This will be true if we need to
   // invalidate the intermediate surface.
   bool mChildrenChanged;
 };
 
 /**
  * A Layer which just renders a solid color in its visible region. It actually
--- a/gfx/layers/RepaintRequest.h
+++ b/gfx/layers/RepaintRequest.h
@@ -12,16 +12,17 @@
 
 #include "FrameMetrics.h"             // for FrameMetrics
 #include "mozilla/DefineEnum.h"       // for MOZ_DEFINE_ENUM
 #include "mozilla/gfx/BasePoint.h"    // for BasePoint
 #include "mozilla/gfx/Rect.h"         // for RoundedIn
 #include "mozilla/gfx/ScaleFactor.h"  // for ScaleFactor
 #include "mozilla/TimeStamp.h"        // for TimeStamp
 #include "Units.h"                    // for CSSRect, CSSPixel, etc
+#include "UnitTransforms.h"           // for ViewAs
 
 namespace IPC {
 template <typename T>
 struct ParamTraits;
 }  // namespace IPC
 
 namespace mozilla {
 namespace layers {
@@ -49,16 +50,17 @@ struct RepaintRequest {
         mCumulativeResolution(),
         mDevPixelsPerCSSPixel(1),
         mScrollOffset(0, 0),
         mZoom(),
         mDisplayPortMargins(0, 0, 0, 0),
         mPresShellId(-1),
         mLayoutViewport(0, 0, 0, 0),
         mExtraResolution(),
+        mTransformToAncestorScale(),
         mPaintRequestTime(),
         mScrollUpdateType(eNone),
         mIsRootContent(false),
         mIsAnimationInProgress(false),
         mIsScrollInfoLayer(false) {}
 
   RepaintRequest(const FrameMetrics& aOther,
                  const ScreenMargin& aDisplayportMargins,
@@ -71,16 +73,17 @@ struct RepaintRequest {
         mDevPixelsPerCSSPixel(aOther.GetDevPixelsPerCSSPixel()),
         mScrollOffset(aOther.GetVisualScrollOffset()),
         mZoom(aOther.GetZoom()),
         mScrollGeneration(aOther.GetScrollGeneration()),
         mDisplayPortMargins(aDisplayportMargins),
         mPresShellId(aOther.GetPresShellId()),
         mLayoutViewport(aOther.GetLayoutViewport()),
         mExtraResolution(aOther.GetExtraResolution()),
+        mTransformToAncestorScale(aOther.GetTransformToAncestorScale()),
         mPaintRequestTime(aOther.GetPaintRequestTime()),
         mScrollUpdateType(aScrollUpdateType),
         mIsRootContent(aOther.IsRootContent()),
         mIsAnimationInProgress(aIsAnimationInProgress),
         mIsScrollInfoLayer(aOther.IsScrollInfoLayer()) {}
 
   // Default copy ctor and operator= are fine
 
@@ -93,39 +96,35 @@ struct RepaintRequest {
            mDevPixelsPerCSSPixel == aOther.mDevPixelsPerCSSPixel &&
            mScrollOffset == aOther.mScrollOffset &&
            // don't compare mZoom
            mScrollGeneration == aOther.mScrollGeneration &&
            mDisplayPortMargins == aOther.mDisplayPortMargins &&
            mPresShellId == aOther.mPresShellId &&
            mLayoutViewport.IsEqualEdges(aOther.mLayoutViewport) &&
            mExtraResolution == aOther.mExtraResolution &&
+           mTransformToAncestorScale == aOther.mTransformToAncestorScale &&
            mPaintRequestTime == aOther.mPaintRequestTime &&
            mScrollUpdateType == aOther.mScrollUpdateType &&
            mIsRootContent == aOther.mIsRootContent &&
            mIsAnimationInProgress == aOther.mIsAnimationInProgress &&
            mIsScrollInfoLayer == aOther.mIsScrollInfoLayer;
   }
 
   bool operator!=(const RepaintRequest& aOther) const {
     return !operator==(aOther);
   }
 
   friend std::ostream& operator<<(std::ostream& aOut,
                                   const RepaintRequest& aRequest);
 
   CSSToScreenScale2D DisplayportPixelsPerCSSPixel() const {
-    // Note: use 'mZoom * ParentLayerToLayerScale(1.0f)' as the CSS-to-Layer
-    // scale instead of LayersPixelsPerCSSPixel(), because displayport
-    // calculations are done in the context of a repaint request, where we ask
-    // Layout to repaint at a new resolution that includes any async zoom. Until
-    // this repaint request is processed, LayersPixelsPerCSSPixel() does not yet
-    // include the async zoom, but it will when the displayport is interpreted
-    // for the repaint.
-    return mZoom * ParentLayerToLayerScale(1.0f) / mExtraResolution;
+    // Refer to FrameMetrics::DisplayportPixelsPerCSSPixel() for explanation.
+    return mZoom * ParentLayerToLayerScale(1.0f) *
+           ViewAs<LayerToScreenScale2D>(mTransformToAncestorScale);
   }
 
   CSSToLayerScale2D LayersPixelsPerCSSPixel() const {
     return mDevPixelsPerCSSPixel * mCumulativeResolution;
   }
 
   // Get the amount by which this frame has been zoomed since the last repaint.
   LayerToParentLayerScale GetAsyncZoom() const {
@@ -182,16 +181,20 @@ struct RepaintRequest {
   uint32_t GetPresShellId() const { return mPresShellId; }
 
   const CSSRect& GetLayoutViewport() const { return mLayoutViewport; }
 
   const ScreenToLayerScale2D& GetExtraResolution() const {
     return mExtraResolution;
   }
 
+  const Scale2D& GetTransformToAncestorScale() const {
+    return mTransformToAncestorScale;
+  }
+
   const TimeStamp& GetPaintRequestTime() const { return mPaintRequestTime; }
 
   bool IsScrollInfoLayer() const { return mIsScrollInfoLayer; }
 
  protected:
   void SetIsAnimationInProgress(bool aInProgress) {
     mIsAnimationInProgress = aInProgress;
   }
@@ -282,16 +285,19 @@ struct RepaintRequest {
   // For a scroll frame that is not an RSF, this metric is meaningless and
   // invalid.
   CSSRect mLayoutViewport;
 
   // The extra resolution at which content in this scroll frame is drawn beyond
   // that necessary to draw one Layer pixel per Screen pixel.
   ScreenToLayerScale2D mExtraResolution;
 
+  // The scale on this scroll frame induced by enclosing CSS transforms.
+  Scale2D mTransformToAncestorScale;
+
   // The time at which the APZC last requested a repaint for this scroll frame.
   TimeStamp mPaintRequestTime;
 
   // The type of repaint request this represents.
   ScrollOffsetUpdateType mScrollUpdateType;
 
   // Whether or not this is the root scroll frame for the root content document.
   bool mIsRootContent : 1;
--- a/gfx/layers/apz/src/AsyncPanZoomController.cpp
+++ b/gfx/layers/apz/src/AsyncPanZoomController.cpp
@@ -5044,16 +5044,18 @@ void AsyncPanZoomController::NotifyLayer
       Metrics().SetCompositionSizeWithoutDynamicToolbar(
           aLayerMetrics.GetCompositionSizeWithoutDynamicToolbar());
       needToReclampScroll = true;
     }
     Metrics().SetBoundingCompositionSize(
         aLayerMetrics.GetBoundingCompositionSize());
     Metrics().SetPresShellResolution(aLayerMetrics.GetPresShellResolution());
     Metrics().SetCumulativeResolution(aLayerMetrics.GetCumulativeResolution());
+    Metrics().SetTransformToAncestorScale(
+        aLayerMetrics.GetTransformToAncestorScale());
     mScrollMetadata.SetHasScrollgrab(aScrollMetadata.GetHasScrollgrab());
     mScrollMetadata.SetLineScrollAmount(aScrollMetadata.GetLineScrollAmount());
     mScrollMetadata.SetPageScrollAmount(aScrollMetadata.GetPageScrollAmount());
     mScrollMetadata.SetSnapInfo(ScrollSnapInfo(aScrollMetadata.GetSnapInfo()));
     mScrollMetadata.SetIsLayersIdRoot(aScrollMetadata.IsLayersIdRoot());
     mScrollMetadata.SetIsAutoDirRootContentRTL(
         aScrollMetadata.IsAutoDirRootContentRTL());
     Metrics().SetIsScrollInfoLayer(aLayerMetrics.IsScrollInfoLayer());
deleted file mode 100644
--- a/gfx/layers/composite/PaintCounter.cpp
+++ /dev/null
@@ -1,79 +0,0 @@
-/* -*- 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 "mozilla/gfx/Point.h"          // for IntSize, Point
-#include "mozilla/gfx/Rect.h"           // for Rect
-#include "mozilla/gfx/Types.h"          // for Color, SurfaceFormat
-#include "mozilla/layers/Compositor.h"  // for Compositor
-#include "mozilla/layers/CompositorTypes.h"
-#include "mozilla/layers/Effects.h"  // for Effect, EffectChain, etc
-#include "mozilla/TimeStamp.h"       // for TimeStamp, TimeDuration
-#include "mozilla/Sprintf.h"
-
-#include "mozilla/gfx/HelpersSkia.h"
-#include "skia/include/core/SkFont.h"
-#include "PaintCounter.h"
-
-namespace mozilla {
-namespace layers {
-
-using namespace mozilla::gfx;
-
-// Positioned below the chrome UI
-IntRect PaintCounter::mRect = IntRect(0, 175, 300, 60);
-
-PaintCounter::PaintCounter() {
-  mFormat = SurfaceFormat::B8G8R8A8;
-  mSurface = Factory::CreateDataSourceSurface(mRect.Size(), mFormat);
-  mMap.emplace(mSurface, DataSourceSurface::READ_WRITE);
-  mStride = mMap->GetStride();
-
-  mCanvas = SkCanvas::MakeRasterDirect(MakeSkiaImageInfo(mRect.Size(), mFormat),
-                                       mMap->GetData(), mStride);
-  mCanvas->clear(SK_ColorWHITE);
-}
-
-PaintCounter::~PaintCounter() {
-  mSurface = nullptr;
-  mTextureSource = nullptr;
-  mTexturedEffect = nullptr;
-}
-
-void PaintCounter::Draw(Compositor* aCompositor, TimeDuration aPaintTime,
-                        TimeDuration aCompositeTime) {
-  char buffer[48];
-  SprintfLiteral(buffer, "P: %.2f  C: %.2f", aPaintTime.ToMilliseconds(),
-                 aCompositeTime.ToMilliseconds());
-
-  SkPaint paint;
-  paint.setColor(SkColorSetRGB(0, 255, 0));
-  paint.setAntiAlias(true);
-
-  SkFont font(SkTypeface::MakeDefault(), 32);
-
-  mCanvas->clear(SK_ColorTRANSPARENT);
-  mCanvas->drawString(buffer, 10, 30, font, paint);
-  mCanvas->flush();
-
-  if (!mTextureSource) {
-    mTextureSource = aCompositor->CreateDataTextureSource();
-    mTexturedEffect = CreateTexturedEffect(mFormat, mTextureSource,
-                                           SamplingFilter::POINT, true);
-    mTexturedEffect->mTextureCoords = Rect(0, 0, 1.0f, 1.0f);
-  }
-
-  mTextureSource->Update(mSurface);
-
-  EffectChain effectChain;
-  effectChain.mPrimaryEffect = mTexturedEffect;
-
-  gfx::Matrix4x4 identity;
-  Rect rect(mRect.X(), mRect.Y(), mRect.Width(), mRect.Height());
-  aCompositor->DrawQuad(rect, mRect, effectChain, 1.0, identity);
-}
-
-}  // end namespace layers
-}  // end namespace mozilla
deleted file mode 100644
--- a/gfx/layers/composite/PaintCounter.h
+++ /dev/null
@@ -1,58 +0,0 @@
-/* -*- 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 mozilla_layers_PaintCounter_h_
-#define mozilla_layers_PaintCounter_h_
-
-#include <memory>
-#include "mozilla/Maybe.h"
-#include "mozilla/RefPtr.h"     // for already_AddRefed, RefCounted
-#include "mozilla/TimeStamp.h"  // for TimeStamp, TimeDuration
-#include "mozilla/gfx/2D.h"
-#include "mozilla/gfx/Point.h"
-#include "mozilla/gfx/Rect.h"
-#include "mozilla/gfx/Types.h"
-#include "nsISupports.h"
-
-class SkCanvas;
-
-namespace mozilla {
-namespace layers {
-
-class Compositor;
-class DataTextureSource;
-struct TexturedEffect;
-
-// Keeps track and paints how long a full invalidation paint takes to rasterize
-// and composite.
-class PaintCounter {
- public:
-  NS_INLINE_DECL_THREADSAFE_REFCOUNTING(PaintCounter)
-
-  PaintCounter();
-  void Draw(Compositor* aCompositor, TimeDuration aPaintTime,
-            TimeDuration aCompositeTime);
-  static gfx::IntRect GetPaintRect() { return PaintCounter::mRect; }
-
- private:
-  virtual ~PaintCounter();
-
-  gfx::SurfaceFormat mFormat;
-  std::unique_ptr<SkCanvas> mCanvas;
-  gfx::IntSize mSize;
-  int mStride;
-
-  RefPtr<gfx::DataSourceSurface> mSurface;
-  RefPtr<DataTextureSource> mTextureSource;
-  RefPtr<TexturedEffect> mTexturedEffect;
-  Maybe<gfx::DataSourceSurface::ScopedMap> mMap;
-  static gfx::IntRect mRect;
-};
-
-}  // namespace layers
-}  // namespace mozilla
-
-#endif  // mozilla_layers_opengl_PaintCounter_h_
--- a/gfx/layers/ipc/CompositorBridgeParent.cpp
+++ b/gfx/layers/ipc/CompositorBridgeParent.cpp
@@ -1333,18 +1333,17 @@ void CompositorBridgeParent::AccumulateM
     }
   }
 }
 
 /*static*/
 void CompositorBridgeParent::InitializeStatics() {
   gfxVars::SetForceSubpixelAAWherePossibleListener(&UpdateQualitySettings);
   gfxVars::SetWebRenderDebugFlagsListener(&UpdateDebugFlags);
-  gfxVars::SetUseWebRenderMultithreadingListener(
-      &UpdateWebRenderMultithreading);
+  gfxVars::SetWebRenderBoolParametersListener(&UpdateWebRenderBoolParameters);
   gfxVars::SetWebRenderBatchingLookbackListener(&UpdateWebRenderParameters);
   gfxVars::SetWebRenderBlobTileSizeListener(&UpdateWebRenderParameters);
 
   gfxVars::SetWebRenderProfilerUIListener(&UpdateWebRenderProfilerUI);
 }
 
 /*static*/
 void CompositorBridgeParent::UpdateQualitySettings() {
@@ -1382,30 +1381,30 @@ void CompositorBridgeParent::UpdateDebug
 
   MonitorAutoLock lock(*sIndirectLayerTreesLock);
   ForEachWebRenderBridgeParent([&](WebRenderBridgeParent* wrBridge) -> void {
     wrBridge->UpdateDebugFlags();
   });
 }
 
 /*static*/
-void CompositorBridgeParent::UpdateWebRenderMultithreading() {
+void CompositorBridgeParent::UpdateWebRenderBoolParameters() {
   if (!CompositorThreadHolder::IsInCompositorThread()) {
     if (CompositorThread()) {
       CompositorThread()->Dispatch(NewRunnableFunction(
-          "CompositorBridgeParent::UpdateWebRenderMultithreading",
-          &CompositorBridgeParent::UpdateWebRenderMultithreading));
+          "CompositorBridgeParent::UpdateWebRenderBoolParameters",
+          &CompositorBridgeParent::UpdateWebRenderBoolParameters));
     }
 
     return;
   }
 
   MonitorAutoLock lock(*sIndirectLayerTreesLock);
   ForEachWebRenderBridgeParent([&](WebRenderBridgeParent* wrBridge) -> void {
-    wrBridge->UpdateMultithreading();
+    wrBridge->UpdateBoolParameters();
   });
 }
 
 /*static*/
 void CompositorBridgeParent::UpdateWebRenderParameters() {
   if (!CompositorThreadHolder::IsInCompositorThread()) {
     if (CompositorThread()) {
       CompositorThread()->Dispatch(NewRunnableFunction(
--- a/gfx/layers/ipc/CompositorBridgeParent.h
+++ b/gfx/layers/ipc/CompositorBridgeParent.h
@@ -609,24 +609,24 @@ class CompositorBridgeParent final : pub
   static void UpdateQualitySettings();
 
   /**
    * Notify the compositor the debug flags have been updated.
    */
   static void UpdateDebugFlags();
 
   /**
-   * Notify the compositor the debug flags have been updated.
+   * Notify the compositor some webrender parameters have been updated.
    */
-  static void UpdateWebRenderMultithreading();
+  static void UpdateWebRenderParameters();
 
   /**
    * Notify the compositor some webrender parameters have been updated.
    */
-  static void UpdateWebRenderParameters();
+  static void UpdateWebRenderBoolParameters();
 
   /**
    * Notify the compositor webrender profiler UI string has been updated.
    */
   static void UpdateWebRenderProfilerUI();
 
   /**
    * Wrap the data structure to be sent over IPC.
--- a/gfx/layers/ipc/LayersMessageUtils.h
+++ b/gfx/layers/ipc/LayersMessageUtils.h
@@ -223,16 +223,17 @@ struct ParamTraits<mozilla::layers::Fram
     WriteParam(aMsg, aParam.mDevPixelsPerCSSPixel);
     WriteParam(aMsg, aParam.mScrollOffset);
     WriteParam(aMsg, aParam.mZoom);
     WriteParam(aMsg, aParam.mScrollGeneration);
     WriteParam(aMsg, aParam.mBoundingCompositionSize);
     WriteParam(aMsg, aParam.mPresShellId);
     WriteParam(aMsg, aParam.mLayoutViewport);
     WriteParam(aMsg, aParam.mExtraResolution);
+    WriteParam(aMsg, aParam.mTransformToAncestorScale);
     WriteParam(aMsg, aParam.mPaintRequestTime);
     WriteParam(aMsg, aParam.mVisualDestination);
     WriteParam(aMsg, aParam.mVisualScrollUpdateType);
     WriteParam(aMsg, aParam.mFixedLayerMargins);
     WriteParam(aMsg, aParam.mCompositionSizeWithoutDynamicToolbar);
     WriteParam(aMsg, aParam.mIsRootContent);
     WriteParam(aMsg, aParam.mIsScrollInfoLayer);
     WriteParam(aMsg, aParam.mHasNonZeroDisplayPortMargins);
@@ -253,16 +254,17 @@ struct ParamTraits<mozilla::layers::Fram
             ReadParam(aMsg, aIter, &aResult->mDevPixelsPerCSSPixel) &&
             ReadParam(aMsg, aIter, &aResult->mScrollOffset) &&
             ReadParam(aMsg, aIter, &aResult->mZoom) &&
             ReadParam(aMsg, aIter, &aResult->mScrollGeneration) &&
             ReadParam(aMsg, aIter, &aResult->mBoundingCompositionSize) &&
             ReadParam(aMsg, aIter, &aResult->mPresShellId) &&
             ReadParam(aMsg, aIter, &aResult->mLayoutViewport) &&
             ReadParam(aMsg, aIter, &aResult->mExtraResolution) &&
+            ReadParam(aMsg, aIter, &aResult->mTransformToAncestorScale) &&
             ReadParam(aMsg, aIter, &aResult->mPaintRequestTime) &&
             ReadParam(aMsg, aIter, &aResult->mVisualDestination) &&
             ReadParam(aMsg, aIter, &aResult->mVisualScrollUpdateType) &&
             ReadParam(aMsg, aIter, &aResult->mFixedLayerMargins) &&
             ReadParam(aMsg, aIter,
                       &aResult->mCompositionSizeWithoutDynamicToolbar) &&
             ReadBoolForBitfield(aMsg, aIter, aResult,
                                 &paramType::SetIsRootContent) &&
@@ -288,16 +290,17 @@ struct ParamTraits<mozilla::layers::Repa
     WriteParam(aMsg, aParam.mDevPixelsPerCSSPixel);
     WriteParam(aMsg, aParam.mScrollOffset);
     WriteParam(aMsg, aParam.mZoom);
     WriteParam(aMsg, aParam.mScrollGeneration);
     WriteParam(aMsg, aParam.mDisplayPortMargins);
     WriteParam(aMsg, aParam.mPresShellId);
     WriteParam(aMsg, aParam.mLayoutViewport);
     WriteParam(aMsg, aParam.mExtraResolution);
+    WriteParam(aMsg, aParam.mTransformToAncestorScale);
     WriteParam(aMsg, aParam.mPaintRequestTime);
     WriteParam(aMsg, aParam.mScrollUpdateType);
     WriteParam(aMsg, aParam.mIsRootContent);
     WriteParam(aMsg, aParam.mIsAnimationInProgress);
     WriteParam(aMsg, aParam.mIsScrollInfoLayer);
   }
 
   static bool Read(const Message* aMsg, PickleIterator* aIter,
@@ -309,16 +312,17 @@ struct ParamTraits<mozilla::layers::Repa
             ReadParam(aMsg, aIter, &aResult->mDevPixelsPerCSSPixel) &&
             ReadParam(aMsg, aIter, &aResult->mScrollOffset) &&
             ReadParam(aMsg, aIter, &aResult->mZoom) &&
             ReadParam(aMsg, aIter, &aResult->mScrollGeneration) &&
             ReadParam(aMsg, aIter, &aResult->mDisplayPortMargins) &&
             ReadParam(aMsg, aIter, &aResult->mPresShellId) &&
             ReadParam(aMsg, aIter, &aResult->mLayoutViewport) &&
             ReadParam(aMsg, aIter, &aResult->mExtraResolution) &&
+            ReadParam(aMsg, aIter, &aResult->mTransformToAncestorScale) &&
             ReadParam(aMsg, aIter, &aResult->mPaintRequestTime) &&
             ReadParam(aMsg, aIter, &aResult->mScrollUpdateType) &&
             ReadBoolForBitfield(aMsg, aIter, aResult,
                                 &paramType::SetIsRootContent) &&
             ReadBoolForBitfield(aMsg, aIter, aResult,
                                 &paramType::SetIsAnimationInProgress) &&
             ReadBoolForBitfield(aMsg, aIter, aResult,
                                 &paramType::SetIsScrollInfoLayer));
--- a/gfx/layers/moz.build
+++ b/gfx/layers/moz.build
@@ -512,17 +512,15 @@ CXXFLAGS += ["-Werror=switch"]
 
 LOCAL_INCLUDES += CONFIG["SKIA_INCLUDES"]
 
 if CONFIG["CC_TYPE"] in ("clang", "gcc"):
     CXXFLAGS += ["-Wno-error=shadow"]
     # Suppress warnings in third-party code.
     CXXFLAGS += ["-Wno-maybe-uninitialized"]
 
-UNIFIED_SOURCES += [
-    "composite/PaintCounter.cpp",
-]
+UNIFIED_SOURCES += []
 
 if CONFIG["FUZZING"] and CONFIG["FUZZING_INTERFACES"]:
     TEST_DIRS += ["ipc/fuzztest"]
 
 # Add libFuzzer configuration directives
 include("/tools/fuzzing/libfuzzer-config.mozbuild")
--- a/gfx/layers/wr/WebRenderBridgeParent.cpp
+++ b/gfx/layers/wr/WebRenderBridgeParent.cpp
@@ -12,16 +12,17 @@
 #include "gfxOTSUtils.h"
 #include "GeckoProfiler.h"
 #include "GLContext.h"
 #include "GLContextProvider.h"
 #include "GLLibraryLoader.h"
 #include "Layers.h"
 #include "nsExceptionHandler.h"
 #include "mozilla/Range.h"
+#include "mozilla/EnumeratedRange.h"
 #include "mozilla/StaticPrefs_gfx.h"
 #include "mozilla/UniquePtr.h"
 #include "mozilla/gfx/gfxVars.h"
 #include "mozilla/gfx/GPUParent.h"
 #include "mozilla/layers/AnimationHelper.h"
 #include "mozilla/layers/APZSampler.h"
 #include "mozilla/layers/APZUpdater.h"
 #include "mozilla/layers/Compositor.h"
@@ -353,16 +354,20 @@ WebRenderBridgeParent::WebRenderBridgePa
   if (IsRootWebRenderBridgeParent()) {
     MOZ_ASSERT(!mCompositorScheduler);
     mCompositorScheduler = new CompositorVsyncScheduler(this, mWidget);
   }
   UpdateDebugFlags();
   UpdateQualitySettings();
   UpdateProfilerUI();
   UpdateParameters();
+  // Start with the cached bool parameter bits inverted so that we update
+  // them all.
+  mBoolParameterBits = ~gfxVars::WebRenderBoolParameters();
+  UpdateBoolParameters();
 }
 
 WebRenderBridgeParent::WebRenderBridgeParent(const wr::PipelineId& aPipelineId,
                                              nsCString&& aError)
     : mCompositorBridge(nullptr),
       mPipelineId(aPipelineId),
       mChildLayersObserverEpoch{0},
       mParentLayersObserverEpoch{0},
@@ -1647,27 +1652,37 @@ void WebRenderBridgeParent::UpdateDebugF
   mApi->UpdateDebugFlags(gfxVars::WebRenderDebugFlags());
 }
 
 void WebRenderBridgeParent::UpdateProfilerUI() {
   nsCString uiString = gfxVars::GetWebRenderProfilerUIOrDefault();
   mApi->SetProfilerUI(uiString);
 }
 
-void WebRenderBridgeParent::UpdateMultithreading() {
-  mApi->EnableMultithreading(gfxVars::UseWebRenderMultithreading());
-}
-
 void WebRenderBridgeParent::UpdateParameters() {
   uint32_t count = gfxVars::WebRenderBatchingLookback();
   mApi->SetBatchingLookback(count);
 
   mBlobTileSize = gfxVars::WebRenderBlobTileSize();
 }
 
+void WebRenderBridgeParent::UpdateBoolParameters() {
+  uint32_t bits = gfxVars::WebRenderBoolParameters();
+  uint32_t changedBits = mBoolParameterBits ^ bits;
+
+  for (auto paramName : MakeEnumeratedRange(wr::BoolParameter::Sentinel)) {
+    uint32_t i = (uint32_t)paramName;
+    if (changedBits & (1 << i)) {
+      bool value = (bits & (1 << i)) != 0;
+      mApi->SetBool(paramName, value);
+    }
+  }
+  mBoolParameterBits = bits;
+}
+
 #if defined(MOZ_WIDGET_ANDROID)
 void WebRenderBridgeParent::RequestScreenPixels(
     UiCompositorControllerParent* aController) {
   mScreenPixelsTarget = aController;
 }
 
 void WebRenderBridgeParent::MaybeCaptureScreenPixels() {
   if (!mScreenPixelsTarget) {
--- a/gfx/layers/wr/WebRenderBridgeParent.h
+++ b/gfx/layers/wr/WebRenderBridgeParent.h
@@ -87,18 +87,18 @@ class WebRenderBridgeParent final : publ
     return mCompositorScheduler.get();
   }
   CompositorBridgeParentBase* GetCompositorBridge() {
     return mCompositorBridge;
   }
 
   void UpdateQualitySettings();
   void UpdateDebugFlags();
-  void UpdateMultithreading();
   void UpdateParameters();
+  void UpdateBoolParameters();
   void UpdateProfilerUI();
 
   mozilla::ipc::IPCResult RecvEnsureConnected(
       TextureFactoryIdentifier* aTextureFactoryIdentifier,
       MaybeIdNamespace* aMaybeIdNamespace, nsCString* aError) override;
 
   mozilla::ipc::IPCResult RecvNewCompositable(
       const CompositableHandle& aHandle, const TextureInfo& aInfo) override;
@@ -497,16 +497,17 @@ class WebRenderBridgeParent final : publ
 
   TimeStamp mMostRecentComposite;
 
   RefPtr<WebRenderBridgeParentRef> mWebRenderBridgeRef;
 
 #if defined(MOZ_WIDGET_ANDROID)
   UiCompositorControllerParent* mScreenPixelsTarget;
 #endif
+  uint32_t mBoolParameterBits;
   uint16_t mBlobTileSize;
   bool mDestroyed;
   bool mReceivedDisplayList;
   bool mIsFirstPaint;
   bool mSkippedComposite;
   bool mDisablingNativeCompositor;
   // These payloads are being used for SCROLL_PRESENT_LATENCY telemetry
   DataMutex<nsClassHashtable<nsUint64HashKey, nsTArray<CompositionPayload>>>
--- a/gfx/thebes/gfxFontEntry.h
+++ b/gfx/thebes/gfxFontEntry.h
@@ -828,17 +828,17 @@ class gfxFontFamily {
         mHasStyles(false),
         mIsSimpleFamily(false),
         mIsBadUnderlineFamily(false),
         mFamilyCharacterMapInitialized(false),
         mSkipDefaultFeatureSpaceCheck(false),
         mCheckForFallbackFaces(false),
         mCheckedForLegacyFamilyNames(false) {}
 
-  const nsCString& Name() { return mName; }
+  const nsCString& Name() const { return mName; }
 
   virtual void LocalizedName(nsACString& aLocalizedName);
   virtual bool HasOtherFamilyNames();
 
   // See https://bugzilla.mozilla.org/show_bug.cgi?id=835204:
   // check the font's 'name' table to see if it has a legacy family name
   // that would have been used by GDI (e.g. to split extra-bold or light
   // faces in a large family into separate "styled families" because of
--- a/gfx/thebes/gfxPlatform.cpp
+++ b/gfx/thebes/gfxPlatform.cpp
@@ -483,16 +483,54 @@ void gfxPlatform::InitChild(const Conten
 static void WebRendeProfilerUIPrefChangeCallback(const char* aPrefName, void*) {
   nsCString uiString;
   if (NS_SUCCEEDED(Preferences::GetCString("gfx.webrender.debug.profiler-ui",
                                            uiString))) {
     gfxVars::SetWebRenderProfilerUI(uiString);
   }
 }
 
+// List of boolean dynamic parameter for WebRender.
+//
+// The parameters in this list are:
+//  - The pref name.
+//  - The BoolParameter enum variant (see webrender_api/src/lib.rs)
+//  - A default value.
+#define WR_BOOL_PARAMETER_LIST(_)                                     \
+  _("gfx.webrender.batched-texture-uploads",                          \
+    wr::BoolParameter::BatchedUploads, true)                          \
+  _("gfx.webrender.draw-calls-for-texture-copy",                      \
+    wr::BoolParameter::DrawCallsForTextureCopy, true)                 \
+  _("gfx.webrender.pbo-uploads", wr::BoolParameter::PboUploads, true) \
+  _("gfx.webrender.multithreading", wr::BoolParameter::Multithreading, true)
+
+static void WebRenderBoolParameterChangeCallback(const char*, void*) {
+  uint32_t bits = 0;
+
+#define WR_BOOL_PARAMETER(name, key, default_val) \
+  if (Preferences::GetBool(name, default_val)) {  \
+    bits |= 1 << (uint32_t)key;                   \
+  }
+
+  WR_BOOL_PARAMETER_LIST(WR_BOOL_PARAMETER)
+#undef WR_BOOL_PARAMETER
+
+  gfx::gfxVars::SetWebRenderBoolParameters(bits);
+}
+
+static void RegisterWebRenderBoolParamCallback() {
+#define WR_BOOL_PARAMETER(name, _key, _default_val) \
+  Preferences::RegisterCallback(WebRenderBoolParameterChangeCallback, name);
+
+  WR_BOOL_PARAMETER_LIST(WR_BOOL_PARAMETER)
+#undef WR_BOOL_PARAMETER
+
+  WebRenderBoolParameterChangeCallback(nullptr, nullptr);
+}
+
 static void WebRenderDebugPrefChangeCallback(const char* aPrefName, void*) {
   wr::DebugFlags flags{0};
 #define GFX_WEBRENDER_DEBUG(suffix, bit)                   \
   if (Preferences::GetBool(WR_DEBUG_PREF suffix, false)) { \
     flags |= (bit);                                        \
   }
 
   GFX_WEBRENDER_DEBUG(".profiler", wr::DebugFlags::PROFILER_DBG)
@@ -523,37 +561,25 @@ static void WebRenderDebugPrefChangeCall
   GFX_WEBRENDER_DEBUG(".disable-alpha-pass", wr::DebugFlags::DISABLE_ALPHA_PASS)
   GFX_WEBRENDER_DEBUG(".disable-clip-masks", wr::DebugFlags::DISABLE_CLIP_MASKS)
   GFX_WEBRENDER_DEBUG(".disable-text-prims", wr::DebugFlags::DISABLE_TEXT_PRIMS)
   GFX_WEBRENDER_DEBUG(".disable-gradient-prims",
                       wr::DebugFlags::DISABLE_GRADIENT_PRIMS)
   GFX_WEBRENDER_DEBUG(".obscure-images", wr::DebugFlags::OBSCURE_IMAGES)
   GFX_WEBRENDER_DEBUG(".glyph-flashing", wr::DebugFlags::GLYPH_FLASHING)
   GFX_WEBRENDER_DEBUG(".capture-profiler", wr::DebugFlags::PROFILER_CAPTURE)
-  GFX_WEBRENDER_DEBUG(".batched-texture-uploads",
-                      wr::DebugFlags::USE_BATCHED_TEXTURE_UPLOADS)
-  GFX_WEBRENDER_DEBUG(".draw-calls-for-texture-copy",
-                      wr::DebugFlags::USE_DRAW_CALLS_FOR_TEXTURE_COPY)
 #undef GFX_WEBRENDER_DEBUG
 
   gfx::gfxVars::SetWebRenderDebugFlags(flags.bits);
 }
 
 static void WebRenderQualityPrefChangeCallback(const char* aPref, void*) {
   gfxPlatform::GetPlatform()->UpdateForceSubpixelAAWherePossible();
 }
 
-static void WebRenderMultithreadingPrefChangeCallback(const char* aPrefName,
-                                                      void*) {
-  bool enable = Preferences::GetBool(
-      StaticPrefs::GetPrefName_gfx_webrender_enable_multithreading(), true);
-
-  gfx::gfxVars::SetUseWebRenderMultithreading(enable);
-}
-
 static void WebRenderBatchingPrefChangeCallback(const char* aPrefName, void*) {
   uint32_t count = Preferences::GetUint(
       StaticPrefs::GetPrefName_gfx_webrender_batching_lookback(), 10);
 
   gfx::gfxVars::SetWebRenderBatchingLookback(count);
 }
 
 static void WebRenderBlobTileSizePrefChangeCallback(const char* aPrefName,
@@ -2184,26 +2210,28 @@ void gfxPlatform::FlushFontAndWordCaches
   if (fontCache) {
     fontCache->Flush();
   }
 
   gfxPlatform::PurgeSkiaFontCache();
 }
 
 /* static */
-void gfxPlatform::ForceGlobalReflow(NeedsReframe aNeedsReframe) {
+void gfxPlatform::ForceGlobalReflow(NeedsReframe aNeedsReframe,
+                                    BroadcastToChildren aBroadcastToChildren) {
   MOZ_ASSERT(NS_IsMainThread());
   const bool reframe = aNeedsReframe == NeedsReframe::Yes;
   // Send a notification that will be observed by PresShells in this process
   // only.
   if (nsCOMPtr<nsIObserverService> obs = services::GetObserverService()) {
     char16_t needsReframe[] = {char16_t(reframe), 0};
     obs->NotifyObservers(nullptr, "font-info-updated", needsReframe);
   }
-  if (XRE_IsParentProcess()) {
+  if (XRE_IsParentProcess() &&
+      aBroadcastToChildren == BroadcastToChildren::Yes) {
     // Propagate the change to child processes.
     for (auto* process :
          dom::ContentParent::AllProcesses(dom::ContentParent::eLive)) {
       Unused << process->SendForceGlobalReflow(reframe);
     }
   }
 }
 
@@ -2573,28 +2601,27 @@ void gfxPlatform::InitWebRenderConfig() 
   // gfxFeature is not usable in the GPU process, so we use gfxVars to transmit
   // this feature
   if (hasWebRender) {
     gfxVars::SetUseWebRender(true);
     reporter.SetSuccessful();
 
     Preferences::RegisterPrefixCallbackAndCall(WebRenderDebugPrefChangeCallback,
                                                WR_DEBUG_PREF);
+
+    RegisterWebRenderBoolParamCallback();
+
     Preferences::RegisterPrefixCallbackAndCall(
         WebRendeProfilerUIPrefChangeCallback,
         "gfx.webrender.debug.profiler-ui");
     Preferences::RegisterCallback(
         WebRenderQualityPrefChangeCallback,
         nsDependentCString(
             StaticPrefs::
                 GetPrefName_gfx_webrender_quality_force_subpixel_aa_where_possible()));
-    Preferences::RegisterCallback(
-        WebRenderMultithreadingPrefChangeCallback,
-        nsDependentCString(
-            StaticPrefs::GetPrefName_gfx_webrender_enable_multithreading()));
 
     Preferences::RegisterCallback(
         WebRenderBatchingPrefChangeCallback,
         nsDependentCString(
             StaticPrefs::GetPrefName_gfx_webrender_batching_lookback()));
 
     Preferences::RegisterCallbackAndCall(
         WebRenderBlobTileSizePrefChangeCallback,
--- a/gfx/thebes/gfxPlatform.h
+++ b/gfx/thebes/gfxPlatform.h
@@ -602,23 +602,29 @@ class gfxPlatform : public mozilla::laye
    */
   static qcms_data_type GetCMSOSRGBAType();
 
   virtual void FontsPrefsChanged(const char* aPref);
 
   int32_t GetBidiNumeralOption();
 
   /**
-   * This is a bit ugly, but useful... force all presContexts to reflow,
-   * by toggling a preference that they observe. This is used when
-   * something about platform settings changes that might have an effect
-   * on layout, such as font rendering settings that influence metrics.
+   * Force all presContexts to reflow (and reframe if needed).
+   *
+   * This is used when something about platform settings changes that might have
+   * an effect on layout, such as font rendering settings that influence
+   * metrics, or installed fonts.
+   *
+   * By default it also broadcast it to child processes, but some callers might
+   * not need it if they implement their own notification.
    */
   enum class NeedsReframe : bool { No, Yes };
-  static void ForceGlobalReflow(NeedsReframe);
+  enum class BroadcastToChildren : bool { No, Yes };
+  static void ForceGlobalReflow(NeedsReframe,
+                                BroadcastToChildren = BroadcastToChildren::Yes);
 
   static void FlushFontAndWordCaches();
 
   /**
    * Returns a 1x1 DrawTarget that can be used for measuring text etc. as
    * it would measure if rendered on-screen.  Guaranteed to return a
    * non-null and valid DrawTarget.
    */
--- a/gfx/thebes/gfxPlatformFontList.cpp
+++ b/gfx/thebes/gfxPlatformFontList.cpp
@@ -466,32 +466,27 @@ bool gfxPlatformFontList::InitFontList()
     gfxFontCache* fontCache = gfxFontCache::GetCache();
     if (fontCache) {
       fontCache->FlushShapedWordCaches();
       fontCache->Flush();
     }
 
     gfxPlatform::PurgeSkiaFontCache();
 
+    // There's no need to broadcast this reflow request to child processes, as
+    // ContentParent::NotifyUpdatedFonts deals with it by re-entering into this
+    // function on child processes.
     if (NS_IsMainThread()) {
-      nsCOMPtr<nsIObserverService> obs =
-          mozilla::services::GetObserverService();
-      if (obs) {
-        // Notify any current presContexts that fonts are being updated, so
-        // existing caches will no longer be valid.
-        obs->NotifyObservers(nullptr, "font-info-updated", nullptr);
-      }
+      gfxPlatform::ForceGlobalReflow(gfxPlatform::NeedsReframe::Yes,
+                                     gfxPlatform::BroadcastToChildren::No);
     } else {
       NS_DispatchToMainThread(
           NS_NewRunnableFunction("font-info-updated notification callback", [] {
-            nsCOMPtr<nsIObserverService> obs =
-                mozilla::services::GetObserverService();
-            if (obs) {
-              obs->NotifyObservers(nullptr, "font-info-updated", nullptr);
-            }
+            gfxPlatform::ForceGlobalReflow(gfxPlatform::NeedsReframe::Yes,
+                                           gfxPlatform::BroadcastToChildren::No);
           }));
     }
 
     mAliasTable.Clear();
     mLocalNameTable.Clear();
 
     CancelLoadCmapsTask();
     mStartedLoadingCmapsFrom = 0xffffffffu;
@@ -787,16 +782,19 @@ gfxFontEntry* gfxPlatformFontList::Looku
     list->SearchForLocalFace(keyName, &family, &face);
   }
   if (!face || !family) {
     return nullptr;
   }
   FontVisibility level =
       aPresContext ? aPresContext->GetFontVisibility() : FontVisibility::User;
   if (!IsVisibleToCSS(*family, level)) {
+    if (aPresContext) {
+      aPresContext->ReportBlockedFontFamily(*family);
+    }
     return nullptr;
   }
   gfxFontEntry* fe = CreateFontEntry(face, family);
   if (fe) {
     fe->mIsLocalUserFont = true;
     fe->mWeightRange = aWeightForEntry;
     fe->mStretchRange = aStretchForEntry;
     fe->mStyleRange = aStyleForEntry;
@@ -1422,41 +1420,59 @@ bool gfxPlatformFontList::FindAndAddFami
         if (!mOtherNamesMissed) {
           mOtherNamesMissed = MakeUnique<nsTHashSet<nsCString>>(2);
         }
         mOtherNamesMissed->Insert(key);
       }
     }
     // Check whether the family we found is actually allowed to be looked up,
     // according to current font-visibility prefs.
-    if (family && (IsVisibleToCSS(*family, visibilityLevel) ||
-                   (allowHidden && family->IsHidden()))) {
-      aOutput->AppendElement(FamilyAndGeneric(family, aGeneric));
-      return true;
+    if (family) {
+      bool visible = IsVisibleToCSS(*family, visibilityLevel);
+      if (visible || (allowHidden && family->IsHidden())) {
+        aOutput->AppendElement(FamilyAndGeneric(family, aGeneric));
+        return true;
+      }
+      if (aPresContext) {
+        aPresContext->ReportBlockedFontFamily(*family);
+      }
     }
     return false;
   }
 
   NS_ASSERTION(mFontFamilies.Count() != 0,
                "system font list was not initialized correctly");
 
+  auto isBlockedByVisibilityLevel = [=](gfxFontFamily* aFamily) -> bool {
+    bool visible = IsVisibleToCSS(*aFamily, visibilityLevel);
+    if (visible || (allowHidden && aFamily->IsHidden())) {
+      return false;
+    }
+    if (aPresContext) {
+      aPresContext->ReportBlockedFontFamily(*aFamily);
+    }
+    return true;
+  };
+
   // lookup in canonical (i.e. English) family name list
   gfxFontFamily* familyEntry = mFontFamilies.GetWeak(key);
-  if (familyEntry && !IsVisibleToCSS(*familyEntry, visibilityLevel) &&
-      !(allowHidden && familyEntry->IsHidden())) {
-    return false;
+  if (familyEntry) {
+    if (isBlockedByVisibilityLevel(familyEntry)) {
+      return false;
+    }
   }
 
   // if not found, lookup in other family names list (mostly localized names)
   if (!familyEntry) {
     familyEntry = mOtherFamilyNames.GetWeak(key);
   }
-  if (familyEntry && !IsVisibleToCSS(*familyEntry, visibilityLevel) &&
-      !(allowHidden && familyEntry->IsHidden())) {
-    return false;
+  if (familyEntry) {
+    if (isBlockedByVisibilityLevel(familyEntry)) {
+      return false;
+    }
   }
 
   // if still not found and other family names not yet fully initialized,
   // initialize the rest of the list and try again.  this is done lazily
   // since reading name table entries is expensive.
   // although ASCII localized family names are possible they don't occur
   // in practice so avoid pulling in names at startup
   if (!familyEntry && !mOtherFamilyNamesInitialized && !IsAscii(aFamily)) {
@@ -1467,19 +1483,20 @@ bool gfxPlatformFontList::FindAndAddFami
         !(aFlags & FindFamiliesFlags::eNoAddToNamesMissedWhenSearching)) {
       // localized family names load timed out, add name to list of
       // names to check after localized names are loaded
       if (!mOtherNamesMissed) {
         mOtherNamesMissed = MakeUnique<nsTHashSet<nsCString>>(2);
       }
       mOtherNamesMissed->Insert(key);
     }
-    if (familyEntry && !IsVisibleToCSS(*familyEntry, visibilityLevel) &&
-        !(allowHidden && familyEntry->IsHidden())) {
-      return false;
+    if (familyEntry) {
+      if (isBlockedByVisibilityLevel(familyEntry)) {
+        return false;
+      }
     }
   }
 
   familyEntry = CheckFamily(familyEntry);
 
   // If we failed to find the requested family, check for a space in the
   // name; if found, and if the "base" name (up to the last space) exists
   // as a family, then this might be a legacy GDI-style family name for
@@ -1500,19 +1517,20 @@ bool gfxPlatformFontList::FindAndAddFami
           FindUnsharedFamily(aPresContext, Substring(aFamily, 0, index),
                              FindFamiliesFlags::eNoSearchForLegacyFamilyNames);
       // If we found the "base" family name, and if it has members with
       // legacy names, this will add corresponding font-family entries to
       // the mOtherFamilyNames list; then retry the legacy-family search.
       if (base && base->CheckForLegacyFamilyNames(this)) {
         familyEntry = mOtherFamilyNames.GetWeak(key);
       }
-      if (familyEntry && !IsVisibleToCSS(*familyEntry, visibilityLevel) &&
-          !(allowHidden && familyEntry->IsHidden())) {
-        return false;
+      if (familyEntry) {
+        if (isBlockedByVisibilityLevel(familyEntry)) {
+          return false;
+        }
       }
     }
   }
 
   if (familyEntry) {
     aOutput->AppendElement(FamilyAndGeneric(familyEntry, aGeneric));
     return true;
   }
--- a/gfx/webrender_bindings/WebRenderAPI.cpp
+++ b/gfx/webrender_bindings/WebRenderAPI.cpp
@@ -560,22 +560,22 @@ void WebRenderAPI::Readback(const TimeSt
 }
 
 void WebRenderAPI::ClearAllCaches() { wr_api_clear_all_caches(mDocHandle); }
 
 void WebRenderAPI::EnableNativeCompositor(bool aEnable) {
   wr_api_enable_native_compositor(mDocHandle, aEnable);
 }
 
-void WebRenderAPI::EnableMultithreading(bool aEnable) {
-  wr_api_enable_multithreading(mDocHandle, aEnable);
+void WebRenderAPI::SetBatchingLookback(uint32_t aCount) {
+  wr_api_set_batching_lookback(mDocHandle, aCount);
 }
 
-void WebRenderAPI::SetBatchingLookback(uint32_t aCount) {
-  wr_api_set_batching_lookback(mDocHandle, aCount);
+void WebRenderAPI::SetBool(wr::BoolParameter aKey, bool aValue) {
+  wr_api_set_bool(mDocHandle, aKey, aValue);
 }
 
 void WebRenderAPI::SetClearColor(const gfx::DeviceColor& aColor) {
   RenderThread::Get()->SetClearColor(mId, ToColorF(aColor));
 }
 
 void WebRenderAPI::SetProfilerUI(const nsCString& aUIString) {
   RenderThread::Get()->SetProfilerUI(mId, aUIString);
--- a/gfx/webrender_bindings/WebRenderAPI.h
+++ b/gfx/webrender_bindings/WebRenderAPI.h
@@ -258,18 +258,18 @@ class WebRenderAPI final {
   void RunOnRenderThread(UniquePtr<RendererEvent> aEvent);
 
   void Readback(const TimeStamp& aStartTime, gfx::IntSize aSize,
                 const gfx::SurfaceFormat& aFormat,
                 const Range<uint8_t>& aBuffer, bool* aNeedsYFlip);
 
   void ClearAllCaches();
   void EnableNativeCompositor(bool aEnable);
-  void EnableMultithreading(bool aEnable);
   void SetBatchingLookback(uint32_t aCount);
+  void SetBool(wr::BoolParameter, bool value);
 
   void SetClearColor(const gfx::DeviceColor& aColor);
   void SetProfilerUI(const nsCString& aUIString);
 
   void Pause();
   bool Resume();
 
   void WakeSceneBuilder();
--- a/gfx/webrender_bindings/src/bindings.rs
+++ b/gfx/webrender_bindings/src/bindings.rs
@@ -1729,16 +1729,21 @@ pub unsafe extern "C" fn wr_api_notify_m
 }
 
 #[no_mangle]
 pub extern "C" fn wr_api_set_debug_flags(dh: &mut DocumentHandle, flags: DebugFlags) {
     dh.api.set_debug_flags(flags);
 }
 
 #[no_mangle]
+pub extern "C" fn wr_api_set_bool(dh: &mut DocumentHandle, param_name: BoolParameter, val: bool) {
+    dh.api.set_parameter(Parameter::Bool(param_name, val));
+}
+
+#[no_mangle]
 pub unsafe extern "C" fn wr_api_accumulate_memory_report(
     dh: &mut DocumentHandle,
     report: &mut MemoryReport,
     // we manually expand VoidPtrToSizeFn here because cbindgen otherwise fails to fold the Option<fn()>
     // https://github.com/eqrion/cbindgen/issues/552
     size_of_op: unsafe extern "C" fn(ptr: *const c_void) -> usize,
     enclosing_size_of_op: Option<unsafe extern "C" fn(ptr: *const c_void) -> usize>,
 ) {
@@ -1752,21 +1757,16 @@ pub unsafe extern "C" fn wr_api_clear_al
 }
 
 #[no_mangle]
 pub unsafe extern "C" fn wr_api_enable_native_compositor(dh: &mut DocumentHandle, enable: bool) {
     dh.api.send_debug_cmd(DebugCommand::EnableNativeCompositor(enable));
 }
 
 #[no_mangle]
-pub unsafe extern "C" fn wr_api_enable_multithreading(dh: &mut DocumentHandle, enable: bool) {
-    dh.api.send_debug_cmd(DebugCommand::EnableMultithreading(enable));
-}
-
-#[no_mangle]
 pub unsafe extern "C" fn wr_api_set_batching_lookback(dh: &mut DocumentHandle, count: u32) {
     dh.api.send_debug_cmd(DebugCommand::SetBatchingLookback(count));
 }
 
 fn make_transaction(do_async: bool) -> Transaction {
     let mut transaction = Transaction::new();
     // Ensure that we either use async scene building or not based on the
     // gecko pref, regardless of what the default is. We can remove this once
--- a/gfx/wr/webrender/src/device/gl.rs
+++ b/gfx/wr/webrender/src/device/gl.rs
@@ -1,14 +1,14 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 use super::super::shader_source::{OPTIMIZED_SHADERS, UNOPTIMIZED_SHADERS};
-use api::{ColorF, ImageDescriptor, ImageFormat};
+use api::{ColorF, ImageDescriptor, ImageFormat, Parameter, BoolParameter};
 use api::{MixBlendMode, ImageBufferKind, VoidPtrToSizeFn};
 use api::{CrashAnnotator, CrashAnnotation, CrashAnnotatorGuard};
 use api::units::*;
 use euclid::default::Transform3D;
 use gleam::gl;
 use crate::render_api::MemoryReport;
 use crate::internal_types::{FastHashMap, RenderTargetInfo, Swizzle, SwizzleSettings};
 use crate::util::round_up_to_multiple;
@@ -1133,16 +1133,19 @@ pub struct Device {
     /// Whether we must ensure the source strings passed to glShaderSource()
     /// are null-terminated, to work around driver bugs.
     requires_null_terminated_shader_source: bool,
 
     /// Whether we must unbind any texture from GL_TEXTURE_EXTERNAL_OES before
     /// binding to GL_TEXTURE_2D, to work around an android emulator bug.
     requires_texture_external_unbind: bool,
 
+    ///
+    is_software_webrender: bool,
+
     // GL extensions
     extensions: Vec<String>,
 
     /// Dumps the source of the shader with the given name
     dump_shader_source: Option<String>,
 
     surface_origin_is_top_left: bool,
 
@@ -1809,16 +1812,17 @@ impl Device {
 
             max_texture_size,
             cached_programs,
             frame_id: GpuFrameId(0),
             extensions,
             texture_storage_usage,
             requires_null_terminated_shader_source,
             requires_texture_external_unbind,
+            is_software_webrender,
             required_pbo_stride,
             dump_shader_source,
             surface_origin_is_top_left,
 
             #[cfg(debug_assertions)]
             shader_is_ready: false,
         }
     }
@@ -1826,16 +1830,39 @@ impl Device {
     pub fn gl(&self) -> &dyn gl::Gl {
         &*self.gl
     }
 
     pub fn rc_gl(&self) -> &Rc<dyn gl::Gl> {
         &self.gl
     }
 
+    pub fn set_parameter(&mut self, param: &Parameter) {
+        match param {
+            Parameter::Bool(BoolParameter::PboUploads, enabled) => {
+                if !self.is_software_webrender {
+                    self.upload_method = if *enabled {
+                        UploadMethod::PixelBuffer(crate::ONE_TIME_USAGE_HINT)
+                    } else {
+                        UploadMethod::Immediate
+                    };
+                }
+            }
+            Parameter::Bool(BoolParameter::BatchedUploads, enabled) => {
+                self.use_batched_texture_uploads = *enabled;
+            }
+            Parameter::Bool(BoolParameter::DrawCallsForTextureCopy, enabled) => {
+                if self.capabilities.requires_batched_texture_uploads.is_none() {
+                    self.use_draw_calls_for_texture_copy = *enabled;
+                }
+            }
+            _ => {}
+        }
+    }
+
     /// Ensures that the maximum texture size is less than or equal to the
     /// provided value. If the provided value is less than the value supported
     /// by the driver, the latter is used.
     pub fn clamp_max_texture_size(&mut self, size: i32) {
         self.max_texture_size = self.max_texture_size.min(size);
     }
 
     /// Returns the limit on texture dimensions (width or height).
@@ -1896,27 +1923,16 @@ impl Device {
     pub fn use_batched_texture_uploads(&self) -> bool {
         self.use_batched_texture_uploads
     }
 
     pub fn use_draw_calls_for_texture_copy(&self) -> bool {
         self.use_draw_calls_for_texture_copy
     }
 
-    pub fn set_use_batched_texture_uploads(&mut self, enabled: bool) {
-        if self.capabilities.requires_batched_texture_uploads.is_some() {
-            return;
-        }
-        self.use_batched_texture_uploads = enabled;
-    }
-
-    pub fn set_use_draw_calls_for_texture_copy(&mut self, enabled: bool) {
-        self.use_draw_calls_for_texture_copy = enabled;
-    }
-
     pub fn reset_state(&mut self) {
         for i in 0 .. self.bound_textures.len() {
             self.bound_textures[i] = 0;
             self.gl.active_texture(gl::TEXTURE0 + i as gl::GLuint);
             self.gl.bind_texture(gl::TEXTURE_2D, 0);
         }
 
         self.bound_vao = 0;
--- a/gfx/wr/webrender/src/internal_types.rs
+++ b/gfx/wr/webrender/src/internal_types.rs
@@ -1,13 +1,13 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
-use api::{ColorF, DocumentId, ExternalImageId, PrimitiveFlags};
+use api::{ColorF, DocumentId, ExternalImageId, PrimitiveFlags, Parameter};
 use api::{ImageFormat, NotificationRequest, Shadow, FilterOp, ImageBufferKind};
 use api::units::*;
 use api;
 use crate::render_api::DebugCommand;
 use crate::composite::NativeSurfaceOperation;
 use crate::device::TextureFilter;
 use crate::renderer::{FullFrameStats, PipelineInfo};
 use crate::gpu_cache::GpuCacheUpdateList;
@@ -543,16 +543,17 @@ pub enum ResultMsg {
     },
     PublishPipelineInfo(PipelineInfo),
     PublishDocument(
         DocumentId,
         RenderedDocument,
         ResourceUpdateList,
     ),
     AppendNotificationRequests(Vec<NotificationRequest>),
+    SetParameter(Parameter),
     ForceRedraw,
 }
 
 #[derive(Clone, Debug)]
 pub struct ResourceCacheError {
     description: String,
 }
 
--- a/gfx/wr/webrender/src/render_api.rs
+++ b/gfx/wr/webrender/src/render_api.rs
@@ -8,17 +8,17 @@ use std::cell::Cell;
 use std::fmt;
 use std::marker::PhantomData;
 use std::path::PathBuf;
 use std::sync::Arc;
 use std::u32;
 use time::precise_time_ns;
 //use crate::api::peek_poke::PeekPoke;
 use crate::api::channel::{Sender, single_msg_channel, unbounded_channel};
-use crate::api::{ColorF, BuiltDisplayList, IdNamespace, ExternalScrollId};
+use crate::api::{ColorF, BuiltDisplayList, IdNamespace, ExternalScrollId, Parameter, BoolParameter};
 use crate::api::{SharedFontInstanceMap, FontKey, FontInstanceKey, NativeFontHandle};
 use crate::api::{BlobImageData, BlobImageKey, ImageData, ImageDescriptor, ImageKey, Epoch, QualitySettings};
 use crate::api::{BlobImageParams, BlobImageRequest, BlobImageResult, AsyncBlobImageRasterizer, BlobImageHandler};
 use crate::api::{DocumentId, PipelineId, PropertyBindingId, PropertyBindingKey, ExternalEvent};
 use crate::api::{HitTestResult, HitTesterRequest, ApiHitTester, PropertyValue, DynamicProperties};
 use crate::api::{ScrollClamping, TileSize, NotificationRequest, DebugFlags, ScrollNodeState};
 use crate::api::{GlyphDimensionRequest, GlyphIndexRequest, GlyphIndex, GlyphDimensions};
 use crate::api::{FontInstanceOptions, FontInstancePlatformOptions, FontVariation};
@@ -899,18 +899,16 @@ pub enum DebugCommand {
     /// Start capturing a sequence of scene/frame changes.
     StartCaptureSequence(PathBuf, CaptureBits),
     /// Stop capturing a sequence of scene/frame changes.
     StopCaptureSequence,
     /// Clear cached resources, forcing them to be re-uploaded from templates.
     ClearCaches(ClearCache),
     /// Enable/disable native compositor usage
     EnableNativeCompositor(bool),
-    /// Enable/disable parallel job execution with rayon.
-    EnableMultithreading(bool),
     /// Sets the maximum amount of existing batches to visit before creating a new one.
     SetBatchingLookback(u32),
     /// Invalidate GPU cache, forcing the update from the CPU mirror.
     InvalidateGpuCache,
     /// Causes the scene builder to pause for a given amount of milliseconds each time it
     /// processes a transaction.
     SimulateLongSceneBuild(u32),
     /// Set an override tile size to use for picture caches
@@ -1352,24 +1350,31 @@ impl RenderApi {
 
     /// Stop capturing sequences of frames.
     pub fn stop_capture_sequence(&self) {
         let msg = ApiMsg::DebugCommand(DebugCommand::StopCaptureSequence);
         self.send_message(msg);
     }
 
     /// Update the state of builtin debugging facilities.
-    pub fn send_debug_cmd(&mut self, cmd: DebugCommand) {
-        if let DebugCommand::EnableMultithreading(enable) = cmd {
-            // TODO(nical) we should enable it for all RenderApis.
-            self.resources.enable_multithreading(enable);
-        }
+    pub fn send_debug_cmd(&self, cmd: DebugCommand) {
         let msg = ApiMsg::DebugCommand(cmd);
         self.send_message(msg);
     }
+
+    /// Update a instance-global parameter.
+    pub fn set_parameter(&mut self, parameter: Parameter) {
+        if let Parameter::Bool(BoolParameter::Multithreading, enabled) = parameter {
+            self.resources.enable_multithreading(enabled);
+        }
+
+        let _ = self.low_priority_scene_sender.send(
+            SceneBuilderRequest::SetParameter(parameter)
+        );
+    }
 }
 
 impl Drop for RenderApi {
     fn drop(&mut self) {
         let msg = SceneBuilderRequest::ClearNamespace(self.namespace_id);
         let _ = self.low_priority_scene_sender.send(msg);
     }
 }
--- a/gfx/wr/webrender/src/render_backend.rs
+++ b/gfx/wr/webrender/src/render_backend.rs
@@ -3,17 +3,17 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 //! The high-level module responsible for managing the pipeline and preparing
 //! commands to be issued by the `Renderer`.
 //!
 //! See the comment at the top of the `renderer` module for a description of
 //! how these two pieces interact.
 
-use api::{DebugFlags, BlobImageHandler};
+use api::{DebugFlags, BlobImageHandler, Parameter, BoolParameter};
 use api::{DocumentId, ExternalScrollId, HitTestResult};
 use api::{IdNamespace, PipelineId, RenderNotifier, ScrollClamping};
 use api::{NotificationRequest, Checkpoint, QualitySettings};
 use api::{PrimitiveKeyKind};
 use api::units::*;
 use api::channel::{single_msg_channel, Sender, Receiver};
 #[cfg(any(feature = "capture", feature = "replay"))]
 use crate::render_api::CaptureBits;
@@ -1133,20 +1133,16 @@ impl RenderBackend {
                         }
 
                         self.frame_config.compositor_kind = compositor_kind;
                         self.update_frame_builder_config();
 
                         // We don't want to forward this message to the renderer.
                         return RenderBackendStatus::Continue;
                     }
-                    DebugCommand::EnableMultithreading(enable) => {
-                        self.resource_cache.enable_multithreading(enable);
-                        return RenderBackendStatus::Continue;
-                    }
                     DebugCommand::SetBatchingLookback(count) => {
                         self.frame_config.batch_lookback_count = count as usize;
                         self.update_frame_builder_config();
 
                         return RenderBackendStatus::Continue;
                     }
                     DebugCommand::SimulateLongSceneBuild(time_ms) => {
                         let _ = self.scene_tx.send(SceneBuilderRequest::SimulateLongSceneBuild(time_ms));
@@ -1272,16 +1268,22 @@ impl RenderBackend {
                 self.documents.retain(|doc_id, _doc| doc_id.namespace_id != id);
                 if let Some(handler) = &mut self.blob_image_handler {
                     handler.clear_namespace(id);
                 }
             }
             SceneBuilderResult::DeleteDocument(document_id) => {
                 self.documents.remove(&document_id);
             }
+            SceneBuilderResult::SetParameter(param) => {
+                if let Parameter::Bool(BoolParameter::Multithreading, enabled) = param {
+                    self.resource_cache.enable_multithreading(enabled);
+                }
+                let _ = self.result_tx.send(ResultMsg::SetParameter(param));
+            }
             SceneBuilderResult::StopRenderBackend => {
                 return RenderBackendStatus::StopRenderBackend;
             }
             SceneBuilderResult::ShutDown(sender) => {
                 info!("Recycling stats: {:?}", self.recycler);
                 return RenderBackendStatus::ShutDown(sender);
             }
         }
--- a/gfx/wr/webrender/src/renderer/mod.rs
+++ b/gfx/wr/webrender/src/renderer/mod.rs
@@ -1619,16 +1619,19 @@ impl Renderer {
                     self.notifications.append(&mut notifications);
                 }
                 ResultMsg::ForceRedraw => {
                     self.force_redraw = true;
                 }
                 ResultMsg::RefreshShader(path) => {
                     self.pending_shader_updates.push(path);
                 }
+                ResultMsg::SetParameter(ref param) => {
+                    self.device.set_parameter(param);
+                }
                 ResultMsg::DebugOutput(output) => match output {
                     #[cfg(feature = "capture")]
                     DebugOutput::SaveCapture(config, deferred) => {
                         self.save_capture(config, deferred);
                     }
                     #[cfg(feature = "replay")]
                     DebugOutput::LoadCapture(config, plain_externals) => {
                         self.active_documents.clear();
@@ -1652,18 +1655,17 @@ impl Renderer {
             DebugCommand::LoadCapture(..) |
             DebugCommand::StartCaptureSequence(..) |
             DebugCommand::StopCaptureSequence => {
                 panic!("Capture commands are not welcome here! Did you build with 'capture' feature?")
             }
             DebugCommand::ClearCaches(_)
             | DebugCommand::SimulateLongSceneBuild(_)
             | DebugCommand::EnableNativeCompositor(_)
-            | DebugCommand::SetBatchingLookback(_)
-            | DebugCommand::EnableMultithreading(_) => {}
+            | DebugCommand::SetBatchingLookback(_) => {}
             DebugCommand::InvalidateGpuCache => {
                 self.gpu_cache_texture.invalidate();
             }
             DebugCommand::SetFlags(flags) => {
                 self.set_debug_flags(flags);
             }
         }
     }
@@ -4838,19 +4840,16 @@ impl Renderer {
         if let Some(enabled) = flag_changed(self.debug_flags, flags, DebugFlags::GPU_SAMPLE_QUERIES) {
             if enabled {
                 self.gpu_profiler.enable_samplers();
             } else {
                 self.gpu_profiler.disable_samplers();
             }
         }
 
-        self.device.set_use_batched_texture_uploads(flags.contains(DebugFlags::USE_BATCHED_TEXTURE_UPLOADS));
-        self.device.set_use_draw_calls_for_texture_copy(flags.contains(DebugFlags::USE_DRAW_CALLS_FOR_TEXTURE_COPY));
-
         self.debug_flags = flags;
     }
 
     pub fn set_profiler_ui(&mut self, ui_str: &str) {
         self.profiler.set_ui(ui_str);
     }
 
     fn draw_frame_debug_items(&mut self, items: &[DebugItem]) {
--- a/gfx/wr/webrender/src/scene_builder_thread.rs
+++ b/gfx/wr/webrender/src/scene_builder_thread.rs
@@ -1,13 +1,13 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
-use api::{AsyncBlobImageRasterizer, BlobImageResult};
+use api::{AsyncBlobImageRasterizer, BlobImageResult, Parameter};
 use api::{DocumentId, PipelineId, ExternalEvent, BlobImageRequest};
 use api::{NotificationRequest, Checkpoint, IdNamespace, QualitySettings};
 use api::{PrimitiveKeyKind, SharedFontInstanceMap};
 use api::{GlyphDimensionRequest, GlyphIndexRequest};
 use api::channel::{unbounded_channel, single_msg_channel, Receiver, Sender};
 use api::units::*;
 use crate::render_api::{ApiMsg, FrameMsg, SceneMsg, ResourceUpdate, TransactionMsg, MemoryReport};
 #[cfg(feature = "capture")]
@@ -91,16 +91,17 @@ pub enum SceneBuilderRequest {
     ClearNamespace(IdNamespace),
     SimulateLongSceneBuild(u32),
     ExternalEvent(ExternalEvent),
     WakeUp,
     StopRenderBackend,
     ShutDown(Option<Sender<()>>),
     Flush(Sender<()>),
     SetFrameBuilderConfig(FrameBuilderConfig),
+    SetParameter(Parameter),
     ReportMemory(Box<MemoryReport>, Sender<Box<MemoryReport>>),
     #[cfg(feature = "capture")]
     SaveScene(CaptureConfig),
     #[cfg(feature = "replay")]
     LoadScenes(Vec<LoadScene>),
     #[cfg(feature = "capture")]
     StartCaptureSequence(CaptureConfig),
     #[cfg(feature = "capture")]
@@ -111,16 +112,17 @@ pub enum SceneBuilderRequest {
 pub enum SceneBuilderResult {
     Transactions(Vec<Box<BuiltTransaction>>, Option<Sender<SceneSwapResult>>),
     ExternalEvent(ExternalEvent),
     FlushComplete(Sender<()>),
     DeleteDocument(DocumentId),
     ClearNamespace(IdNamespace),
     GetGlyphDimensions(GlyphDimensionRequest),
     GetGlyphIndices(GlyphIndexRequest),
+    SetParameter(Parameter),
     StopRenderBackend,
     ShutDown(Option<Sender<()>>),
 
     #[cfg(feature = "capture")]
     /// The same as `Transactions`, but also supplies a `CaptureConfig` that the
     /// render backend should use for sequence capture, until the next
     /// `CapturedTransactions` or `StopCaptureSequence` result.
     CapturedTransactions(Vec<Box<BuiltTransaction>>, CaptureConfig, Option<Sender<SceneSwapResult>>),
@@ -347,16 +349,19 @@ impl SceneBuilderThread {
                 }
                 Ok(SceneBuilderRequest::ReportMemory(mut report, tx)) => {
                     (*report) += self.report_memory();
                     tx.send(report).unwrap();
                 }
                 Ok(SceneBuilderRequest::SetFrameBuilderConfig(cfg)) => {
                     self.config = cfg;
                 }
+                Ok(SceneBuilderRequest::SetParameter(prop)) => {
+                    self.send(SceneBuilderResult::SetParameter(prop));
+                }
                 #[cfg(feature = "replay")]
                 Ok(SceneBuilderRequest::LoadScenes(msg)) => {
                     self.load_scenes(msg);
                 }
                 #[cfg(feature = "capture")]
                 Ok(SceneBuilderRequest::SaveScene(config)) => {
                     self.save_scene(config);
                 }
--- a/gfx/wr/webrender_api/src/lib.rs
+++ b/gfx/wr/webrender_api/src/lib.rs
@@ -496,16 +496,40 @@ impl DynamicProperties {
 }
 
 /// A C function that takes a pointer to a heap allocation and returns its size.
 ///
 /// This is borrowed from the malloc_size_of crate, upon which we want to avoid
 /// a dependency from WebRender.
 pub type VoidPtrToSizeFn = unsafe extern "C" fn(ptr: *const c_void) -> usize;
 
+/// A configuration option that can be changed at runtime.
+///
+/// # Adding a new configuration option
+///
+///  - Add a new enum variant here.
+///  - Add the entry in WR_BOOL_PARAMETER_LIST in gfxPlatform.cpp.
+///  - React to the parameter change anywhere in WebRender where a SetParam message is received.
+#[derive(Copy, Clone, Debug, PartialEq, Eq)]
+pub enum Parameter {
+    Bool(BoolParameter, bool),
+}
+
+
+/// Boolean configuration option.
+#[derive(Copy, Clone, Debug, PartialEq, Eq)]
+#[repr(u32)]
+pub enum BoolParameter {
+    PboUploads = 0,
+    Multithreading = 1,
+    BatchedUploads = 2,
+    DrawCallsForTextureCopy = 3,
+}
+
+
 bitflags! {
     /// Flags to enable/disable various builtin debugging tools.
     #[repr(C)]
     #[derive(Default, Deserialize, MallocSizeOf, Serialize)]
     pub struct DebugFlags: u32 {
         /// Display the frame profiler on screen.
         const PROFILER_DBG          = 1 << 0;
         /// Display intermediate render targets on screen.
@@ -564,18 +588,16 @@ bitflags! {
         /// If set, dump picture cache invalidation debug to console.
         const INVALIDATION_DBG = 1 << 23;
         /// Log tile cache to memory for later saving as part of wr-capture
         const TILE_CACHE_LOGGING_DBG   = 1 << 24;
         /// Collect and dump profiler statistics to captures.
         const PROFILER_CAPTURE = (1 as u32) << 25; // need "as u32" until we have cbindgen#556
         /// Invalidate picture tiles every frames (useful when inspecting GPU work in external tools).
         const FORCE_PICTURE_INVALIDATION = (1 as u32) << 26;
-        const USE_BATCHED_TEXTURE_UPLOADS = (1 as u32) << 27;
-        const USE_DRAW_CALLS_FOR_TEXTURE_COPY = (1 as u32) << 28;
     }
 }
 
 /// Information specific to a primitive type that
 /// uniquely identifies a primitive template by key.
 #[derive(Debug, Clone, Eq, MallocSizeOf, PartialEq, Hash, Serialize, Deserialize)]
 pub enum PrimitiveKeyKind {
     /// Clear an existing rect, used for special effects on some platforms.
--- a/image/decoders/icon/nsIconURI.cpp
+++ b/image/decoders/icon/nsIconURI.cpp
@@ -31,28 +31,21 @@ using namespace mozilla::ipc;
 #elif defined(PATH_MAX)
 #  define SANE_FILE_NAME_LEN PATH_MAX
 #else
 #  define SANE_FILE_NAME_LEN 1024
 #endif
 
 static NS_DEFINE_CID(kThisIconURIImplementationCID,
                      NS_THIS_ICONURI_IMPLEMENTATION_CID);
-static NS_DEFINE_CID(kIconURICID, NS_ICONURI_CID);
 
-// helper function for parsing out attributes like size, and contentType
-// from the icon url.
-static void extractAttributeValue(const char* aSearchString,
-                                  const char* aAttributeName,
-                                  nsCString& aResult);
+static const char* const kSizeStrings[] = {"button", "toolbar", "toolbarsmall",
+                                           "menu",   "dnd",     "dialog"};
 
-static const char* kSizeStrings[] = {"button", "toolbar", "toolbarsmall",
-                                     "menu",   "dnd",     "dialog"};
-
-static const char* kStateStrings[] = {"normal", "disabled"};
+static const char* const kStateStrings[] = {"normal", "disabled"};
 
 ////////////////////////////////////////////////////////////////////////////////
 
 NS_IMPL_CLASSINFO(nsMozIconURI, nullptr, nsIClassInfo::THREADSAFE,
                   NS_ICONURI_CID)
 // Empty CI getter. We only need nsIClassInfo for Serialization
 NS_IMPL_CI_INTERFACE_GETTER0(nsMozIconURI)
 
@@ -159,24 +152,25 @@ nsMozIconURI::Mutate(nsIURIMutator** aMu
   nsresult rv = mutator->InitFromURI(this);
   if (NS_FAILED(rv)) {
     return rv;
   }
   mutator.forget(aMutator);
   return NS_OK;
 }
 
+// helper function for parsing out attributes like size, and contentType
+// from the icon url.
 // takes a string like ?size=32&contentType=text/html and returns a new string
 // containing just the attribute value. i.e you could pass in this string with
 // an attribute name of 'size=', this will return 32
 // Assumption: attribute pairs in the string are separated by '&'.
-void extractAttributeValue(const char* aSearchString,
-                           const char* aAttributeName, nsCString& aResult) {
-  // NS_ENSURE_ARG_POINTER(extractAttributeValue);
-
+static void extractAttributeValue(const char* aSearchString,
+                                  const char* aAttributeName,
+                                  nsCString& aResult) {
   aResult.Truncate();
 
   if (aSearchString && aAttributeName) {
     // search the string for attributeName
     uint32_t attributeNameSize = strlen(aAttributeName);
     const char* startOfAttribute = PL_strcasestr(aSearchString, aAttributeName);
     if (startOfAttribute &&
         (*(startOfAttribute - 1) == '?' || *(startOfAttribute - 1) == '&')) {
--- a/intl/components/gtest/TestTimeZone.cpp
+++ b/intl/components/gtest/TestTimeZone.cpp
@@ -7,27 +7,131 @@
 #include "mozilla/Maybe.h"
 #include "mozilla/Span.h"
 #include "TestBuffer.h"
 
 #include <string>
 
 namespace mozilla::intl {
 
+// Firefox 1.0 release date.
+static constexpr int64_t RELEASE_DATE = 1'032'800'850'000;
+
+// Date.UTC(2021, 11-1, 7, 2, 0, 0)
+static constexpr int64_t DST_CHANGE_DATE = 1'636'250'400'000;
+
 // These tests are dependent on the machine that this test is being run on.
 // Unwrap the results to ensure it doesn't fail, but don't check the values.
 TEST(IntlTimeZone, SystemDependentTests)
 {
-  auto timeZone = TimeZone::TryCreate(Some(MakeStringSpan(u"GMT+3"))).unwrap();
+  // e.g. "America/Chicago"
   TestBuffer<char16_t> buffer;
-  // e.g. For America/Chicago: 1000 * 60 * 60 * -6
-  timeZone->GetRawOffsetMs().unwrap();
+  TimeZone::GetDefaultTimeZone(buffer).unwrap();
+}
+
+TEST(IntlTimeZone, GetRawOffsetMs)
+{
+  auto timeZone = TimeZone::TryCreate(Some(MakeStringSpan(u"GMT+3"))).unwrap();
+  ASSERT_EQ(timeZone->GetRawOffsetMs().unwrap(), 3 * 60 * 60 * 1000);
+
+  timeZone = TimeZone::TryCreate(Some(MakeStringSpan(u"Etc/GMT+3"))).unwrap();
+  ASSERT_EQ(timeZone->GetRawOffsetMs().unwrap(), -(3 * 60 * 60 * 1000));
+
+  timeZone =
+      TimeZone::TryCreate(Some(MakeStringSpan(u"America/New_York"))).unwrap();
+  ASSERT_EQ(timeZone->GetRawOffsetMs().unwrap(), -(5 * 60 * 60 * 1000));
+}
+
+TEST(IntlTimeZone, GetDSTOffsetMs)
+{
+  auto timeZone = TimeZone::TryCreate(Some(MakeStringSpan(u"GMT+3"))).unwrap();
+  ASSERT_EQ(timeZone->GetDSTOffsetMs(0).unwrap(), 0);
+
+  timeZone = TimeZone::TryCreate(Some(MakeStringSpan(u"Etc/GMT+3"))).unwrap();
+  ASSERT_EQ(timeZone->GetDSTOffsetMs(0).unwrap(), 0);
+
+  timeZone =
+      TimeZone::TryCreate(Some(MakeStringSpan(u"America/New_York"))).unwrap();
+  ASSERT_EQ(timeZone->GetDSTOffsetMs(0).unwrap(), 0);
+  ASSERT_EQ(timeZone->GetDSTOffsetMs(RELEASE_DATE).unwrap(),
+            1 * 60 * 60 * 1000);
+  ASSERT_EQ(timeZone->GetDSTOffsetMs(DST_CHANGE_DATE).unwrap(),
+            1 * 60 * 60 * 1000);
+}
+
+TEST(IntlTimeZone, GetOffsetMs)
+{
+  auto timeZone = TimeZone::TryCreate(Some(MakeStringSpan(u"GMT+3"))).unwrap();
+  ASSERT_EQ(timeZone->GetOffsetMs(0).unwrap(), 3 * 60 * 60 * 1000);
+
+  timeZone = TimeZone::TryCreate(Some(MakeStringSpan(u"Etc/GMT+3"))).unwrap();
+  ASSERT_EQ(timeZone->GetOffsetMs(0).unwrap(), -(3 * 60 * 60 * 1000));
+
+  timeZone =
+      TimeZone::TryCreate(Some(MakeStringSpan(u"America/New_York"))).unwrap();
+  ASSERT_EQ(timeZone->GetOffsetMs(0).unwrap(), -(5 * 60 * 60 * 1000));
+  ASSERT_EQ(timeZone->GetOffsetMs(RELEASE_DATE).unwrap(),
+            -(4 * 60 * 60 * 1000));
+  ASSERT_EQ(timeZone->GetOffsetMs(DST_CHANGE_DATE).unwrap(),
+            -(4 * 60 * 60 * 1000));
+}
 
-  // e.g. "America/Chicago"
-  TimeZone::GetDefaultTimeZone(buffer).unwrap();
+TEST(IntlTimeZone, GetUTCOffsetMs)
+{
+  auto timeZone = TimeZone::TryCreate(Some(MakeStringSpan(u"GMT+3"))).unwrap();
+  ASSERT_EQ(timeZone->GetUTCOffsetMs(0).unwrap(), 3 * 60 * 60 * 1000);
+
+  timeZone = TimeZone::TryCreate(Some(MakeStringSpan(u"Etc/GMT+3"))).unwrap();
+  ASSERT_EQ(timeZone->GetUTCOffsetMs(0).unwrap(), -(3 * 60 * 60 * 1000));
+
+  timeZone =
+      TimeZone::TryCreate(Some(MakeStringSpan(u"America/New_York"))).unwrap();
+  ASSERT_EQ(timeZone->GetUTCOffsetMs(0).unwrap(), -(5 * 60 * 60 * 1000));
+  ASSERT_EQ(timeZone->GetUTCOffsetMs(RELEASE_DATE).unwrap(),
+            -(4 * 60 * 60 * 1000));
+  ASSERT_EQ(timeZone->GetUTCOffsetMs(DST_CHANGE_DATE).unwrap(),
+            -(5 * 60 * 60 * 1000));
+}
+
+TEST(IntlTimeZone, GetDisplayName)
+{
+  using DaylightSavings = TimeZone::DaylightSavings;
+
+  TestBuffer<char16_t> buffer;
+
+  auto timeZone = TimeZone::TryCreate(Some(MakeStringSpan(u"GMT+3"))).unwrap();
+
+  buffer.clear();
+  timeZone->GetDisplayName("en", DaylightSavings::No, buffer).unwrap();
+  ASSERT_EQ(buffer.get_string_view(), u"GMT+03:00");
+
+  buffer.clear();
+  timeZone->GetDisplayName("en", DaylightSavings::Yes, buffer).unwrap();
+  ASSERT_EQ(buffer.get_string_view(), u"GMT+03:00");
+
+  timeZone = TimeZone::TryCreate(Some(MakeStringSpan(u"Etc/GMT+3"))).unwrap();
+
+  buffer.clear();
+  timeZone->GetDisplayName("en", DaylightSavings::No, buffer).unwrap();
+  ASSERT_EQ(buffer.get_string_view(), u"GMT-03:00");
+
+  buffer.clear();
+  timeZone->GetDisplayName("en", DaylightSavings::Yes, buffer).unwrap();
+  ASSERT_EQ(buffer.get_string_view(), u"GMT-03:00");
+
+  timeZone =
+      TimeZone::TryCreate(Some(MakeStringSpan(u"America/New_York"))).unwrap();
+
+  buffer.clear();
+  timeZone->GetDisplayName("en", DaylightSavings::No, buffer).unwrap();
+  ASSERT_EQ(buffer.get_string_view(), u"Eastern Standard Time");
+
+  buffer.clear();
+  timeZone->GetDisplayName("en", DaylightSavings::Yes, buffer).unwrap();
+  ASSERT_EQ(buffer.get_string_view(), u"Eastern Daylight Time");
 }
 
 TEST(IntlTimeZone, GetCanonicalTimeZoneID)
 {
   TestBuffer<char16_t> buffer;
 
   // Providing a canonical time zone results in the same string at the end.
   TimeZone::GetCanonicalTimeZoneID(MakeStringSpan(u"America/Chicago"), buffer)
--- a/intl/components/src/TimeZone.cpp
+++ b/intl/components/src/TimeZone.cpp
@@ -1,52 +1,312 @@
 /* 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/intl/TimeZone.h"
 
+#include "mozilla/Vector.h"
+
+#include <algorithm>
+#include <string_view>
+
 #include "unicode/uenum.h"
+#if MOZ_INTL_USE_ICU_CPP_TIMEZONE
+#  include "unicode/basictz.h"
+#endif
 
 namespace mozilla::intl {
 
 /* static */
 Result<UniquePtr<TimeZone>, ICUError> TimeZone::TryCreate(
     Maybe<Span<const char16_t>> aTimeZoneOverride) {
-  // An empty string is used for the root locale. This is regarded as the base
-  // locale of all locales, and is used as the language/country neutral locale
-  // for locale sensitive operations.
-  const char* rootLocale = "";
-
-  UErrorCode status = U_ZERO_ERROR;
   const UChar* zoneID = nullptr;
   int32_t zoneIDLen = 0;
   if (aTimeZoneOverride) {
     zoneIDLen = static_cast<int32_t>(aTimeZoneOverride->Length());
     zoneID = aTimeZoneOverride->Elements();
   }
 
+#if MOZ_INTL_USE_ICU_CPP_TIMEZONE
+  UniquePtr<icu::TimeZone> tz;
+  if (zoneID) {
+    tz.reset(
+        icu::TimeZone::createTimeZone(icu::UnicodeString(zoneID, zoneIDLen)));
+  } else {
+    tz.reset(icu::TimeZone::createDefault());
+  }
+  MOZ_ASSERT(tz);
+
+  if (*tz == icu::TimeZone::getUnknown()) {
+    return Err(ICUError::InternalError);
+  }
+
+  return MakeUnique<TimeZone>(std::move(tz));
+#else
+  // An empty string is used for the root locale. This is regarded as the base
+  // locale of all locales, and is used as the language/country neutral locale
+  // for locale sensitive operations.
+  const char* rootLocale = "";
+
+  UErrorCode status = U_ZERO_ERROR;
   UCalendar* calendar =
       ucal_open(zoneID, zoneIDLen, rootLocale, UCAL_DEFAULT, &status);
 
   if (U_FAILURE(status)) {
     return Err(ToICUError(status));
   }
 
+  // https://tc39.es/ecma262/#sec-time-values-and-time-range
+  //
+  // A time value supports a slightly smaller range of -8,640,000,000,000,000 to
+  // 8,640,000,000,000,000 milliseconds.
+  constexpr double StartOfTime = -8.64e15;
+
+  // Ensure all computations are performed in the proleptic Gregorian calendar.
+  ucal_setGregorianChange(calendar, StartOfTime, &status);
+
+  if (U_FAILURE(status)) {
+    return Err(ToICUError(status));
+  }
+
   return MakeUnique<TimeZone>(calendar);
+#endif
 }
 
 Result<int32_t, ICUError> TimeZone::GetRawOffsetMs() {
+#if MOZ_INTL_USE_ICU_CPP_TIMEZONE
+  return mTimeZone->getRawOffset();
+#else
+  // Reset the time in case the calendar has been modified.
   UErrorCode status = U_ZERO_ERROR;
+  ucal_setMillis(mCalendar, ucal_getNow(), &status);
+  if (U_FAILURE(status)) {
+    return Err(ToICUError(status));
+  }
+
   int32_t offset = ucal_get(mCalendar, UCAL_ZONE_OFFSET, &status);
   if (U_FAILURE(status)) {
     return Err(ToICUError(status));
   }
 
   return offset;
+#endif
+}
+
+Result<int32_t, ICUError> TimeZone::GetDSTOffsetMs(int64_t aUTCMilliseconds) {
+  UDate date = UDate(aUTCMilliseconds);
+
+#if MOZ_INTL_USE_ICU_CPP_TIMEZONE
+  constexpr bool dateIsLocalTime = false;
+  int32_t rawOffset, dstOffset;
+  UErrorCode status = U_ZERO_ERROR;
+
+  mTimeZone->getOffset(date, dateIsLocalTime, rawOffset, dstOffset, status);
+  if (U_FAILURE(status)) {
+    return Err(ToICUError(status));
+  }
+
+  return dstOffset;
+#else
+  UErrorCode status = U_ZERO_ERROR;
+  ucal_setMillis(mCalendar, date, &status);
+  if (U_FAILURE(status)) {
+    return Err(ToICUError(status));
+  }
+
+  int32_t dstOffset = ucal_get(mCalendar, UCAL_DST_OFFSET, &status);
+  if (U_FAILURE(status)) {
+    return Err(ToICUError(status));
+  }
+
+  return dstOffset;
+#endif
+}
+
+Result<int32_t, ICUError> TimeZone::GetOffsetMs(int64_t aUTCMilliseconds) {
+  UDate date = UDate(aUTCMilliseconds);
+
+#if MOZ_INTL_USE_ICU_CPP_TIMEZONE
+  constexpr bool dateIsLocalTime = false;
+  int32_t rawOffset, dstOffset;
+  UErrorCode status = U_ZERO_ERROR;
+
+  mTimeZone->getOffset(date, dateIsLocalTime, rawOffset, dstOffset, status);
+  if (U_FAILURE(status)) {
+    return Err(ToICUError(status));
+  }
+
+  return rawOffset + dstOffset;
+#else
+  UErrorCode status = U_ZERO_ERROR;
+  ucal_setMillis(mCalendar, date, &status);
+  if (U_FAILURE(status)) {
+    return Err(ToICUError(status));
+  }
+
+  int32_t rawOffset = ucal_get(mCalendar, UCAL_ZONE_OFFSET, &status);
+  if (U_FAILURE(status)) {
+    return Err(ToICUError(status));
+  }
+
+  int32_t dstOffset = ucal_get(mCalendar, UCAL_DST_OFFSET, &status);
+  if (U_FAILURE(status)) {
+    return Err(ToICUError(status));
+  }
+
+  return rawOffset + dstOffset;
+#endif
+}
+
+Result<int32_t, ICUError> TimeZone::GetUTCOffsetMs(int64_t aLocalMilliseconds) {
+  // https://tc39.es/ecma262/#sec-local-time-zone-adjustment
+  //
+  // LocalTZA ( t, isUTC )
+  //
+  // When t_local represents local time repeating multiple times at a negative
+  // time zone transition (e.g. when the daylight saving time ends or the time
+  // zone offset is decreased due to a time zone rule change) or skipped local
+  // time at a positive time zone transitions (e.g. when the daylight saving
+  // time starts or the time zone offset is increased due to a time zone rule
+  // change), t_local must be interpreted using the time zone offset before the
+  // transition.
+#ifndef U_HIDE_DRAFT_API
+  constexpr UTimeZoneLocalOption skippedTime = UCAL_TZ_LOCAL_FORMER;
+  constexpr UTimeZoneLocalOption repeatedTime = UCAL_TZ_LOCAL_FORMER;
+#else
+  constexpr UTimeZoneLocalOption skippedTime = UTimeZoneLocalOption(0x4);
+  constexpr UTimeZoneLocalOption repeatedTime = UTimeZoneLocalOption(0x4);
+#endif
+
+  UDate date = UDate(aLocalMilliseconds);
+
+#if MOZ_INTL_USE_ICU_CPP_TIMEZONE
+  int32_t rawOffset, dstOffset;
+  UErrorCode status = U_ZERO_ERROR;
+
+  // All ICU TimeZone classes derive from BasicTimeZone, so we can safely
+  // perform the static_cast.
+  // Once <https://unicode-org.atlassian.net/browse/ICU-13705> is fixed we
+  // can remove this extra cast.
+  auto* basicTz = static_cast<icu::BasicTimeZone*>(mTimeZone.get());
+  basicTz->getOffsetFromLocal(date, skippedTime, repeatedTime, rawOffset,
+                              dstOffset, status);
+  if (U_FAILURE(status)) {
+    return Err(ToICUError(status));
+  }
+
+  return rawOffset + dstOffset;
+#else
+  UErrorCode status = U_ZERO_ERROR;
+  ucal_setMillis(mCalendar, date, &status);
+  if (U_FAILURE(status)) {
+    return Err(ToICUError(status));
+  }
+
+  int32_t rawOffset, dstOffset;
+  ucal_getTimeZoneOffsetFromLocal(mCalendar, skippedTime, repeatedTime,
+                                  &rawOffset, &dstOffset, &status);
+  if (U_FAILURE(status)) {
+    return Err(ToICUError(status));
+  }
+
+  return rawOffset + dstOffset;
+#endif
+}
+
+using TimeZoneIdentifierVector =
+    Vector<char16_t, TimeZone::TimeZoneIdentifierLength>;
+
+#if !MOZ_INTL_USE_ICU_CPP_TIMEZONE
+static bool IsUnknownTimeZone(const TimeZoneIdentifierVector& timeZone) {
+  constexpr std::string_view unknownTimeZone = UCAL_UNKNOWN_ZONE_ID;
+
+  return timeZone.length() == unknownTimeZone.length() &&
+         std::equal(timeZone.begin(), timeZone.end(), unknownTimeZone.begin(),
+                    unknownTimeZone.end());
+}
+
+static ICUResult SetDefaultTimeZone(TimeZoneIdentifierVector& timeZone) {
+  // The string mustn't already be null-terminated.
+  MOZ_ASSERT_IF(!timeZone.empty(), timeZone.end()[-1] != '\0');
+
+  // The time zone identifier must be a null-terminated string.
+  if (!timeZone.append('\0')) {
+    return Err(ICUError::OutOfMemory);
+  }
+
+  UErrorCode status = U_ZERO_ERROR;
+  ucal_setDefaultTimeZone(timeZone.begin(), &status);
+  if (U_FAILURE(status)) {
+    return Err(ToICUError(status));
+  }
+
+  return Ok{};
+}
+#endif
+
+Result<bool, ICUError> TimeZone::SetDefaultTimeZone(
+    Span<const char> aTimeZone) {
+#if MOZ_INTL_USE_ICU_CPP_TIMEZONE
+  icu::UnicodeString tzid(aTimeZone.data(), aTimeZone.size(), US_INV);
+  if (tzid.isBogus()) {
+    return Err(ICUError::OutOfMemory);
+  }
+
+  UniquePtr<icu::TimeZone> newTimeZone(icu::TimeZone::createTimeZone(tzid));
+  MOZ_ASSERT(newTimeZone);
+
+  if (*newTimeZone != icu::TimeZone::getUnknown()) {
+    // adoptDefault() takes ownership of the time zone.
+    icu::TimeZone::adoptDefault(newTimeZone.release());
+    return true;
+  }
+#else
+  TimeZoneIdentifierVector tzid;
+  if (!tzid.append(aTimeZone.data(), aTimeZone.size())) {
+    return Err(ICUError::OutOfMemory);
+  }
+
+  // Retrieve the current default time zone in case we need to restore it.
+  TimeZoneIdentifierVector defaultTimeZone;
+  MOZ_TRY(FillVectorWithICUCall(defaultTimeZone, ucal_getDefaultTimeZone));
+
+  // Try to set the new time zone.
+  MOZ_TRY(mozilla::intl::SetDefaultTimeZone(tzid));
+
+  // Check if the time zone was actually applied.
+  TimeZoneIdentifierVector newTimeZone;
+  MOZ_TRY(FillVectorWithICUCall(newTimeZone, ucal_getDefaultTimeZone));
+
+  // Return if the new time zone was successfully applied.
+  if (!IsUnknownTimeZone(newTimeZone)) {
+    return true;
+  }
+
+  // Otherwise restore the original time zone.
+  MOZ_TRY(mozilla::intl::SetDefaultTimeZone(defaultTimeZone));
+#endif
+
+  return false;
+}
+
+ICUResult TimeZone::SetDefaultTimeZoneFromHostTimeZone() {
+#if MOZ_INTL_USE_ICU_CPP_TIMEZONE
+  if (icu::TimeZone* defaultZone = icu::TimeZone::detectHostTimeZone()) {
+    icu::TimeZone::adoptDefault(defaultZone);
+  }
+#else
+  TimeZoneIdentifierVector hostTimeZone;
+  MOZ_TRY(FillVectorWithICUCall(hostTimeZone, ucal_getHostTimeZone));
+
+  MOZ_TRY(mozilla::intl::SetDefaultTimeZone(hostTimeZone));
+#endif
+
+  return Ok{};
 }
 
 Result<SpanEnumeration<char>, ICUError> TimeZone::GetAvailableTimeZones(
     const char* aRegion) {
   // Get the time zones that are commonly used in the given region. Uses the
   // UCAL_ZONE_TYPE_ANY filter so we have more fine-grained control over the
   // returned time zones and don't omit time zones which are considered links in
   // ICU, but are treated as proper zones in IANA.
@@ -55,14 +315,16 @@ Result<SpanEnumeration<char>, ICUError> 
       UCAL_ZONE_TYPE_ANY, aRegion, nullptr, &status);
   if (U_FAILURE(status)) {
     return Err(ToICUError(status));
   }
 
   return SpanEnumeration<char>(enumeration);
 }
 
+#if !MOZ_INTL_USE_ICU_CPP_TIMEZONE
 TimeZone::~TimeZone() {
   MOZ_ASSERT(mCalendar);
   ucal_close(mCalendar);
 }
+#endif
 
 }  // namespace mozilla::intl
--- a/intl/components/src/TimeZone.h
+++ b/intl/components/src/TimeZone.h
@@ -1,85 +1,201 @@
 /* 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 intl_components_TimeZone_h_
 #define intl_components_TimeZone_h_
 
+// ICU doesn't provide a separate C API for time zone functions, but instead
+// requires to use UCalendar. This adds a measurable overhead when compared to
+// using ICU's C++ TimeZone API, therefore we prefer to use the C++ API when
+// possible. Due to the lack of a stable ABI in C++, it's only possible to use
+// the C++ API when we use our in-tree ICU copy.
+#if !MOZ_SYSTEM_ICU
+#  define MOZ_INTL_USE_ICU_CPP_TIMEZONE 1
+#else
+#  define MOZ_INTL_USE_ICU_CPP_TIMEZONE 0
+#endif
+
+#include <stdint.h>
+#include <utility>
+
 #include "unicode/ucal.h"
 #include "unicode/utypes.h"
+#if MOZ_INTL_USE_ICU_CPP_TIMEZONE
+#  include "unicode/locid.h"
+#  include "unicode/timezone.h"
+#  include "unicode/unistr.h"
+#endif
 
 #include "mozilla/Assertions.h"
+#include "mozilla/Casting.h"
 #include "mozilla/intl/ICU4CGlue.h"
 #include "mozilla/intl/ICUError.h"
 #include "mozilla/Maybe.h"
 #include "mozilla/Result.h"
 #include "mozilla/Span.h"
 #include "mozilla/UniquePtr.h"
 
 namespace mozilla::intl {
 
 /**
  * This component is a Mozilla-focused API for working with time zones in
  * internationalization code. It is used in coordination with other operations
  * such as datetime formatting.
  */
 class TimeZone final {
  public:
+#if MOZ_INTL_USE_ICU_CPP_TIMEZONE
+  explicit TimeZone(UniquePtr<icu::TimeZone> aTimeZone)
+      : mTimeZone(std::move(aTimeZone)) {
+    MOZ_ASSERT(mTimeZone);
+  }
+#else
   explicit TimeZone(UCalendar* aCalendar) : mCalendar(aCalendar) {
-    MOZ_ASSERT(aCalendar);
+    MOZ_ASSERT(mCalendar);
   }
+#endif
 
   // Do not allow copy as this class owns the ICU resource. Move is not
   // currently implemented, but a custom move operator could be created if
   // needed.
   TimeZone(const TimeZone&) = delete;
   TimeZone& operator=(const TimeZone&) = delete;
 
+#if MOZ_INTL_USE_ICU_CPP_TIMEZONE
+  ~TimeZone() = default;
+#else
   ~TimeZone();
+#endif
 
   /**
    * Create a TimeZone.
    */
   static Result<UniquePtr<TimeZone>, ICUError> TryCreate(
       Maybe<Span<const char16_t>> aTimeZoneOverride = Nothing{});
 
   /**
    * A number indicating the raw offset from GMT in milliseconds.
    */
   Result<int32_t, ICUError> GetRawOffsetMs();
 
   /**
+   * Return the daylight saving offset in milliseconds at the given UTC time.
+   */
+  Result<int32_t, ICUError> GetDSTOffsetMs(int64_t aUTCMilliseconds);
+
+  /**
+   * Return the local offset in milliseconds at the given UTC time.
+   */
+  Result<int32_t, ICUError> GetOffsetMs(int64_t aUTCMilliseconds);
+
+  /**
+   * Return the UTC offset in milliseconds at the given local time.
+   */
+  Result<int32_t, ICUError> GetUTCOffsetMs(int64_t aLocalMilliseconds);
+
+  enum class DaylightSavings : bool { No, Yes };
+
+  /**
+   * Return the display name for this time zone.
+   */
+  template <typename B>
+  ICUResult GetDisplayName(const char* aLocale,
+                           DaylightSavings aDaylightSavings, B& aBuffer) {
+#if MOZ_INTL_USE_ICU_CPP_TIMEZONE
+    icu::UnicodeString displayName;
+    mTimeZone->getDisplayName(static_cast<bool>(aDaylightSavings),
+                              icu::TimeZone::LONG, icu::Locale(aLocale),
+                              displayName);
+
+    int32_t length = displayName.length();
+    if (!aBuffer.reserve(AssertedCast<size_t>(length))) {
+      return Err(ICUError::OutOfMemory);
+    }
+
+    // Copy the display name.
+    UErrorCode status = U_ZERO_ERROR;
+    int32_t written = displayName.extract(aBuffer.data(), length, status);
+    if (!ICUSuccessForStringSpan(status)) {
+      return Err(ToICUError(status));
+    }
+    MOZ_ASSERT(written == length);
+
+    aBuffer.written(written);
+
+    return Ok{};
+#else
+    return FillBufferWithICUCall(
+        aBuffer, [&](UChar* target, int32_t length, UErrorCode* status) {
+          UCalendarDisplayNameType type =
+              static_cast<bool>(aDaylightSavings) ? UCAL_DST : UCAL_STANDARD;
+          return ucal_getTimeZoneDisplayName(mCalendar, type, aLocale, target,
+                                             length, status);
+        });
+#endif
+  }
+
+  /**
    * Fill the buffer with the system's default IANA time zone identifier, e.g.
    * "America/Chicago".
    */
   template <typename B>
   static ICUResult GetDefaultTimeZone(B& aBuffer) {
     return FillBufferWithICUCall(aBuffer, ucal_getDefaultTimeZone);
   }
 
   /**
+   * Fill the buffer with the host system's default IANA time zone identifier,
+   * e.g. "America/Chicago".
+   *
+   * NOTE: This function is not thread-safe.
+   */
+  template <typename B>
+  static ICUResult GetHostTimeZone(B& aBuffer) {
+    return FillBufferWithICUCall(aBuffer, ucal_getHostTimeZone);
+  }
+
+  /**
+   * Set the default time zone.
+   */
+  static Result<bool, ICUError> SetDefaultTimeZone(Span<const char> aTimeZone);
+
+  /**
+   * Set the default time zone using the host system's time zone.
+   *
+   * NOTE: This function is not thread-safe.
+   */
+  static ICUResult SetDefaultTimeZoneFromHostTimeZone();
+
+  /**
+   * Constant for the typical maximal length of a time zone identifier.
+   *
+   * At the time of this writing 32 characters fits every supported time zone:
+   *
+   * Intl.supportedValuesOf("timeZone")
+   *     .reduce((acc, v) => Math.max(acc, v.length), 0)
+   */
+  static constexpr size_t TimeZoneIdentifierLength = 32;
+
+  /**
    * Returns the canonical system time zone ID or the normalized custom time
    * zone ID for the given time zone ID.
    */
   template <typename B>
   static ICUResult GetCanonicalTimeZoneID(Span<const char16_t> inputTimeZone,
                                           B& aBuffer) {
     static_assert(std::is_same_v<typename B::CharType, char16_t>,
                   "Currently only UTF-16 buffers are supported.");
 
     if (aBuffer.capacity() == 0) {
       // ucal_getCanonicalTimeZoneID differs from other API calls and fails when
       // passed a nullptr or 0 length result. Reserve some space initially so
       // that a real pointer will be used in the API.
-      //
-      // At the time of this writing 32 characters fits every time zone listed
-      // in: https://en.wikipedia.org/wiki/List_of_tz_database_time_zones
-      // https://gist.github.com/gregtatum/f926de157a44e5965864da866fe71e63
-      if (!aBuffer.reserve(32)) {
+      if (!aBuffer.reserve(TimeZoneIdentifierLength)) {
         return Err(ICUError::OutOfMemory);
       }
     }
 
     return FillBufferWithICUCall(
         aBuffer,
         [&inputTimeZone](UChar* target, int32_t length, UErrorCode* status) {
           return ucal_getCanonicalTimeZoneID(
@@ -92,14 +208,18 @@ class TimeZone final {
   /**
    * Return an enumeration over all time zones commonly used in the given
    * region.
    */
   static Result<SpanEnumeration<char>, ICUError> GetAvailableTimeZones(
       const char* aRegion);
 
  private:
+#if MOZ_INTL_USE_ICU_CPP_TIMEZONE
+  UniquePtr<icu::TimeZone> mTimeZone = nullptr;
+#else
   UCalendar* mCalendar = nullptr;
+#endif
 };
 
 }  // namespace mozilla::intl
 
 #endif
--- a/intl/docs/dataintl.rst
+++ b/intl/docs/dataintl.rst
@@ -136,45 +136,40 @@ produce the correct pattern for short da
 
 
 mozIntl.getCalendarInfo(locale)
 -------------------------------
 
 The API will return the following calendar information for a given locale code:
 
 * firstDayOfWeek
-    an integer in the range 1=Sunday to 7=Saturday indicating the day
-    considered the first day of the week in calendars, e.g. 1 for en-US,
-    2 for en-GB, 1 for bn-IN
+    an integer in the range 1=Monday to 7=Sunday indicating the day
+    considered the first day of the week in calendars, e.g. 7 for en-US,
+    1 for en-GB, 7 for bn-IN
 * minDays
     an integer in the range of 1 to 7 indicating the minimum number
     of days required in the first week of the year, e.g. 1 for en-US, 4 for de
-* weekendStart
-    an integer in the range 1=Sunday to 7=Saturday indicating the day
-    considered the beginning of a weekend, e.g. 7 for en-US, 7 for en-GB,
-    1 for bn-IN
-* weekendEnd
-    an integer in the range 1=Sunday to 7=Saturday indicating the day
-    considered the end of a weekend, e.g. 1 for en-US, 1 for en-GB,
-    1 for bn-IN (note that "weekend" is *not* necessarily two days)
+* weekend
+    an array with values in the range 1=Monday to 7=Sunday indicating the days
+    of the week considered as part of the weekend, e.g. [6, 7] for en-US and en-GB,
+    [7] for bn-IN (note that "weekend" is *not* necessarily two days)
 
 Those bits of information should be especially useful for any UI that works
 with calendar data.
 
 Example:
 
 .. code-block:: javascript
 
     // omitting the `locale` argument will make the API return data for the
     // current Gecko application UI locale.
     let {
-      firstDayOfWeek,  // 2
+      firstDayOfWeek,  // 1
       minDays,         // 4
-      weekendStart,    // 7
-      weekendEnd,      // 1
+      weekend,         // [6, 7]
       calendar,        // "gregory"
       locale,          // "pl"
     } = Services.intl.getCalendarInfo();
 
 
 mozIntl.DisplayNames(locales, options)
 -----------------------------------------
 
--- a/js/public/GCAPI.h
+++ b/js/public/GCAPI.h
@@ -1193,18 +1193,19 @@ extern JS_PUBLIC_API void SetHostCleanup
     JSContext* cx, JSHostCleanupFinalizationRegistryCallback cb, void* data);
 
 /**
  * Clear kept alive objects in JS WeakRef.
  * https://tc39.es/proposal-weakrefs/#sec-clear-kept-objects
  */
 extern JS_PUBLIC_API void ClearKeptObjects(JSContext* cx);
 
-inline JS_PUBLIC_API bool ZoneIsGrayMarking(Zone* zone) {
-  return shadow::Zone::from(zone)->isGCMarkingBlackAndGray();
+inline JS_PUBLIC_API bool NeedGrayRootsForZone(Zone* zoneArg) {
+  shadow::Zone* zone = shadow::Zone::from(zoneArg);
+  return zone->isGCMarkingBlackAndGray() || zone->isGCCompacting();
 }
 
 extern JS_PUBLIC_API bool AtomsZoneIsCollecting(JSRuntime* runtime);
 extern JS_PUBLIC_API bool IsAtomsZone(Zone* zone);
 
 }  // namespace JS
 
 namespace js {
--- a/js/public/Id.h
+++ b/js/public/Id.h
@@ -257,24 +257,19 @@ namespace js {
 template <>
 struct BarrierMethods<jsid> {
   static gc::Cell* asGCThingOrNull(jsid id) {
     if (id.isGCThing()) {
       return id.toGCThing();
     }
     return nullptr;
   }
-  static void writeBarriers(jsid* idp, jsid prev, jsid next) {
-    if (prev.isString()) {
-      JS::IncrementalPreWriteBarrier(JS::GCCellPtr(prev.toString()));
-      MOZ_ASSERT(!gc::IsInsideNursery(next.toString()));
-    }
-    if (prev.isSymbol()) {
-      JS::IncrementalPreWriteBarrier(JS::GCCellPtr(prev.toSymbol()));
-    }
+  static void postWriteBarrier(jsid* idp, jsid prev, jsid next) {
+    MOZ_ASSERT_IF(JSID_IS_STRING(next),
+                  !gc::IsInsideNursery(JSID_TO_STRING(next)));
   }
   static void exposeToJS(jsid id) {
     if (id.isGCThing()) {
       js::gc::ExposeGCThingToActiveJS(id.toGCCellPtr());
     }
   }
 };
 
--- a/js/public/RootingAPI.h
+++ b/js/public/RootingAPI.h
@@ -191,16 +191,23 @@ struct Cell;
 #define DECLARE_NONPOINTER_MUTABLE_ACCESSOR_METHODS(ptr) \
   T* address() { return &(ptr); }                        \
   T& get() { return (ptr); }
 
 } /* namespace js */
 
 namespace JS {
 
+JS_PUBLIC_API void HeapObjectPostWriteBarrier(JSObject** objp, JSObject* prev,
+                                              JSObject* next);
+JS_PUBLIC_API void HeapStringPostWriteBarrier(JSString** objp, JSString* prev,
+                                              JSString* next);
+JS_PUBLIC_API void HeapBigIntPostWriteBarrier(JS::BigInt** bip,
+                                              JS::BigInt* prev,
+                                              JS::BigInt* next);
 JS_PUBLIC_API void HeapObjectWriteBarriers(JSObject** objp, JSObject* prev,
                                            JSObject* next);
 JS_PUBLIC_API void HeapStringWriteBarriers(JSString** objp, JSString* prev,
                                            JSString* next);
 JS_PUBLIC_API void HeapBigIntWriteBarriers(JS::BigInt** bip, JS::BigInt* prev,
                                            JS::BigInt* next);
 JS_PUBLIC_API void HeapScriptWriteBarriers(JSScript** objp, JSScript* prev,
                                            JSScript* next);
@@ -258,19 +265,23 @@ inline void AssertGCThingIsNotNurseryAll
  *
  * Heap<T> is an abstraction that hides some of the complexity required to
  * maintain GC invariants for the contained reference. It uses operator
  * overloading to provide a normal pointer interface, but adds barriers to
  * notify the GC of changes.
  *
  * Heap<T> implements the following barriers:
  *
- *  - Pre-write barrier (necessary for incremental GC).
  *  - Post-write barrier (necessary for generational GC).
- *  - Read barrier (necessary for cycle collector integration).
+ *  - Read barrier (necessary for incremental GC and cycle collector
+ *    integration).
+ *
+ * Note Heap<T> does not have a pre-write barrier as used internally in the
+ * engine. The read barrier is used to mark anything read from a Heap<T> during
+ * an incremental GC.
  *
  * Heap<T> may be moved or destroyed outside of GC finalization and hence may be
  * used in dynamic storage such as a Vector.
  *
  * Heap<T> instances must be traced when their containing object is traced to
  * keep the pointed-to GC thing alive.
  *
  * Heap<T> objects should only be used on the heap. GC references stored on the
@@ -305,55 +316,55 @@ class MOZ_NON_MEMMOVABLE Heap : public j
   Heap(Heap<T>&& other) { init(other.ptr); }
 
   Heap& operator=(Heap<T>&& other) {
     set(other.unbarrieredGet());
     other.set(SafelyInitialized<T>());
     return *this;
   }
 
-  ~Heap() { writeBarriers(ptr, SafelyInitialized<T>()); }
+  ~Heap() { postWriteBarrier(ptr, SafelyInitialized<T>()); }
 
   DECLARE_POINTER_CONSTREF_OPS(T);
   DECLARE_POINTER_ASSIGN_OPS(Heap, T);
 
   const T* address() const { return &ptr; }
 
   void exposeToActiveJS() const { js::BarrierMethods<T>::exposeToJS(ptr); }
   const T& get() const {
     exposeToActiveJS();
     return ptr;
   }
   const T& unbarrieredGet() const { return ptr; }
 
   void set(const T& newPtr) {
     T tmp = ptr;
     ptr = newPtr;
-    writeBarriers(tmp, ptr);
+    postWriteBarrier(tmp, ptr);
   }
 
   T* unsafeGet() { return &ptr; }
 
   void unbarrieredSet(const T& newPtr) { ptr = newPtr; }
 
   explicit operator bool() const {
     return bool(js::BarrierMethods<T>::asGCThingOrNull(ptr));
   }
   explicit operator bool() {
     return bool(js::BarrierMethods<T>::asGCThingOrNull(ptr));
   }
 
  private:
   void init(const T& newPtr) {
     ptr = newPtr;
-    writeBarriers(SafelyInitialized<T>(), ptr);
+    postWriteBarrier(SafelyInitialized<T>(), ptr);
   }
 
-  void writeBarriers(const T& prev, const T& next) {
-    js::BarrierMethods<T>::writeBarriers(&ptr, prev, next);
+  void postWriteBarrier(const T& prev, const T& next) {
+    js::BarrierMethods<T>::postWriteBarrier(&ptr, prev, next);
   }
 
   T ptr;
 };
 
 namespace detail {
 
 template <typename T>
@@ -446,29 +457,23 @@ class TenuredHeap : public js::HeapOpera
   TenuredHeap() : bits(0) {
     static_assert(sizeof(T) == sizeof(TenuredHeap<T>),
                   "TenuredHeap<T> must be binary compatible with T.");
   }
   explicit TenuredHeap(T p) : bits(0) { setPtr(p); }
   explicit TenuredHeap(const TenuredHeap<T>& p) : bits(0) {
     setPtr(p.getPtr());
   }
-  ~TenuredHeap() { pre(); }
 
   void setPtr(T newPtr) {
     MOZ_ASSERT((reinterpret_cast<uintptr_t>(newPtr) & flagsMask) == 0);
     MOZ_ASSERT(js::gc::IsCellPointerValidOrNull(newPtr));
     if (newPtr) {
       AssertGCThingMustBeTenured(newPtr);
     }
-    pre();
-    unbarrieredSetPtr(newPtr);
-  }
-
-  void unbarrieredSetPtr(T newPtr) {
     bits = (bits & flagsMask) | reinterpret_cast<uintptr_t>(newPtr);
   }
 
   void setFlags(uintptr_t flagsToSet) {
     MOZ_ASSERT((flagsToSet & ~flagsMask) == 0);
     bits |= flagsToSet;
   }
 
@@ -514,22 +519,16 @@ class TenuredHeap : public js::HeapOpera
   }
 
  private:
   enum {
     maskBits = 3,
     flagsMask = (1 << maskBits) - 1,
   };
 
-  void pre() {
-    if (T prev = unbarrieredGetPtr()) {
-      JS::IncrementalPreWriteBarrier(JS::GCCellPtr(prev));
-    }
-  }
-
   uintptr_t bits;
 };
 
 namespace detail {
 
 template <typename T>
 struct DefineComparisonOps<TenuredHeap<T>> : std::true_type {
   static const T get(const TenuredHeap<T>& v) { return v.unbarrieredGetPtr(); }
@@ -752,78 +751,67 @@ struct PtrBarrierMethodsBase {
     }
   }
 };
 
 }  // namespace detail
 
 template <typename T>
 struct BarrierMethods<T*> : public detail::PtrBarrierMethodsBase<T> {
-  static void writeBarriers(T** vp, T* prev, T* next) {
-    if (prev) {
-      JS::IncrementalPreWriteBarrier(JS::GCCellPtr(prev));
-    }
+  static void postWriteBarrier(T** vp, T* prev, T* next) {
     if (next) {
       JS::AssertGCThingIsNotNurseryAllocable(
           reinterpret_cast<js::gc::Cell*>(next));
     }
   }
 };
 
 template <>
 struct BarrierMethods<JSObject*>
     : public detail::PtrBarrierMethodsBase<JSObject> {
-  static void writeBarriers(JSObject** vp, JSObject* prev, JSObject* next) {
-    JS::HeapObjectWriteBarriers(vp, prev, next);
+  static void postWriteBarrier(JSObject** vp, JSObject* prev, JSObject* next) {
+    JS::HeapObjectPostWriteBarrier(vp, prev, next);
   }
   static void exposeToJS(JSObject* obj) {
     if (obj) {
       JS::ExposeObjectToActiveJS(obj);
     }
   }
 };
 
 template <>
 struct BarrierMethods<JSFunction*>
     : public detail::PtrBarrierMethodsBase<JSFunction> {
-  static void writeBarriers(JSFunction** vp, JSFunction* prev,
-                            JSFunction* next) {
-    JS::HeapObjectWriteBarriers(reinterpret_cast<JSObject**>(vp),
-                                reinterpret_cast<JSObject*>(prev),
-                                reinterpret_cast<JSObject*>(next));
+  static void postWriteBarrier(JSFunction** vp, JSFunction* prev,
+                               JSFunction* next) {
+    JS::HeapObjectPostWriteBarrier(reinterpret_cast<JSObject**>(vp),
+                                   reinterpret_cast<JSObject*>(prev),
+                                   reinterpret_cast<JSObject*>(next));
   }
   static void exposeToJS(JSFunction* fun) {
     if (fun) {
       JS::ExposeObjectToActiveJS(reinterpret_cast<JSObject*>(fun));
     }
   }
 };
 
 template <>
 struct BarrierMethods<JSString*>
     : public detail::PtrBarrierMethodsBase<JSString> {
-  static void writeBarriers(JSString** vp, JSString* prev, JSString* next) {
-    JS::HeapStringWriteBarriers(vp, prev, next);
-  }
-};
-
-template <>
-struct BarrierMethods<JSScript*>
-    : public detail::PtrBarrierMethodsBase<JSScript> {
-  static void writeBarriers(JSScript** vp, JSScript* prev, JSScript* next) {
-    JS::HeapScriptWriteBarriers(vp, prev, next);
+  static void postWriteBarrier(JSString** vp, JSString* prev, JSString* next) {
+    JS::HeapStringPostWriteBarrier(vp, prev, next);
   }
 };
 
 template <>
 struct BarrierMethods<JS::BigInt*>
     : public detail::PtrBarrierMethodsBase<JS::BigInt> {
-  static void writeBarriers(JS::BigInt** vp, JS::BigInt* prev,
-                            JS::BigInt* next) {
-    JS::HeapBigIntWriteBarriers(vp, prev, next);
+  static void postWriteBarrier(JS::BigInt** vp, JS::BigInt* prev,
+                               JS::BigInt* next) {
+    JS::HeapBigIntPostWriteBarrier(vp, prev, next);
   }
 };
 
 // Provide hash codes for Cell kinds that may be relocated and, thus, not have
 // a stable address to use as the base for a hash code. Instead of the address,
 // this hasher uses Cell::getUniqueId to provide exact matches and as a base
 // for generating hash codes.
 //
--- a/js/public/TracingAPI.h
+++ b/js/public/TracingAPI.h
@@ -252,84 +252,88 @@ class GenericTracer : public JSTracer {
   virtual js::RegExpShared* onRegExpSharedEdge(js::RegExpShared* shared) = 0;
   virtual js::GetterSetter* onGetterSetterEdge(js::GetterSetter* gs) = 0;
   virtual js::PropMap* onPropMapEdge(js::PropMap* map) = 0;
   virtual js::BaseShape* onBaseShapeEdge(js::BaseShape* base) = 0;
   virtual js::jit::JitCode* onJitCodeEdge(js::jit::JitCode* code) = 0;
   virtual js::Scope* onScopeEdge(js::Scope* scope) = 0;
 };
 
+// A helper class that implements a GenericTracer by calling template method
+// on a derived type for each edge kind.
+template <typename T>
+class GenericTracerImpl : public GenericTracer {
+ public:
+  GenericTracerImpl(JSRuntime* rt, JS::TracerKind kind,
+                    JS::TraceOptions options)
+      : GenericTracer(rt, kind, options) {}
+
+ private:
+  T* derived() { return static_cast<T*>(this); }
+
+  JSObject* onObjectEdge(JSObject* obj) override {
+    return derived()->onEdge(obj);
+  }
+  Shape* onShapeEdge(Shape* shape) override { return derived()->onEdge(shape); }
+  JSString* onStringEdge(JSString* string) override {
+    return derived()->onEdge(string);
+  }
+  BaseScript* onScriptEdge(BaseScript* script) override {
+    return derived()->onEdge(script);
+  }
+  BaseShape* onBaseShapeEdge(BaseShape* base) override {
+    return derived()->onEdge(base);
+  }
+  GetterSetter* onGetterSetterEdge(GetterSetter* gs) override {
+    return derived()->onEdge(gs);
+  }
+  PropMap* onPropMapEdge(PropMap* map) override {
+    return derived()->onEdge(map);
+  }
+  Scope* onScopeEdge(Scope* scope) override { return derived()->onEdge(scope); }
+  RegExpShared* onRegExpSharedEdge(RegExpShared* shared) override {
+    return derived()->onEdge(shared);
+  }
+  JS::BigInt* onBigIntEdge(JS::BigInt* bi) override {
+    return derived()->onEdge(bi);
+  }
+  JS::Symbol* onSymbolEdge(JS::Symbol* sym) override {
+    return derived()->onEdge(sym);
+  }
+  jit::JitCode* onJitCodeEdge(jit::JitCode* jit) override {
+    return derived()->onEdge(jit);
+  }
+};
+
 }  // namespace js
 
 namespace JS {
 
-class JS_PUBLIC_API CallbackTracer : public js::GenericTracer {
+class JS_PUBLIC_API CallbackTracer
+    : public js::GenericTracerImpl<CallbackTracer> {
  public:
   CallbackTracer(JSRuntime* rt, JS::TracerKind kind = JS::TracerKind::Callback,
                  JS::TraceOptions options = JS::TraceOptions())
-      : GenericTracer(rt, kind, options) {
+      : GenericTracerImpl(rt, kind, options) {
     MOZ_ASSERT(isCallbackTracer());
   }
   CallbackTracer(JSContext* cx, JS::TracerKind kind = JS::TracerKind::Callback,
                  JS::TraceOptions options = JS::TraceOptions());
 
   // Override this method to receive notification when a node in the GC
   // heap graph is visited.
   virtual void onChild(const JS::GCCellPtr& thing) = 0;
 
  private:
-  // This class implements the GenericTracer interface to dispatches to onChild.
-  virtual JSObject* onObjectEdge(JSObject* obj) {
-    onChild(JS::GCCellPtr(obj));
-    return obj;
-  }
-  virtual JSString* onStringEdge(JSString* str) {
-    onChild(JS::GCCellPtr(str));
-    return str;
-  }
-  virtual JS::Symbol* onSymbolEdge(JS::Symbol* sym) {
-    onChild(JS::GCCellPtr(sym));
-    return sym;
-  }
-  virtual JS::BigInt* onBigIntEdge(JS::BigInt* bi) {
-    onChild(JS::GCCellPtr(bi));
-    return bi;
-  }
-  virtual js::BaseScript* onScriptEdge(js::BaseScript* script) {
-    onChild(JS::GCCellPtr(script));
-    return script;
-  }
-  virtual js::Shape* onShapeEdge(js::Shape* shape) {
-    onChild(JS::GCCellPtr(shape, JS::TraceKind::Shape));
-    return shape;
+  template <typename T>
+  T* onEdge(T* thing) {
+    onChild(JS::GCCellPtr(thing));
+    return thing;
   }
-  virtual js::BaseShape* onBaseShapeEdge(js::BaseShape* base) {
-    onChild(JS::GCCellPtr(base, JS::TraceKind::BaseShape));
-    return base;
-  }
-  virtual js::GetterSetter* onGetterSetterEdge(js::GetterSetter* gs) {
-    onChild(JS::GCCellPtr(gs, JS::TraceKind::GetterSetter));
-    return gs;
-  }
-  virtual js::PropMap* onPropMapEdge(js::PropMap* map) {
-    onChild(JS::GCCellPtr(map, JS::TraceKind::PropMap));
-    return map;
-  }
-  virtual js::jit::JitCode* onJitCodeEdge(js::jit::JitCode* code) {
-    onChild(JS::GCCellPtr(code, JS::TraceKind::JitCode));
-    return code;
-  }
-  virtual js::Scope* onScopeEdge(js::Scope* scope) {
-    onChild(JS::GCCellPtr(scope, JS::TraceKind::Scope));
-    return scope;
-  }
-  virtual js::RegExpShared* onRegExpSharedEdge(js::RegExpShared* shared) {
-    onChild(JS::GCCellPtr(shared, JS::TraceKind::RegExpShared));
-    return shared;
-  }
+  friend class js::GenericTracerImpl<CallbackTracer>;
 };
 
 // Set the name portion of the tracer's context for the current edge.
 class MOZ_RAII AutoTracingName {
   JSTracer* trc_;
 
  public:
   AutoTracingName(JSTracer* trc, const char* name) : trc_(trc) {
@@ -451,17 +455,17 @@ inline void TraceEdge(JSTracer* trc, JS:
 }
 
 template <typename T>
 inline void TraceEdge(JSTracer* trc, JS::TenuredHeap<T>* thingp,
                       const char* name) {
   MOZ_ASSERT(thingp);
   if (T ptr = thingp->unbarrieredGetPtr()) {
     js::gc::TraceExternalEdge(trc, &ptr, name);
-    thingp->unbarrieredSetPtr(ptr);
+    thingp->setPtr(ptr);
   }
 }
 
 // Edges that are always traced as part of root marking do not require
 // incremental barriers. |JS::UnsafeTraceRoot| overloads allow for marking
 // non-barriered pointers but assert that this happens during root marking.
 //
 // Note that while |edgep| must never be null, it is fine for |*edgep| to be
--- a/js/public/Value.h
+++ b/js/public/Value.h
@@ -1067,16 +1067,18 @@ inline bool SameType(const Value& lhs, c
 #endif
 }
 
 }  // namespace JS
 
 /************************************************************************/
 
 namespace JS {
+JS_PUBLIC_API void HeapValuePostWriteBarrier(Value* valuep, const Value& prev,
+                                             const Value& next);
 JS_PUBLIC_API void HeapValueWriteBarriers(Value* valuep, const Value& prev,
                                           const Value& next);
 
 template <>
 struct GCPolicy<JS::Value> {
   static void trace(JSTracer* trc, Value* v, const char* name) {
     // It's not safe to trace unbarriered pointers except as part of root
     // marking.
@@ -1094,19 +1096,19 @@ struct GCPolicy<JS::Value> {
 
 namespace js {
 
 template <>
 struct BarrierMethods<JS::Value> {
   static gc::Cell* asGCThingOrNull(const JS::Value& v) {
     return v.isGCThing() ? v.toGCThing() : nullptr;
   }
-  static void writeBarriers(JS::Value* v, const JS::Value& prev,
-                            const JS::Value& next) {
-    JS::HeapValueWriteBarriers(v, prev, next);
+  static void postWriteBarrier(JS::Value* v, const JS::Value& prev,
+                               const JS::Value& next) {
+    JS::HeapValuePostWriteBarrier(v, prev, next);
   }
   static void exposeToJS(const JS::Value& v) { JS::ExposeValueToActiveJS(v); }
 };
 
 template <class Wrapper>
 class MutableValueOperations;
 
 /**
--- a/js/src/builtin/String.cpp
+++ b/js/src/builtin/String.cpp
@@ -956,17 +956,17 @@ bool js::intl_toLocaleLowerCase(JSContex
   intl::FormatBuffer<char16_t, INLINE_CAPACITY> buffer(cx);
 
   auto ok = mozilla::intl::String::ToLocaleLowerCase(locale, input, buffer);
   if (ok.isErr()) {
     intl::ReportInternalError(cx, ok.unwrapErr());
     return false;
   }
 
-  JSString* result = buffer.toString();
+  JSString* result = buffer.toString(cx);
   if (!result) {
     return false;
   }
 
   args.rval().setString(result);
   return true;
 }
 
@@ -1362,17 +1362,17 @@ bool js::intl_toLocaleUpperCase(JSContex
   intl::FormatBuffer<char16_t, INLINE_CAPACITY> buffer(cx);
 
   auto ok = mozilla::intl::String::ToLocaleUpperCase(locale, input, buffer);
   if (ok.isErr()) {
     intl::ReportInternalError(cx, ok.unwrapErr());
     return false;
   }
 
-  JSString* result = buffer.toString();
+  JSString* result = buffer.toString(cx);
   if (!result) {
     return false;
   }
 
   args.rval().setString(result);
   return true;
 }
 
@@ -1542,17 +1542,17 @@ static bool str_normalize(JSContext* cx,
 
   // Return if the input string is already normalized.
   if (alreadyNormalized.unwrap() == AlreadyNormalized::Yes) {
     // Step 7.
     args.rval().setString(str);
     return true;
   }
 
-  JSString* ns = buffer.toString();
+  JSString* ns = buffer.toString(cx);
   if (!ns) {
     return false;
   }
 
   // Step 7.
   args.rval().setString(ns);
   return true;
 }
--- a/js/src/builtin/TestingFunctions.cpp
+++ b/js/src/builtin/TestingFunctions.cpp
@@ -4,16 +4,19 @@
  * 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 "builtin/TestingFunctions.h"
 
 #include "mozilla/Atomics.h"
 #include "mozilla/Casting.h"
 #include "mozilla/FloatingPoint.h"
+#ifdef JS_HAS_INTL_API
+#  include "mozilla/intl/TimeZone.h"
+#endif
 #include "mozilla/Maybe.h"
 #include "mozilla/ScopeExit.h"
 #include "mozilla/Span.h"
 #include "mozilla/Sprintf.h"
 #include "mozilla/TextUtils.h"
 #include "mozilla/ThreadLocal.h"
 #include "mozilla/Tuple.h"
 
@@ -35,16 +38,17 @@
 #endif
 
 #include "jsapi.h"
 #include "jsfriendapi.h"
 #include "jsmath.h"
 
 #ifdef JS_HAS_INTL_API
 #  include "builtin/intl/CommonFunctions.h"
+#  include "builtin/intl/FormatBuffer.h"
 #  include "builtin/intl/SharedIntlData.h"
 #endif
 #include "builtin/Promise.h"
 #include "builtin/SelfHostingDefines.h"
 #include "builtin/TestingUtility.h"  // js::ParseCompileOptions
 #ifdef DEBUG
 #  include "frontend/TokenStream.h"
 #endif
@@ -7281,22 +7285,34 @@ static bool GetICUOptions(JSContext* cx,
     return false;
   }
 
   str = NewStringCopyZ<CanGC>(cx, tzdataVersion);
   if (!str || !JS_DefineProperty(cx, info, "tzdata", str, JSPROP_ENUMERATE)) {
     return false;
   }
 
-  str = intl::CallICU(cx, ucal_getDefaultTimeZone);
+  intl::FormatBuffer<char16_t, intl::INITIAL_CHAR_BUFFER_SIZE> buf(cx);
+
+  if (auto ok = mozilla::intl::TimeZone::GetDefaultTimeZone(buf); ok.isErr()) {
+    intl::ReportInternalError(cx, ok.unwrapErr());
+    return false;
+  }
+
+  str = buf.toString(cx);
   if (!str || !JS_DefineProperty(cx, info, "timezone", str, JSPROP_ENUMERATE)) {
     return false;
   }
 
-  str = intl::CallICU(cx, ucal_getHostTimeZone);
+  if (auto ok = mozilla::intl::TimeZone::GetHostTimeZone(buf); ok.isErr()) {
+    intl::ReportInternalError(cx, ok.unwrapErr());
+    return false;
+  }
+
+  str = buf.toString(cx);
   if (!str ||
       !JS_DefineProperty(cx, info, "host-timezone", str, JSPROP_ENUMERATE)) {
     return false;
   }
 #endif
 
   args.rval().setObject(*info);
   return true;
--- a/js/src/builtin/intl/DateTimeFormat.cpp
+++ b/js/src/builtin/intl/DateTimeFormat.cpp
@@ -391,17 +391,17 @@ bool js::intl_canonicalizeTimeZone(JSCon
   FormatBuffer<char16_t, intl::INITIAL_CHAR_BUFFER_SIZE> canonicalTimeZone(cx);
   auto result = mozilla::intl::TimeZone::GetCanonicalTimeZoneID(
       stableChars.twoByteRange(), canonicalTimeZone);
   if (result.isErr()) {
     intl::ReportInternalError(cx, result.unwrapErr());
     return false;
   }
 
-  JSString* str = canonicalTimeZone.toString();
+  JSString* str = canonicalTimeZone.toString(cx);
   if (!str) {
     return false;
   }
 
   args.rval().setString(str);
   return true;
 }
 
@@ -416,17 +416,17 @@ bool js::intl_defaultTimeZone(JSContext*
 
   FormatBuffer<char16_t, intl::INITIAL_CHAR_BUFFER_SIZE> timeZone(cx);
   auto result = mozilla::intl::TimeZone::GetDefaultTimeZone(timeZone);
   if (result.isErr()) {
     intl::ReportInternalError(cx);
     return false;
   }
 
-  JSString* str = timeZone.toString();
+  JSString* str = timeZone.toString(cx);
   if (!str) {
     return false;
   }
 
   args.rval().setString(str);
   return true;
 }
 
@@ -1113,17 +1113,17 @@ static bool intl_FormatDateTime(JSContex
 
   FormatBuffer<char16_t, INITIAL_CHAR_BUFFER_SIZE> buffer(cx);
   auto dfResult = df->TryFormat(x.toDouble(), buffer);
   if (dfResult.isErr()) {
     intl::ReportInternalError(cx, dfResult.unwrapErr());
     return false;
   }
 
-  JSString* str = buffer.toString();
+  JSString* str = buffer.toString(cx);
   if (!str) {
     return false;
   }
 
   result.setString(str);
   return true;
 }
 
--- a/js/src/builtin/intl/FormatBuffer.h
+++ b/js/src/builtin/intl/FormatBuffer.h
@@ -12,37 +12,38 @@
 #include "mozilla/Span.h"
 
 #include <stddef.h>
 #include <stdint.h>
 
 #include "gc/Allocator.h"
 #include "js/AllocPolicy.h"
 #include "js/TypeDecls.h"
+#include "js/UniquePtr.h"
 #include "js/Vector.h"
 #include "vm/StringType.h"
 
 namespace js::intl {
 
 /**
  * A buffer for formatting unified intl data.
  */
-template <typename CharT, size_t MinInlineCapacity = 0>
+template <typename CharT, size_t MinInlineCapacity = 0,
+          class AllocPolicy = TempAllocPolicy>
 class FormatBuffer {
  public:
   using CharType = CharT;
 
   // Allow move constructors, but not copy constructors, as this class owns a
   // js::Vector.
   FormatBuffer(FormatBuffer&& other) noexcept = default;
   FormatBuffer& operator=(FormatBuffer&& other) noexcept = default;
 
-  explicit FormatBuffer(JSContext* cx) : cx_(cx), buffer_(cx) {
-    MOZ_ASSERT(cx);
-  }
+  explicit FormatBuffer(AllocPolicy aP = AllocPolicy())
+      : buffer_(std::move(aP)) {}
 
   // Implicitly convert to a Span.
   operator mozilla::Span<CharType>() { return buffer_; }
   operator mozilla::Span<const CharType>() const { return buffer_; }
 
   /**
    * Ensures the buffer has enough space to accommodate |size| elements.
    */
@@ -77,31 +78,46 @@ class FormatBuffer {
 
   /**
    * Copies the buffer's data to a JSString.
    *
    * TODO(#1715842) - This should be more explicit on needing to handle OOM
    * errors. In this case it returns a nullptr that must be checked, but it may
    * not be obvious.
    */
-  JSLinearString* toString() const {
+  JSLinearString* toString(JSContext* cx) const {
     if constexpr (std::is_same_v<CharT, uint8_t> ||
                   std::is_same_v<CharT, unsigned char> ||
                   std::is_same_v<CharT, char>) {
       // Handle the UTF-8 encoding case.
       return NewStringCopyUTF8N<CanGC>(
-          cx_, mozilla::Range(reinterpret_cast<unsigned char>(buffer_.begin()),
-                              buffer_.length()));
+          cx, mozilla::Range(reinterpret_cast<unsigned char>(buffer_.begin()),
+                             buffer_.length()));
     } else {
       // Handle the UTF-16 encoding case.
       static_assert(std::is_same_v<CharT, char16_t>);
-      return NewStringCopyN<CanGC>(cx_, buffer_.begin(), buffer_.length());
+      return NewStringCopyN<CanGC>(cx, buffer_.begin(), buffer_.length());
     }
   }
 
+  /**
+   * Extract this buffer's content as a null-terminated string.
+   */
+  UniquePtr<CharType[], JS::FreePolicy> extractStringZ() {
+    // Adding the NUL character on an already null-terminated string is likely
+    // an error. If there's ever a valid use case which triggers this assertion,
+    // we should change the below code to only conditionally add '\0'.
+    MOZ_ASSERT_IF(!buffer_.empty(), buffer_.end()[-1] != '\0');
+
+    if (!buffer_.append('\0')) {
+      return nullptr;
+    }
+    return UniquePtr<CharType[], JS::FreePolicy>(
+        buffer_.extractOrCopyRawBuffer());
+  }
+
  private:
-  JSContext* cx_;
-  js::Vector<CharT, MinInlineCapacity> buffer_;
+  js::Vector<CharT, MinInlineCapacity, AllocPolicy> buffer_;
 };
 
 }  // namespace js::intl
 
 #endif /* builtin_intl_FormatBuffer_h */
--- a/js/src/builtin/intl/IntlObject.cpp
+++ b/js/src/builtin/intl/IntlObject.cpp
@@ -26,29 +26,26 @@
 #include "builtin/intl/DateTimeFormat.h"
 #include "builtin/intl/FormatBuffer.h"
 #include "builtin/intl/LanguageTag.h"
 #include "builtin/intl/MeasureUnitGenerated.h"
 #include "builtin/intl/NumberFormat.h"
 #include "builtin/intl/NumberingSystemsGenerated.h"
 #include "builtin/intl/PluralRules.h"
 #include "builtin/intl/RelativeTimeFormat.h"
-#include "builtin/intl/ScopedICUObject.h"
 #include "builtin/intl/SharedIntlData.h"
 #include "ds/Sort.h"
 #include "js/CharacterEncoding.h"
 #include "js/Class.h"
 #include "js/friend/ErrorMessages.h"  // js::GetErrorMessage, JSMSG_*
 #include "js/GCAPI.h"
 #include "js/GCVector.h"
 #include "js/PropertySpec.h"
 #include "js/Result.h"
 #include "js/StableStringChars.h"
-#include "unicode/ucal.h"
-#include "unicode/utypes.h"
 #include "vm/GlobalObject.h"
 #include "vm/JSAtom.h"
 #include "vm/JSContext.h"
 #include "vm/JSObject.h"
 #include "vm/PlainObject.h"  // js::PlainObject
 #include "vm/StringType.h"
 #include "vm/WellKnownAtom.h"  // js_*_str
 
@@ -63,97 +60,60 @@ bool js::intl_GetCalendarInfo(JSContext*
   CallArgs args = CallArgsFromVp(argc, vp);
   MOZ_ASSERT(args.length() == 1);
 
   UniqueChars locale = intl::EncodeLocale(cx, args[0].toString());
   if (!locale) {
     return false;
   }
 
-  UErrorCode status = U_ZERO_ERROR;
-  const UChar* uTimeZone = nullptr;
-  int32_t uTimeZoneLength = 0;
-  UCalendar* cal = ucal_open(uTimeZone, uTimeZoneLength, locale.get(),
-                             UCAL_DEFAULT, &status);
-  if (U_FAILURE(status)) {
-    intl::ReportInternalError(cx);
+  auto result = mozilla::intl::Calendar::TryCreate(locale.get());
+  if (result.isErr()) {
+    intl::ReportInternalError(cx, result.unwrapErr());
     return false;
   }
-  ScopedICUObject<UCalendar, ucal_close> toClose(cal);
+  auto calendar = result.unwrap();
 
   RootedObject info(cx, NewPlainObject(cx));
   if (!info) {
     return false;
   }
 
   RootedValue v(cx);
-  int32_t firstDayOfWeek = ucal_getAttribute(cal, UCAL_FIRST_DAY_OF_WEEK);
-  v.setInt32(firstDayOfWeek);
 
+  v.setInt32(static_cast<int32_t>(calendar->GetFirstDayOfWeek()));
   if (!DefineDataProperty(cx, info, cx->names().firstDayOfWeek, v)) {
     return false;
   }
 
-  int32_t minDays = ucal_getAttribute(cal, UCAL_MINIMAL_DAYS_IN_FIRST_WEEK);
-  v.setInt32(minDays);
+  v.setInt32(calendar->GetMinimalDaysInFirstWeek());
   if (!DefineDataProperty(cx, info, cx->names().minDays, v)) {
     return false;
   }
 
-  UCalendarWeekdayType prevDayType =
-      ucal_getDayOfWeekType(cal, UCAL_SATURDAY, &status);
-  if (U_FAILURE(status)) {
-    intl::ReportInternalError(cx);
+  RootedArrayObject weekendArray(cx, NewDenseEmptyArray(cx));
+  if (!weekendArray) {
     return false;
   }
 
-  RootedValue weekendStart(cx), weekendEnd(cx);
-
-  for (int i = UCAL_SUNDAY; i <= UCAL_SATURDAY; i++) {
-    UCalendarDaysOfWeek dayOfWeek = static_cast<UCalendarDaysOfWeek>(i);
-    UCalendarWeekdayType type = ucal_getDayOfWeekType(cal, dayOfWeek, &status);
-    if (U_FAILURE(status)) {
-      intl::ReportInternalError(cx);
-      return false;
-    }
-
-    if (prevDayType != type) {
-      switch (type) {
-        case UCAL_WEEKDAY:
-          // If the first Weekday after Weekend is Sunday (1),
-          // then the last Weekend day is Saturday (7).
-          // Otherwise we'll just take the previous days number.
-          weekendEnd.setInt32(i == 1 ? 7 : i - 1);
-          break;
-        case UCAL_WEEKEND:
-          weekendStart.setInt32(i);
-          break;
-        case UCAL_WEEKEND_ONSET:
-        case UCAL_WEEKEND_CEASE:
-          // At the time this code was added, ICU apparently never behaves this
-          // way, so just throw, so that users will report a bug and we can
-          // decide what to do.
-          intl::ReportInternalError(cx);
-          return false;
-        default:
-          break;
-      }
-    }
-
-    prevDayType = type;
-  }
-
-  MOZ_ASSERT(weekendStart.isInt32());
-  MOZ_ASSERT(weekendEnd.isInt32());
-
-  if (!DefineDataProperty(cx, info, cx->names().weekendStart, weekendStart)) {
+  auto weekend = calendar->GetWeekend();
+  if (weekend.isErr()) {
+    intl::ReportInternalError(cx, weekend.unwrapErr());
     return false;
   }
 
-  if (!DefineDataProperty(cx, info, cx->names().weekendEnd, weekendEnd)) {
+  for (auto day : weekend.unwrap()) {
+    if (!NewbornArrayPush(cx, weekendArray,
+                          Int32Value(static_cast<int32_t>(day)))) {
+      return false;
+    }
+  }
+
+  v.setObject(*weekendArray);
+  if (!DefineDataProperty(cx, info, cx->names().weekend, v)) {
     return false;
   }
 
   args.rval().setObject(*info);
   return true;
 }
 
 static void ReportBadKey(JSContext* cx, JSString* key) {
@@ -805,17 +765,17 @@ static ArrayObject* AvailableTimeZones(J
           canonicalTimeZone(cx);
       auto result = mozilla::intl::TimeZone::GetCanonicalTimeZoneID(
           stableChars.twoByteRange(), canonicalTimeZone);
       if (result.isErr()) {
         intl::ReportInternalError(cx, result.unwrapErr());
         return nullptr;
       }
 
-      timeZone = canonicalTimeZone.toString();
+      timeZone = canonicalTimeZone.toString(cx);
       if (!timeZone) {
         return nullptr;
       }
 
       // Canonicalize both to "UTC" per CanonicalizeTimeZoneName().
       if (StringEqualsLiteral(timeZone, "Etc/UTC") ||
           StringEqualsLiteral(timeZone, "Etc/GMT")) {
         timeZone = cx->names().UTC;
--- a/js/src/builtin/intl/IntlObject.h
+++ b/js/src/builtin/intl/IntlObject.h
@@ -15,31 +15,28 @@ namespace js {
 extern const JSClass IntlClass;
 
 /**
  * Returns a plain object with calendar information for a single valid locale
  * (callers must perform this validation).  The object will have these
  * properties:
  *
  *   firstDayOfWeek
- *     an integer in the range 1=Sunday to 7=Saturday indicating the day
- *     considered the first day of the week in calendars, e.g. 1 for en-US,
- *     2 for en-GB, 1 for bn-IN
+ *     an integer in the range 1=Monday to 7=Sunday indicating the day
+ *     considered the first day of the week in calendars, e.g. 7 for en-US,
+ *     1 for en-GB, 7 for bn-IN
  *   minDays
  *     an integer in the range of 1 to 7 indicating the minimum number
  *     of days required in the first week of the year, e.g. 1 for en-US,
  *     4 for de
- *   weekendStart
- *     an integer in the range 1=Sunday to 7=Saturday indicating the day
- *     considered the beginning of a weekend, e.g. 7 for en-US, 7 for en-GB,
- *     1 for bn-IN
- *   weekendEnd
- *     an integer in the range 1=Sunday to 7=Saturday indicating the day
- *     considered the end of a weekend, e.g. 1 for en-US, 1 for en-GB,
- *     1 for bn-IN (note that "weekend" is *not* necessarily two days)
+ *   weekend
+ *     an array with values in the range 1=Monday to 7=Sunday indicating the
+ *     days of the week considered as part of the weekend, e.g. [6, 7] for en-US
+ *     and en-GB, [7] for bn-IN (note that "weekend" is *not* necessarily two
+ *     days)
  *
  * NOTE: "calendar" and "locale" properties are *not* added to the object.
  */
 [[nodiscard]] extern bool intl_GetCalendarInfo(JSContext* cx, unsigned argc,
                                                JS::Value* vp);
 
 /**
  * Compares a BCP 47 language tag against the locales in availableLocales and
--- a/js/src/builtin/intl/IntlObject.js
+++ b/js/src/builtin/intl/IntlObject.js
@@ -35,23 +35,20 @@ function Intl_supportedValuesOf(key) {
  *     The default calendar of the resolved locale.
  *
  *   firstDayOfWeek:
  *     The first day of the week for the resolved locale.
  *
  *   minDays:
  *     The minimum number of days in a week for the resolved locale.
  *
- *   weekendStart:
- *     The day considered the beginning of a weekend for the resolved locale.
+ *   weekend:
+ *     The days of the week considered as the weekend for the resolved locale.
  *
- *   weekendEnd:
- *     The day considered the end of a weekend for the resolved locale.
- *
- * Days are encoded as integers in the range 1=Sunday to 7=Saturday.
+ * Days are encoded as integers in the range 1=Monday to 7=Sunday.
  */
 function Intl_getCalendarInfo(locales) {
     // 1. Let requestLocales be ? CanonicalizeLocaleList(locales).
     const requestedLocales = CanonicalizeLocaleList(locales);
 
     const DateTimeFormat = dateTimeFormatInternalProperties;
 
     // 2. Let localeData be %DateTimeFormat%.[[localeData]].
--- a/js/src/builtin/intl/ListFormat.cpp
+++ b/js/src/builtin/intl/ListFormat.cpp
@@ -224,17 +224,17 @@ static bool FormatList(JSContext* cx, mo
                        MutableHandleValue result) {
   intl::FormatBuffer<char16_t, intl::INITIAL_CHAR_BUFFER_SIZE> formatBuffer(cx);
   auto formatResult = lf->Format(list, formatBuffer);
   if (formatResult.isErr()) {
     js::intl::ReportInternalError(cx, formatResult.unwrapErr());
     return false;
   }
 
-  JSString* str = formatBuffer.toString();
+  JSString* str = formatBuffer.toString(cx);
   if (!str) {
     return false;
   }
   result.setString(str);
   return true;
 }
 
 /**
--- a/js/src/builtin/intl/RelativeTimeFormat.cpp
+++ b/js/src/builtin/intl/RelativeTimeFormat.cpp
@@ -375,16 +375,16 @@ bool js::intl_FormatRelativeTime(JSConte
   js::intl::FormatBuffer<char16_t, intl::INITIAL_CHAR_BUFFER_SIZE> buffer(cx);
   mozilla::Result<Ok, ICUError> result = rtf->format(t, relTimeUnit, buffer);
 
   if (result.isErr()) {
     intl::ReportInternalError(cx, result.unwrapErr());
     return false;
   }
 
-  JSString* str = buffer.toString();
+  JSString* str = buffer.toString(cx);
   if (!str) {
     return false;
   }
 
   args.rval().setString(str);
   return true;
 }
--- a/js/src/frontend/BytecodeCompiler.cpp
+++ b/js/src/frontend/BytecodeCompiler.cpp
@@ -1056,34 +1056,40 @@ static bool CompileLazyFunction(JSContex
   }
 
   mozilla::DebugOnly<uint32_t> lazyFlags =
       static_cast<uint32_t>(input.immutableFlags());
 
   Rooted<CompilationGCOutput> gcOutput(cx);
   {
     BorrowingCompilationStencil borrowingStencil(compilationState);
-    if (!CompilationStencil::instantiateStencils(cx, input, borrowingStencil,
-                                                 gcOutput.get())) {
-      return false;
-    }
-
-    MOZ_ASSERT(lazyFlags == gcOutput.get().script->immutableFlags());
-    MOZ_ASSERT(gcOutput.get().script->outermostScope()->hasOnChain(
-                   ScopeKind::NonSyntactic) ==
-               gcOutput.get().script->immutableFlags().hasFlag(
-                   JSScript::ImmutableFlags::HasNonSyntacticScope));
 
     if (input.source->hasEncoder()) {
       MOZ_ASSERT(!js::UseOffThreadParseGlobal());
       if (!input.source->addDelazificationToIncrementalEncoding(
               cx, borrowingStencil)) {
         return false;
       }
     }
+
+    if (!CompilationStencil::instantiateStencils(cx, input, borrowingStencil,
+                                                 gcOutput.get())) {
+      return false;
+    }
+
+    // NOTE: After instantiation succeeds and bytecode is attached, the rest of
+    //       this operation should be infallible. Any failure during
+    //       delazification should restore the function back to a consistent
+    //       lazy state.
+
+    MOZ_ASSERT(lazyFlags == gcOutput.get().script->immutableFlags());
+    MOZ_ASSERT(gcOutput.get().script->outermostScope()->hasOnChain(
+                   ScopeKind::NonSyntactic) ==
+               gcOutput.get().script->immutableFlags().hasFlag(
+                   JSScript::ImmutableFlags::HasNonSyntacticScope));
   }
 
   assertException.reset();
   return true;
 }
 
 template <typename Unit>
 static bool DelazifyCanonicalScriptedFunctionImpl(JSContext* cx,
--- a/js/src/frontend/smoosh/Cargo.toml
+++ b/js/src/frontend/smoosh/Cargo.toml
@@ -7,17 +7,17 @@ license = "MIT/Apache-2.0"
 
 [dependencies]
 bumpalo = "3.4.0"
 log = "0.4"
 # Setup RUST_LOG logging.
 # Disable regex feature for code size.
 env_logger = {version = "0.8", default-features = false}
 # For non-jsparagus developers.
-jsparagus = { git