Merge mozilla-central to inbound. a=merge CLOSED TREE
authorGurzau Raul <rgurzau@mozilla.com>
Fri, 21 Dec 2018 18:40:39 +0200
changeset 451716 78e69c0b9293e493b1ecbb66b964d2411f7acada
parent 451715 9382786d8bae1973dbecfece8f64be9ff641b5cb (current diff)
parent 451673 38dac70dcec66ea4c5f14da8ae600aa6ccca9389 (diff)
child 451717 2b9466c27a2185685742ea4d5ee21898b9f31168
push id35252
push userccoroiu@mozilla.com
push dateFri, 21 Dec 2018 21:56:22 +0000
treeherdermozilla-central@b23630094b9c [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone66.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Merge mozilla-central to inbound. a=merge CLOSED TREE
--- a/.eslintignore
+++ b/.eslintignore
@@ -180,17 +180,16 @@ dom/canvas/**
 dom/encoding/**
 dom/events/**
 dom/fetch/**
 dom/file/**
 dom/flex/**
 dom/grid/**
 dom/html/**
 dom/jsurl/**
-dom/localstorage/**
 dom/media/test/**
 dom/media/tests/**
 dom/media/webaudio/**
 dom/media/webspeech/**
 dom/messagechannel/**
 dom/midi/**
 dom/network/**
 dom/notification/Notification*.*
--- a/accessible/android/DocAccessibleWrap.cpp
+++ b/accessible/android/DocAccessibleWrap.cpp
@@ -98,17 +98,17 @@ void DocAccessibleWrap::CacheViewportCal
   }
 
   nsTArray<nsIFrame*> frames;
   nsIScrollableFrame* sf = presShell->GetRootScrollFrameAsScrollable();
   nsRect scrollPort = sf ? sf->GetScrollPortRect() : rootFrame->GetRect();
 
   nsLayoutUtils::GetFramesForArea(
       presShell->GetRootFrame(), scrollPort, frames,
-      nsLayoutUtils::FrameForPointFlags::ONLY_VISIBLE);
+      nsLayoutUtils::FrameForPointOption::OnlyVisible);
   AccessibleHashtable inViewAccs;
   for (size_t i = 0; i < frames.Length(); i++) {
     nsIContent* content = frames.ElementAt(i)->GetContent();
     if (!content) {
       continue;
     }
 
     Accessible* visibleAcc = docAcc->GetAccessibleOrContainer(content);
--- a/accessible/base/nsAccUtils.cpp
+++ b/accessible/base/nsAccUtils.cpp
@@ -434,8 +434,74 @@ bool nsAccUtils::PersistentPropertiesToA
     rv = propElem->GetValue(value);
     NS_ENSURE_SUCCESS(rv, false);
 
     aAttributes->AppendElement(Attribute(name, value));
   }
 
   return true;
 }
+
+bool nsAccUtils::IsARIALive(const Accessible* aAccessible) {
+  // Get computed aria-live property based on the closest container with the
+  // attribute. Inner nodes override outer nodes within the same
+  // document, but nodes in outer documents override nodes in inner documents.
+  // This should be the same as the container-live attribute, but we don't need
+  // the other container-* attributes, so we can't use the same function.
+  nsAutoString live;
+  nsIContent* startContent = aAccessible->GetContent();
+  while (startContent) {
+    nsIDocument* doc = startContent->GetComposedDoc();
+    if (!doc) {
+      break;
+    }
+
+    dom::Element* aTopEl = doc->GetRootElement();
+    nsIContent* ancestor = startContent;
+    while (ancestor) {
+      nsAutoString docLive;
+      const nsRoleMapEntry* role = nullptr;
+      if (ancestor->IsElement()) {
+        role = aria::GetRoleMap(ancestor->AsElement());
+      }
+      if (HasDefinedARIAToken(ancestor, nsGkAtoms::aria_live)) {
+        ancestor->AsElement()->GetAttr(kNameSpaceID_None, nsGkAtoms::aria_live,
+                                       docLive);
+      } else if (role) {
+        GetLiveAttrValue(role->liveAttRule, docLive);
+      }
+      if (!docLive.IsEmpty()) {
+        live = docLive;
+        break;
+      }
+
+      if (ancestor == aTopEl) {
+        break;
+      }
+
+      ancestor = ancestor->GetParent();
+      if (!ancestor) {
+        ancestor = aTopEl;  // Use <body>/<frameset>
+      }
+    }
+
+    // Allow ARIA live region markup from outer documents to override.
+    nsCOMPtr<nsIDocShellTreeItem> docShellTreeItem = doc->GetDocShell();
+    if (!docShellTreeItem) {
+      break;
+    }
+
+    nsCOMPtr<nsIDocShellTreeItem> sameTypeParent;
+    docShellTreeItem->GetSameTypeParent(getter_AddRefs(sameTypeParent));
+    if (!sameTypeParent || sameTypeParent == docShellTreeItem) {
+      break;
+    }
+
+    nsIDocument* parentDoc = doc->GetParentDocument();
+    if (!parentDoc) {
+      break;
+    }
+
+    startContent = parentDoc->FindContentForSubDocument(doc);
+  }
+
+  return !live.IsEmpty() && !live.EqualsLiteral("off");
+}
--- a/accessible/base/nsAccUtils.h
+++ b/accessible/base/nsAccUtils.h
@@ -250,14 +250,20 @@ class nsAccUtils {
   /**
    * Return true if the given accessible can't have children. Used when exposing
    * to platform accessibility APIs, should the children be pruned off?
    */
   static bool MustPrune(Accessible* aAccessible);
 
   static bool PersistentPropertiesToArray(nsIPersistentProperties* aProps,
                                           nsTArray<Attribute>* aAttributes);
+
+  /**
+   * Return true if the given accessible is within an ARIA live region; i.e.
+   * the container-live attribute would be something other than "off" or empty.
+   */
+  static bool IsARIALive(const Accessible* aAccessible);
 };
 
 }  // namespace a11y
 }  // namespace mozilla
 
 #endif
--- a/accessible/generic/Accessible.cpp
+++ b/accessible/generic/Accessible.cpp
@@ -841,20 +841,33 @@ nsresult Accessible::HandleAccEvent(AccE
         case nsIAccessibleEvent::EVENT_TEXT_CARET_MOVED: {
           AccCaretMoveEvent* event = downcast_accEvent(aEvent);
           ipcDoc->SendCaretMoveEvent(id, event->GetCaretOffset());
           break;
         }
         case nsIAccessibleEvent::EVENT_TEXT_INSERTED:
         case nsIAccessibleEvent::EVENT_TEXT_REMOVED: {
           AccTextChangeEvent* event = downcast_accEvent(aEvent);
-          ipcDoc->SendTextChangeEvent(
-              id, event->ModifiedText(), event->GetStartOffset(),
-              event->GetLength(), event->IsTextInserted(),
-              event->IsFromUserInput());
+          const nsString& text = event->ModifiedText();
+#if defined(XP_WIN)
+          // On Windows, events for live region updates containing embedded
+          // objects require us to dispatch synchronous events.
+          bool sync = text.Contains(L'\xfffc') &&
+                      nsAccUtils::IsARIALive(aEvent->GetAccessible());
+#endif
+          ipcDoc->SendTextChangeEvent(id, text, event->GetStartOffset(),
+                                      event->GetLength(),
+                                      event->IsTextInserted(),
+                                      event->IsFromUserInput()
+#if defined(XP_WIN)
+                                      // This parameter only exists on Windows.
+                                      ,
+                                      sync
+#endif
+          );
           break;
         }
         case nsIAccessibleEvent::EVENT_SELECTION:
         case nsIAccessibleEvent::EVENT_SELECTION_ADD:
         case nsIAccessibleEvent::EVENT_SELECTION_REMOVE: {
           AccSelChangeEvent* selEvent = downcast_accEvent(aEvent);
           uint64_t widgetID =
               selEvent->Widget()->IsDoc()
--- a/accessible/ipc/win/DocAccessibleChild.cpp
+++ b/accessible/ipc/win/DocAccessibleChild.cpp
@@ -192,19 +192,19 @@ bool DocAccessibleChild::SendCaretMoveEv
   PushDeferredEvent(
       MakeUnique<SerializedCaretMove>(this, aID, aCaretRect, aOffset));
   return true;
 }
 
 bool DocAccessibleChild::SendTextChangeEvent(
     const uint64_t& aID, const nsString& aStr, const int32_t& aStart,
     const uint32_t& aLen, const bool& aIsInsert, const bool& aFromUser,
-    const bool aDoSyncCheck) {
+    const bool aDoSync) {
   if (IsConstructedInParentProcess()) {
-    if (aDoSyncCheck && aStr.Contains(L'\xfffc')) {
+    if (aDoSync) {
       // The AT is going to need to reenter content while the event is being
       // dispatched synchronously.
       return PDocAccessibleChild::SendSyncTextChangeEvent(
           aID, aStr, aStart, aLen, aIsInsert, aFromUser);
     }
     return PDocAccessibleChild::SendTextChangeEvent(aID, aStr, aStart, aLen,
                                                     aIsInsert, aFromUser);
   }
--- a/accessible/ipc/win/DocAccessibleChild.h
+++ b/accessible/ipc/win/DocAccessibleChild.h
@@ -49,17 +49,17 @@ class DocAccessibleChild : public DocAcc
                           const LayoutDeviceIntRect& aCaretRect,
                           const int32_t& aOffset);
   bool SendFocusEvent(const uint64_t& aID);
   bool SendFocusEvent(const uint64_t& aID,
                       const LayoutDeviceIntRect& aCaretRect);
   bool SendTextChangeEvent(const uint64_t& aID, const nsString& aStr,
                            const int32_t& aStart, const uint32_t& aLen,
                            const bool& aIsInsert, const bool& aFromUser,
-                           const bool aDoSyncCheck = true);
+                           const bool aDoSync = false);
   bool SendSelectionEvent(const uint64_t& aID, const uint64_t& aWidgetID,
                           const uint32_t& aType);
   bool SendRoleChangedEvent(const a11y::role& aRole);
   bool SendScrollingEvent(const uint64_t& aID, const uint64_t& aType,
                           const uint32_t& aScrollX, const uint32_t& aScrollY,
                           const uint32_t& aMaxScrollX,
                           const uint32_t& aMaxScrollY);
 
--- a/dom/base/DocumentOrShadowRoot.cpp
+++ b/dom/base/DocumentOrShadowRoot.cpp
@@ -189,128 +189,194 @@ Element* DocumentOrShadowRoot::GetFullsc
   nsIContent* retargeted = Retarget(element);
   if (retargeted && retargeted->IsElement()) {
     return retargeted->AsElement();
   }
 
   return nullptr;
 }
 
-Element* DocumentOrShadowRoot::ElementFromPoint(float aX, float aY) {
-  return ElementFromPointHelper(aX, aY, false, true);
-}
+namespace {
+
+using FrameForPointOption = nsLayoutUtils::FrameForPointOption;
+
+// Whether only one node or multiple nodes is requested.
+enum class Multiple {
+  No,
+  Yes,
+};
 
-void DocumentOrShadowRoot::ElementsFromPoint(
-    float aX, float aY, nsTArray<RefPtr<Element>>& aElements) {
-  ElementsFromPointHelper(aX, aY, nsIDocument::FLUSH_LAYOUT, aElements);
+// Whether we should flush layout or not.
+enum class FlushLayout {
+  No,
+  Yes,
+};
+
+template <typename NodeOrElement>
+NodeOrElement* CastTo(nsIContent* aContent);
+
+template <>
+Element* CastTo<Element>(nsIContent* aContent) {
+  return aContent->AsElement();
 }
 
-Element* DocumentOrShadowRoot::ElementFromPointHelper(
-    float aX, float aY, bool aIgnoreRootScrollFrame, bool aFlushLayout) {
-  AutoTArray<RefPtr<Element>, 1> elementArray;
-  ElementsFromPointHelper(
-      aX, aY,
-      ((aIgnoreRootScrollFrame ? nsIDocument::IGNORE_ROOT_SCROLL_FRAME : 0) |
-       (aFlushLayout ? nsIDocument::FLUSH_LAYOUT : 0) |
-       nsIDocument::IS_ELEMENT_FROM_POINT),
-      elementArray);
-  if (elementArray.IsEmpty()) {
-    return nullptr;
-  }
-  return elementArray[0];
+template <>
+nsINode* CastTo<nsINode>(nsIContent* aContent) {
+  return aContent;
 }
 
-void DocumentOrShadowRoot::ElementsFromPointHelper(
-    float aX, float aY, uint32_t aFlags,
-    nsTArray<RefPtr<mozilla::dom::Element>>& aElements) {
-  // As per the the spec, we return null if either coord is negative
-  if (!(aFlags & nsIDocument::IGNORE_ROOT_SCROLL_FRAME) && (aX < 0 || aY < 0)) {
-    return;
-  }
+template <typename NodeOrElement>
+static void QueryNodesFromRect(DocumentOrShadowRoot& aRoot, const nsRect& aRect,
+                               EnumSet<FrameForPointOption> aOptions,
+                               FlushLayout aShouldFlushLayout,
+                               Multiple aMultiple,
+                               nsTArray<RefPtr<NodeOrElement>>& aNodes) {
+  static_assert(std::is_same<nsINode, NodeOrElement>::value ||
+                    std::is_same<Element, NodeOrElement>::value,
+                "Should returning nodes or elements");
 
-  nscoord x = nsPresContext::CSSPixelsToAppUnits(aX);
-  nscoord y = nsPresContext::CSSPixelsToAppUnits(aY);
-  nsPoint pt(x, y);
+  constexpr bool returningElements =
+      std::is_same<Element, NodeOrElement>::value;
 
-  nsCOMPtr<nsIDocument> doc = AsNode().OwnerDoc();
+  nsCOMPtr<nsIDocument> doc = aRoot.AsNode().OwnerDoc();
 
   // Make sure the layout information we get is up-to-date, and
   // ensure we get a root frame (for everything but XUL)
-  if (aFlags & nsIDocument::FLUSH_LAYOUT) {
+  if (aShouldFlushLayout == FlushLayout::Yes) {
     doc->FlushPendingNotifications(FlushType::Layout);
   }
 
   nsIPresShell* ps = doc->GetShell();
   if (!ps) {
     return;
   }
+
   nsIFrame* rootFrame = ps->GetRootFrame();
-
   // XUL docs, unlike HTML, have no frame tree until everything's done loading
   if (!rootFrame) {
     return;  // return null to premature XUL callers as a reminder to wait
   }
 
-  nsTArray<nsIFrame*> outFrames;
-  // Emulate what GetFrameAtPoint does, since we want all the frames under our
-  // point.
-  nsLayoutUtils::GetFramesForArea(
-      rootFrame, nsRect(pt, nsSize(1, 1)), outFrames,
-      nsLayoutUtils::IGNORE_PAINT_SUPPRESSION |
-          nsLayoutUtils::IGNORE_CROSS_DOC |
-          ((aFlags & nsIDocument::IGNORE_ROOT_SCROLL_FRAME)
-               ? nsLayoutUtils::IGNORE_ROOT_SCROLL_FRAME
-               : 0));
+  aOptions += FrameForPointOption::IgnorePaintSuppression;
+  aOptions += FrameForPointOption::IgnoreCrossDoc;
+
+  AutoTArray<nsIFrame*, 8> frames;
+  nsLayoutUtils::GetFramesForArea(rootFrame, aRect, frames, aOptions);
 
-  // Dunno when this would ever happen, as we should at least have a root frame
-  // under us?
-  if (outFrames.IsEmpty()) {
-    return;
-  }
+  for (nsIFrame* frame : frames) {
+    nsIContent* content = doc->GetContentInThisDocument(frame);
+    if (!content) {
+      continue;
+    }
 
-  // Used to filter out repeated elements in sequence.
-  nsIContent* lastAdded = nullptr;
-
-  for (uint32_t i = 0; i < outFrames.Length(); i++) {
-    nsIContent* node = doc->GetContentInThisDocument(outFrames[i]);
-
-    if (!node || !node->IsElement()) {
+    if (returningElements && !content->IsElement()) {
       // If this helper is called via ElementsFromPoint, we need to make sure
       // our frame is an element. Otherwise return whatever the top frame is
       // even if it isn't the top-painted element.
       // SVG 'text' element's SVGTextFrame doesn't respond to hit-testing, so
-      // if 'node' is a child of such an element then we need to manually defer
-      // to the parent here.
-      if (!(aFlags & nsIDocument::IS_ELEMENT_FROM_POINT) &&
-          !nsSVGUtils::IsInSVGTextSubtree(outFrames[i])) {
+      // if 'content' is a child of such an element then we need to manually
+      // defer to the parent here.
+      if (aMultiple == Multiple::Yes &&
+          !nsSVGUtils::IsInSVGTextSubtree(frame)) {
         continue;
       }
-      node = node->GetParent();
-      if (ShadowRoot* shadow = ShadowRoot::FromNodeOrNull(node)) {
-        node = shadow->Host();
+
+      content = content->GetParent();
+      if (ShadowRoot* shadow = ShadowRoot::FromNodeOrNull(content)) {
+        content = shadow->Host();
       }
     }
 
     // XXXsmaug There is plenty of unspec'ed behavior here
     //         https://github.com/w3c/webcomponents/issues/735
     //         https://github.com/w3c/webcomponents/issues/736
-    node = Retarget(node);
+    content = aRoot.Retarget(content);
 
-    if (node && node != lastAdded) {
-      aElements.AppendElement(node->AsElement());
-      lastAdded = node;
-      // If this helper is called via ElementFromPoint, just return the first
-      // element we find.
-      if (aFlags & nsIDocument::IS_ELEMENT_FROM_POINT) {
+    if (content && content != aNodes.SafeLastElement(nullptr)) {
+      aNodes.AppendElement(CastTo<NodeOrElement>(content));
+      if (aMultiple == Multiple::No) {
         return;
       }
     }
   }
 }
 
+template <typename NodeOrElement>
+static void QueryNodesFromPoint(DocumentOrShadowRoot& aRoot, float aX, float aY,
+                                EnumSet<FrameForPointOption> aOptions,
+                                FlushLayout aShouldFlushLayout,
+                                Multiple aMultiple,
+                                nsTArray<RefPtr<NodeOrElement>>& aNodes) {
+  // As per the spec, we return null if either coord is negative.
+  if (!aOptions.contains(FrameForPointOption::IgnoreRootScrollFrame) &&
+      (aX < 0 || aY < 0)) {
+    return;
+  }
+
+  nscoord x = nsPresContext::CSSPixelsToAppUnits(aX);
+  nscoord y = nsPresContext::CSSPixelsToAppUnits(aY);
+  nsPoint pt(x, y);
+  QueryNodesFromRect(aRoot, nsRect(pt, nsSize(1, 1)), aOptions,
+                     aShouldFlushLayout, aMultiple, aNodes);
+}
+
+}  // namespace
+
+Element* DocumentOrShadowRoot::ElementFromPoint(float aX, float aY) {
+  return ElementFromPointHelper(aX, aY, false, true);
+}
+
+void DocumentOrShadowRoot::ElementsFromPoint(
+    float aX, float aY, nsTArray<RefPtr<Element>>& aElements) {
+  QueryNodesFromPoint(*this, aX, aY, {}, FlushLayout::Yes, Multiple::Yes,
+                      aElements);
+}
+
+Element* DocumentOrShadowRoot::ElementFromPointHelper(
+    float aX, float aY, bool aIgnoreRootScrollFrame, bool aFlushLayout) {
+  EnumSet<FrameForPointOption> options;
+  if (aIgnoreRootScrollFrame) {
+    options += FrameForPointOption::IgnoreRootScrollFrame;
+  }
+
+  auto flush = aFlushLayout ? FlushLayout::Yes : FlushLayout::No;
+
+  AutoTArray<RefPtr<Element>, 1> elements;
+  QueryNodesFromPoint(*this, aX, aY, options, flush, Multiple::No, elements);
+  return elements.SafeElementAt(0);
+}
+
+void DocumentOrShadowRoot::NodesFromRect(float aX, float aY, float aTopSize,
+                                         float aRightSize, float aBottomSize,
+                                         float aLeftSize,
+                                         bool aIgnoreRootScrollFrame,
+                                         bool aFlushLayout,
+                                         nsTArray<RefPtr<nsINode>>& aReturn) {
+  // Following the same behavior of elementFromPoint,
+  // we don't return anything if either coord is negative
+  if (!aIgnoreRootScrollFrame && (aX < 0 || aY < 0)) {
+    return;
+  }
+
+  nscoord x = nsPresContext::CSSPixelsToAppUnits(aX - aLeftSize);
+  nscoord y = nsPresContext::CSSPixelsToAppUnits(aY - aTopSize);
+  nscoord w = nsPresContext::CSSPixelsToAppUnits(aLeftSize + aRightSize) + 1;
+  nscoord h = nsPresContext::CSSPixelsToAppUnits(aTopSize + aBottomSize) + 1;
+
+  nsRect rect(x, y, w, h);
+
+  EnumSet<FrameForPointOption> options;
+  if (aIgnoreRootScrollFrame) {
+    options += FrameForPointOption::IgnoreRootScrollFrame;
+  }
+
+  auto flush = aFlushLayout ? FlushLayout::Yes : FlushLayout::No;
+  QueryNodesFromRect(*this, rect, options, flush, Multiple::Yes, aReturn);
+}
+
 Element* DocumentOrShadowRoot::AddIDTargetObserver(nsAtom* aID,
                                                    IDTargetObserver aObserver,
                                                    void* aData,
                                                    bool aForImage) {
   nsDependentAtomString id(aID);
 
   if (!CheckGetElementByIdArg(id)) {
     return nullptr;
--- a/dom/base/DocumentOrShadowRoot.h
+++ b/dom/base/DocumentOrShadowRoot.h
@@ -12,16 +12,17 @@
 #include "nsContentListDeclarations.h"
 #include "nsTArray.h"
 #include "nsIdentifierMapEntry.h"
 
 class nsContentList;
 class nsCycleCollectionTraversalCallback;
 class nsIDocument;
 class nsINode;
+class nsINodeList;
 class nsIRadioVisitor;
 class nsWindowSizes;
 
 namespace mozilla {
 class StyleSheet;
 
 namespace dom {
 
@@ -109,25 +110,21 @@ class DocumentOrShadowRoot {
    * Helper for elementFromPoint implementation that allows
    * ignoring the scroll frame and/or avoiding layout flushes.
    *
    * @see nsIDOMWindowUtils::elementFromPoint
    */
   Element* ElementFromPointHelper(float aX, float aY,
                                   bool aIgnoreRootScrollFrame,
                                   bool aFlushLayout);
-  enum ElementsFromPointFlags {
-    IGNORE_ROOT_SCROLL_FRAME = 1,
-    FLUSH_LAYOUT = 2,
-    IS_ELEMENT_FROM_POINT = 4
-  };
 
-  void ElementsFromPointHelper(
-      float aX, float aY, uint32_t aFlags,
-      nsTArray<RefPtr<mozilla::dom::Element>>& aElements);
+  void NodesFromRect(float aX, float aY, float aTopSize, float aRightSize,
+                     float aBottomSize, float aLeftSize,
+                     bool aIgnoreRootScrollFrame, bool aFlushLayout,
+                     nsTArray<RefPtr<nsINode>>&);
 
   /**
    * This gets fired when the element that an id refers to changes.
    * This fires at difficult times. It is generally not safe to do anything
    * which could modify the DOM in any way. Use
    * nsContentUtils::AddScriptRunner.
    * @return true to keep the callback in the callback set, false
    * to remove it.
@@ -194,27 +191,27 @@ class DocumentOrShadowRoot {
   void RadioRequiredWillChange(const nsAString& aName, bool aRequiredAdded);
   bool GetValueMissingState(const nsAString& aName) const;
   void SetValueMissingState(const nsAString& aName, bool aValue);
 
   // for radio group
   nsRadioGroupStruct* GetRadioGroup(const nsAString& aName) const;
   nsRadioGroupStruct* GetOrCreateRadioGroup(const nsAString& aName);
 
+  nsIContent* Retarget(nsIContent* aContent) const;
+
  protected:
   // Returns the reference to the sheet, if found in mStyleSheets.
   already_AddRefed<StyleSheet> RemoveSheet(StyleSheet& aSheet);
   void InsertSheetAt(size_t aIndex, StyleSheet& aSheet);
 
   void AddSizeOfExcludingThis(nsWindowSizes&) const;
   void AddSizeOfOwnedSheetArrayExcludingThis(
       nsWindowSizes&, const nsTArray<RefPtr<StyleSheet>>&) const;
 
-  nsIContent* Retarget(nsIContent* aContent) const;
-
   /**
    * If focused element's subtree root is this document or shadow root, return
    * focused element, otherwise, get the shadow host recursively until the
    * shadow host's subtree root is this document or shadow root.
    */
   Element* GetRetargetedFocusedElement();
 
   nsTArray<RefPtr<mozilla::StyleSheet>> mStyleSheets;
--- a/dom/base/nsDOMWindowUtils.cpp
+++ b/dom/base/nsDOMWindowUtils.cpp
@@ -1148,19 +1148,28 @@ nsDOMWindowUtils::ElementFromPoint(float
 NS_IMETHODIMP
 nsDOMWindowUtils::NodesFromRect(float aX, float aY, float aTopSize,
                                 float aRightSize, float aBottomSize,
                                 float aLeftSize, bool aIgnoreRootScrollFrame,
                                 bool aFlushLayout, nsINodeList** aReturn) {
   nsCOMPtr<nsIDocument> doc = GetDocument();
   NS_ENSURE_STATE(doc);
 
-  return doc->NodesFromRectHelper(aX, aY, aTopSize, aRightSize, aBottomSize,
-                                  aLeftSize, aIgnoreRootScrollFrame,
-                                  aFlushLayout, aReturn);
+  nsSimpleContentList* list = new nsSimpleContentList(doc);
+  NS_ADDREF(list);
+  *aReturn = list;
+
+  AutoTArray<RefPtr<nsINode>, 8> nodes;
+  doc->NodesFromRect(aX, aY, aTopSize, aRightSize, aBottomSize, aLeftSize,
+                     aIgnoreRootScrollFrame, aFlushLayout, nodes);
+  list->SetCapacity(nodes.Length());
+  for (auto& node : nodes) {
+    list->AppendElement(node->AsContent());
+  }
+  return NS_OK;
 }
 
 NS_IMETHODIMP
 nsDOMWindowUtils::GetTranslationNodes(nsINode* aRoot,
                                       nsITranslationNodeList** aRetVal) {
   NS_ENSURE_ARG_POINTER(aRetVal);
   nsCOMPtr<nsIContent> root = do_QueryInterface(aRoot);
   NS_ENSURE_STATE(root);
--- a/dom/base/nsDocument.cpp
+++ b/dom/base/nsDocument.cpp
@@ -3389,82 +3389,16 @@ Element* nsIDocument::GetActiveElement()
   return GetDocumentElement();
 }
 
 Element* nsIDocument::GetCurrentScript() {
   nsCOMPtr<Element> el(do_QueryInterface(ScriptLoader()->GetCurrentScript()));
   return el;
 }
 
-nsresult nsIDocument::NodesFromRectHelper(float aX, float aY, float aTopSize,
-                                          float aRightSize, float aBottomSize,
-                                          float aLeftSize,
-                                          bool aIgnoreRootScrollFrame,
-                                          bool aFlushLayout,
-                                          nsINodeList** aReturn) {
-  NS_ENSURE_ARG_POINTER(aReturn);
-
-  nsSimpleContentList* elements = new nsSimpleContentList(this);
-  NS_ADDREF(elements);
-  *aReturn = elements;
-
-  // Following the same behavior of elementFromPoint,
-  // we don't return anything if either coord is negative
-  if (!aIgnoreRootScrollFrame && (aX < 0 || aY < 0)) return NS_OK;
-
-  nscoord x = nsPresContext::CSSPixelsToAppUnits(aX - aLeftSize);
-  nscoord y = nsPresContext::CSSPixelsToAppUnits(aY - aTopSize);
-  nscoord w = nsPresContext::CSSPixelsToAppUnits(aLeftSize + aRightSize) + 1;
-  nscoord h = nsPresContext::CSSPixelsToAppUnits(aTopSize + aBottomSize) + 1;
-
-  nsRect rect(x, y, w, h);
-
-  // Make sure the layout information we get is up-to-date, and
-  // ensure we get a root frame (for everything but XUL)
-  if (aFlushLayout) {
-    FlushPendingNotifications(FlushType::Layout);
-  }
-
-  nsIPresShell* ps = GetShell();
-  NS_ENSURE_STATE(ps);
-  nsIFrame* rootFrame = ps->GetRootFrame();
-
-  // XUL docs, unlike HTML, have no frame tree until everything's done loading
-  if (!rootFrame)
-    return NS_OK;  // return nothing to premature XUL callers as a reminder to
-                   // wait
-
-  AutoTArray<nsIFrame*, 8> outFrames;
-  nsLayoutUtils::GetFramesForArea(
-      rootFrame, rect, outFrames,
-      nsLayoutUtils::IGNORE_PAINT_SUPPRESSION |
-          nsLayoutUtils::IGNORE_CROSS_DOC |
-          (aIgnoreRootScrollFrame ? nsLayoutUtils::IGNORE_ROOT_SCROLL_FRAME
-                                  : 0));
-
-  // Used to filter out repeated elements in sequence.
-  nsIContent* lastAdded = nullptr;
-
-  for (uint32_t i = 0; i < outFrames.Length(); i++) {
-    nsIContent* node = GetContentInThisDocument(outFrames[i]);
-
-    if (node && !node->IsElement() && !node->IsText()) {
-      // We have a node that isn't an element or a text node,
-      // use its parent content instead.
-      node = node->GetParent();
-    }
-    if (node && node != lastAdded) {
-      elements->AppendElement(node);
-      lastAdded = node;
-    }
-  }
-
-  return NS_OK;
-}
-
 void nsIDocument::ReleaseCapture() const {
   // only release the capture if the caller can access it. This prevents a
   // page from stopping a scrollbar grab for example.
   nsCOMPtr<nsINode> node = nsIPresShell::GetCapturingContent();
   if (node && nsContentUtils::CanCallerAccess(node)) {
     nsIPresShell::SetCapturingContent(nullptr, 0);
   }
 }
@@ -9248,16 +9182,18 @@ already_AddRefed<TouchList> nsIDocument:
   for (uint32_t i = 0; i < aTouches.Length(); ++i) {
     retval->Append(aTouches[i].get());
   }
   return retval.forget();
 }
 
 already_AddRefed<nsDOMCaretPosition> nsIDocument::CaretPositionFromPoint(
     float aX, float aY) {
+  using FrameForPointOption = nsLayoutUtils::FrameForPointOption;
+
   nscoord x = nsPresContext::CSSPixelsToAppUnits(aX);
   nscoord y = nsPresContext::CSSPixelsToAppUnits(aY);
   nsPoint pt(x, y);
 
   FlushPendingNotifications(FlushType::Layout);
 
   nsIPresShell* ps = GetShell();
   if (!ps) {
@@ -9266,20 +9202,20 @@ already_AddRefed<nsDOMCaretPosition> nsI
 
   nsIFrame* rootFrame = ps->GetRootFrame();
 
   // XUL docs, unlike HTML, have no frame tree until everything's done loading
   if (!rootFrame) {
     return nullptr;
   }
 
-  nsIFrame* ptFrame =
-      nsLayoutUtils::GetFrameForPoint(rootFrame, pt,
-                                      nsLayoutUtils::IGNORE_PAINT_SUPPRESSION |
-                                          nsLayoutUtils::IGNORE_CROSS_DOC);
+  nsIFrame* ptFrame = nsLayoutUtils::GetFrameForPoint(
+      rootFrame, pt,
+      {FrameForPointOption::IgnorePaintSuppression,
+       FrameForPointOption::IgnoreCrossDoc});
   if (!ptFrame) {
     return nullptr;
   }
 
   // We require frame-relative coordinates for GetContentOffsetsFromPoint.
   nsPoint aOffset;
   nsCOMPtr<nsIWidget> widget = nsContentUtils::GetWidget(ps, &aOffset);
   LayoutDeviceIntPoint refPoint = nsContentUtils::ToWidgetPoint(
--- a/dom/base/nsIDocument.h
+++ b/dom/base/nsIDocument.h
@@ -2179,21 +2179,16 @@ class nsIDocument : public nsINode,
    * document.
    */
   bool HaveFiredDOMTitleChange() const { return mHaveFiredTitleChange; }
 
   Element* GetAnonymousElementByAttribute(nsIContent* aElement,
                                           nsAtom* aAttrName,
                                           const nsAString& aAttrValue) const;
 
-  nsresult NodesFromRectHelper(float aX, float aY, float aTopSize,
-                               float aRightSize, float aBottomSize,
-                               float aLeftSize, bool aIgnoreRootScrollFrame,
-                               bool aFlushLayout, nsINodeList** aReturn);
-
   /**
    * See FlushSkinBindings on nsBindingManager
    */
   void FlushSkinBindings();
 
   /**
    * To batch DOMSubtreeModified, document needs to be informed when
    * a mutation event might be dispatched, even if the event isn't actually
new file mode 100644
--- /dev/null
+++ b/dom/localstorage/test/unit/.eslintrc.js
@@ -0,0 +1,7 @@
+"use strict";
+
+module.exports = {
+  "extends": [
+    "plugin:mozilla/xpcshell-test",
+  ]
+};
--- a/dom/localstorage/test/unit/databaseShadowing-shared.js
+++ b/dom/localstorage/test/unit/databaseShadowing-shared.js
@@ -1,37 +1,36 @@
+/* import-globals-from head.js */
+
 const principalInfos = [
   { url: "http://example.com", attrs: {} },
 
   { url: "http://origin.test", attrs: {} },
 
   { url: "http://prefix.test", attrs: {} },
   { url: "http://prefix.test", attrs: { userContextId: 10 } },
 
   { url: "http://pattern.test", attrs: { userContextId: 15 } },
   { url: "http://pattern.test:8080", attrs: { userContextId: 15 } },
   { url: "https://pattern.test", attrs: { userContextId: 15 } },
 ];
 
-function enableNextGenLocalStorage()
-{
+function enableNextGenLocalStorage() {
   info("Setting pref");
 
   Services.prefs.setBoolPref("dom.storage.next_gen", true);
 }
 
-function disableNextGenLocalStorage()
-{
+function disableNextGenLocalStorage() {
   info("Setting pref");
 
   Services.prefs.setBoolPref("dom.storage.next_gen", false);
 }
 
-function storeData()
-{
+function storeData() {
   for (let i = 0; i < principalInfos.length; i++) {
     let principalInfo = principalInfos[i];
     let principal = getPrincipal(principalInfo.url, principalInfo.attrs);
 
     info("Getting storage");
 
     let storage = getLocalStorage(principal);
 
@@ -44,35 +43,33 @@ function storeData()
     storage.setItem("key2", "value2");
 
     info("Closing storage");
 
     storage.close();
   }
 }
 
-function exportShadowDatabase(name)
-{
+function exportShadowDatabase(name) {
   info("Verifying shadow database");
 
   let profileDir = Services.dirsvc.get("ProfD", Ci.nsIFile);
   let shadowDatabase = profileDir.clone();
   shadowDatabase.append("webappsstore.sqlite");
 
   let exists = shadowDatabase.exists();
   ok(exists, "Shadow database does exist");
 
   info("Copying shadow database");
 
   let currentDir = Services.dirsvc.get("CurWorkD", Ci.nsIFile);
   shadowDatabase.copyTo(currentDir, name);
 }
 
-function importShadowDatabase(name)
-{
+function importShadowDatabase(name) {
   info("Verifying shadow database");
 
   let currentDir = Services.dirsvc.get("CurWorkD", Ci.nsIFile);
   let shadowDatabase = currentDir.clone();
   shadowDatabase.append(name);
 
   let exists = shadowDatabase.exists();
   if (!exists) {
@@ -82,18 +79,17 @@ function importShadowDatabase(name)
   info("Copying shadow database");
 
   let profileDir = Services.dirsvc.get("ProfD", Ci.nsIFile);
   shadowDatabase.copyTo(profileDir, "webappsstore.sqlite");
 
   return true;
 }
 
-function verifyData(clearedOrigins)
-{
+function verifyData(clearedOrigins) {
   for (let i = 0; i < principalInfos.length; i++) {
     let principalInfo = principalInfos[i];
     let principal = getPrincipal(principalInfo.url, principalInfo.attrs);
 
     info("Getting storage");
 
     let storage = getLocalStorage(principal);
 
--- a/dom/localstorage/test/unit/head.js
+++ b/dom/localstorage/test/unit/head.js
@@ -1,165 +1,144 @@
 /**
  * Any copyright is dedicated to the Public Domain.
  * http://creativecommons.org/publicdomain/zero/1.0/
  */
 
+// Tests are expected to define testSteps.
+/* globals testSteps */
+
 const NS_ERROR_DOM_QUOTA_EXCEEDED_ERR = 22;
 
 ChromeUtils.import("resource://gre/modules/Services.jsm");
 
-function is(a, b, msg)
-{
+function is(a, b, msg) {
   Assert.equal(a, b, msg);
 }
 
-function ok(cond, msg)
-{
+function ok(cond, msg) {
   Assert.ok(!!cond, msg);
 }
 
-function run_test()
-{
+function run_test() {
   runTest();
-};
+}
 
 if (!this.runTest) {
-  this.runTest = function()
-  {
+  this.runTest = function() {
     do_get_profile();
 
     enableTesting();
 
     Assert.ok(typeof testSteps === "function",
               "There should be a testSteps function");
     Assert.ok(testSteps.constructor.name === "AsyncFunction",
               "testSteps should be an async function");
 
     registerCleanupFunction(resetTesting);
 
     add_task(testSteps);
 
     // Since we defined run_test, we must invoke run_next_test() to start the
     // async test.
     run_next_test();
-  }
+  };
 }
 
-function returnToEventLoop()
-{
+function returnToEventLoop() {
   return new Promise(function(resolve) {
     executeSoon(resolve);
   });
 }
 
-function enableTesting()
-{
+function enableTesting() {
   Services.prefs.setBoolPref("dom.storage.testing", true);
   Services.prefs.setBoolPref("dom.quotaManager.testing", true);
 }
 
-function resetTesting()
-{
+function resetTesting() {
   Services.prefs.clearUserPref("dom.quotaManager.testing");
   Services.prefs.clearUserPref("dom.storage.testing");
 }
 
-function setGlobalLimit(globalLimit)
-{
+function setGlobalLimit(globalLimit) {
   Services.prefs.setIntPref("dom.quotaManager.temporaryStorage.fixedLimit",
                             globalLimit);
 }
 
-function resetGlobalLimit()
-{
+function resetGlobalLimit() {
   Services.prefs.clearUserPref("dom.quotaManager.temporaryStorage.fixedLimit");
 }
 
-function setOriginLimit(originLimit)
-{
+function setOriginLimit(originLimit) {
   Services.prefs.setIntPref("dom.storage.default_quota", originLimit);
 }
 
-function resetOriginLimit()
-{
+function resetOriginLimit() {
   Services.prefs.clearUserPref("dom.storage.default_quota");
 }
 
-function init()
-{
+function init() {
   let request = Services.qms.init();
 
   return request;
 }
 
-function initOrigin(principal, persistence)
-{
+function initOrigin(principal, persistence) {
   let request = Services.qms.initStoragesForPrincipal(principal, persistence);
 
   return request;
 }
 
-function getOriginUsage(principal)
-{
+function getOriginUsage(principal) {
   let request = Services.qms.getUsageForPrincipal(principal, function() { });
 
   return request;
 }
 
-function clear()
-{
+function clear() {
   let request = Services.qms.clear();
 
   return request;
 }
 
-function clearOriginsByPattern(pattern)
-{
+function clearOriginsByPattern(pattern) {
   let request = Services.qms.clearStoragesForOriginAttributesPattern(pattern);
 
   return request;
 }
 
-function clearOriginsByPrefix(principal, persistence)
-{
+function clearOriginsByPrefix(principal, persistence) {
   let request =
     Services.qms.clearStoragesForPrincipal(principal, persistence, null, true);
 
   return request;
 }
 
-function clearOrigin(principal, persistence)
-{
+function clearOrigin(principal, persistence) {
   let request = Services.qms.clearStoragesForPrincipal(principal, persistence);
 
   return request;
 }
 
-function reset()
-{
+function reset() {
   let request = Services.qms.reset();
 
   return request;
 }
 
-function resetOrigin(principal)
-{
+function resetOrigin(principal) {
   let request =
     Services.qms.resetStoragesForPrincipal(principal, "default", "ls");
 
   return request;
 }
 
-function installPackage(packageName)
-{
-  let directoryService = Cc["@mozilla.org/file/directory_service;1"]
-                         .getService(Ci.nsIProperties);
-
-  let currentDir = directoryService.get("CurWorkD", Ci.nsIFile);
+function installPackage(packageName) {
+  let currentDir = Services.dirsvc.get("CurWorkD", Ci.nsIFile);
 
   let packageFile = currentDir.clone();
   packageFile.append(packageName + ".zip");
 
   let zipReader = Cc["@mozilla.org/libjar/zip-reader;1"]
                   .createInstance(Ci.nsIZipReader);
   zipReader.open(packageFile);
 
@@ -180,49 +159,44 @@ function installPackage(packageName)
       file.create(Ci.nsIFile.DIRECTORY_TYPE, parseInt("0755", 8));
     } else {
       let istream = zipReader.getInputStream(entryName);
 
       var ostream = Cc["@mozilla.org/network/file-output-stream;1"]
                     .createInstance(Ci.nsIFileOutputStream);
       ostream.init(file, -1, parseInt("0644", 8), 0);
 
-      let bostream = Cc['@mozilla.org/network/buffered-output-stream;1']
+      let bostream = Cc["@mozilla.org/network/buffered-output-stream;1"]
                      .createInstance(Ci.nsIBufferedOutputStream);
       bostream.init(ostream, 32768);
 
       bostream.writeFrom(istream, istream.available());
 
       istream.close();
       bostream.close();
     }
   }
 
   zipReader.close();
 }
 
-function getProfileDir()
-{
-  let directoryService =
-    Cc["@mozilla.org/file/directory_service;1"].getService(Ci.nsIProperties);
-
-  return directoryService.get("ProfD", Ci.nsIFile);
+function getProfileDir() {
+  return Services.dirsvc.get("ProfD", Ci.nsIFile);
 }
 
 // Given a "/"-delimited path relative to the profile directory,
 // return an nsIFile representing the path.  This does not test
 // for the existence of the file or parent directories.
 // It is safe even on Windows where the directory separator is not "/",
 // but make sure you're not passing in a "\"-delimited path.
-function getRelativeFile(relativePath)
-{
+function getRelativeFile(relativePath) {
   let profileDir = getProfileDir();
 
   let file = profileDir.clone();
-  relativePath.split('/').forEach(function(component) {
+  relativePath.split("/").forEach(function(component) {
     file.append(component);
   });
 
   return file;
 }
 
 function repeatChar(count, ch) {
   if (count == 0) {
@@ -236,49 +210,45 @@ function repeatChar(count, ch) {
   while (result.length <= count2) {
     result += result;
   }
 
   // Use substring to hit the precise length target without using extra memory.
   return result + result.substring(0, count - result.length);
 }
 
-function getPrincipal(url, attrs)
-{
+function getPrincipal(url, attrs) {
   let uri = Services.io.newURI(url);
   if (!attrs) {
     attrs = {};
   }
   return Services.scriptSecurityManager.createCodebasePrincipal(uri, attrs);
 }
 
-function getCurrentPrincipal()
-{
+function getCurrentPrincipal() {
   return Cc["@mozilla.org/systemprincipal;1"].createInstance(Ci.nsIPrincipal);
 }
 
-function getLocalStorage(principal)
-{
+function getLocalStorage(principal) {
   if (!principal) {
     principal = getCurrentPrincipal();
   }
 
   return Services.domStorageManager.createStorage(null, principal, "");
 }
 
 function requestFinished(request) {
   return new Promise(function(resolve, reject) {
-    request.callback = function(request) {
-      if (request.resultCode == Cr.NS_OK) {
-        resolve(request.result);
+    request.callback = function(requestInner) {
+      if (requestInner.resultCode == Cr.NS_OK) {
+        resolve(requestInner.result);
       } else {
-        reject(request.resultCode);
+        reject(requestInner.resultCode);
       }
-    }
+    };
   });
 }
 
-function loadSubscript(path)
-{
+function loadSubscript(path) {
   let file = do_get_file(path, false);
   let uri = Services.io.newFileURI(file);
   Services.scriptloader.loadSubScript(uri.spec);
 }
--- a/dom/localstorage/test/unit/test_archive.js
+++ b/dom/localstorage/test/unit/test_archive.js
@@ -1,30 +1,28 @@
 /**
  * Any copyright is dedicated to the Public Domain.
  * http://creativecommons.org/publicdomain/zero/1.0/
  */
 
-async function testSteps()
-{
+async function testSteps() {
   const lsArchiveFile = "storage/ls-archive.sqlite";
 
   const principalInfo = {
     url: "http://example.com",
-    attrs: {}
+    attrs: {},
   };
 
-  function checkStorage()
-  {
+  function checkStorage() {
     let principal = getPrincipal(principalInfo.url, principalInfo.attrs);
     let storage = getLocalStorage(principal);
     try {
       storage.open();
       ok(true, "Did not throw");
-    } catch(ex) {
+    } catch (ex) {
       ok(false, "Should not have thrown");
     }
   }
 
   info("Setting pref");
 
   Services.prefs.setBoolPref("dom.storage.next_gen", true);
 
--- a/dom/localstorage/test/unit/test_corruptedDatabase.js
+++ b/dom/localstorage/test/unit/test_corruptedDatabase.js
@@ -1,15 +1,14 @@
 /**
  * Any copyright is dedicated to the Public Domain.
  * http://creativecommons.org/publicdomain/zero/1.0/
  */
 
-async function testSteps()
-{
+async function testSteps() {
   const principal = getPrincipal("http://example.org");
 
   info("Setting pref");
 
   Services.prefs.setBoolPref("dom.storage.next_gen", true);
 
   info("Clearing");
 
--- a/dom/localstorage/test/unit/test_databaseShadowing1.js
+++ b/dom/localstorage/test/unit/test_databaseShadowing1.js
@@ -1,17 +1,17 @@
 /**
  * Any copyright is dedicated to the Public Domain.
  * http://creativecommons.org/publicdomain/zero/1.0/
  */
 
+/* import-globals-from databaseShadowing-shared.js */
 loadSubscript("databaseShadowing-shared.js");
 
-async function testSteps()
-{
+async function testSteps() {
   enableNextGenLocalStorage();
 
   storeData();
 
   verifyData([]);
 
   // Wait for all database connections to close.
   let request = reset();
--- a/dom/localstorage/test/unit/test_databaseShadowing2.js
+++ b/dom/localstorage/test/unit/test_databaseShadowing2.js
@@ -1,17 +1,17 @@
 /**
  * Any copyright is dedicated to the Public Domain.
  * http://creativecommons.org/publicdomain/zero/1.0/
  */
 
+/* import-globals-from databaseShadowing-shared.js */
 loadSubscript("databaseShadowing-shared.js");
 
-async function testSteps()
-{
+async function testSteps() {
   // The shadow database was prepared in test_databaseShadowing1.js
 
   disableNextGenLocalStorage();
 
   if (!importShadowDatabase("shadowdb.sqlite")) {
     return;
   }
 
--- a/dom/localstorage/test/unit/test_databaseShadowing_clearOrigin1.js
+++ b/dom/localstorage/test/unit/test_databaseShadowing_clearOrigin1.js
@@ -1,17 +1,17 @@
 /**
  * Any copyright is dedicated to the Public Domain.
  * http://creativecommons.org/publicdomain/zero/1.0/
  */
 
+/* import-globals-from databaseShadowing-shared.js */
 loadSubscript("databaseShadowing-shared.js");
 
-async function testSteps()
-{
+async function testSteps() {
   enableNextGenLocalStorage();
 
   storeData();
 
   verifyData([]);
 
   let principal = getPrincipal("http://origin.test", {});
   let request = clearOrigin(principal, "default");
--- a/dom/localstorage/test/unit/test_databaseShadowing_clearOrigin2.js
+++ b/dom/localstorage/test/unit/test_databaseShadowing_clearOrigin2.js
@@ -1,17 +1,17 @@
 /**
  * Any copyright is dedicated to the Public Domain.
  * http://creativecommons.org/publicdomain/zero/1.0/
  */
 
+/* import-globals-from databaseShadowing-shared.js */
 loadSubscript("databaseShadowing-shared.js");
 
-async function testSteps()
-{
+async function testSteps() {
   // The shadow database was prepared in test_databaseShadowing_clearOrigin1.js
 
   disableNextGenLocalStorage();
 
   if (!importShadowDatabase("shadowdb-clearedOrigin.sqlite")) {
     return;
   }
 
--- a/dom/localstorage/test/unit/test_databaseShadowing_clearOriginsByPattern1.js
+++ b/dom/localstorage/test/unit/test_databaseShadowing_clearOriginsByPattern1.js
@@ -1,27 +1,27 @@
 /**
  * Any copyright is dedicated to the Public Domain.
  * http://creativecommons.org/publicdomain/zero/1.0/
  */
 
+/* import-globals-from databaseShadowing-shared.js */
 loadSubscript("databaseShadowing-shared.js");
 
-async function testSteps()
-{
+async function testSteps() {
   enableNextGenLocalStorage();
 
   storeData();
 
   verifyData([]);
 
   let request = clearOriginsByPattern(JSON.stringify({ userContextId: 15 }));
   await requestFinished(request);
 
-  verifyData([4,5,6]);
+  verifyData([4, 5, 6]);
 
   // Wait for all database connections to close.
   request = reset();
   await requestFinished(request);
 
   exportShadowDatabase("shadowdb-clearedOriginsByPattern.sqlite");
 
   // The shadow database is now prepared for
--- a/dom/localstorage/test/unit/test_databaseShadowing_clearOriginsByPattern2.js
+++ b/dom/localstorage/test/unit/test_databaseShadowing_clearOriginsByPattern2.js
@@ -1,20 +1,20 @@
 /**
  * Any copyright is dedicated to the Public Domain.
  * http://creativecommons.org/publicdomain/zero/1.0/
  */
 
+/* import-globals-from databaseShadowing-shared.js */
 loadSubscript("databaseShadowing-shared.js");
 
-async function testSteps()
-{
+async function testSteps() {
   // The shadow database was prepared in
   // test_databaseShadowing_clearOriginsByPattern1.js
 
   disableNextGenLocalStorage();
 
   if (!importShadowDatabase("shadowdb-clearedOriginsByPattern.sqlite")) {
     return;
   }
 
-  verifyData([4,5,6]);
+  verifyData([4, 5, 6]);
 }
--- a/dom/localstorage/test/unit/test_databaseShadowing_clearOriginsByPrefix1.js
+++ b/dom/localstorage/test/unit/test_databaseShadowing_clearOriginsByPrefix1.js
@@ -1,17 +1,17 @@
 /**
  * Any copyright is dedicated to the Public Domain.
  * http://creativecommons.org/publicdomain/zero/1.0/
  */
 
+/* import-globals-from databaseShadowing-shared.js */
 loadSubscript("databaseShadowing-shared.js");
 
-async function testSteps()
-{
+async function testSteps() {
   enableNextGenLocalStorage();
 
   storeData();
 
   verifyData([]);
 
   let principal = getPrincipal("http://prefix.test", {});
   let request = clearOriginsByPrefix(principal, "default");
--- a/dom/localstorage/test/unit/test_databaseShadowing_clearOriginsByPrefix2.js
+++ b/dom/localstorage/test/unit/test_databaseShadowing_clearOriginsByPrefix2.js
@@ -1,20 +1,20 @@
 /**
  * Any copyright is dedicated to the Public Domain.
  * http://creativecommons.org/publicdomain/zero/1.0/
  */
 
+/* import-globals-from databaseShadowing-shared.js */
 loadSubscript("databaseShadowing-shared.js");
 
-async function testSteps()
-{
+async function testSteps() {
   // The shadow database was prepared in
   // test_databaseShadowing_clearOriginsByPrefix1.js
 
   disableNextGenLocalStorage();
 
   if (!importShadowDatabase("shadowdb-clearedOriginsByPrefix.sqlite")) {
     return;
   }
 
-  verifyData([2,3]);
+  verifyData([2, 3]);
 }
--- a/dom/localstorage/test/unit/test_eviction.js
+++ b/dom/localstorage/test/unit/test_eviction.js
@@ -1,15 +1,14 @@
 /**
  * Any copyright is dedicated to the Public Domain.
  * http://creativecommons.org/publicdomain/zero/1.0/
  */
 
-async function testSteps()
-{
+async function testSteps() {
   const globalLimitKB = 5 * 1024;
 
   const data = {};
   data.sizeKB = 1 * 1024;
   data.key = "A";
   data.value = repeatChar(data.sizeKB * 1024 - data.key.length, ".");
   data.urlCount = globalLimitKB / data.sizeKB;
 
@@ -44,17 +43,17 @@ async function testSteps()
   }
 
   info("Verifying no more data can be written");
 
   for (let i = 0; i < data.urlCount; i++) {
     try {
       storages[i].setItem("B", "");
       ok(false, "Should have thrown");
-    } catch(ex) {
+    } catch (ex) {
       ok(true, "Did throw");
       ok(ex instanceof DOMException, "Threw DOMException");
       is(ex.name, "QuotaExceededError", "Threw right DOMException");
       is(ex.code, NS_ERROR_DOM_QUOTA_EXCEEDED_ERR, "Threw with right code");
     }
   }
 
   info("Closing first origin");
--- a/dom/localstorage/test/unit/test_groupLimit.js
+++ b/dom/localstorage/test/unit/test_groupLimit.js
@@ -1,26 +1,25 @@
 /**
  * Any copyright is dedicated to the Public Domain.
  * http://creativecommons.org/publicdomain/zero/1.0/
  */
 
-async function testSteps()
-{
+async function testSteps() {
   const groupLimitKB = 10 * 1024;
 
   const globalLimitKB = groupLimitKB * 5;
 
   const originLimit = 10 * 1024;
 
   const urls = [
     "http://example.com",
     "http://test1.example.com",
     "https://test2.example.com",
-    "http://test3.example.com:8080"
+    "http://test3.example.com:8080",
   ];
 
   const data = {};
   data.sizeKB = 5 * 1024;
   data.key = "A";
   data.value = repeatChar(data.sizeKB * 1024 - data.key.length, ".");
   data.urlCount = groupLimitKB / data.sizeKB;
 
@@ -52,17 +51,17 @@ async function testSteps()
   }
 
   info("Verifying no more data can be written");
 
   for (let i = 0; i < urls.length; i++) {
     try {
       storages[i].setItem("B", "");
       ok(false, "Should have thrown");
-    } catch(ex) {
+    } catch (ex) {
       ok(true, "Did throw");
       ok(ex instanceof DOMException, "Threw DOMException");
       is(ex.name, "QuotaExceededError", "Threw right DOMException");
       is(ex.code, NS_ERROR_DOM_QUOTA_EXCEEDED_ERR, "Threw with right code");
     }
   }
 
   info("Clearing first origin");
--- a/dom/localstorage/test/unit/test_migration.js
+++ b/dom/localstorage/test/unit/test_migration.js
@@ -1,15 +1,14 @@
 /**
  * Any copyright is dedicated to the Public Domain.
  * http://creativecommons.org/publicdomain/zero/1.0/
  */
 
-async function testSteps()
-{
+async function testSteps() {
   const principalInfos = [
     { url: "http://localhost", attrs: {} },
     { url: "http://www.mozilla.org", attrs: {} },
     { url: "http://example.com", attrs: {} },
     { url: "http://example.org", attrs: { userContextId: 5 } },
 
     { url: "http://origin.test", attrs: {} },
 
@@ -18,17 +17,17 @@ async function testSteps()
 
     { url: "http://pattern.test", attrs: { userContextId: 15 } },
     { url: "http://pattern.test:8080", attrs: { userContextId: 15 } },
     { url: "https://pattern.test", attrs: { userContextId: 15 } },
   ];
 
   const data = {
     key: "foo",
-    value: "bar"
+    value: "bar",
   };
 
   function verifyData(clearedOrigins) {
     info("Getting storages");
 
     let storages = [];
     for (let i = 0; i < principalInfos.length; i++) {
       let principalInfo = principalInfos[i];
@@ -111,15 +110,15 @@ async function testSteps()
         await requestFinished(request);
 
         clearedOrigins.push(7, 8, 9);
 
         break;
       }
 
       default: {
-        throw("Unknown type: " + type);
+        throw ("Unknown type: " + type);
       }
     }
 
     verifyData(clearedOrigins);
   }
 }
--- a/dom/localstorage/test/unit/test_originInit.js
+++ b/dom/localstorage/test/unit/test_originInit.js
@@ -1,16 +1,15 @@
 /**
  * Any copyright is dedicated to the Public Domain.
  * http://creativecommons.org/publicdomain/zero/1.0/
  */
 
-async function testSteps()
-{
-  const principal = getPrincipal("http://example.com")
+async function testSteps() {
+  const principal = getPrincipal("http://example.com");
 
   const dataFile =
     getRelativeFile("storage/default/http+++example.com/ls/data.sqlite");
 
   const usageJournalFile =
     getRelativeFile("storage/default/http+++example.com/ls/usage-journal");
 
   const usageFile =
@@ -88,17 +87,17 @@ async function testSteps()
     if (wantUsage) {
       ok(exists, "Usage file does exist");
     } else {
       ok(!exists, "Usage file doesn't exist");
       return;
     }
 
     let usage = await readUsageFromUsageFile();
-    ok (usage == data.usage, "Correct usage");
+    ok(usage == data.usage, "Correct usage");
   }
 
   async function clearTestOrigin() {
     let request = clearOrigin(principal, "default");
     await requestFinished(request);
   }
 
   info("Setting prefs");
--- a/dom/localstorage/test/unit/test_snapshotting.js
+++ b/dom/localstorage/test/unit/test_snapshotting.js
@@ -1,32 +1,30 @@
 /**
  * Any copyright is dedicated to the Public Domain.
  * http://creativecommons.org/publicdomain/zero/1.0/
  */
 
-async function testSteps()
-{
+async function testSteps() {
   const url = "http://example.com";
 
   const items = [
     { key: "key1", value: "value1" },
     { key: "key2", value: "value2" },
     { key: "key3", value: "value3" },
     { key: "key4", value: "value4" },
     { key: "key5", value: "value5" },
     { key: "key6", value: "value6" },
     { key: "key7", value: "value7" },
     { key: "key8", value: "value8" },
     { key: "key9", value: "value9" },
-    { key: "key10", value: "value10" }
+    { key: "key10", value: "value10" },
   ];
 
-  function getPartialPrefill()
-  {
+  function getPartialPrefill() {
     let size = 0;
     for (let i = 0; i < items.length / 2; i++) {
       let item = items[i];
       size += item.key.length + item.value.length;
     }
     return size;
   }
 
--- a/gfx/layers/apz/util/APZCCallbackHelper.cpp
+++ b/gfx/layers/apz/util/APZCCallbackHelper.cpp
@@ -632,36 +632,41 @@ static nsIFrame* UpdateRootFrameForTouch
         return frame;
       }
     }
   }
 #endif
   return aRootFrame;
 }
 
+namespace {
+
+using FrameForPointOption = nsLayoutUtils::FrameForPointOption;
+
 // Determine the scrollable target frame for the given point and add it to
 // the target list. If the frame doesn't have a displayport, set one.
 // Return whether or not a displayport was set.
 static bool PrepareForSetTargetAPZCNotification(
     nsIWidget* aWidget, const ScrollableLayerGuid& aGuid, nsIFrame* aRootFrame,
     const LayoutDeviceIntPoint& aRefPoint,
     nsTArray<ScrollableLayerGuid>* aTargets) {
   ScrollableLayerGuid guid(aGuid.mLayersId, 0,
                            ScrollableLayerGuid::NULL_SCROLL_ID);
   nsPoint point = nsLayoutUtils::GetEventCoordinatesRelativeTo(
       aWidget, aRefPoint, aRootFrame);
-  uint32_t flags = 0;
+  EnumSet<FrameForPointOption> options;
   if (gfxPrefs::APZAllowZooming()) {
-    // If zooming is enabled, we need IGNORE_ROOT_SCROLL_FRAME for correct
+    // If zooming is enabled, we need IgnoreRootScrollFrame for correct
     // hit testing. Otherwise, don't use it because it interferes with
     // hit testing for some purposes such as scrollbar dragging (this will
     // need to be fixed before enabling zooming by default on desktop).
-    flags = nsLayoutUtils::IGNORE_ROOT_SCROLL_FRAME;
+    options += FrameForPointOption::IgnoreRootScrollFrame;
   }
-  nsIFrame* target = nsLayoutUtils::GetFrameForPoint(aRootFrame, point, flags);
+  nsIFrame* target =
+      nsLayoutUtils::GetFrameForPoint(aRootFrame, point, options);
   nsIScrollableFrame* scrollAncestor =
       target ? nsLayoutUtils::GetAsyncScrollableAncestorFrame(target)
              : aRootFrame->PresShell()->GetRootScrollFrameAsScrollable();
 
   // Assuming that if there's no scrollAncestor, there's already a displayPort.
   nsCOMPtr<dom::Element> dpElement =
       scrollAncestor ? GetDisplayportElementFor(scrollAncestor)
                      : GetRootDocumentElementFor(aWidget);
@@ -732,16 +737,18 @@ static void SendLayersDependentApzcTarge
   LayerTransactionChild* shadow = lf->GetShadowManager();
   if (!shadow) {
     return;
   }
 
   shadow->SendSetConfirmedTargetAPZC(aInputBlockId, aTargets);
 }
 
+}  // namespace
+
 DisplayportSetListener::DisplayportSetListener(
     nsIWidget* aWidget, nsIPresShell* aPresShell, const uint64_t& aInputBlockId,
     const nsTArray<ScrollableLayerGuid>& aTargets)
     : mWidget(aWidget),
       mPresShell(aPresShell),
       mInputBlockId(aInputBlockId),
       mTargets(aTargets) {}
 
--- a/gfx/layers/apz/util/DoubleTapToZoom.cpp
+++ b/gfx/layers/apz/util/DoubleTapToZoom.cpp
@@ -18,44 +18,54 @@
 #include "nsIFrameInlines.h"
 #include "nsIPresShell.h"
 #include "nsLayoutUtils.h"
 #include "nsStyleConsts.h"
 
 namespace mozilla {
 namespace layers {
 
+namespace {
+
+using FrameForPointOption = nsLayoutUtils::FrameForPointOption;
+
 // Returns the DOM element found at |aPoint|, interpreted as being relative to
 // the root frame of |aShell|. If the point is inside a subdocument, returns
 // an element inside the subdocument, rather than the subdocument element
 // (and does so recursively).
 // The implementation was adapted from nsDocument::ElementFromPoint(), with
 // the notable exception that we don't pass nsLayoutUtils::IGNORE_CROSS_DOC
 // to GetFrameForPoint(), so as to get the behaviour described above in the
 // presence of subdocuments.
 static already_AddRefed<dom::Element> ElementFromPoint(
     const nsCOMPtr<nsIPresShell>& aShell, const CSSPoint& aPoint) {
-  if (nsIFrame* rootFrame = aShell->GetRootFrame()) {
-    if (nsIFrame* frame = nsLayoutUtils::GetFrameForPoint(
-            rootFrame, CSSPoint::ToAppUnits(aPoint),
-            nsLayoutUtils::IGNORE_PAINT_SUPPRESSION |
-                nsLayoutUtils::IGNORE_ROOT_SCROLL_FRAME)) {
-      while (frame && (!frame->GetContent() ||
-                       frame->GetContent()->IsInAnonymousSubtree())) {
-        frame = nsLayoutUtils::GetParentOrPlaceholderFor(frame);
-      }
-      nsIContent* content = frame->GetContent();
-      if (content && !content->IsElement()) {
-        content = content->GetParent();
-      }
-      if (content) {
-        nsCOMPtr<dom::Element> result = content->AsElement();
-        return result.forget();
-      }
-    }
+  nsIFrame* rootFrame = aShell->GetRootFrame();
+  if (!rootFrame) {
+    return nullptr;
+  }
+  nsIFrame* frame = nsLayoutUtils::GetFrameForPoint(
+      rootFrame, CSSPoint::ToAppUnits(aPoint),
+      {FrameForPointOption::IgnorePaintSuppression,
+       FrameForPointOption::IgnoreRootScrollFrame});
+  while (frame && (!frame->GetContent() ||
+                   frame->GetContent()->IsInAnonymousSubtree())) {
+    frame = nsLayoutUtils::GetParentOrPlaceholderFor(frame);
+  }
+  if (!frame) {
+    return nullptr;
+  }
+  // FIXME(emilio): This should probably use the flattened tree, GetParent() is
+  // not guaranteed to be an element in presence of shadow DOM.
+  nsIContent* content = frame->GetContent();
+  if (content && !content->IsElement()) {
+    content = content->GetParent();
+  }
+  if (content && content->IsElement()) {
+    nsCOMPtr<dom::Element> result = content->AsElement();
+    return result.forget();
   }
   return nullptr;
 }
 
 static bool ShouldZoomToElement(const nsCOMPtr<dom::Element>& aElement) {
   if (nsIFrame* frame = aElement->GetPrimaryFrame()) {
     if (frame->GetDisplay() == StyleDisplay::Inline) {
       return false;
@@ -79,16 +89,18 @@ static bool IsRectZoomedIn(const CSSRect
       aRect.Height());
   float showing = overlapArea / (aRect.Width() * availHeight);
   float ratioW = aRect.Width() / aCompositedArea.Width();
   float ratioH = aRect.Height() / aCompositedArea.Height();
 
   return showing > 0.9 && (ratioW > 0.9 || ratioH > 0.9);
 }
 
+}  // namespace
+
 CSSRect CalculateRectToZoomTo(const nsCOMPtr<nsIDocument>& aRootContentDocument,
                               const CSSPoint& aPoint) {
   // Ensure the layout information we get is up-to-date.
   aRootContentDocument->FlushPendingNotifications(FlushType::Layout);
 
   // An empty rect as return value is interpreted as "zoom out".
   const CSSRect zoomOut;
 
--- a/gfx/layers/apz/util/TouchActionHelper.cpp
+++ b/gfx/layers/apz/util/TouchActionHelper.cpp
@@ -52,17 +52,18 @@ TouchBehaviorFlags TouchActionHelper::Ge
                                 AllowedTouchBehavior::HORIZONTAL_PAN |
                                 AllowedTouchBehavior::PINCH_ZOOM |
                                 AllowedTouchBehavior::DOUBLE_TAP_ZOOM;
 
   nsPoint relativePoint =
       nsLayoutUtils::GetEventCoordinatesRelativeTo(aWidget, aPoint, aRootFrame);
 
   nsIFrame* target = nsLayoutUtils::GetFrameForPoint(
-      aRootFrame, relativePoint, nsLayoutUtils::IGNORE_ROOT_SCROLL_FRAME);
+      aRootFrame, relativePoint,
+      nsLayoutUtils::FrameForPointOption::IgnoreRootScrollFrame);
   if (!target) {
     return behavior;
   }
   nsIScrollableFrame* nearestScrollableParent =
       nsLayoutUtils::GetNearestScrollableFrame(target, 0);
   nsIFrame* nearestScrollableFrame = do_QueryFrame(nearestScrollableParent);
 
   // We're walking up the DOM tree until we meet the element with touch behavior
--- a/js/xpconnect/idl/nsIXPConnect.idl
+++ b/js/xpconnect/idl/nsIXPConnect.idl
@@ -66,17 +66,21 @@ public:
 
 [builtinclass, uuid(3a01b0d6-074b-49ed-bac3-08c76366cae4)]
 interface nsIXPConnectWrappedJS : nsIXPConnectJSObjectHolder
 {
     /* attribute 'JSObject' inherited from nsIXPConnectJSObjectHolder */
     readonly attribute InterfaceInfoPtr InterfaceInfo;
     readonly attribute nsIIDPtr         InterfaceIID;
 
-    // Match the GetJSObject() signature.
+    // Returns the global object for our JS object. If this object is a
+    // cross-compartment wrapper, returns the compartment's first global.
+    // The global we return is guaranteed to be same-compartment with the
+    // object.
+    // Note: this matches the GetJSObject() signature.
     [notxpcom, nostdcall] JSObjectPtr GetJSObjectGlobal();
 
     void debugDump(in short depth);
 
     void aggregatedQueryInterface(in nsIIDRef uuid,
                                   [iid_is(uuid),retval] out nsQIResult result);
 
 };
--- a/js/xpconnect/src/Sandbox.cpp
+++ b/js/xpconnect/src/Sandbox.cpp
@@ -510,16 +510,23 @@ class SandboxProxyHandler : public js::W
       JS::MutableHandle<JS::PropertyDescriptor> desc) const override;
   virtual bool hasOwn(JSContext* cx, JS::Handle<JSObject*> proxy,
                       JS::Handle<jsid> id, bool* bp) const override;
   virtual bool getOwnEnumerablePropertyKeys(
       JSContext* cx, JS::Handle<JSObject*> proxy,
       JS::AutoIdVector& props) const override;
   virtual JSObject* enumerate(JSContext* cx,
                               JS::Handle<JSObject*> proxy) const override;
+
+ private:
+  // Implements the custom getPropertyDescriptor behavior. If the getOwn
+  // argument is true we only look for "own" properties.
+  bool getPropertyDescriptorImpl(
+      JSContext* cx, JS::Handle<JSObject*> proxy, JS::Handle<jsid> id,
+      bool getOwn, JS::MutableHandle<JS::PropertyDescriptor> desc) const;
 };
 
 static const SandboxProxyHandler sandboxProxyHandler;
 
 namespace xpc {
 
 bool IsSandboxPrototypeProxy(JSObject* obj) {
   return js::IsProxy(obj) && js::GetProxyHandler(obj) == &sandboxProxyHandler;
@@ -663,24 +670,31 @@ static bool IsMaybeWrappedDOMConstructor
   obj = js::CheckedUnwrap(obj);
   if (!obj) {
     return false;
   }
 
   return dom::IsDOMConstructor(obj);
 }
 
-bool SandboxProxyHandler::getPropertyDescriptor(
+bool SandboxProxyHandler::getPropertyDescriptorImpl(
     JSContext* cx, JS::Handle<JSObject*> proxy, JS::Handle<jsid> id,
-    JS::MutableHandle<PropertyDescriptor> desc) const {
+    bool getOwn, JS::MutableHandle<PropertyDescriptor> desc) const {
   JS::RootedObject obj(cx, wrappedObject(proxy));
 
   MOZ_ASSERT(js::GetObjectCompartment(obj) == js::GetObjectCompartment(proxy));
-  if (!JS_GetPropertyDescriptorById(cx, obj, id, desc)) {
-    return false;
+
+  if (getOwn) {
+    if (!JS_GetOwnPropertyDescriptorById(cx, obj, id, desc)) {
+      return false;
+    }
+  } else {
+    if (!JS_GetPropertyDescriptorById(cx, obj, id, desc)) {
+      return false;
+    }
   }
 
   if (!desc.object()) {
     return true;  // No property, nothing to do
   }
 
   // Now fix up the getter/setter/value as needed to be bound to desc->obj.
   if (!WrapAccessorFunction(cx, desc.getter(), desc.address(), JSPROP_GETTER,
@@ -702,28 +716,26 @@ bool SandboxProxyHandler::getPropertyDes
       }
       desc.value().setObject(*val);
     }
   }
 
   return true;
 }
 
+bool SandboxProxyHandler::getPropertyDescriptor(
+    JSContext* cx, JS::Handle<JSObject*> proxy, JS::Handle<jsid> id,
+    JS::MutableHandle<PropertyDescriptor> desc) const {
+  return getPropertyDescriptorImpl(cx, proxy, id, /* getOwn = */ false, desc);
+}
+
 bool SandboxProxyHandler::getOwnPropertyDescriptor(
     JSContext* cx, JS::Handle<JSObject*> proxy, JS::Handle<jsid> id,
     JS::MutableHandle<PropertyDescriptor> desc) const {
-  if (!getPropertyDescriptor(cx, proxy, id, desc)) {
-    return false;
-  }
-
-  if (desc.object() != wrappedObject(proxy)) {
-    desc.object().set(nullptr);
-  }
-
-  return true;
+  return getPropertyDescriptorImpl(cx, proxy, id, /* getOwn = */ true, desc);
 }
 
 /*
  * Reuse the BaseProxyHandler versions of the derived traps that are implemented
  * in terms of the fundamental traps.
  */
 
 bool SandboxProxyHandler::has(JSContext* cx, JS::Handle<JSObject*> proxy,
--- a/js/xpconnect/src/XPCWrappedJS.cpp
+++ b/js/xpconnect/src/XPCWrappedJS.cpp
@@ -63,20 +63,16 @@ bool nsXPCWrappedJS::CanSkip() {
     return true;
   }
 
   // If this wrapper holds a gray object, need to trace it.
   JSObject* obj = GetJSObjectPreserveColor();
   if (obj && JS::ObjectIsMarkedGray(obj)) {
     return false;
   }
-  JSObject* global = GetJSObjectGlobalPreserveColor();
-  if (global && JS::ObjectIsMarkedGray(global)) {
-    return false;
-  }
 
   // For non-root wrappers, check if the root wrapper will be
   // added to the CC graph.
   if (!IsRootWrapper()) {
     // mRoot points to null after unlinking.
     NS_ENSURE_TRUE(mRoot, false);
     return mRoot->CanSkip();
   }
@@ -128,18 +124,16 @@ NS_CYCLE_COLLECTION_CLASSNAME(nsXPCWrapp
   // that is not subject to finalization alive.
   NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, "self");
   cb.NoteXPCOMChild(s);
 
   if (tmp->IsValid()) {
     MOZ_ASSERT(refcnt > 1);
     NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, "mJSObj");
     cb.NoteJSChild(JS::GCCellPtr(tmp->GetJSObjectPreserveColor()));
-    NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, "mJSObjGlobal");
-    cb.NoteJSChild(JS::GCCellPtr(tmp->GetJSObjectGlobalPreserveColor()));
   }
 
   if (tmp->IsRootWrapper()) {
     NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, "aggregated native");
     cb.NoteXPCOMChild(tmp->GetAggregatedNativeObject());
   } else {
     NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, "root");
     cb.NoteXPCOMChild(ToSupports(tmp->GetRootWrapper()));
@@ -302,31 +296,37 @@ MozExternalRefCountType nsXPCWrappedJS::
 }
 
 NS_IMETHODIMP_(void)
 nsXPCWrappedJS::DeleteCycleCollectable(void) { delete this; }
 
 void nsXPCWrappedJS::TraceJS(JSTracer* trc) {
   MOZ_ASSERT(mRefCnt >= 2 && IsValid(), "must be strongly referenced");
   JS::TraceEdge(trc, &mJSObj, "nsXPCWrappedJS::mJSObj");
-  JS::TraceEdge(trc, &mJSObjGlobal, "nsXPCWrappedJS::mJSObjGlobal");
 }
 
 NS_IMETHODIMP
 nsXPCWrappedJS::GetWeakReference(nsIWeakReference** aInstancePtr) {
   if (!IsRootWrapper()) {
     return mRoot->GetWeakReference(aInstancePtr);
   }
 
   return nsSupportsWeakReference::GetWeakReference(aInstancePtr);
 }
 
 JSObject* nsXPCWrappedJS::GetJSObject() { return mJSObj; }
 
-JSObject* nsXPCWrappedJS::GetJSObjectGlobal() { return mJSObjGlobal; }
+JSObject* nsXPCWrappedJS::GetJSObjectGlobal() {
+  JSObject* obj = mJSObj;
+  if (js::IsCrossCompartmentWrapper(obj)) {
+    JS::Compartment* comp = js::GetObjectCompartment(obj);
+    return js::GetFirstGlobalInCompartment(comp);
+  }
+  return JS::GetNonCCWObjectGlobal(obj);
+}
 
 // static
 nsresult nsXPCWrappedJS::GetNewOrUsed(JSContext* cx, JS::HandleObject jsObj,
                                       REFNSIID aIID,
                                       nsXPCWrappedJS** wrapperResult) {
   // Do a release-mode assert against accessing nsXPCWrappedJS off-main-thread.
   MOZ_RELEASE_ASSERT(NS_IsMainThread(),
                      "nsXPCWrappedJS::GetNewOrUsed called off main thread");
@@ -371,48 +371,38 @@ nsresult nsXPCWrappedJS::GetNewOrUsed(JS
     // root wrapper, and the wrapper we are trying to make isn't
     // a root.
     RefPtr<nsXPCWrappedJSClass> rootClasp =
         nsXPCWrappedJSClass::GetNewOrUsed(cx, NS_GET_IID(nsISupports));
     if (!rootClasp) {
       return NS_ERROR_FAILURE;
     }
 
-    // Note: rootJSObj is never a CCW because GetRootJSObject unwraps. We
-    // also rely on this in nsXPCWrappedJS::UpdateObjectPointerAfterGC.
-    RootedObject global(cx, JS::GetNonCCWObjectGlobal(rootJSObj));
-    root = new nsXPCWrappedJS(cx, rootJSObj, global, rootClasp, nullptr, &rv);
+    root = new nsXPCWrappedJS(cx, rootJSObj, rootClasp, nullptr, &rv);
     if (NS_FAILED(rv)) {
       return rv;
     }
   }
 
-  RootedObject global(cx, JS::CurrentGlobalOrNull(cx));
   RefPtr<nsXPCWrappedJS> wrapper =
-      new nsXPCWrappedJS(cx, jsObj, global, clasp, root, &rv);
+      new nsXPCWrappedJS(cx, jsObj, clasp, root, &rv);
   if (NS_FAILED(rv)) {
     return rv;
   }
   wrapper.forget(wrapperResult);
   return NS_OK;
 }
 
 nsXPCWrappedJS::nsXPCWrappedJS(JSContext* cx, JSObject* aJSObj,
-                               JSObject* aJSObjGlobal,
                                nsXPCWrappedJSClass* aClass,
                                nsXPCWrappedJS* root, nsresult* rv)
     : mJSObj(aJSObj),
-      mJSObjGlobal(aJSObjGlobal),
       mClass(aClass),
       mRoot(root ? root : this),
       mNext(nullptr) {
-  MOZ_ASSERT(JS_IsGlobalObject(aJSObjGlobal));
-  MOZ_RELEASE_ASSERT(js::GetObjectCompartment(aJSObj) ==
-                     js::GetObjectCompartment(aJSObjGlobal));
-
   *rv = InitStub(GetClass()->GetIID());
   // Continue even in the failure case, so that our refcounting/Destroy
   // behavior works correctly.
 
   // There is an extra AddRef to support weak references to wrappers
   // that are subject to finalization. See the top of the file for more
   // details.
   NS_ADDREF_THIS();
@@ -513,17 +503,16 @@ void nsXPCWrappedJS::Unlink() {
       }
 
       if (mRefCnt > 1) {
         RemoveFromRootSet();
       }
     }
 
     mJSObj = nullptr;
-    mJSObjGlobal = nullptr;
   }
 
   if (IsRootWrapper()) {
     ClearWeakReferences();
   } else if (mRoot) {
     // unlink this wrapper
     nsXPCWrappedJS* cur = mRoot;
     while (1) {
@@ -642,17 +631,16 @@ void nsXPCWrappedJS::SystemIsBeingShutDo
   // work (and avoid crashing some platforms).
 
   // Clear the contents of the pointer using unsafeGet() to avoid
   // triggering post barriers in shutdown, as this will access the chunk
   // containing mJSObj, which may have been freed at this point. This is safe
   // if we are not currently running an incremental GC.
   MOZ_ASSERT(!IsIncrementalGCInProgress(xpc_GetSafeJSContext()));
   *mJSObj.unsafeGet() = nullptr;
-  *mJSObjGlobal.unsafeGet() = nullptr;
 
   // Notify other wrappers in the chain.
   if (mNext) {
     mNext->SystemIsBeingShutDown();
   }
 }
 
 size_t nsXPCWrappedJS::SizeOfIncludingThis(
--- a/js/xpconnect/src/xpcprivate.h
+++ b/js/xpconnect/src/xpcprivate.h
@@ -1746,20 +1746,16 @@ class nsXPCWrappedJS final : protected n
    * object returned is not guaranteed to be kept alive past the next CC.
    *
    * This should only be called if you are certain that the return value won't
    * be passed into a JS API function and that it won't be stored without
    * being rooted (or otherwise signaling the stored value to the CC).
    */
   JSObject* GetJSObjectPreserveColor() const { return mJSObj.unbarrieredGet(); }
 
-  JSObject* GetJSObjectGlobalPreserveColor() const {
-    return mJSObjGlobal.unbarrieredGet();
-  }
-
   // Returns true if the wrapper chain contains references to multiple
   // compartments. If the wrapper chain contains references to multiple
   // compartments, then it must be registered on the XPCJSContext. Otherwise,
   // it should be registered in the CompartmentPrivate for the compartment of
   // the root's JS object. This will only return correct results when called
   // on the root wrapper and will assert if not called on a root wrapper.
   bool IsMultiCompartment() const;
 
@@ -1785,21 +1781,16 @@ class nsXPCWrappedJS final : protected n
   // These two methods are used by JSObject2WrappedJSMap::FindDyingJSObjects
   // to find non-rooting wrappers for dying JS objects. See the top of
   // XPCWrappedJS.cpp for more details.
   bool IsSubjectToFinalization() const { return IsValid() && mRefCnt == 1; }
 
   void UpdateObjectPointerAfterGC() {
     MOZ_ASSERT(IsRootWrapper());
     JS_UpdateWeakPointerAfterGC(&mJSObj);
-    JS_UpdateWeakPointerAfterGC(&mJSObjGlobal);
-    // Note: this is a root wrapper, so mJSObj is never a CCW. Therefore,
-    // if mJSObj is still alive, mJSObjGlobal must also still be alive,
-    // because marking a JSObject will also mark its global.
-    MOZ_ASSERT_IF(mJSObj, mJSObjGlobal);
   }
 
   bool IsAggregatedToNative() const { return mRoot->mOuter != nullptr; }
   nsISupports* GetAggregatedNativeObject() const { return mRoot->mOuter; }
   void SetAggregatedNativeObject(nsISupports* aNative) {
     MOZ_ASSERT(aNative);
     if (mRoot->mOuter) {
       MOZ_ASSERT(mRoot->mOuter == aNative,
@@ -1812,37 +1803,29 @@ class nsXPCWrappedJS final : protected n
   void TraceJS(JSTracer* trc);
 
   size_t SizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf) const;
 
   virtual ~nsXPCWrappedJS();
 
  protected:
   nsXPCWrappedJS() = delete;
-  nsXPCWrappedJS(JSContext* cx, JSObject* aJSObj, JSObject* aJSObjGlobal,
-                 nsXPCWrappedJSClass* aClass, nsXPCWrappedJS* root,
-                 nsresult* rv);
+  nsXPCWrappedJS(JSContext* cx, JSObject* aJSObj, nsXPCWrappedJSClass* aClass,
+                 nsXPCWrappedJS* root, nsresult* rv);
 
   bool CanSkip();
   void Destroy();
   void Unlink();
 
  private:
   JS::Compartment* Compartment() const {
     return js::GetObjectCompartment(mJSObj.unbarrieredGet());
   }
 
   JS::Heap<JSObject*> mJSObj;
-  // A global object that must be same-compartment with mJSObj. This is the
-  // global/realm we enter when making calls into JS. Note that we cannot
-  // simply use mJSObj's global here because mJSObj might be a
-  // cross-compartment wrapper and CCWs are not associated with a single
-  // global. After removing in-content XBL, we no longer need this field
-  // because we can then assert against CCWs. See bug 1480121.
-  JS::Heap<JSObject*> mJSObjGlobal;
   RefPtr<nsXPCWrappedJSClass> mClass;
   nsXPCWrappedJS* mRoot;  // If mRoot != this, it is an owning pointer.
   nsXPCWrappedJS* mNext;
   nsCOMPtr<nsISupports> mOuter;  // only set in root
 };
 
 /***************************************************************************
 ****************************************************************************
--- a/layout/base/AccessibleCaretManager.cpp
+++ b/layout/base/AccessibleCaretManager.cpp
@@ -491,26 +491,30 @@ nsresult AccessibleCaretManager::SelectW
   }
 
   nsIFrame* rootFrame = mPresShell->GetRootFrame();
   if (!rootFrame) {
     return NS_ERROR_NOT_AVAILABLE;
   }
 
   // Find the frame under point.
-  uint32_t flags =
-      nsLayoutUtils::IGNORE_PAINT_SUPPRESSION | nsLayoutUtils::IGNORE_CROSS_DOC;
+  EnumSet<nsLayoutUtils::FrameForPointOption> options = {
+      nsLayoutUtils::FrameForPointOption::IgnorePaintSuppression,
+      nsLayoutUtils::FrameForPointOption::IgnoreCrossDoc};
 #ifdef MOZ_WIDGET_ANDROID
-  // On Android, we need IGNORE_ROOT_SCROLL_FRAME for correct hit testing
-  // when zoomed in or out.
-  flags = nsLayoutUtils::IGNORE_ROOT_SCROLL_FRAME;
+  // On Android, we need IgnoreRootScrollFrame for correct hit testing when
+  // zoomed in or out.
+  //
+  // FIXME(emilio): But do we really want to override the other two flags?
+  options = nsLayoutUtils::FrameForPointOption::IgnoreRootScrollFrame;
 #endif
+
   AutoWeakFrame ptFrame =
-      nsLayoutUtils::GetFrameForPoint(rootFrame, aPoint, flags);
-  if (!ptFrame.IsAlive()) {
+      nsLayoutUtils::GetFrameForPoint(rootFrame, aPoint, options);
+  if (!ptFrame.GetFrame()) {
     return NS_ERROR_FAILURE;
   }
 
   nsIFrame* focusableFrame = GetFocusableFrame(ptFrame);
 
 #ifdef DEBUG_FRAME_DUMP
   AC_LOG("%s: Found %s under (%d, %d)", __FUNCTION__, ptFrame->ListTag().get(),
          aPoint.x, aPoint.y);
@@ -1081,20 +1085,20 @@ nsresult AccessibleCaretManager::DragCar
 
   nsIFrame* rootFrame = mPresShell->GetRootFrame();
   MOZ_ASSERT(rootFrame, "We need root frame to compute caret dragging!");
 
   nsPoint point = AdjustDragBoundary(
       nsPoint(aPoint.x, aPoint.y + mOffsetYToCaretLogicalPosition));
 
   // Find out which content we point to
-  nsIFrame* ptFrame =
-      nsLayoutUtils::GetFrameForPoint(rootFrame, point,
-                                      nsLayoutUtils::IGNORE_PAINT_SUPPRESSION |
-                                          nsLayoutUtils::IGNORE_CROSS_DOC);
+  nsIFrame* ptFrame = nsLayoutUtils::GetFrameForPoint(
+      rootFrame, point,
+      {nsLayoutUtils::FrameForPointOption::IgnorePaintSuppression,
+       nsLayoutUtils::FrameForPointOption::IgnoreCrossDoc});
   if (!ptFrame) {
     return NS_ERROR_FAILURE;
   }
 
   RefPtr<nsFrameSelection> fs = GetFrameSelection();
   MOZ_ASSERT(fs);
 
   nsresult result;
--- a/layout/base/PositionedEventTargeting.cpp
+++ b/layout/base/PositionedEventTargeting.cpp
@@ -534,21 +534,23 @@ static bool IsElementClickableAndReadabl
   }
 
   return true;
 }
 
 nsIFrame* FindFrameTargetedByInputEvent(
     WidgetGUIEvent* aEvent, nsIFrame* aRootFrame,
     const nsPoint& aPointRelativeToRootFrame, uint32_t aFlags) {
-  uint32_t flags = (aFlags & INPUT_IGNORE_ROOT_SCROLL_FRAME)
-                       ? nsLayoutUtils::IGNORE_ROOT_SCROLL_FRAME
-                       : 0;
+  using FrameForPointOption = nsLayoutUtils::FrameForPointOption;
+  EnumSet<FrameForPointOption> options;
+  if (aFlags & INPUT_IGNORE_ROOT_SCROLL_FRAME) {
+    options += FrameForPointOption::IgnoreRootScrollFrame;
+  }
   nsIFrame* target = nsLayoutUtils::GetFrameForPoint(
-      aRootFrame, aPointRelativeToRootFrame, flags);
+      aRootFrame, aPointRelativeToRootFrame, options);
   PET_LOG(
       "Found initial target %p for event class %s point %s relative to root "
       "frame %p\n",
       target,
       (aEvent->mClass == eMouseEventClass
            ? "mouse"
            : (aEvent->mClass == eTouchEventClass ? "touch" : "other")),
       mozilla::layers::Stringify(aPointRelativeToRootFrame).c_str(),
@@ -593,17 +595,17 @@ nsIFrame* FindFrameTargetedByInputEvent(
       target ? target->PresShell()->GetRootFrame() : aRootFrame;
 
   nsRect targetRect = GetTargetRect(aRootFrame, aPointRelativeToRootFrame,
                                     restrictToDescendants, prefs, aFlags);
   PET_LOG("Expanded point to target rect %s\n",
           mozilla::layers::Stringify(targetRect).c_str());
   AutoTArray<nsIFrame*, 8> candidates;
   nsresult rv = nsLayoutUtils::GetFramesForArea(aRootFrame, targetRect,
-                                                candidates, flags);
+                                                candidates, options);
   if (NS_FAILED(rv)) {
     return target;
   }
 
   int32_t elementsInCluster = 0;
 
   nsIFrame* closestClickable = GetClosest(
       aRootFrame, aPointRelativeToRootFrame, targetRect, prefs,
--- a/layout/base/nsLayoutUtils.cpp
+++ b/layout/base/nsLayoutUtils.cpp
@@ -2956,52 +2956,52 @@ StaticAutoPtr<nsTArray<int>> gPaintCount
 
 struct AutoNestedPaintCount {
   AutoNestedPaintCount() { gPaintCountStack->AppendElement(0); }
   ~AutoNestedPaintCount() { gPaintCountStack->RemoveLastElement(); }
 };
 
 #endif
 
-nsIFrame* nsLayoutUtils::GetFrameForPoint(nsIFrame* aFrame, nsPoint aPt,
-                                          uint32_t aFlags) {
+nsIFrame* nsLayoutUtils::GetFrameForPoint(
+    nsIFrame* aFrame, nsPoint aPt, EnumSet<FrameForPointOption> aOptions) {
   AUTO_PROFILER_LABEL("nsLayoutUtils::GetFrameForPoint", LAYOUT);
 
   nsresult rv;
   AutoTArray<nsIFrame*, 8> outFrames;
-  rv = GetFramesForArea(aFrame, nsRect(aPt, nsSize(1, 1)), outFrames, aFlags);
+  rv = GetFramesForArea(aFrame, nsRect(aPt, nsSize(1, 1)), outFrames, aOptions);
   NS_ENSURE_SUCCESS(rv, nullptr);
   return outFrames.Length() ? outFrames.ElementAt(0) : nullptr;
 }
 
-nsresult nsLayoutUtils::GetFramesForArea(nsIFrame* aFrame, const nsRect& aRect,
-                                         nsTArray<nsIFrame*>& aOutFrames,
-                                         uint32_t aFlags) {
+nsresult nsLayoutUtils::GetFramesForArea(
+    nsIFrame* aFrame, const nsRect& aRect, nsTArray<nsIFrame*>& aOutFrames,
+    EnumSet<FrameForPointOption> aOptions) {
   AUTO_PROFILER_LABEL("nsLayoutUtils::GetFramesForArea", LAYOUT);
 
   nsDisplayListBuilder builder(aFrame, nsDisplayListBuilderMode::EVENT_DELIVERY,
                                false);
   builder.BeginFrame();
   nsDisplayList list;
 
-  if (aFlags & IGNORE_PAINT_SUPPRESSION) {
+  if (aOptions.contains(FrameForPointOption::IgnorePaintSuppression)) {
     builder.IgnorePaintSuppression();
   }
-
-  if (aFlags & IGNORE_ROOT_SCROLL_FRAME) {
+  if (aOptions.contains(FrameForPointOption::IgnoreRootScrollFrame)) {
     nsIFrame* rootScrollFrame = aFrame->PresShell()->GetRootScrollFrame();
     if (rootScrollFrame) {
       builder.SetIgnoreScrollFrame(rootScrollFrame);
     }
   }
-  if (aFlags & IGNORE_CROSS_DOC) {
+  if (aOptions.contains(FrameForPointOption::IgnoreCrossDoc)) {
     builder.SetDescendIntoSubdocuments(false);
   }
 
-  builder.SetHitTestIsForVisibility(aFlags & ONLY_VISIBLE);
+  builder.SetHitTestIsForVisibility(
+      aOptions.contains(FrameForPointOption::OnlyVisible));
 
   builder.EnterPresShell(aFrame);
 
   builder.SetVisibleRect(aRect);
   builder.SetDirtyRect(aRect);
 
   aFrame->BuildDisplayListForStackingContext(&builder, &list);
   builder.LeavePresShell(aFrame, nullptr);
--- a/layout/base/nsLayoutUtils.h
+++ b/layout/base/nsLayoutUtils.h
@@ -796,58 +796,58 @@ class nsLayoutUtils {
    */
   static mozilla::LayoutDeviceIntPoint TranslateViewToWidget(
       nsPresContext* aPresContext, nsView* aView, nsPoint aPt,
       nsIWidget* aWidget);
 
   static mozilla::LayoutDeviceIntPoint WidgetToWidgetOffset(
       nsIWidget* aFromWidget, nsIWidget* aToWidget);
 
-  enum FrameForPointFlags {
+  enum class FrameForPointOption {
     /**
      * When set, paint suppression is ignored, so we'll return non-root page
      * elements even if paint suppression is stopping them from painting.
      */
-    IGNORE_PAINT_SUPPRESSION = 0x01,
+    IgnorePaintSuppression = 1,
     /**
      * When set, clipping due to the root scroll frame (and any other viewport-
      * related clipping) is ignored.
      */
-    IGNORE_ROOT_SCROLL_FRAME = 0x02,
+    IgnoreRootScrollFrame,
     /**
      * When set, return only content in the same document as aFrame.
      */
-    IGNORE_CROSS_DOC = 0x04,
+    IgnoreCrossDoc,
     /**
      * When set, return only content that is actually visible.
      */
-    ONLY_VISIBLE = 0x08
+    OnlyVisible,
   };
 
   /**
    * Given aFrame, the root frame of a stacking context, find its descendant
    * frame under the point aPt that receives a mouse event at that location,
    * or nullptr if there is no such frame.
    * @param aPt the point, relative to the frame origin
-   * @param aFlags some combination of FrameForPointFlags
+   * @param aFlags some combination of FrameForPointOption.
    */
   static nsIFrame* GetFrameForPoint(nsIFrame* aFrame, nsPoint aPt,
-                                    uint32_t aFlags = 0);
+                                    mozilla::EnumSet<FrameForPointOption> = {});
 
   /**
    * Given aFrame, the root frame of a stacking context, find all descendant
    * frames under the area of a rectangle that receives a mouse event,
    * or nullptr if there is no such frame.
    * @param aRect the rect, relative to the frame origin
    * @param aOutFrames an array to add all the frames found
-   * @param aFlags some combination of FrameForPointFlags
+   * @param aFlags some combination of FrameForPointOption.
    */
   static nsresult GetFramesForArea(nsIFrame* aFrame, const nsRect& aRect,
                                    nsTArray<nsIFrame*>& aOutFrames,
-                                   uint32_t aFlags = 0);
+                                   mozilla::EnumSet<FrameForPointOption> = {});
 
   /**
    * Transform aRect relative to aFrame up to the coordinate system of
    * aAncestor. Computes the bounding-box of the true quadrilateral.
    * Pass non-null aPreservesAxisAlignedRectangles and it will be set to true if
    * we only need to use a 2d transform that PreservesAxisAlignedRectangles().
    *
    * |aMatrixCache| allows for optimizations in recomputing the same matrix over
--- a/mobile/android/base/java/org/mozilla/gecko/BrowserApp.java
+++ b/mobile/android/base/java/org/mozilla/gecko/BrowserApp.java
@@ -209,18 +209,16 @@ public class BrowserApp extends GeckoApp
 
     // TODO: Replace with kinto endpoint.
     private static final String SWITCHBOARD_SERVER = "https://firefox.settings.services.mozilla.com/v1/buckets/fennec/collections/experiments/records";
 
     private static final String STATE_ABOUT_HOME_TOP_PADDING = "abouthome_top_padding";
 
     private static final String BROWSER_SEARCH_TAG = "browser_search";
 
-    private static final int MAX_BUNDLE_SIZE = 300000; // 300 kilobytes
-
     // Request ID for startActivityForResult.
     public static final int ACTIVITY_REQUEST_PREFERENCES = 1001;
     private static final int ACTIVITY_REQUEST_TAB_QUEUE = 2001;
     public static final int ACTIVITY_REQUEST_FIRST_READERVIEW_BOOKMARK = 3001;
     public static final int ACTIVITY_RESULT_FIRST_READERVIEW_BOOKMARKS_GOTO_BOOKMARKS = 3002;
     public static final int ACTIVITY_RESULT_FIRST_READERVIEW_BOOKMARKS_IGNORE = 3003;
     public static final int ACTIVITY_REQUEST_TRIPLE_READERVIEW = 4001;
     public static final int ACTIVITY_RESULT_TRIPLE_READERVIEW_ADD_BOOKMARK = 4002;
@@ -2294,17 +2292,17 @@ public class BrowserApp extends GeckoApp
         mDynamicToolbar.onSaveInstanceState(outState);
         outState.putInt(STATE_ABOUT_HOME_TOP_PADDING, mHomeScreenContainer.getPaddingTop());
 
         // Under key “android:viewHierarchyState” the OS stores another object of type Bundle.
         // This bundle stores the state of the view which is in a SparseArray that can grow pretty big.
         // This in some cases can lead to TransactionTooLargeException as per
         // [https://developer.android.com/reference/android/os/TransactionTooLargeException] it's
         // specified that the limit is fixed to 1MB per process.
-        if (getBundleSizeInBytes(outState) > MAX_BUNDLE_SIZE) {
+        if (getBundleSizeInBytes(outState) > MAX_BUNDLE_SIZE_BYTES) {
             outState.remove("android:viewHierarchyState");
         }
     }
 
     /**
      * Attempts to switch to an open tab with the given URL.
      * <p>
      * If the tab exists, this method cancels any in-progress editing as well as
--- a/mobile/android/base/java/org/mozilla/gecko/GeckoApp.java
+++ b/mobile/android/base/java/org/mozilla/gecko/GeckoApp.java
@@ -104,16 +104,17 @@ import java.util.Map;
 import java.util.Set;
 import java.util.concurrent.TimeUnit;
 
 import static org.mozilla.gecko.Tabs.INTENT_EXTRA_SESSION_UUID;
 import static org.mozilla.gecko.Tabs.INTENT_EXTRA_TAB_ID;
 import static org.mozilla.gecko.Tabs.INVALID_TAB_ID;
 import static org.mozilla.gecko.mma.MmaDelegate.DOWNLOAD_MEDIA_SAVED_IMAGE;
 import static org.mozilla.gecko.mma.MmaDelegate.READER_AVAILABLE;
+import static org.mozilla.gecko.util.JavaUtil.getBundleSizeInBytes;
 
 public abstract class GeckoApp extends GeckoActivity
                                implements AnchoredPopup.OnVisibilityChangeListener,
                                           BundleEventListener,
                                           GeckoMenu.Callback,
                                           GeckoMenu.MenuPresenter,
                                           GeckoScreenOrientation.OrientationChangeListener,
                                           GeckoSession.ContentDelegate,
@@ -151,16 +152,21 @@ public abstract class GeckoApp extends G
      * Originally, this was only used for the telemetry core ping logic. To avoid
      * having to write custom migration logic, we just keep the original pref key.
      * Be aware of {@link org.mozilla.gecko.fxa.EnvironmentUtils#GECKO_PREFS_IS_FIRST_RUN}.
      */
     public static final String PREFS_IS_FIRST_RUN = "telemetry-isFirstRun";
 
     public static final String SAVED_STATE_IN_BACKGROUND   = "inBackground";
     public static final String SAVED_STATE_PRIVATE_SESSION = "privateSession";
+    /**
+     * Speculative value for the maximum size the Activity Bundle can have in the hope to avoid
+     * TransactionTooLarge exceptions.
+     */
+    protected static final int MAX_BUNDLE_SIZE_BYTES = 300_000;
 
     // Delay before running one-time "cleanup" tasks that may be needed
     // after a version upgrade.
     private static final int CLEANUP_DEFERRAL_SECONDS = 15;
 
     // Length of time in ms during which crashes are classified as startup crashes
     // for crash loop detection purposes.
     private static final int STARTUP_PHASE_DURATION_MS = 30 * 1000;
@@ -630,16 +636,21 @@ public abstract class GeckoApp extends G
         }
         synchronized (this) {
             if (GeckoThread.isRunning() && mPrivateBrowsingSessionOutdated) {
                 try {
                     wait(MAX_PRIVATE_TABS_UPDATE_WAIT_MSEC);
                 } catch (final InterruptedException e) { }
             }
             outState.putString(SAVED_STATE_PRIVATE_SESSION, mPrivateBrowsingSession);
+
+            // Make sure we are not bloating the Bundle which can result in TransactionTooLargeException
+            if (getBundleSizeInBytes(outState) > MAX_BUNDLE_SIZE_BYTES) {
+                outState.remove(SAVED_STATE_PRIVATE_SESSION);
+            }
         }
 
         outState.putBoolean(SAVED_STATE_IN_BACKGROUND, isApplicationInBackground());
     }
 
     public void addTab(int flags) { }
 
     public void addTab() { }
--- a/parser/html/javasrc/TreeBuilder.java
+++ b/parser/html/javasrc/TreeBuilder.java
@@ -3354,24 +3354,25 @@ public abstract class TreeBuilder<T> imp
                 if (stack[currentPtr].name != name) {
                     if (currentPtr == 0) {
                         errStrayEndTag(name);
                     } else {
                         errEndTagDidNotMatchCurrentOpenElement(name, stack[currentPtr].popName);
                     }
                 }
                 eltPos = currentPtr;
+                int origPos = currentPtr;
                 for (;;) {
                     if (eltPos == 0) {
                         assert fragment: "We can get this close to the root of the stack in foreign content only in the fragment case.";
                         break endtagloop;
                     }
                     if (stack[eltPos].name == name) {
                         while (currentPtr >= eltPos) {
-                            pop();
+                          popForeign(origPos);
                         }
                         break endtagloop;
                     }
                     if (stack[--eltPos].ns == "http://www.w3.org/1999/xhtml") {
                         break;
                     }
                 }
             }
@@ -5220,16 +5221,27 @@ public abstract class TreeBuilder<T> imp
     private void pop() throws SAXException {
         StackNode<T> node = stack[currentPtr];
         assert debugOnlyClearLastStackSlot();
         currentPtr--;
         elementPopped(node.ns, node.popName, node.node);
         node.release(this);
     }
 
+    private void popForeign(int origPos) throws SAXException {
+      StackNode<T> node = stack[currentPtr];
+      if (origPos != currentPtr) {
+        markMalformedIfScript(node.node);
+      }
+      assert debugOnlyClearLastStackSlot();
+      currentPtr--;
+      elementPopped(node.ns, node.popName, node.node);
+      node.release(this);
+    }
+
     private void silentPop() throws SAXException {
         StackNode<T> node = stack[currentPtr];
         assert debugOnlyClearLastStackSlot();
         currentPtr--;
         node.release(this);
     }
 
     private void popOnEof() throws SAXException {
--- a/parser/html/nsHtml5TreeBuilder.cpp
+++ b/parser/html/nsHtml5TreeBuilder.cpp
@@ -2344,26 +2344,27 @@ void nsHtml5TreeBuilder::endTag(nsHtml5E
         if (!currentPtr) {
           errStrayEndTag(name);
         } else {
           errEndTagDidNotMatchCurrentOpenElement(name,
                                                  stack[currentPtr]->popName);
         }
       }
       eltPos = currentPtr;
+      int32_t origPos = currentPtr;
       for (;;) {
         if (!eltPos) {
           MOZ_ASSERT(fragment,
                      "We can get this close to the root of the stack in "
                      "foreign content only in the fragment case.");
           NS_HTML5_BREAK(endtagloop);
         }
         if (stack[eltPos]->name == name) {
           while (currentPtr >= eltPos) {
-            pop();
+            popForeign(origPos);
           }
           NS_HTML5_BREAK(endtagloop);
         }
         if (stack[--eltPos]->ns == kNameSpaceID_XHTML) {
           break;
         }
       }
     }
@@ -4081,16 +4082,27 @@ void nsHtml5TreeBuilder::popTemplateMode
 void nsHtml5TreeBuilder::pop() {
   nsHtml5StackNode* node = stack[currentPtr];
   MOZ_ASSERT(debugOnlyClearLastStackSlot());
   currentPtr--;
   elementPopped(node->ns, node->popName, node->node);
   node->release(this);
 }
 
+void nsHtml5TreeBuilder::popForeign(int32_t origPos) {
+  nsHtml5StackNode* node = stack[currentPtr];
+  if (origPos != currentPtr) {
+    markMalformedIfScript(node->node);
+  }
+  MOZ_ASSERT(debugOnlyClearLastStackSlot());
+  currentPtr--;
+  elementPopped(node->ns, node->popName, node->node);
+  node->release(this);
+}
+
 void nsHtml5TreeBuilder::silentPop() {
   nsHtml5StackNode* node = stack[currentPtr];
   MOZ_ASSERT(debugOnlyClearLastStackSlot());
   currentPtr--;
   node->release(this);
 }
 
 void nsHtml5TreeBuilder::popOnEof() {
--- a/parser/html/nsHtml5TreeBuilder.h
+++ b/parser/html/nsHtml5TreeBuilder.h
@@ -451,16 +451,17 @@ class nsHtml5TreeBuilder : public nsAHtm
       int32_t ns, nsAtom* name, nsHtml5HtmlAttributes* attributes,
       nsHtml5ContentCreatorFunction creator);
   nsIContentHandle* createAndInsertFosterParentedElement(
       int32_t ns, nsAtom* name, nsHtml5HtmlAttributes* attributes,
       nsIContentHandle* form, nsHtml5ContentCreatorFunction creator);
   bool isInStack(nsHtml5StackNode* node);
   void popTemplateMode();
   void pop();
+  void popForeign(int32_t origPos);
   void silentPop();
   void popOnEof();
   void appendHtmlElementToDocumentAndPush(nsHtml5HtmlAttributes* attributes);
   void appendHtmlElementToDocumentAndPush();
   void appendToCurrentNodeAndPushHeadElement(nsHtml5HtmlAttributes* attributes);
   void appendToCurrentNodeAndPushBodyElement(nsHtml5HtmlAttributes* attributes);
   void appendToCurrentNodeAndPushBodyElement();
   void appendToCurrentNodeAndPushFormElementMayFoster(
--- a/testing/marionette/evaluate.js
+++ b/testing/marionette/evaluate.js
@@ -429,16 +429,17 @@ sandbox.augment = function(sb, adapter) 
  */
 sandbox.create = function(win, principal = null, opts = {}) {
   let p = principal || win;
   opts = Object.assign({
     sameZoneAs: win,
     sandboxPrototype: win,
     wantComponents: true,
     wantXrays: true,
+    wantGlobalProperties: ["ChromeUtils"],
   }, opts);
   return new Cu.Sandbox(p, opts);
 };
 
 /**
  * Creates a mutable sandbox, where changes to the global scope
  * will have lasting side-effects.
  *
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/html/syntax/parsing/unclosed-svg-script.html
@@ -0,0 +1,30 @@
+<!doctype html>
+<meta charset=utf-8>
+<title></title>
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+    var scriptWithEndTagRan = false;
+    var scriptWithoutEndTagRan = false;
+    var scriptWithBogusEndTagInsideRan = false;
+</script>
+<svg>
+    <script>scriptWithEndTagRan = true;</script>
+</svg>
+<svg>
+    <script>scriptWithoutEndTagRan = true;
+</svg>
+<svg>
+    <script>scriptWithBogusEndTagInsideRan = true;</g></script>
+</svg>
+<script>
+    test(function() {
+        assert_true(scriptWithEndTagRan);
+    }, "SVG scripts with end tag should run");
+    test(function() {
+        assert_false(scriptWithoutEndTagRan);
+    }, "SVG scripts without end tag should not run");
+    test(function() {
+        assert_true(scriptWithBogusEndTagInsideRan);
+    }, "SVG scripts with bogus end tag inside should run");
+</script>
--- a/toolkit/components/typeaheadfind/nsTypeAheadFind.cpp
+++ b/toolkit/components/typeaheadfind/nsTypeAheadFind.cpp
@@ -1330,16 +1330,17 @@ nsTypeAheadFind::IsRangeRendered(nsRange
   RefPtr<nsPresContext> presContext = presShell->GetPresContext();
   *aResult = IsRangeRendered(presShell, presContext, aRange);
   return NS_OK;
 }
 
 bool nsTypeAheadFind::IsRangeRendered(nsIPresShell* aPresShell,
                                       nsPresContext* aPresContext,
                                       nsRange* aRange) {
+  using FrameForPointOption = nsLayoutUtils::FrameForPointOption;
   NS_ASSERTION(aPresShell && aPresContext && aRange, "params are invalid");
 
   nsCOMPtr<nsIContent> content = do_QueryInterface(aRange->GetCommonAncestor());
   if (!content) {
     return false;
   }
 
   nsIFrame* frame = content->GetPrimaryFrame();
@@ -1361,19 +1362,19 @@ bool nsTypeAheadFind::IsRangeRendered(ns
     RefPtr<mozilla::dom::DOMRect> rect = rects->Item(i);
     nsRect r(nsPresContext::CSSPixelsToAppUnits((float)rect->X()),
              nsPresContext::CSSPixelsToAppUnits((float)rect->Y()),
              nsPresContext::CSSPixelsToAppUnits((float)rect->Width()),
              nsPresContext::CSSPixelsToAppUnits((float)rect->Height()));
     // Append visible frames to frames array.
     nsLayoutUtils::GetFramesForArea(
         rootFrame, r, frames,
-        nsLayoutUtils::IGNORE_PAINT_SUPPRESSION |
-            nsLayoutUtils::IGNORE_ROOT_SCROLL_FRAME |
-            nsLayoutUtils::ONLY_VISIBLE);
+        {FrameForPointOption::IgnorePaintSuppression,
+         FrameForPointOption::IgnoreRootScrollFrame,
+         FrameForPointOption::OnlyVisible});
 
     // See if any of the frames contain the content. If they do, then the range
     // is visible. We search for the content rather than the original frame,
     // because nsTextContinuation frames might be returned instead of the
     // original frame.
     for (const auto& f : frames) {
       if (f->GetContent() == content) {
         return true;