merge autoland to mozilla-central. r=merge a=merge
authorSebastian Hengst <archaeopteryx@coole-files.de>
Sat, 04 Nov 2017 22:53:35 +0100
changeset 390203 707aeb170e7e91d4e31317b05fdf9152f01adc0b
parent 390189 55562f81645e7636a1cf6afbb6d28d65a9e5c3a3 (current diff)
parent 390202 f8164446177a17254ee649da73e12b726eb9d325 (diff)
child 390209 e7fee7042d971d73c0e5caafba3b8d15da1bc8ca
push id32811
push userarchaeopteryx@coole-files.de
push dateSat, 04 Nov 2017 21:53:56 +0000
treeherdermozilla-central@707aeb170e7e [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge, merge
milestone58.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
merge autoland to mozilla-central. r=merge a=merge MozReview-Commit-ID: 45Z1APQfTZq
--- a/browser/base/content/aboutDialog.xul
+++ b/browser/base/content/aboutDialog.xul
@@ -125,17 +125,17 @@
             <description class="text-blurb" id="communityExperimentalDesc">
               &community.exp.start;<label class="text-link" href="http://www.mozilla.org/">&community.exp.mozillaLink;</label>&community.exp.middle;<label class="text-link" useoriginprincipal="true" href="about:credits">&community.exp.creditsLink;</label>&community.exp.end;
             </description>
           </vbox>
           <description class="text-blurb" id="communityDesc">
             &community.start2;<label class="text-link" href="http://www.mozilla.org/">&community.mozillaLink;</label>&community.middle2;<label class="text-link" useoriginprincipal="true" href="about:credits">&community.creditsLink;</label>&community.end3;
           </description>
           <description class="text-blurb" id="contributeDesc">
-              &helpus.start;<label class="text-link" href="https://sendto.mozilla.org/page/contribute/Give-Now?source=mozillaorg_default_footer&#38;ref=firefox_about&#38;utm_campaign=firefox_about&#38;tm_source=firefox&#38;tm_medium=referral&#38;utm_content=20140929_FireFoxAbout">&helpus.donateLink;</label>&helpus.middle;<label class="text-link" href="http://www.mozilla.org/contribute/">&helpus.getInvolvedLink;</label>&helpus.end;
+              &helpus.start;<label class="text-link" href="https://donate.mozilla.org/?utm_source=firefox&#38;utm_medium=referral&#38;utm_campaign=firefox_about&#38;utm_content=firefox_about">&helpus.donateLink;</label>&helpus.middle;<label class="text-link" href="http://www.mozilla.org/contribute/">&helpus.getInvolvedLink;</label>&helpus.end;
           </description>
         </vbox>
       </vbox>
     </hbox>
     <vbox id="bottomBox">
       <hbox pack="center">
         <label class="text-link bottom-link" useoriginprincipal="true" href="about:license">&bottomLinks.license;</label>
         <label class="text-link bottom-link" useoriginprincipal="true" href="about:rights">&bottomLinks.rights;</label>
--- a/browser/locales/en-US/chrome/browser/aboutDialog.dtd
+++ b/browser/locales/en-US/chrome/browser/aboutDialog.dtd
@@ -36,17 +36,17 @@
 <!-- LOCALIZATION NOTE (community.mozillaLink): This is a link title that links to http://www.mozilla.org/. -->
 <!ENTITY community.mozillaLink      "&vendorShortName;">
 <!ENTITY community.middle2          ", a ">
 <!-- LOCALIZATION NOTE (community.creditsLink): This is a link title that links to about:credits. -->
 <!ENTITY community.creditsLink      "global community">
 <!ENTITY community.end3             " working together to keep the Web open, public and accessible to all.">
 
 <!ENTITY helpus.start               "Want to help? ">
-<!-- LOCALIZATION NOTE (helpus.donateLink): This is a link title that links to https://sendto.mozilla.org/page/contribute/Give-Now?source=mozillaorg_default_footer&ref=firefox_about&utm_campaign=firefox_about&utm_source=firefox&utm_medium=referral&utm_content=20140929_FireFoxAbout. -->
+<!-- LOCALIZATION NOTE (helpus.donateLink): This is a link title that links to https://donate.mozilla.org/?utm_source=firefox&utm_medium=referral&utm_campaign=firefox_about&utm_content=firefox_about. -->
 <!ENTITY helpus.donateLink          "Make a donation">
 <!ENTITY helpus.middle              " or ">
 <!-- LOCALIZATION NOTE (helpus.getInvolvedLink): This is a link title that links to http://www.mozilla.org/contribute/. -->
 <!ENTITY helpus.getInvolvedLink     "get involved!">
 <!ENTITY helpus.end                 "">
 
 <!ENTITY releaseNotes.link          "What’s new">
 
--- a/devtools/server/actors/highlighters/shapes.js
+++ b/devtools/server/actors/highlighters/shapes.js
@@ -220,24 +220,27 @@ class ShapesHighlighter extends AutoRefr
       return;
     }
 
     let { target, type, pageX, pageY } = event;
 
     // For events on highlighted nodes in an iframe, when the event takes place
     // outside the iframe. Check if event target belongs to the iframe. If it doesn't,
     // adjust pageX/pageY to be relative to the iframe rather than the parent.
-    if (target.ownerDocument !== this.currentNode.ownerDocument) {
+    let nodeDocument = this.currentNode.ownerDocument;
+    if (target !== nodeDocument && target.ownerDocument !== nodeDocument) {
       let [xOffset, yOffset] = getFrameOffsets(target.ownerGlobal, this.currentNode);
       // xOffset/yOffset are relative to the viewport, so first find the top/left
       // edges of the viewport relative to the page.
       let viewportLeft = pageX - event.clientX;
       let viewportTop = pageY - event.clientY;
-      pageX -= viewportLeft + xOffset;
-      pageY -= viewportTop + yOffset;
+      // Also adjust for scrolling in the iframe.
+      let { scrollTop, scrollLeft } = nodeDocument.documentElement;
+      pageX -= viewportLeft + xOffset - scrollLeft;
+      pageY -= viewportTop + yOffset - scrollTop;
     }
 
     switch (type) {
       case "pagehide":
         // If a page hide event is triggered for current window's highlighter, hide the
         // highlighter.
         if (target.defaultView === this.win) {
           this.destroy();
@@ -1125,17 +1128,17 @@ class ShapesHighlighter extends AutoRefr
    * element.
    * @param {Number} pageX the x coordinate on the page
    * @param {Number} pageY the y coordinate on the page
    * @returns {Object} object of form {percentX, percentY}, which are the x/y coords
    *          in percentages relative to the element.
    */
   convertPageCoordsToPercent(pageX, pageY) {
     // If the current node is in an iframe, we get dimensions relative to the frame.
-    let dims = (this.highlighterEnv.window.document === this.currentNode.ownerDocument) ?
+    let dims = this.highlighterEnv.window.document === this.currentNode.ownerDocument ?
                this.zoomAdjustedDimensions : this.frameDimensions;
     let { top, left, width, height } = dims;
     pageX -= left;
     pageY -= top;
     let percentX = pageX * 100 / width;
     let percentY = pageY * 100 / height;
     return { percentX, percentY };
   }
@@ -1146,17 +1149,19 @@ class ShapesHighlighter extends AutoRefr
    * @param {Number} x the x coordinate
    * @param {Number} y the y coordinate
    * @returns {Object} object of form {x, y}, which are the x/y coords in pixels
    *          relative to the page
    *
    * @memberof ShapesHighlighter
    */
   convertPercentToPageCoords(x, y) {
-    let { top, left, width, height } = this.zoomAdjustedDimensions;
+    let dims = this.highlighterEnv.window.document === this.currentNode.ownerDocument ?
+               this.zoomAdjustedDimensions : this.frameDimensions;
+    let { top, left, width, height } = dims;
     x = x * width / 100;
     y = y * height / 100;
     x += left;
     y += top;
     return { x, y };
   }
 
   /**
--- a/dom/base/ChildIterator.cpp
+++ b/dom/base/ChildIterator.cpp
@@ -157,27 +157,27 @@ FlattenedChildIterator::Init(bool aIgnor
         mXBLInvolved = true;
         break;
       }
     }
   }
 }
 
 bool
-ExplicitChildIterator::Seek(nsIContent* aChildToFind)
+ExplicitChildIterator::Seek(const nsIContent* aChildToFind)
 {
   if (aChildToFind->GetParent() == mParent &&
       !aChildToFind->IsRootOfAnonymousSubtree()) {
     // Fast path: just point ourselves to aChildToFind, which is a
     // normal DOM child of ours.
-    MOZ_ASSERT(!nsContentUtils::IsContentInsertionPoint(aChildToFind));
-    mChild = aChildToFind;
+    mChild = const_cast<nsIContent*>(aChildToFind);
     mIndexInInserted = 0;
     mDefaultChild = nullptr;
     mIsFirst = false;
+    MOZ_ASSERT(!nsContentUtils::IsContentInsertionPoint(mChild));
     return true;
   }
 
   // Can we add more fast paths here based on whether the parent of aChildToFind
   // is a shadow insertion point or content insertion point?
 
   // Slow path: just walk all our kids.
   return Seek(aChildToFind, nullptr);
@@ -281,17 +281,17 @@ AllChildrenIterator::Get() const
 
     default:
       return nullptr;
   }
 }
 
 
 bool
-AllChildrenIterator::Seek(nsIContent* aChildToFind)
+AllChildrenIterator::Seek(const nsIContent* aChildToFind)
 {
   if (mPhase == eAtBegin || mPhase == eAtBeforeKid) {
     mPhase = eAtExplicitKids;
     Element* beforePseudo = nsLayoutUtils::GetBeforePseudo(mOriginalContent);
     if (beforePseudo && beforePseudo == aChildToFind) {
       mPhase = eAtBeforeKid;
       return true;
     }
--- a/dom/base/ChildIterator.h
+++ b/dom/base/ChildIterator.h
@@ -58,23 +58,23 @@ public:
       mIndexInInserted(aOther.mIndexInInserted) {}
 
   nsIContent* GetNextChild();
 
   // Looks for aChildToFind respecting insertion points until aChildToFind is
   // found.  This version can take shortcuts that the two-argument version
   // can't, so can be faster (and in fact can be O(1) instead of O(N) in many
   // cases).
-  bool Seek(nsIContent* aChildToFind);
+  bool Seek(const nsIContent* aChildToFind);
 
   // Looks for aChildToFind respecting insertion points until aChildToFind is found.
   // or aBound is found. If aBound is nullptr then the seek is unbounded. Returns
   // whether aChildToFind was found as an explicit child prior to encountering
   // aBound.
-  bool Seek(nsIContent* aChildToFind, nsIContent* aBound)
+  bool Seek(const nsIContent* aChildToFind, nsIContent* aBound)
   {
     // It would be nice to assert that we find aChildToFind, but bz thinks that
     // we might not find aChildToFind when called from ContentInserted
     // if first-letter frames are about.
 
     // We can't easily take shortcuts here because we'd have to have a way to
     // compare aChildToFind to aBound.
     nsIContent* child;
@@ -127,47 +127,61 @@ protected:
 // children, those are iterated over.  The iterator can be initialized to start
 // at the end by providing false for aStartAtBeginning in order to start
 // iterating in reverse from the last child.
 class FlattenedChildIterator : public ExplicitChildIterator
 {
 public:
   explicit FlattenedChildIterator(const nsIContent* aParent,
                                   bool aStartAtBeginning = true)
-    : ExplicitChildIterator(aParent, aStartAtBeginning), mXBLInvolved(false)
+    : ExplicitChildIterator(aParent, aStartAtBeginning)
+    , mXBLInvolved(false)
+    , mOriginalContent(aParent)
   {
     Init(false);
   }
 
   FlattenedChildIterator(FlattenedChildIterator&& aOther)
-    : ExplicitChildIterator(Move(aOther)), mXBLInvolved(aOther.mXBLInvolved) {}
+    : ExplicitChildIterator(Move(aOther))
+    , mXBLInvolved(aOther.mXBLInvolved)
+    , mOriginalContent(aOther.mOriginalContent)
+  {}
 
   FlattenedChildIterator(const FlattenedChildIterator& aOther)
-    : ExplicitChildIterator(aOther), mXBLInvolved(aOther.mXBLInvolved) {}
+    : ExplicitChildIterator(aOther)
+    , mXBLInvolved(aOther.mXBLInvolved)
+    , mOriginalContent(aOther.mOriginalContent)
+  {}
 
   bool XBLInvolved() { return mXBLInvolved; }
 
+  const nsIContent* Parent() const { return mOriginalContent; }
+
 protected:
   /**
    * This constructor is a hack to help AllChildrenIterator which sometimes
    * doesn't want to consider XBL.
    */
   FlattenedChildIterator(const nsIContent* aParent, uint32_t aFlags,
                          bool aStartAtBeginning = true)
-    : ExplicitChildIterator(aParent, aStartAtBeginning), mXBLInvolved(false)
+    : ExplicitChildIterator(aParent, aStartAtBeginning)
+    , mXBLInvolved(false)
+    , mOriginalContent(aParent)
   {
     bool ignoreXBL = aFlags & nsIContent::eAllButXBL;
     Init(ignoreXBL);
   }
 
   void Init(bool aIgnoreXBL);
 
   // For certain optimizations, nsCSSFrameConstructor needs to know if the
   // child list of the element that we're iterating matches its .childNodes.
   bool mXBLInvolved;
+
+  const nsIContent* mOriginalContent;
 };
 
 /**
  * AllChildrenIterator traverses the children of an element including before /
  * after content and optionally XBL children.  The iterator can be initialized
  * to start at the end by providing false for aStartAtBeginning in order to
  * start iterating in reverse from the last child.
  *
@@ -175,22 +189,21 @@ protected:
  * iteration, and will break horribly if that is not true.
  */
 class AllChildrenIterator : private FlattenedChildIterator
 {
 public:
   AllChildrenIterator(const nsIContent* aNode, uint32_t aFlags,
                       bool aStartAtBeginning = true) :
     FlattenedChildIterator(aNode, aFlags, aStartAtBeginning),
-    mOriginalContent(aNode), mAnonKidsIdx(aStartAtBeginning ? UINT32_MAX : 0),
+    mAnonKidsIdx(aStartAtBeginning ? UINT32_MAX : 0),
     mFlags(aFlags), mPhase(aStartAtBeginning ? eAtBegin : eAtEnd) { }
 
   AllChildrenIterator(AllChildrenIterator&& aOther)
     : FlattenedChildIterator(Move(aOther)),
-      mOriginalContent(aOther.mOriginalContent),
       mAnonKids(Move(aOther.mAnonKids)), mAnonKidsIdx(aOther.mAnonKidsIdx),
       mFlags(aOther.mFlags), mPhase(aOther.mPhase)
 #ifdef DEBUG
       , mMutationGuard(aOther.mMutationGuard)
 #endif
       {}
 
 #ifdef DEBUG
@@ -199,37 +212,35 @@ public:
 
   // Returns the current target the iterator is at, or null if the iterator
   // doesn't point to any child node (either eAtBegin or eAtEnd phase).
   nsIContent* Get() const;
 
   // Seeks the given node in children of a parent element, starting from
   // the current iterator's position, and sets the iterator at the given child
   // node if it was found.
-  bool Seek(nsIContent* aChildToFind);
+  bool Seek(const nsIContent* aChildToFind);
 
   nsIContent* GetNextChild();
   nsIContent* GetPreviousChild();
-  const nsIContent* Parent() const { return mOriginalContent; }
 
   enum IteratorPhase
   {
     eAtBegin,
     eAtBeforeKid,
     eAtExplicitKids,
     eAtAnonKids,
     eAtAfterKid,
     eAtEnd
   };
   IteratorPhase Phase() const { return mPhase; }
 
 private:
   // Helpers.
   void AppendNativeAnonymousChildren();
-  const nsIContent* mOriginalContent;
 
   // mAnonKids is an array of native anonymous children, mAnonKidsIdx is index
   // in the array. If mAnonKidsIdx < mAnonKids.Length() and mPhase is
   // eAtAnonKids then the iterator points at a child at mAnonKidsIdx index. If
   // mAnonKidsIdx == mAnonKids.Length() then the iterator is somewhere after
   // the last native anon child. If mAnonKidsIdx == UINT32_MAX then the iterator
   // is somewhere before the first native anon child.
   nsTArray<nsIContent*> mAnonKids;
--- a/dom/base/nsINode.cpp
+++ b/dom/base/nsINode.cpp
@@ -2924,42 +2924,64 @@ struct ElementHolder {
   Element* ElementAt(uint32_t aIndex) { return nullptr; }
 
   Element* mElement;
 };
 
 Element*
 nsINode::QuerySelector(const nsAString& aSelector, ErrorResult& aResult)
 {
-  nsCSSSelectorList* selectorList = ParseSelectorList(aSelector, aResult);
-  if (!selectorList) {
-    // Either we failed (and aResult already has the exception), or this
-    // is a pseudo-element-only selector that matches nothing.
-    return nullptr;
-  }
-  ElementHolder holder;
-  FindMatchingElements<true, ElementHolder>(this, selectorList, holder, aResult);
-  return holder.mElement;
+  return WithSelectorList<Element*>(
+    aSelector,
+    aResult,
+    [&](const RawServoSelectorList* aList) -> Element* {
+      if (!aList) {
+        return nullptr;
+      }
+      const bool useInvalidation = false;
+      return const_cast<Element*>(
+          Servo_SelectorList_QueryFirst(this, aList, useInvalidation));
+    },
+    [&](nsCSSSelectorList* aList) -> Element* {
+      if (!aList) {
+        // Either we failed (and aResult already has the exception), or this
+        // is a pseudo-element-only selector that matches nothing.
+        return nullptr;
+      }
+      ElementHolder holder;
+      FindMatchingElements<true, ElementHolder>(this, aList, holder, aResult);
+      return holder.mElement;
+    }
+  );
 }
 
 already_AddRefed<nsINodeList>
 nsINode::QuerySelectorAll(const nsAString& aSelector, ErrorResult& aResult)
 {
   RefPtr<nsSimpleContentList> contentList = new nsSimpleContentList(this);
 
-  nsCSSSelectorList* selectorList = ParseSelectorList(aSelector, aResult);
-  if (selectorList) {
-    FindMatchingElements<false, AutoTArray<Element*, 128>>(this,
-                                                             selectorList,
-                                                             *contentList,
-                                                             aResult);
-  } else {
-    // Either we failed (and aResult already has the exception), or this
-    // is a pseudo-element-only selector that matches nothing.
-  }
+  WithSelectorList<void>(
+    aSelector,
+    aResult,
+    [&](const RawServoSelectorList* aList) {
+      if (!aList) {
+        return;
+      }
+      const bool useInvalidation = false;
+      Servo_SelectorList_QueryAll(
+        this, aList, contentList.get(), useInvalidation);
+    },
+    [&](nsCSSSelectorList* aList) {
+      if (!aList) {
+        return;
+      }
+      FindMatchingElements<false, AutoTArray<Element*, 128>>(
+        this, aList, *contentList, aResult);
+    }
+  );
 
   return contentList.forget();
 }
 
 Element*
 nsINode::GetElementById(const nsAString& aId)
 {
   MOZ_ASSERT(IsElement() || IsNodeOfType(eDOCUMENT_FRAGMENT),
--- a/layout/base/nsCSSFrameConstructor.cpp
+++ b/layout/base/nsCSSFrameConstructor.cpp
@@ -6864,125 +6864,157 @@ nsCSSFrameConstructor::IsValidSibling(ns
     if ((legendContent && (LayoutFrameType::Legend != sibType)) ||
         (!legendContent && (LayoutFrameType::Legend == sibType)))
       return false;
   }
 
   return true;
 }
 
+// FIXME(emilio): If we ever kill IsValidSibling() we can simplify this quite a
+// bit (no need to pass aTargetContent or aTargetContentDisplay, and the
+// adjust() calls can be responsibility of the caller).
+template<nsCSSFrameConstructor::SiblingDirection aDirection>
 nsIFrame*
-nsCSSFrameConstructor::FindFrameForContentSibling(nsIContent* aContent,
-                                                  nsIContent* aTargetContent,
-                                                  StyleDisplay& aTargetContentDisplay,
-                                                  nsContainerFrame* aParentFrame,
-                                                  bool aPrevSibling)
-{
-  nsIFrame* sibling = aContent->GetPrimaryFrame();
-  if (!sibling && GetDisplayContentsStyleFor(aContent)) {
-    // A display:contents node - check if it has a ::before / ::after frame...
-    sibling = aPrevSibling ? nsLayoutUtils::GetAfterFrame(aContent)
-                           : nsLayoutUtils::GetBeforeFrame(aContent);
-    if (!sibling) {
-      // ... then recurse into children ...
-      const bool forward = !aPrevSibling;
-      FlattenedChildIterator iter(aContent, forward);
-      sibling = aPrevSibling ?
-        FindPreviousSibling(iter, aTargetContent, aTargetContentDisplay, aParentFrame) :
-        FindNextSibling(iter, aTargetContent, aTargetContentDisplay, aParentFrame);
-
-      // The recursion above has already done all the placeholder and
-      // continuation fixups.
+nsCSSFrameConstructor::FindSiblingInternal(
+  FlattenedChildIterator aIter,
+  nsIContent* aTargetContent,
+  StyleDisplay& aTargetContentDisplay)
+{
+  auto adjust = [&](nsIFrame* aPotentialSiblingFrame) -> nsIFrame* {
+    return AdjustSiblingFrame(
+      aPotentialSiblingFrame, aTargetContent, aTargetContentDisplay,
+      aDirection);
+  };
+
+  auto nextDomSibling = [](FlattenedChildIterator& aIter) -> nsIContent* {
+    return aDirection == SiblingDirection::Forward
+      ? aIter.GetNextChild() : aIter.GetPreviousChild();
+  };
+
+  auto getNearPseudo = [](const nsIContent* aContent) -> nsIFrame* {
+    return aDirection == SiblingDirection::Forward
+      ? nsLayoutUtils::GetBeforeFrame(aContent)
+      : nsLayoutUtils::GetAfterFrame(aContent);
+  };
+
+  auto getFarPseudo = [](const nsIContent* aContent) -> nsIFrame* {
+    return aDirection == SiblingDirection::Forward
+      ? nsLayoutUtils::GetAfterFrame(aContent)
+      : nsLayoutUtils::GetBeforeFrame(aContent);
+  };
+
+  while (nsIContent* sibling = nextDomSibling(aIter)) {
+    if (nsIFrame* primaryFrame = sibling->GetPrimaryFrame()) {
+      // XXX the GetContent() == sibling check is needed due to bug 135040.
+      // Remove it once that's fixed.
+      if (primaryFrame->GetContent() == sibling) {
+        if (nsIFrame* frame = adjust(primaryFrame)) {
+          return frame;
+        }
+      }
+    }
+
+    if (GetDisplayContentsStyleFor(sibling)) {
+      if (nsIFrame* frame = adjust(getNearPseudo(sibling))) {
+        return frame;
+      }
+
+      const bool startFromBeginning = aDirection == SiblingDirection::Forward;
+      FlattenedChildIterator iter(sibling, startFromBeginning);
+      nsIFrame* sibling = FindSiblingInternal<aDirection>(
+        iter, aTargetContent, aTargetContentDisplay);
       if (sibling) {
         return sibling;
       }
     }
-    if (!sibling) {
-      // ... then ::after / ::before on the opposite end.
-      sibling = aPrevSibling ? nsLayoutUtils::GetAfterFrame(aContent)
-                             : nsLayoutUtils::GetBeforeFrame(aContent);
-    }
-    if (!sibling) {
-      return nullptr;
-    }
-  } else if (!sibling || sibling->GetContent() != aContent) {
-    // XXX the GetContent() != aContent check is needed due to bug 135040.
-    // Remove it once that's fixed.
+  }
+
+  return adjust(getFarPseudo(aIter.Parent()));
+}
+
+nsIFrame*
+nsCSSFrameConstructor::AdjustSiblingFrame(
+  nsIFrame* aSibling,
+  nsIContent* aTargetContent,
+  mozilla::StyleDisplay& aTargetContentDisplay,
+  SiblingDirection aDirection)
+{
+  if (!aSibling) {
     return nullptr;
   }
 
-  // If the frame is out-of-flow, GetPrimaryFrame() will have returned the
-  // out-of-flow frame; we want the placeholder.
-  if (sibling->GetStateBits() & NS_FRAME_OUT_OF_FLOW) {
-    nsIFrame* placeholderFrame = sibling->GetPlaceholderFrame();
-    NS_ASSERTION(placeholderFrame, "no placeholder for out-of-flow frame");
-    sibling = placeholderFrame;
-  }
-
-  // The frame we have now should never be a continuation.
-  NS_ASSERTION(!sibling->GetPrevContinuation(), "How did that happen?");
-
-  if (aPrevSibling) {
-    // The frame may be a ib-split frame (a split inline frame that
-    // contains a block).  Get the last part of that split.
-    if (IsFramePartOfIBSplit(sibling)) {
-      sibling = GetLastIBSplitSibling(sibling, true);
+  if (aSibling->GetStateBits() & NS_FRAME_OUT_OF_FLOW) {
+    aSibling = aSibling->GetPlaceholderFrame();
+    MOZ_ASSERT(aSibling);
+  }
+
+  MOZ_ASSERT(!aSibling->GetPrevContinuation(), "How?");
+  if (aDirection == SiblingDirection::Backward) {
+    // The frame may be a ib-split frame (a split inline frame that contains a
+    // block).  Get the last part of that split.
+    if (IsFramePartOfIBSplit(aSibling)) {
+      aSibling = GetLastIBSplitSibling(aSibling, true);
     }
 
     // The frame may have a continuation. If so, we want the last
     // non-overflow-container continuation as our previous sibling.
-    sibling = sibling->GetTailContinuation();
-  }
-
-  if (aTargetContent &&
-      !IsValidSibling(sibling, aTargetContent, aTargetContentDisplay)) {
-    sibling = nullptr;
-  }
-
-  return sibling;
+    aSibling = aSibling->GetTailContinuation();
+  }
+
+  if (!IsValidSibling(aSibling, aTargetContent, aTargetContentDisplay)) {
+    return nullptr;
+  }
+
+  return aSibling;
+}
+
+nsIFrame*
+nsCSSFrameConstructor::FindPreviousSibling(const FlattenedChildIterator& aIter,
+                                           StyleDisplay& aTargetContentDisplay)
+{
+  return FindSibling<SiblingDirection::Backward>(aIter, aTargetContentDisplay);
 }
 
 nsIFrame*
-nsCSSFrameConstructor::FindPreviousSibling(FlattenedChildIterator aIter,
-                                           nsIContent* aTargetContent,
-                                           StyleDisplay& aTargetContentDisplay,
-                                           nsContainerFrame* aParentFrame)
-{
-  // Note: not all content objects are associated with a frame (e.g., if it's
-  // `display: none') so keep looking until we find a previous frame.
-  while (nsIContent* sibling = aIter.GetPreviousChild()) {
-    MOZ_ASSERT(sibling != aTargetContent);
-    nsIFrame* prevSibling =
-      FindFrameForContentSibling(sibling, aTargetContent, aTargetContentDisplay,
-                                 aParentFrame, true);
-    if (prevSibling) {
-      // Found a previous sibling, we're done!
-      return prevSibling;
-    }
-  }
-
-  return nullptr;
-}
-
+nsCSSFrameConstructor::FindNextSibling(const FlattenedChildIterator& aIter,
+                                       StyleDisplay& aTargetContentDisplay)
+{
+  return FindSibling<SiblingDirection::Forward>(aIter, aTargetContentDisplay);
+}
+
+template<nsCSSFrameConstructor::SiblingDirection aDirection>
 nsIFrame*
-nsCSSFrameConstructor::FindNextSibling(FlattenedChildIterator aIter,
-                                       nsIContent* aTargetContent,
-                                       StyleDisplay& aTargetContentDisplay,
-                                       nsContainerFrame* aParentFrame)
-{
-  while (nsIContent* sibling = aIter.GetNextChild()) {
-    MOZ_ASSERT(sibling != aTargetContent);
-    nsIFrame* nextSibling =
-      FindFrameForContentSibling(sibling, aTargetContent, aTargetContentDisplay,
-                                 aParentFrame, false);
-
-    if (nextSibling) {
-      // We found a next sibling, we're done!
-      return nextSibling;
-    }
+nsCSSFrameConstructor::FindSibling(const FlattenedChildIterator& aIter,
+                                   StyleDisplay& aTargetContentDisplay)
+{
+  nsIContent* targetContent = aIter.Get();
+  nsIFrame* sibling =
+    FindSiblingInternal<aDirection>(aIter, targetContent, aTargetContentDisplay);
+  if (sibling) {
+    return sibling;
+  }
+
+  // Our siblings (if any) do not have a frame to guide us. The frame for the
+  // target content should be inserted whereever a frame for the container would
+  // be inserted. This is needed when inserting into display: contents nodes.
+  const nsIContent* current = aIter.Parent();
+  while (GetDisplayContentsStyleFor(current)) {
+    const nsIContent* parent = current->GetFlattenedTreeParent();
+    MOZ_ASSERT(parent, "No display: contents on the root");
+
+    FlattenedChildIterator iter(parent);
+    iter.Seek(current);
+    sibling = FindSiblingInternal<aDirection>(
+        iter, targetContent, aTargetContentDisplay);
+    if (sibling) {
+      return sibling;
+    }
+
+    current = parent;
   }
 
   return nullptr;
 }
 
 // For fieldsets, returns the area frame, if the child is not a legend.
 static nsContainerFrame*
 GetAdjustedParentFrame(nsContainerFrame* aParentFrame,
@@ -7037,56 +7069,32 @@ nsCSSFrameConstructor::GetInsertionPrevS
     MOZ_ASSERT(aChild->GetProperty(nsGkAtoms::restylableAnonymousNode),
                "Someone passed native anonymous content directly into frame "
                "construction.  Stop doing that!");
   }
 
   // Note that FindPreviousSibling is passed the iterator by value, so that
   // the later usage of the iterator starts from the same place.
   StyleDisplay childDisplay = UNSET_DISPLAY;
-  nsIFrame* prevSibling =
-    FindPreviousSibling(iter, iter.Get(), childDisplay, aInsertion->mParentFrame);
+  nsIFrame* prevSibling = FindPreviousSibling(iter, childDisplay);
 
   // Now, find the geometric parent so that we can handle
   // continuations properly. Use the prev sibling if we have it;
   // otherwise use the next sibling.
   if (prevSibling) {
     aInsertion->mParentFrame = prevSibling->GetParent()->GetContentInsertionFrame();
   } else {
     // If there is no previous sibling, then find the frame that follows
+    //
+    // FIXME(emilio): This is really complex and probably shouldn't be.
     if (aEndSkipChild) {
       iter.Seek(aEndSkipChild);
       iter.GetPreviousChild();
     }
-    nsIFrame* nextSibling =
-      FindNextSibling(iter, iter.Get(), childDisplay, aInsertion->mParentFrame);
-    if (GetDisplayContentsStyleFor(aInsertion->mContainer)) {
-      if (!nextSibling) {
-        // Our siblings (if any) does not have a frame to guide us.
-        // The frame for aChild should be inserted whereever a frame for
-        // the container would be inserted.  This is needed when inserting
-        // into nested display:contents nodes.
-        nsIContent* child = aInsertion->mContainer;
-        nsIContent* parent = child->GetParent();
-        aInsertion->mParentFrame =
-          ::GetAdjustedParentFrame(aInsertion->mParentFrame, parent);
-        InsertionPoint fakeInsertion(aInsertion->mParentFrame, parent);
-        nsIFrame* result = GetInsertionPrevSibling(&fakeInsertion, child, aIsAppend,
-                                                   aIsRangeInsertSafe, nullptr, nullptr);
-        MOZ_ASSERT(aInsertion->mParentFrame->GetContent() ==
-                   fakeInsertion.mParentFrame->GetContent());
-        // fakeInsertion.mParentFrame may now be a continuation of the frame
-        // we started with in the ctor above.
-        aInsertion->mParentFrame = fakeInsertion.mParentFrame;
-        return result;
-      }
-
-      prevSibling = nextSibling->GetPrevSibling();
-    }
-
+    nsIFrame* nextSibling = FindNextSibling(iter, childDisplay);
     if (nextSibling) {
       aInsertion->mParentFrame = nextSibling->GetParent()->GetContentInsertionFrame();
     } else {
       // No previous or next sibling, so treat this like an appended frame.
       *aIsAppend = true;
       if (IsFramePartOfIBSplit(aInsertion->mParentFrame)) {
         // Since we're appending, we'll walk to the last anonymous frame
         // that was created for the broken inline frame.  But don't walk
@@ -8334,32 +8342,16 @@ nsCSSFrameConstructor::ContentRangeInser
         return;
       }
 
       container = insertion.mParentFrame->GetContent();
       frameType = insertion.mParentFrame->Type();
     }
   }
 
-  if (!prevSibling) {
-    // We're inserting the new frames as the first child. See if the
-    // parent has a :before pseudo-element
-    nsIFrame* firstChild = insertion.mParentFrame->PrincipalChildList().FirstChild();
-
-    if (firstChild &&
-        nsLayoutUtils::IsGeneratedContentFor(container, firstChild,
-                                             nsCSSPseudoElements::before)) {
-      // Insert the new frames after the last continuation of the :before
-      prevSibling = firstChild->GetTailContinuation();
-      insertion.mParentFrame = prevSibling->GetParent()->GetContentInsertionFrame();
-      // Don't change isAppend here; we'll can call AppendFrames as needed, and
-      // the change to our prevSibling doesn't affect that.
-    }
-  }
-
   AutoFrameConstructionItemList items(this);
   ParentType parentType = GetParentType(frameType);
   FlattenedChildIterator iter(aContainer);
   bool haveNoXBLChildren = (!iter.XBLInvolved() || !iter.GetNextChild());
   if (aStartChild->GetPreviousSibling() &&
       parentType == eTypeBlock && haveNoXBLChildren) {
     // If there's a text node in the normal content list just before the
     // new nodes, and it has no frame, make a frame construction item for
--- a/layout/base/nsCSSFrameConstructor.h
+++ b/layout/base/nsCSSFrameConstructor.h
@@ -2115,74 +2115,63 @@ private:
    * resolved for aFrameItems are wrong (they don't take ::first-line into
    * account), and we should fix them up, which is what this method does.
    *
    * This method does not mutate aFrameItems.
    */
   void CheckForFirstLineInsertion(nsIFrame* aParentFrame,
                                   nsFrameItems& aFrameItems);
 
-  /**
-   * Find the right frame to use for aContent when looking for sibling
-   * frames for aTargetContent.  If aPrevSibling is true, this
-   * will look for last continuations, etc, as necessary.  This calls
-   * IsValidSibling as needed; if that returns false it returns null.
-   *
-   * @param aContent the content to search for frames
-   * @param aTargetContent the content we're finding a sibling frame for
-   * @param aTargetContentDisplay the CSS display enum for aTargetContent if
-   *          already known, UNSET_DISPLAY otherwise. It will be filled in
-   *          if needed.
-   * @param aParentFrame the nearest ancestor frame, used internally for
-   *          finding ::after / ::before frames
-   * @param aPrevSibling true if we're searching in reverse DOM order
-   */
-  nsIFrame* FindFrameForContentSibling(nsIContent* aContent,
-                                       nsIContent* aTargetContent,
-                                       mozilla::StyleDisplay& aTargetContentDisplay,
-                                       nsContainerFrame* aParentFrame,
-                                       bool aPrevSibling);
+  // The direction in which we should look for siblings.
+  enum class SiblingDirection
+  {
+    Forward,
+    Backward,
+  };
 
   /**
-   * Find the frame for the content immediately preceding the one aIter
-   * points to, following continuations if necessary.  aIter is passed by
-   * value on purpose, so as not to modify the caller's iterator.
+   * Find the frame for the content immediately next to the one aIter points to,
+   * in the direction SiblingDirection indicates, following continuations if
+   * necessary.
+   *
+   * aIter is passed by const reference on purpose, so as not to modify the
+   * caller's iterator.
    *
    * @param aIter should be positioned such that aIter.GetPreviousChild()
    *          is the first content to search for frames
-   * @param aTargetContent the content we're finding a sibling frame for
-   * @param aTargetContentDisplay the CSS display enum for aTargetContent if
-   *          already known, UNSET_DISPLAY otherwise. It will be filled in
-   *          if needed.
-   * @param aParentFrame the nearest ancestor frame, used inernally for
-   *          finding ::after / ::before frames
+   * @param aTargetContentDisplay the CSS display enum for the content aIter
+   *          points to if already known, UNSET_DISPLAY otherwise. It will be
+   *          filled in if needed.
    */
-  nsIFrame* FindPreviousSibling(mozilla::dom::FlattenedChildIterator aIter,
-                                nsIContent* aTargetContent,
-                                mozilla::StyleDisplay& aTargetContentDisplay,
-                                nsContainerFrame* aParentFrame);
+  template<SiblingDirection>
+  nsIFrame* FindSibling(const mozilla::dom::FlattenedChildIterator& aIter,
+                        mozilla::StyleDisplay& aTargetContentDisplay);
+
+  // Helper for the implementation of FindSibling.
+  template<SiblingDirection>
+  nsIFrame* FindSiblingInternal(
+    mozilla::dom::FlattenedChildIterator,
+    nsIContent* aTargetContent,
+    mozilla::StyleDisplay& aTargetContentDisplay);
 
-  /**
-   * Find the frame for the content node immediately following the one aIter
-   * points to, following continuations if necessary.  aIter is passed by value
-   * on purpose, so as not to modify the caller's iterator.
-   *
-   * @param aIter should be positioned such that aIter.GetNextChild()
-   *          is the first content to search for frames
-   * @param aTargetContent the content we're finding a sibling frame for
-   * @param aTargetContentDisplay the CSS display enum for aTargetContent if
-   *          already known, UNSET_DISPLAY otherwise. It will be filled in
-   *          if needed.
-   * @param aParentFrame the nearest ancestor frame, used inernally for
-   *          finding ::after / ::before frames
-   */
-  nsIFrame* FindNextSibling(mozilla::dom::FlattenedChildIterator aIter,
-                            nsIContent* aTargetContent,
-                            mozilla::StyleDisplay& aTargetContentDisplay,
-                            nsContainerFrame* aParentFrame);
+  // An alias of FindSibling<SiblingDirection::Forward>.
+  nsIFrame* FindNextSibling(const mozilla::dom::FlattenedChildIterator& aIter,
+                            mozilla::StyleDisplay& aTargetContentDisplay);
+  // An alias of FindSibling<SiblingDirection::Backwards>.
+  nsIFrame* FindPreviousSibling(const mozilla::dom::FlattenedChildIterator& aIter,
+                                mozilla::StyleDisplay& aTargetContentDisplay);
+
+  // Given a potential first-continuation sibling frame for aTargetContent,
+  // verify that it is an actual valid sibling for it, and return the
+  // appropriate continuation the new frame for aTargetContent should be
+  // inserted next to.
+  nsIFrame* AdjustSiblingFrame(nsIFrame* aSibling,
+                               nsIContent* aTargetContent,
+                               mozilla::StyleDisplay& aTargetContentDisplay,
+                               SiblingDirection aDirection);
 
   // Find the right previous sibling for an insertion.  This also updates the
   // parent frame to point to the correct continuation of the parent frame to
   // use, and returns whether this insertion is to be treated as an append.
   // aChild is the child being inserted.
   // aIsRangeInsertSafe returns whether it is safe to do a range insert with
   // aChild being the first child in the range. It is the callers'
   // responsibility to check whether a range insert is safe with regards to
--- a/layout/base/nsLayoutUtils.cpp
+++ b/layout/base/nsLayoutUtils.cpp
@@ -1731,43 +1731,16 @@ nsLayoutUtils::GetFloatFromPlaceholder(n
                  "How did that happen?");
     return outOfFlowFrame;
   }
 
   return nullptr;
 }
 
 // static
-bool
-nsLayoutUtils::IsGeneratedContentFor(nsIContent* aContent,
-                                     nsIFrame* aFrame,
-                                     nsAtom* aPseudoElement)
-{
-  NS_PRECONDITION(aFrame, "Must have a frame");
-  NS_PRECONDITION(aPseudoElement, "Must have a pseudo name");
-
-  if (!aFrame->IsGeneratedContentFrame()) {
-    return false;
-  }
-  nsIFrame* parent = aFrame->GetParent();
-  NS_ASSERTION(parent, "Generated content can't be root frame");
-  if (parent->IsGeneratedContentFrame()) {
-    // Not the root of the generated content
-    return false;
-  }
-
-  if (aContent && parent->GetContent() != aContent) {
-    return false;
-  }
-
-  return (aFrame->GetContent()->NodeInfo()->NameAtom() == nsGkAtoms::mozgeneratedcontentbefore) ==
-    (aPseudoElement == nsCSSPseudoElements::before);
-}
-
-// static
 nsIFrame*
 nsLayoutUtils::GetCrossDocParentFrame(const nsIFrame* aFrame,
                                       nsPoint* aExtraOffset)
 {
   nsIFrame* p = aFrame->GetParent();
   if (p)
     return p;
 
--- a/layout/base/nsLayoutUtils.h
+++ b/layout/base/nsLayoutUtils.h
@@ -389,33 +389,16 @@ public:
    * Gets the real primary frame associated with the content object.
    *
    * In the case of absolutely positioned elements and floated elements,
    * the real primary frame is the frame that is out of the flow and not the
    * placeholder frame.
    */
   static nsIFrame* GetRealPrimaryFrameFor(const nsIContent* aContent);
 
-  /**
-   * IsGeneratedContentFor returns true if aFrame is the outermost
-   * frame for generated content of type aPseudoElement for aContent.
-   * aFrame *might not* have the aPseudoElement pseudo-style! For example
-   * it might be a table wrapper frame and the inner table frame might
-   * have the pseudo-style.
-   *
-   * @param aContent the content node we're looking at.  If this is
-   *        null, then we just assume that aFrame has the right content
-   *        pointer.
-   * @param aFrame the frame we're looking at
-   * @param aPseudoElement the pseudo type we're interested in
-   * @return whether aFrame is the generated aPseudoElement frame for aContent
-   */
-  static bool IsGeneratedContentFor(nsIContent* aContent, nsIFrame* aFrame,
-                                      nsAtom* aPseudoElement);
-
 #ifdef DEBUG
   // TODO: remove, see bug 598468.
   static bool gPreventAssertInCompareTreePosition;
 #endif // DEBUG
 
   /**
    * CompareTreePosition determines whether aContent1 comes before or
    * after aContent2 in a preorder traversal of the content tree.
--- a/layout/reftests/css-grid/reftest.list
+++ b/layout/reftests/css-grid/reftest.list
@@ -241,17 +241,17 @@ asserts(0-10) == grid-fragmentation-015.
 == grid-fragmentation-dyn2-018.html grid-fragmentation-018-ref.html
 == grid-fragmentation-dyn1-019.html grid-fragmentation-019-ref.html
 == grid-fragmentation-dyn2-019.html grid-fragmentation-019-ref.html
 == grid-fragmentation-dyn3-019.html grid-fragmentation-019-ref.html
 == grid-fragmentation-dyn4-019.html grid-fragmentation-019-ref.html
 == grid-fragmentation-dyn5-019.html grid-fragmentation-019-ref.html
 == grid-fragmentation-dyn1-020.html grid-fragmentation-020-ref.html
 == grid-fragmentation-dyn2-020.html grid-fragmentation-020-ref.html
-!= grid-fragmentation-dyn1-021.html grid-fragmentation-021-ref.html # bug 1251799
+== grid-fragmentation-dyn1-021.html grid-fragmentation-021-ref.html
 == grid-fragmentation-dyn2-021.html grid-fragmentation-021-ref.html
 == grid-fragmentation-dyn3-021.html grid-fragmentation-021-ref.html
 == grid-fragmentation-dyn4-021.html grid-fragmentation-021-ref.html
 == grid-fragmentation-dyn5-021.html grid-fragmentation-021-ref.html
 == grid-fragmentation-dyn2-022.html grid-fragmentation-007-ref.html
 == grid-fragmentation-dyn1-023.html grid-fragmentation-023-ref.html
 == grid-fragmentation-dyn2-023.html grid-fragmentation-023-ref.html
 == grid-fragmentation-dyn3-023.html grid-fragmentation-023-ref.html
--- a/layout/style/ServoBindingList.h
+++ b/layout/style/ServoBindingList.h
@@ -134,20 +134,22 @@ SERVO_BINDING_FUNC(Servo_SelectorList_Dr
 SERVO_BINDING_FUNC(Servo_SelectorList_Parse,
                    RawServoSelectorList*,
                    const nsACString* selector_list)
 SERVO_BINDING_FUNC(Servo_SelectorList_Matches, bool,
                    RawGeckoElementBorrowed, RawServoSelectorListBorrowed)
 SERVO_BINDING_FUNC(Servo_SelectorList_Closest, const RawGeckoElement*,
                    RawGeckoElementBorrowed, RawServoSelectorListBorrowed)
 SERVO_BINDING_FUNC(Servo_SelectorList_QueryFirst, const RawGeckoElement*,
-                   RawGeckoNodeBorrowed, RawServoSelectorListBorrowed)
+                   RawGeckoNodeBorrowed, RawServoSelectorListBorrowed,
+                   bool may_use_invalidation)
 SERVO_BINDING_FUNC(Servo_SelectorList_QueryAll, void,
                    RawGeckoNodeBorrowed, RawServoSelectorListBorrowed,
-                   nsSimpleContentList* content_list)
+                   nsSimpleContentList* content_list,
+                   bool may_use_invalidation)
 SERVO_BINDING_FUNC(Servo_StyleSet_AddSizeOfExcludingThis, void,
                    mozilla::MallocSizeOf malloc_size_of,
                    mozilla::MallocSizeOf malloc_enclosing_size_of,
                    mozilla::ServoStyleSetSizes* sizes,
                    RawServoStyleSetBorrowed set)
 SERVO_BINDING_FUNC(Servo_UACache_AddSizeOf, void,
                    mozilla::MallocSizeOf malloc_size_of,
                    mozilla::MallocSizeOf malloc_enclosing_size_of,
--- a/layout/style/ServoBindings.cpp
+++ b/layout/style/ServoBindings.cpp
@@ -2861,12 +2861,22 @@ Gecko_ContentList_AppendAll(
   const Element** aElements,
   size_t aLength)
 {
   MOZ_ASSERT(aElements);
   MOZ_ASSERT(aLength);
   MOZ_ASSERT(aList);
 
   aList->SetCapacity(aLength);
+
   for (size_t i = 0; i < aLength; ++i) {
     aList->AppendElement(const_cast<Element*>(aElements[i]));
   }
 }
+
+const nsTArray<Element*>*
+Gecko_GetElementsWithId(const nsIDocument* aDocument, nsAtom* aId)
+{
+  MOZ_ASSERT(aDocument);
+  MOZ_ASSERT(aId);
+
+  return aDocument->GetAllElementsForId(nsDependentAtomString(aId));
+}
--- a/layout/style/ServoBindings.h
+++ b/layout/style/ServoBindings.h
@@ -712,11 +712,15 @@ void Gecko_ReportUnexpectedCSSError(mozi
                                     uint32_t lineNumber,
                                     uint32_t colNumber);
 
 // DOM APIs.
 void Gecko_ContentList_AppendAll(nsSimpleContentList* aContentList,
                                  const RawGeckoElement** aElements,
                                  size_t aLength);
 
+const nsTArray<mozilla::dom::Element*>* Gecko_GetElementsWithId(
+    const nsIDocument* aDocument,
+    nsAtom* aId);
+
 } // extern "C"
 
 #endif // mozilla_ServoBindings_h
--- a/layout/style/ServoBindings.toml
+++ b/layout/style/ServoBindings.toml
@@ -470,16 +470,17 @@ structs-types = [
     "nsIDocument",
     "nsIDocument_DocumentTheme",
     "nsSimpleContentList",
     "RawGeckoAnimationPropertySegment",
     "RawGeckoComputedTiming",
     "RawGeckoCSSPropertyIDList",
     "RawGeckoDocument",
     "RawGeckoElement",
+    "Element",
     "RawGeckoKeyframeList",
     "RawGeckoPropertyValuePairList",
     "RawGeckoComputedKeyframeValuesList",
     "RawGeckoFontFaceRuleList",
     "RawGeckoNode",
     "RawServoAnimationValue",
     "RawGeckoServoAnimationValueList",
     "RawServoMediaList",
--- a/layout/style/test/chrome/chrome.ini
+++ b/layout/style/test/chrome/chrome.ini
@@ -10,16 +10,17 @@ support-files =
   mismatch.png
 
 [test_bug1346623.html]
 [test_bug1371453.html]
 [test_bug418986-2.xul]
 [test_bug1157097.html]
 [test_bug1160724.xul]
 [test_bug535806.xul]
+[test_chrome_only_media_queries.html]
 [test_display_mode.html]
 tags = fullscreen
 [test_display_mode_reflow.html]
 tags = fullscreen
 [test_hover.html]
 skip-if = stylo # bug 1346353
 [test_moz_document_rules.html]
 [test_stylesheet_clone_import_rule.html]
new file mode 100644
--- /dev/null
+++ b/layout/style/test/chrome/test_chrome_only_media_queries.html
@@ -0,0 +1,85 @@
+<!doctype html>
+<title>Test for parsing of non-content-exposed media-queries.</title>
+<script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+<style></style>
+<script>
+const SHEET = document.querySelector('style');
+
+function expect(q, shouldBeParseable) {
+  const NOT_PARSEABLE_TEXT = "@media screen, not all {\n}";
+
+  let text = "@media screen, " + q + " {\n}";
+  SHEET.textContent = text;
+
+  let rules = SHEET.sheet.cssRules;
+  is(rules.length, 1, `Rule not parsed for ${q}`);
+  is(rules[0].cssText,
+     shouldBeParseable ? text : NOT_PARSEABLE_TEXT,
+     `Serialization for ${q}`);
+}
+
+function expectParseable(q) {
+  expect(q, true);
+}
+
+function expectNonParseable(q) {
+  expect(q, false);
+}
+
+// Test a toggle that should always match for `1` or `0`.
+function testToggle(toggle) {
+  expectParseable(`(${toggle})`);
+  expectParseable(`(${toggle}: 1)`);
+  expectParseable(`(${toggle}: 0)`);
+
+  expectNonParseable(`(${toggle}: foo)`);
+  expectNonParseable(`(min-${toggle}: 0)`);
+  expectNonParseable(`(max-${toggle}: 0)`);
+  expectNonParseable(`(max-${toggle})`);
+  expectNonParseable(`(min-${toggle})`);
+
+  let matches_1 = matchMedia(`(${toggle}: 1)`).matches;
+  let matches_0 = matchMedia(`(${toggle}: 0)`).matches;
+  isnot(matches_0, matches_1, `Should not match both true and false: ${toggle}`);
+  is(matches_0 || matches_1, true, `Should match at least one: ${toggle}`);
+}
+
+const TOGGLES = [
+  "-moz-is-glyph",
+  "-moz-scrollbar-start-backward",
+  "-moz-scrollbar-start-forward",
+  "-moz-scrollbar-end-backward",
+  "-moz-scrollbar-end-forward",
+  "-moz-overlay-scrollbars",
+  "-moz-windows-default-theme",
+  "-moz-mac-graphite-theme",
+  "-moz-mac-yosemite-theme",
+  "-moz-windows-accent-color-in-titlebar",
+  "-moz-windows-compositor",
+  "-moz-windows-classic",
+  "-moz-windows-glass",
+  "-moz-swipe-animation-enabled",
+  "-moz-touch-enabled",
+];
+
+for (let i = 0; i < TOGGLES.length; ++i) {
+  testToggle(TOGGLES[i])
+}
+
+expectParseable("(-moz-windows-theme: aero)");
+expectParseable("(-moz-windows-theme: aero-lite)");
+expectParseable("(-moz-windows-theme: luna-blue)");
+expectParseable("(-moz-windows-theme: luna-olive)");
+expectParseable("(-moz-windows-theme: luna-silver)");
+expectParseable("(-moz-windows-theme: royale)");
+expectParseable("(-moz-windows-theme: generic)");
+expectParseable("(-moz-windows-theme: zune)");
+expectParseable("(-moz-windows-theme: garbage)");
+expectNonParseable("(-moz-windows-theme: '')");
+expectNonParseable("(-moz-windows-theme: )");
+
+expectParseable("(-moz-os-version: windows-win7)");
+expectParseable("(-moz-os-version: windows-win8)");
+expectParseable("(-moz-os-version: windows-win10)");
+expectNonParseable("(-moz-os-version: )");
+</script>
--- a/servo/components/layout_thread/dom_wrapper.rs
+++ b/servo/components/layout_thread/dom_wrapper.rs
@@ -208,16 +208,20 @@ impl<'ln> TNode for ServoLayoutNode<'ln>
     fn as_document(&self) -> Option<ServoLayoutDocument<'ln>> {
         self.node.downcast().map(ServoLayoutDocument::from_layout_js)
     }
 
     fn can_be_fragmented(&self) -> bool {
         unsafe { self.node.get_flag(NodeFlags::CAN_BE_FRAGMENTED) }
     }
 
+    fn is_in_document(&self) -> bool {
+        unsafe { self.node.get_flag(NodeFlags::IS_IN_DOC) }
+    }
+
     unsafe fn set_can_be_fragmented(&self, value: bool) {
         self.node.set_flag(NodeFlags::CAN_BE_FRAGMENTED, value)
     }
 }
 
 impl<'ln> LayoutNode for ServoLayoutNode<'ln> {
     type ConcreteThreadSafeLayoutNode = ServoThreadSafeLayoutNode<'ln>;
 
@@ -412,17 +416,17 @@ impl<'le> TElement for ServoLayoutElemen
         unsafe { self.as_node().node.get_flag(NodeFlags::HANDLED_SNAPSHOT) }
     }
 
     unsafe fn set_handled_snapshot(&self) {
         self.as_node().node.set_flag(NodeFlags::HANDLED_SNAPSHOT, true);
     }
 
     unsafe fn set_dirty_descendants(&self) {
-        debug_assert!(self.as_node().node.get_flag(NodeFlags::IS_IN_DOC));
+        debug_assert!(self.as_node().is_in_document());
         self.as_node().node.set_flag(NodeFlags::HAS_DIRTY_DESCENDANTS, true)
     }
 
     unsafe fn unset_dirty_descendants(&self) {
         self.as_node().node.set_flag(NodeFlags::HAS_DIRTY_DESCENDANTS, false)
     }
 
     fn store_children_to_process(&self, n: isize) {
--- a/servo/components/selectors/matching.rs
+++ b/servo/components/selectors/matching.rs
@@ -57,16 +57,17 @@ impl ElementSelectorFlags {
 }
 
 /// Holds per-compound-selector data.
 struct LocalMatchingContext<'a, 'b: 'a, Impl: SelectorImpl> {
     shared: &'a mut MatchingContext<'b, Impl>,
     matches_hover_and_active_quirk: bool,
 }
 
+#[inline(always)]
 pub fn matches_selector_list<E>(
     selector_list: &SelectorList<E::Impl>,
     element: &E,
     context: &mut MatchingContext<E::Impl>,
 ) -> bool
 where
     E: Element
 {
@@ -368,30 +369,31 @@ where
 
         from_offset += 1;
     }
 
     CompoundSelectorMatchingResult::FullyMatched
 }
 
 /// Matches a complex selector.
+#[inline(always)]
 pub fn matches_complex_selector<E, F>(
     mut iter: SelectorIter<E::Impl>,
     element: &E,
     context: &mut MatchingContext<E::Impl>,
     flags_setter: &mut F,
 ) -> bool
 where
     E: Element,
     F: FnMut(&E, ElementSelectorFlags),
 {
     // If this is the special pseudo-element mode, consume the ::pseudo-element
     // before proceeding, since the caller has already handled that part.
-    if context.nesting_level == 0 &&
-        context.matching_mode == MatchingMode::ForStatelessPseudoElement {
+    if context.matching_mode == MatchingMode::ForStatelessPseudoElement &&
+        context.nesting_level == 0 {
         // Consume the pseudo.
         match *iter.next().unwrap() {
             Component::PseudoElement(ref pseudo) => {
                 if let Some(ref f) = context.pseudo_element_matching_fn {
                     if !f(pseudo) {
                         return false;
                     }
                 }
@@ -566,22 +568,22 @@ where
     };
 
     let candidate_not_found = match combinator {
         Combinator::NextSibling |
         Combinator::LaterSibling => {
             // Only ancestor combinators are allowed while looking for
             // relevant links, so switch to not looking.
             *relevant_link = RelevantLinkStatus::NotLooking;
-             SelectorMatchingResult::NotMatchedAndRestartFromClosestDescendant
+            SelectorMatchingResult::NotMatchedAndRestartFromClosestDescendant
         }
         Combinator::Child |
         Combinator::Descendant |
         Combinator::PseudoElement => {
-             SelectorMatchingResult::NotMatchedGlobally
+            SelectorMatchingResult::NotMatchedGlobally
         }
     };
 
     let mut next_element = next_element_for_combinator(element, combinator);
 
     loop {
         let element = match next_element {
             None => return candidate_not_found,
--- a/servo/components/style/dom.rs
+++ b/servo/components/style/dom.rs
@@ -117,16 +117,17 @@ pub struct DomDescendants<N> {
 }
 
 impl<N> Iterator for DomDescendants<N>
 where
     N: TNode
 {
     type Item = N;
 
+    #[inline]
     fn next(&mut self) -> Option<N> {
         let prev = match self.previous.take() {
             None => return None,
             Some(n) => n,
         };
 
         self.previous = prev.next_in_preorder(Some(self.scope));
         self.previous
@@ -141,16 +142,28 @@ pub trait TDocument : Sized + Copy + Clo
     /// Get this document as a `TNode`.
     fn as_node(&self) -> Self::ConcreteNode;
 
     /// Returns whether this document is an HTML document.
     fn is_html_document(&self) -> bool;
 
     /// Returns the quirks mode of this document.
     fn quirks_mode(&self) -> QuirksMode;
+
+    /// Get a list of elements with a given ID in this document, sorted by
+    /// document position.
+    ///
+    /// Can return an error to signal that this list is not available, or also
+    /// return an empty slice.
+    fn elements_with_id(
+        &self,
+        _id: &Atom,
+    ) -> Result<&[<Self::ConcreteNode as TNode>::ConcreteElement], ()> {
+        Err(())
+    }
 }
 
 /// The `TNode` trait. This is the main generic trait over which the style
 /// system can be implemented.
 pub trait TNode : Sized + Copy + Clone + Debug + NodeInfo + PartialEq {
     /// The concrete `TElement` type.
     type ConcreteElement: TElement<ConcreteNode = Self>;
 
@@ -175,26 +188,30 @@ pub trait TNode : Sized + Copy + Clone +
     /// Get the owner document of this node.
     fn owner_doc(&self) -> Self::ConcreteDocument;
 
     /// Iterate over the DOM children of a node.
     fn dom_children(&self) -> DomChildren<Self> {
         DomChildren(self.first_child())
     }
 
+    /// Returns whether the node is attached to a document.
+    fn is_in_document(&self) -> bool;
+
     /// Iterate over the DOM children of a node, in preorder.
     fn dom_descendants(&self) -> DomDescendants<Self> {
         DomDescendants {
             previous: Some(*self),
             scope: *self,
         }
     }
 
     /// Returns the next children in pre-order, optionally scoped to a subtree
     /// root.
+    #[inline]
     fn next_in_preorder(&self, scoped_to: Option<Self>) -> Option<Self> {
         if let Some(c) = self.first_child() {
             return Some(c);
         }
 
         if Some(*self) == scoped_to {
             return None;
         }
--- a/servo/components/style/dom_apis.rs
+++ b/servo/components/style/dom_apis.rs
@@ -1,21 +1,25 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 //! Generic implementations of some DOM APIs so they can be shared between Servo
 //! and Gecko.
 
+use Atom;
 use context::QuirksMode;
 use dom::{TDocument, TElement, TNode};
 use invalidation::element::invalidator::{Invalidation, InvalidationProcessor, InvalidationVector};
 use selectors::{Element, NthIndexCache, SelectorList};
+use selectors::attr::CaseSensitivity;
 use selectors::matching::{self, MatchingContext, MatchingMode};
+use selectors::parser::{Combinator, Component, LocalName};
 use smallvec::SmallVec;
+use std::borrow::Borrow;
 
 /// <https://dom.spec.whatwg.org/#dom-element-matches>
 pub fn element_matches<E>(
     element: &E,
     selector_list: &SelectorList<E::Impl>,
     quirks_mode: QuirksMode,
 ) -> bool
 where
@@ -213,60 +217,132 @@ where
 
         Q::append_element(results, element);
         if Q::should_stop_after_first_match() {
             return;
         }
     }
 }
 
-/// Fast paths for a given selector query.
+/// Returns whether a given element is descendant of a given `root` node.
 ///
-/// FIXME(emilio, nbp): This may very well be a good candidate for code to be
-/// replaced by HolyJit :)
-fn query_selector_fast<E, Q>(
+/// NOTE(emilio): if root == element, this returns false.
+fn element_is_descendant_of<E>(element: E, root: E::ConcreteNode) -> bool
+where
+    E: TElement,
+{
+    if element.as_node().is_in_document() && root == root.owner_doc().as_node() {
+        return true;
+    }
+
+    let mut current = element.as_node().parent_node();
+    while let Some(n) = current.take() {
+        if n == root {
+            return true;
+        }
+
+        current = n.parent_node();
+    }
+    false
+}
+
+/// Fast path for iterating over every element with a given id in the document
+/// that `root` is connected to.
+fn fast_connected_elements_with_id<'a, D>(
+    doc: &'a D,
+    root: D::ConcreteNode,
+    id: &Atom,
+    quirks_mode: QuirksMode,
+) -> Result<&'a [<D::ConcreteNode as TNode>::ConcreteElement], ()>
+where
+    D: TDocument,
+{
+    debug_assert_eq!(root.owner_doc().as_node(), doc.as_node());
+
+    let case_sensitivity = quirks_mode.classes_and_ids_case_sensitivity();
+    if case_sensitivity != CaseSensitivity::CaseSensitive {
+        return Err(());
+    }
+
+    if !root.is_in_document() {
+        return Err(());
+    }
+
+    doc.elements_with_id(id)
+}
+
+/// Collects elements with a given id under `root`, that pass `filter`.
+fn collect_elements_with_id<E, Q, F>(
     root: E::ConcreteNode,
-    selector_list: &SelectorList<E::Impl>,
+    id: &Atom,
+    results: &mut Q::Output,
+    quirks_mode: QuirksMode,
+    mut filter: F,
+)
+where
+    E: TElement,
+    Q: SelectorQuery<E>,
+    F: FnMut(E) -> bool,
+{
+    let doc = root.owner_doc();
+    let elements = match fast_connected_elements_with_id(&doc, root, id, quirks_mode) {
+        Ok(elements) => elements,
+        Err(()) => {
+            let case_sensitivity =
+                quirks_mode.classes_and_ids_case_sensitivity();
+
+            collect_all_elements::<E, Q, _>(root, results, |e| {
+                e.has_id(id, case_sensitivity) && filter(e)
+            });
+
+            return;
+        }
+    };
+
+    for element in elements {
+        // If the element is not an actual descendant of the root, even though
+        // it's connected, we don't really care about it.
+        if !element_is_descendant_of(*element, root) {
+            continue;
+        }
+
+        if !filter(*element) {
+            continue;
+        }
+
+        Q::append_element(results, *element);
+        if Q::should_stop_after_first_match() {
+            break;
+        }
+    }
+}
+
+/// Fast paths for querySelector with a single simple selector.
+fn query_selector_single_query<E, Q>(
+    root: E::ConcreteNode,
+    component: &Component<E::Impl>,
     results: &mut Q::Output,
     quirks_mode: QuirksMode,
 ) -> Result<(), ()>
 where
     E: TElement,
     Q: SelectorQuery<E>,
 {
-    use selectors::parser::{Component, LocalName};
-    use std::borrow::Borrow;
-
-    // We need to return elements in document order, and reordering them
-    // afterwards is kinda silly.
-    if selector_list.0.len() > 1 {
-        return Err(());
-    }
-
-    let selector = &selector_list.0[0];
-
-    // Let's just care about the easy cases for now.
-    //
-    // FIXME(emilio): Blink has a fast path for classes in ancestor combinators
-    // that may be worth stealing.
-    if selector.len() > 1 {
-        return Err(());
-    }
-
-    let component = selector.iter().next().unwrap();
     match *component {
         Component::ExplicitUniversalType => {
             collect_all_elements::<E, Q, _>(root, results, |_| true)
         }
         Component::ID(ref id) => {
-            // TODO(emilio): We may want to reuse Gecko's document ID table.
-            let case_sensitivity = quirks_mode.classes_and_ids_case_sensitivity();
-            collect_all_elements::<E, Q, _>(root, results, |element| {
-                element.has_id(id, case_sensitivity)
-            })
+            collect_elements_with_id::<E, Q, _>(
+                root,
+                id,
+                results,
+                quirks_mode,
+                |_| true,
+            );
         }
         Component::Class(ref class) => {
             let case_sensitivity = quirks_mode.classes_and_ids_case_sensitivity();
             collect_all_elements::<E, Q, _>(root, results, |element| {
                 element.has_class(class, case_sensitivity)
             })
         }
         Component::LocalName(LocalName { ref name, ref lower_name }) => {
@@ -282,77 +358,226 @@ where
         _ => {
             return Err(())
         }
     }
 
     Ok(())
 }
 
+/// Fast paths for a given selector query.
+///
+/// FIXME(emilio, nbp): This may very well be a good candidate for code to be
+/// replaced by HolyJit :)
+fn query_selector_fast<E, Q>(
+    root: E::ConcreteNode,
+    selector_list: &SelectorList<E::Impl>,
+    results: &mut Q::Output,
+    matching_context: &mut MatchingContext<E::Impl>,
+) -> Result<(), ()>
+where
+    E: TElement,
+    Q: SelectorQuery<E>,
+{
+    // We need to return elements in document order, and reordering them
+    // afterwards is kinda silly.
+    if selector_list.0.len() > 1 {
+        return Err(());
+    }
+
+    let selector = &selector_list.0[0];
+    let quirks_mode = matching_context.quirks_mode();
+
+    // Let's just care about the easy cases for now.
+    if selector.len() == 1 {
+        return query_selector_single_query::<E, Q>(
+            root,
+            selector.iter().next().unwrap(),
+            results,
+            quirks_mode,
+        );
+    }
+
+    let mut iter = selector.iter();
+    let mut combinator: Option<Combinator> = None;
+
+    loop {
+        debug_assert!(combinator.map_or(true, |c| !c.is_sibling()));
+
+        'component_loop: for component in &mut iter {
+            match *component {
+                Component::ID(ref id) => {
+                    if combinator.is_none() {
+                        // In the rightmost compound, just find descendants of
+                        // root that match the selector list with that id.
+                        collect_elements_with_id::<E, Q, _>(
+                            root,
+                            id,
+                            results,
+                            quirks_mode,
+                            |e| {
+                                matching::matches_selector_list(
+                                    selector_list,
+                                    &e,
+                                    matching_context,
+                                )
+                            }
+                        );
+
+                        return Ok(());
+                    }
+
+                    let doc = root.owner_doc();
+                    let elements =
+                        fast_connected_elements_with_id(&doc, root, id, quirks_mode)?;
+
+                    if elements.is_empty() {
+                        return Ok(());
+                    }
+
+                    // Results need to be in document order. Let's not bother
+                    // reordering or deduplicating nodes, which we would need to
+                    // do if one element with the given id were a descendant of
+                    // another element with that given id.
+                    if !Q::should_stop_after_first_match() && elements.len() > 1 {
+                        continue;
+                    }
+
+                    for element in elements {
+                        // If the element is not a descendant of the root, then
+                        // it may have descendants that match our selector that
+                        // _are_ descendants of the root, and other descendants
+                        // that match our selector that are _not_.
+                        //
+                        // So we can't just walk over the element's descendants
+                        // and match the selector against all of them, nor can
+                        // we skip looking at this element's descendants.
+                        //
+                        // Give up on trying to optimize based on this id and
+                        // keep walking our selector.
+                        if !element_is_descendant_of(*element, root) {
+                            continue 'component_loop;
+                        }
+
+                        query_selector_slow::<E, Q>(
+                            element.as_node(),
+                            selector_list,
+                            results,
+                            matching_context,
+                        );
+
+                        if Q::should_stop_after_first_match() && !Q::is_empty(&results) {
+                            break;
+                        }
+                    }
+
+                    return Ok(());
+                }
+                _ => {},
+            }
+        }
+
+        loop {
+            let next_combinator = match iter.next_sequence() {
+                None => return Err(()),
+                Some(c) => c,
+            };
+
+            // We don't want to scan stuff affected by sibling combinators,
+            // given we scan the subtree of elements with a given id (and we
+            // don't want to care about scanning the siblings' subtrees).
+            if next_combinator.is_sibling() {
+                // Advance to the next combinator.
+                for _ in &mut iter {}
+                continue;
+            }
+
+            combinator = Some(next_combinator);
+            break;
+        }
+    }
+}
+
 // Slow path for a given selector query.
 fn query_selector_slow<E, Q>(
     root: E::ConcreteNode,
     selector_list: &SelectorList<E::Impl>,
     results: &mut Q::Output,
     matching_context: &mut MatchingContext<E::Impl>,
 )
 where
     E: TElement,
     Q: SelectorQuery<E>,
 {
     collect_all_elements::<E, Q, _>(root, results, |element| {
         matching::matches_selector_list(selector_list, &element, matching_context)
     });
 }
 
+/// Whether the invalidation machinery should be used for this query.
+#[derive(PartialEq)]
+pub enum MayUseInvalidation {
+    /// We may use it if we deem it useful.
+    Yes,
+    /// Don't use it.
+    No,
+}
+
 /// <https://dom.spec.whatwg.org/#dom-parentnode-queryselector>
 pub fn query_selector<E, Q>(
     root: E::ConcreteNode,
     selector_list: &SelectorList<E::Impl>,
     results: &mut Q::Output,
+    may_use_invalidation: MayUseInvalidation,
 )
 where
     E: TElement,
     Q: SelectorQuery<E>,
 {
     use invalidation::element::invalidator::TreeStyleInvalidator;
 
     let quirks_mode = root.owner_doc().quirks_mode();
-    let fast_result = query_selector_fast::<E, Q>(
-        root,
-        selector_list,
-        results,
-        quirks_mode,
-    );
 
-    if fast_result.is_ok() {
-        return;
-    }
-
-    // Slow path: Use the invalidation machinery if we're a root, and tree
-    // traversal otherwise.
-    //
-    // See the comment in collect_invalidations to see why only if we're a root.
     let mut nth_index_cache = NthIndexCache::default();
     let mut matching_context = MatchingContext::new(
         MatchingMode::Normal,
         None,
         Some(&mut nth_index_cache),
         quirks_mode,
     );
 
     let root_element = root.as_element();
     matching_context.scope_element = root_element.map(|e| e.opaque());
 
+    let fast_result = query_selector_fast::<E, Q>(
+        root,
+        selector_list,
+        results,
+        &mut matching_context,
+    );
+
+    if fast_result.is_ok() {
+        return;
+    }
+
+    // Slow path: Use the invalidation machinery if we're a root, and tree
+    // traversal otherwise.
+    //
+    // See the comment in collect_invalidations to see why only if we're a root.
+    //
     // The invalidation mechanism is only useful in presence of combinators.
     //
     // We could do that check properly here, though checking the length of the
     // selectors is a good heuristic.
+    //
+    // A selector with a combinator needs to have a length of at least 3: A
+    // simple selector, a combinator, and another simple selector.
     let invalidation_may_be_useful =
-        selector_list.0.iter().any(|s| s.len() > 1);
+        may_use_invalidation == MayUseInvalidation::Yes &&
+        selector_list.0.iter().any(|s| s.len() > 2);
 
     if root_element.is_some() || !invalidation_may_be_useful {
         query_selector_slow::<E, Q>(
             root,
             selector_list,
             results,
             &mut matching_context,
         );
--- a/servo/components/style/gecko/generated/bindings.rs
+++ b/servo/components/style/gecko/generated/bindings.rs
@@ -25,16 +25,17 @@ use gecko_bindings::structs::nsIContent;
 use gecko_bindings::structs::nsIDocument;
 use gecko_bindings::structs::nsIDocument_DocumentTheme;
 use gecko_bindings::structs::nsSimpleContentList;
 use gecko_bindings::structs::RawGeckoAnimationPropertySegment;
 use gecko_bindings::structs::RawGeckoComputedTiming;
 use gecko_bindings::structs::RawGeckoCSSPropertyIDList;
 use gecko_bindings::structs::RawGeckoDocument;
 use gecko_bindings::structs::RawGeckoElement;
+use gecko_bindings::structs::Element;
 use gecko_bindings::structs::RawGeckoKeyframeList;
 use gecko_bindings::structs::RawGeckoPropertyValuePairList;
 use gecko_bindings::structs::RawGeckoComputedKeyframeValuesList;
 use gecko_bindings::structs::RawGeckoFontFaceRuleList;
 use gecko_bindings::structs::RawGeckoNode;
 use gecko_bindings::structs::RawServoAnimationValue;
 use gecko_bindings::structs::RawGeckoServoAnimationValueList;
 use gecko_bindings::structs::RawServoMediaList;
@@ -1205,32 +1206,34 @@ extern "C" {
  pub fn Servo_StyleSet_ResolveForDeclarations ( set : RawServoStyleSetBorrowed , parent_style : ServoStyleContextBorrowedOrNull , declarations : RawServoDeclarationBlockBorrowed , ) -> ServoStyleContextStrong ; 
 } extern "C" {
  pub fn Servo_SelectorList_Parse ( selector_list : * const nsACString , ) -> * mut RawServoSelectorList ; 
 } extern "C" {
  pub fn Servo_SelectorList_Matches ( arg1 : RawGeckoElementBorrowed , arg2 : RawServoSelectorListBorrowed , ) -> bool ; 
 } extern "C" {
  pub fn Servo_SelectorList_Closest ( arg1 : RawGeckoElementBorrowed , arg2 : RawServoSelectorListBorrowed , ) -> * const RawGeckoElement ; 
 } extern "C" {
- pub fn Servo_SelectorList_QueryFirst ( arg1 : RawGeckoNodeBorrowed , arg2 : RawServoSelectorListBorrowed , ) -> * const RawGeckoElement ; 
+ pub fn Servo_SelectorList_QueryFirst ( arg1 : RawGeckoNodeBorrowed , arg2 : RawServoSelectorListBorrowed , may_use_invalidation : bool , ) -> * const RawGeckoElement ; 
 } extern "C" {
- pub fn Servo_SelectorList_QueryAll ( arg1 : RawGeckoNodeBorrowed , arg2 : RawServoSelectorListBorrowed , content_list : * mut nsSimpleContentList , ) ; 
+ pub fn Servo_SelectorList_QueryAll ( arg1 : RawGeckoNodeBorrowed , arg2 : RawServoSelectorListBorrowed , content_list : * mut nsSimpleContentList , may_use_invalidation : bool , ) ; 
 } extern "C" {
  pub fn Servo_StyleSet_AddSizeOfExcludingThis ( malloc_size_of : MallocSizeOf , malloc_enclosing_size_of : MallocSizeOf , sizes : * mut ServoStyleSetSizes , set : RawServoStyleSetBorrowed , ) ; 
 } extern "C" {
  pub fn Servo_UACache_AddSizeOf ( malloc_size_of : MallocSizeOf , malloc_enclosing_size_of : MallocSizeOf , sizes : * mut ServoStyleSetSizes , ) ; 
 } extern "C" {
  pub fn Servo_StyleContext_AddRef ( ctx : ServoStyleContextBorrowed , ) ; 
 } extern "C" {
  pub fn Servo_StyleContext_Release ( ctx : ServoStyleContextBorrowed , ) ; 
 } extern "C" {
  pub fn Servo_StyleSet_MightHaveAttributeDependency ( set : RawServoStyleSetBorrowed , element : RawGeckoElementBorrowed , local_name : * mut nsAtom , ) -> bool ; 
 } extern "C" {
  pub fn Servo_StyleSet_HasStateDependency ( set : RawServoStyleSetBorrowed , element : RawGeckoElementBorrowed , state : u64 , ) -> bool ; 
 } extern "C" {
+ pub fn Servo_StyleSet_HasDocumentStateDependency ( set : RawServoStyleSetBorrowed , state : u64 , ) -> bool ; 
+} extern "C" {
  pub fn Servo_CssRules_ListTypes ( rules : ServoCssRulesBorrowed , result : nsTArrayBorrowed_uintptr_t , ) ; 
 } extern "C" {
  pub fn Servo_CssRules_InsertRule ( rules : ServoCssRulesBorrowed , sheet : RawServoStyleSheetContentsBorrowed , rule : * const nsACString , index : u32 , nested : bool , loader : * mut Loader , gecko_stylesheet : * mut ServoStyleSheet , rule_type : * mut u16 , ) -> nsresult ; 
 } extern "C" {
  pub fn Servo_CssRules_DeleteRule ( rules : ServoCssRulesBorrowed , index : u32 , ) -> nsresult ; 
 } extern "C" {
  pub fn Servo_CssRules_GetStyleRuleAt ( rules : ServoCssRulesBorrowed , index : u32 , line : * mut u32 , column : * mut u32 , ) -> RawServoStyleRuleStrong ; 
 } extern "C" {
@@ -1568,9 +1571,11 @@ extern "C" {
 } extern "C" {
  pub fn Gecko_CreateCSSErrorReporter ( sheet : * mut ServoStyleSheet , loader : * mut Loader , uri : * mut nsIURI , ) -> * mut ErrorReporter ; 
 } extern "C" {
  pub fn Gecko_DestroyCSSErrorReporter ( reporter : * mut ErrorReporter , ) ; 
 } extern "C" {
  pub fn Gecko_ReportUnexpectedCSSError ( reporter : * mut ErrorReporter , message : * const :: std :: os :: raw :: c_char , param : * const :: std :: os :: raw :: c_char , paramLen : u32 , prefix : * const :: std :: os :: raw :: c_char , prefixParam : * const :: std :: os :: raw :: c_char , prefixParamLen : u32 , suffix : * const :: std :: os :: raw :: c_char , source : * const :: std :: os :: raw :: c_char , sourceLen : u32 , lineNumber : u32 , colNumber : u32 , ) ; 
 } extern "C" {
  pub fn Gecko_ContentList_AppendAll ( aContentList : * mut nsSimpleContentList , aElements : * mut * const RawGeckoElement , aLength : usize , ) ; 
+} extern "C" {
+ pub fn Gecko_GetElementsWithId ( aDocument : * const nsIDocument , aId : * mut nsAtom , ) -> * const nsTArray < * mut Element > ; 
 }
\ No newline at end of file
--- a/servo/components/style/gecko/wrapper.rs
+++ b/servo/components/style/gecko/wrapper.rs
@@ -105,16 +105,36 @@ impl<'ld> TDocument for GeckoDocument<'l
 
     fn is_html_document(&self) -> bool {
         self.0.mType == structs::root::nsIDocument_Type::eHTML
     }
 
     fn quirks_mode(&self) -> QuirksMode {
         self.0.mCompatMode.into()
     }
+
+    fn elements_with_id(&self, id: &Atom) -> Result<&[GeckoElement<'ld>], ()> {
+        unsafe {
+            let array = bindings::Gecko_GetElementsWithId(self.0, id.as_ptr());
+            if array.is_null() {
+                return Ok(&[]);
+            }
+
+            let elements: &[*mut RawGeckoElement] = &**array;
+
+            // NOTE(emilio): We rely on the in-memory representation of
+            // GeckoElement<'ld> and *mut RawGeckoElement being the same.
+            #[allow(dead_code)]
+            unsafe fn static_assert() {
+                mem::transmute::<*mut RawGeckoElement, GeckoElement<'static>>(0xbadc0de as *mut _);
+            }
+
+            Ok(mem::transmute(elements))
+        }
+    }
 }
 
 /// A simple wrapper over a non-null Gecko node (`nsINode`) pointer.
 ///
 /// Important: We don't currently refcount the DOM, because the wrapper lifetime
 /// magic guarantees that our LayoutFoo references won't outlive the root, and
 /// we don't mutate any of the references on the Gecko side during restyle.
 ///
@@ -237,16 +257,17 @@ impl<'ln> NodeInfo for GeckoNode<'ln> {
         self.node_info().mInner.mNodeType == TEXT_NODE
     }
 }
 
 impl<'ln> TNode for GeckoNode<'ln> {
     type ConcreteDocument = GeckoDocument<'ln>;
     type ConcreteElement = GeckoElement<'ln>;
 
+    #[inline]
     fn parent_node(&self) -> Option<Self> {
         unsafe { self.0.mParent.as_ref().map(GeckoNode) }
     }
 
     #[inline]
     fn first_child(&self) -> Option<Self> {
         unsafe { self.0.mFirstChild.as_ref().map(GeckoNode::from_content) }
     }
@@ -267,16 +288,21 @@ impl<'ln> TNode for GeckoNode<'ln> {
     }
 
     #[inline]
     fn owner_doc(&self) -> Self::ConcreteDocument {
         debug_assert!(!self.node_info().mDocument.is_null());
         GeckoDocument(unsafe { &*self.node_info().mDocument })
     }
 
+    #[inline]
+    fn is_in_document(&self) -> bool {
+        self.get_bool_flag(nsINode_BooleanFlag::IsInDocument)
+    }
+
     fn traversal_parent(&self) -> Option<GeckoElement<'ln>> {
         self.flattened_tree_parent().and_then(|n| n.as_element())
     }
 
     fn opaque(&self) -> OpaqueNode {
         let ptr: usize = self.0 as *const _ as usize;
         OpaqueNode(ptr)
     }
@@ -1737,65 +1763,71 @@ impl<'le> Hash for GeckoElement<'le> {
     fn hash<H: Hasher>(&self, state: &mut H) {
         (self.0 as *const _).hash(state);
     }
 }
 
 impl<'le> ::selectors::Element for GeckoElement<'le> {
     type Impl = SelectorImpl;
 
+    #[inline]
     fn opaque(&self) -> OpaqueElement {
         OpaqueElement::new(self.0)
     }
 
+    #[inline]
     fn parent_element(&self) -> Option<Self> {
         // FIXME(emilio): This will need to jump across if the parent node is a
         // shadow root to get the shadow host.
         let parent_node = self.as_node().parent_node();
         parent_node.and_then(|n| n.as_element())
     }
 
     fn pseudo_element_originating_element(&self) -> Option<Self> {
         debug_assert!(self.implemented_pseudo_element().is_some());
         self.closest_non_native_anonymous_ancestor()
     }
 
+    #[inline]
     fn first_child_element(&self) -> Option<Self> {
         let mut child = self.as_node().first_child();
         while let Some(child_node) = child {
             if let Some(el) = child_node.as_element() {
                 return Some(el)
             }
             child = child_node.next_sibling();
         }
         None
     }
 
+    #[inline]
     fn last_child_element(&self) -> Option<Self> {
         let mut child = self.as_node().last_child();
         while let Some(child_node) = child {
             if let Some(el) = child_node.as_element() {
                 return Some(el)
             }
             child = child_node.prev_sibling();
         }
         None
     }
 
+    #[inline]
     fn prev_sibling_element(&self) -> Option<Self> {
         let mut sibling = self.as_node().prev_sibling();
         while let Some(sibling_node) = sibling {
             if let Some(el) = sibling_node.as_element() {
                 return Some(el)
             }
             sibling = sibling_node.prev_sibling();
         }
         None
     }
 
+    #[inline]
     fn next_sibling_element(&self) -> Option<Self> {
         let mut sibling = self.as_node().next_sibling();
         while let Some(sibling_node) = sibling {
             if let Some(el) = sibling_node.as_element() {
                 return Some(el)
             }
             sibling = sibling_node.next_sibling();
         }
@@ -1877,22 +1909,24 @@ impl<'le> ::selectors::Element for Gecko
     }
 
     fn is_empty(&self) -> bool {
         !self.as_node().dom_children().any(|child| unsafe {
             Gecko_IsSignificantChild(child.0, true, true)
         })
     }
 
+    #[inline]
     fn get_local_name(&self) -> &WeakAtom {
         unsafe {
             WeakAtom::new(self.as_node().node_info().mInner.mName)
         }
     }
 
+    #[inline]
     fn get_namespace(&self) -> &WeakNamespace {
         unsafe {
             WeakNamespace::new(Gecko_Namespace(self.0))
         }
     }
 
     fn match_non_ts_pseudo_class<F>(
         &self,
@@ -2049,52 +2083,57 @@ impl<'le> ::selectors::Element for Gecko
         }
     }
 
     #[inline]
     fn is_link(&self) -> bool {
         self.get_state().intersects(NonTSPseudoClass::AnyLink.state_flag())
     }
 
+    #[inline]
     fn has_id(&self, id: &Atom, case_sensitivity: CaseSensitivity) -> bool {
         if !self.has_id() {
             return false
         }
 
         unsafe {
             let ptr = bindings::Gecko_AtomAttrValue(self.0, atom!("id").as_ptr());
 
             if ptr.is_null() {
                 false
             } else {
                 case_sensitivity.eq_atom(WeakAtom::new(ptr), id)
             }
         }
     }
 
+    #[inline]
     fn has_class(&self, name: &Atom, case_sensitivity: CaseSensitivity) -> bool {
         if !self.may_have_class() {
             return false;
         }
 
         snapshot_helpers::has_class(self.0,
                                     name,
                                     case_sensitivity,
                                     Gecko_ClassOrClassList)
     }
 
+    #[inline]
     fn is_html_element_in_html_document(&self) -> bool {
         self.is_html_element() &&
         self.as_node().owner_doc().is_html_document()
     }
 
+    #[inline]
     fn ignores_nth_child_selectors(&self) -> bool {
         self.is_root_of_anonymous_subtree()
     }
 
+    #[inline]
     fn blocks_ancestor_combinators(&self) -> bool {
         if !self.is_root_of_anonymous_subtree() {
             return false
         }
 
         match self.parent_element() {
             Some(e) => {
                 // If this element is the shadow root of an use-element shadow
--- a/servo/components/style/invalidation/element/invalidator.rs
+++ b/servo/components/style/invalidation/element/invalidator.rs
@@ -312,17 +312,17 @@ where
         }
 
         any_invalidated
     }
 
     fn invalidate_pseudo_element_or_nac(
         &mut self,
         child: E,
-        invalidations: &InvalidationVector<'b>,
+        invalidations: &[Invalidation<'b>],
     ) -> bool {
         let mut sibling_invalidations = InvalidationVector::new();
 
         let result = self.invalidate_child(
             child,
             invalidations,
             &mut sibling_invalidations
         );
@@ -337,17 +337,17 @@ where
         result
     }
 
     /// Invalidate a child and recurse down invalidating its descendants if
     /// needed.
     fn invalidate_child(
         &mut self,
         child: E,
-        invalidations: &InvalidationVector<'b>,
+        invalidations: &[Invalidation<'b>],
         sibling_invalidations: &mut InvalidationVector<'b>,
     ) -> bool {
         let mut invalidations_for_descendants = InvalidationVector::new();
 
         let mut invalidated_child = false;
         let invalidated_descendants = {
             let mut child_invalidator = TreeStyleInvalidator::new(
                 child,
@@ -384,17 +384,17 @@ where
             self.processor.invalidated_descendants(self.element, child);
         }
 
         invalidated_child || invalidated_descendants
     }
 
     fn invalidate_nac(
         &mut self,
-        invalidations: &InvalidationVector<'b>,
+        invalidations: &[Invalidation<'b>],
     ) -> bool {
         let mut any_nac_root = false;
 
         let element = self.element;
         element.each_anonymous_content_child(|nac| {
             any_nac_root |=
                 self.invalidate_pseudo_element_or_nac(nac, invalidations);
         });
@@ -402,17 +402,17 @@ where
         any_nac_root
     }
 
     // NB: It's important that this operates on DOM children, which is what
     // selector-matching operates on.
     fn invalidate_dom_descendants_of(
         &mut self,
         parent: E::ConcreteNode,
-        invalidations: &InvalidationVector<'b>,
+        invalidations: &[Invalidation<'b>],
     ) -> bool {
         let mut any_descendant = false;
 
         let mut sibling_invalidations = InvalidationVector::new();
         for child in parent.dom_children() {
             // TODO(emilio): We handle <xbl:children> fine, because they appear
             // in selector-matching (note bug 1374247, though).
             //
@@ -436,17 +436,17 @@ where
 
         any_descendant
     }
 
     /// Given a descendant invalidation list, go through the current element's
     /// descendants, and invalidate style on them.
     fn invalidate_descendants(
         &mut self,
-        invalidations: &InvalidationVector<'b>,
+        invalidations: &[Invalidation<'b>],
     ) -> bool {
         if invalidations.is_empty() {
             return false;
         }
 
         debug!("StyleTreeInvalidator::invalidate_descendants({:?})",
                self.element);
         debug!(" > {:?}", invalidations);
@@ -541,17 +541,17 @@ where
 
     /// Process a given invalidation list coming from our parent,
     /// adding to `descendant_invalidations` and `sibling_invalidations` as
     /// needed.
     ///
     /// Returns whether our style was invalidated as a result.
     fn process_descendant_invalidations(
         &mut self,
-        invalidations: &InvalidationVector<'b>,
+        invalidations: &[Invalidation<'b>],
         descendant_invalidations: &mut InvalidationVector<'b>,
         sibling_invalidations: &mut InvalidationVector<'b>,
     ) -> bool {
         let mut invalidated = false;
 
         for invalidation in invalidations {
             let result = self.process_invalidation(
                 invalidation,
--- a/servo/ports/geckolib/glue.rs
+++ b/servo/ports/geckolib/glue.rs
@@ -1688,50 +1688,69 @@ pub unsafe extern "C" fn Servo_SelectorL
         quirks_mode,
     )
 }
 
 #[no_mangle]
 pub unsafe extern "C" fn Servo_SelectorList_QueryFirst(
     node: RawGeckoNodeBorrowed,
     selectors: RawServoSelectorListBorrowed,
+    may_use_invalidation: bool,
 ) -> *const structs::RawGeckoElement {
     use std::borrow::Borrow;
-    use style::dom_apis::{self, QueryFirst};
+    use style::dom_apis::{self, MayUseInvalidation, QueryFirst};
 
     let node = GeckoNode(node);
     let selectors = ::selectors::SelectorList::from_ffi(selectors).borrow();
     let mut result = None;
+
+    let may_use_invalidation =
+        if may_use_invalidation {
+            MayUseInvalidation::Yes
+        } else {
+            MayUseInvalidation::No
+        };
+
     dom_apis::query_selector::<GeckoElement, QueryFirst>(
         node,
         &selectors,
         &mut result,
+        may_use_invalidation,
     );
 
     result.map_or(ptr::null(), |e| e.0)
 }
 
 #[no_mangle]
 pub unsafe extern "C" fn Servo_SelectorList_QueryAll(
     node: RawGeckoNodeBorrowed,
     selectors: RawServoSelectorListBorrowed,
     content_list: *mut structs::nsSimpleContentList,
+    may_use_invalidation: bool,
 ) {
     use smallvec::SmallVec;
     use std::borrow::Borrow;
-    use style::dom_apis::{self, QueryAll};
+    use style::dom_apis::{self, MayUseInvalidation, QueryAll};
 
     let node = GeckoNode(node);
     let selectors = ::selectors::SelectorList::from_ffi(selectors).borrow();
     let mut result = SmallVec::new();
 
+    let may_use_invalidation =
+        if may_use_invalidation {
+            MayUseInvalidation::Yes
+        } else {
+            MayUseInvalidation::No
+        };
+
     dom_apis::query_selector::<GeckoElement, QueryAll>(
         node,
         &selectors,
         &mut result,
+        may_use_invalidation,
     );
 
     if !result.is_empty() {
         // NOTE(emilio): This relies on a slice of GeckoElement having the same
         // memory representation than a slice of element pointers.
         bindings::Gecko_ContentList_AppendAll(
             content_list,
             result.as_ptr() as *mut *const _,
--- a/testing/web-platform/meta/MANIFEST.json
+++ b/testing/web-platform/meta/MANIFEST.json
@@ -104164,16 +104164,28 @@
       [
        "/css/css-display-3/display-contents-multicol-001-ref.html",
        "=="
       ]
      ],
      {}
     ]
    ],
+   "css/css-display-3/display-contents-dynamic-pseudo-insertion-001.html": [
+    [
+     "/css/css-display-3/display-contents-dynamic-pseudo-insertion-001.html",
+     [
+      [
+       "/css/css-display-3/display-contents-dynamic-pseudo-insertion-001-ref.html",
+       "=="
+      ]
+     ],
+     {}
+    ]
+   ],
    "css/css-display-3/display-contents-dynamic-table-001-inline.html": [
     [
      "/css/css-display-3/display-contents-dynamic-table-001-inline.html",
      [
       [
        "/css/css-display-3/display-contents-table-001-ref.html",
        "=="
       ]
@@ -229639,16 +229651,21 @@
      {}
     ]
    ],
    "css/css-display-3/display-contents-dynamic-generated-content-fieldset-001-ref.html": [
     [
      {}
     ]
    ],
+   "css/css-display-3/display-contents-dynamic-pseudo-insertion-001-ref.html": [
+    [
+     {}
+    ]
+   ],
    "css/css-display-3/display-contents-flex-001-ref.html": [
     [
      {}
     ]
    ],
    "css/css-display-3/display-contents-flex-002-ref.html": [
     [
      {}
@@ -480800,16 +480817,24 @@
   "css/css-display-3/display-contents-dynamic-multicol-001-inline.html": [
    "e5f365024f14c246ed8e9410e1f08e5d48b1c3ff",
    "reftest"
   ],
   "css/css-display-3/display-contents-dynamic-multicol-001-none.html": [
    "4fe143c25be9ecc08e58428f0c588f72c5cb8a3f",
    "reftest"
   ],
+  "css/css-display-3/display-contents-dynamic-pseudo-insertion-001-ref.html": [
+   "af3b16fc8d05be3ab16d65a2f668233668a23583",
+   "support"
+  ],
+  "css/css-display-3/display-contents-dynamic-pseudo-insertion-001.html": [
+   "221fb940d34860f62541ad53bb4b3834f3552577",
+   "reftest"
+  ],
   "css/css-display-3/display-contents-dynamic-table-001-inline.html": [
    "5c105a711c083e61eefc4121c707d3ca2ffda3ec",
    "reftest"
   ],
   "css/css-display-3/display-contents-dynamic-table-001-none.html": [
    "77723986b19e8c6ed8f66cdd92670eec724bf48d",
    "reftest"
   ],
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/css/css-display-3/display-contents-dynamic-pseudo-insertion-001-ref.html
@@ -0,0 +1,5 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>CSS Test Reference</title>
+<link rel="author" title="Emilio Cobos Álvarez" href="mailto:emilio@crisal.io">
+PASS
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/css/css-display-3/display-contents-dynamic-pseudo-insertion-001.html
@@ -0,0 +1,26 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>CSS Test: Dynamic insertion on empty display: contents element with pseudo-elements</title>
+<link rel="author" title="Emilio Cobos Álvarez" href="mailto:emilio@crisal.io">
+<link rel="help" href="https://drafts.csswg.org/css-display-3/#valdef-display-contents">
+<link rel=match href="display-contents-dynamic-pseudo-insertion-001-ref.html">
+<style>
+.contents {
+  display: contents;
+  border: 10px solid red;
+}
+.contents::before {
+  content: "A";
+}
+.contents::after {
+  content: "SS";
+}
+</style>
+<div class="contents"></div>
+<script>
+document.body.offsetTop;
+let span = document.createElement('span');
+span.innerHTML = "P";
+let contents = document.querySelector('.contents');
+contents.parentNode.insertBefore(span, contents);
+</script>
--- a/toolkit/content/tests/widgets/test_videocontrols_error.html
+++ b/toolkit/content/tests/widgets/test_videocontrols_error.html
@@ -1,66 +1,57 @@
 <!DOCTYPE HTML>
 <html>
 <head>
   <title>Video controls test - Error</title>
   <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
   <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+  <script type="text/javascript" src="/tests/SimpleTest/SpawnTask.js"></script>
   <script type="text/javascript" src="head.js"></script>
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
 </head>
 <body>
 <p id="display"></p>
 
 <div id="content">
   <video id="video" controls preload="auto"></video>
 </div>
 
 <pre id="test">
 <script clas="testbody" type="application/javascript">
-  SimpleTest.waitForExplicitFinish();
-
   const video = document.getElementById("video");
   const statusOverlay = getAnonElementWithinVideoByAttribute(video, "anonid", "statusOverlay");
   const statusIcon = getAnonElementWithinVideoByAttribute(video, "anonid", "statusIcon");
+  const clickToPlay = getAnonElementWithinVideoByAttribute(video, "anonid", "clickToPlay");
 
-  const testCases = [];
+  add_task(async function setup() {
+    await new Promise(resolve => window.addEventListener("load", resolve, {once: true}));
+    await SpecialPowers.pushPrefEnv({"set": [["media.cache_size", 40000]]});
+  });
 
-  testCases.push(() => new Promise(resolve => {
+  add_task(async function check_normal_status() {
+    await new Promise(resolve => {
+      video.src = "seek_with_sound.ogg";
+      video.addEventListener("loadedmetadata", resolve);
+    });
+
     ok(statusOverlay.hidden, "statusOverlay shoud not present without error");
     ok(!statusOverlay.hasAttribute("error"), "statusOverlay should not in error state");
     isnot(statusIcon.getAttribute("type"), "error", "should not show error icon");
-
-    resolve();
-  }));
+  });
 
-  testCases.push(() => new Promise(resolve => {
-    video.src = "invalid-path.ogg";
-    video.addEventListener("error", resolve);
-  }));
-
-  testCases.push(() => new Promise(resolve => {
+  add_task(async function invalid_source() {
     const errorType = "errorNoSource";
 
+    await new Promise(resolve => {
+      video.src = "invalid_source.ogg";
+      video.addEventListener("error", resolve)
+    });
+
+    ok(clickToPlay.hasAttribute("hidden"), `click to play button should hide`);
     ok(!statusOverlay.hidden, `statusOverlay should show when ${errorType}`);
     is(statusOverlay.getAttribute("error"), errorType, `statusOverlay should have correct error state: ${errorType}`);
     is(statusIcon.getAttribute("type"), "error", `should show error icon when ${errorType}`);
-
-    resolve();
-  }));
-
-  function executeTestCases(tasks) {
-    return tasks.reduce((promise, task) => promise.then(task), Promise.resolve());
-  }
-
-  function startTest() {
-    executeTestCases(testCases).then(SimpleTest.finish);
-  }
-
-  function loadevent() {
-    SpecialPowers.pushPrefEnv({"set": [["media.cache_size", 40000]]}, startTest);
-  }
-
-  window.addEventListener("load", loadevent);
+  });
 </script>
 </pre>
 </body>
 </html>
--- a/toolkit/content/tests/widgets/test_videocontrols_vtt.html
+++ b/toolkit/content/tests/widgets/test_videocontrols_vtt.html
@@ -17,17 +17,16 @@
 
 <pre id="test">
 <script clas="testbody" type="application/javascript">
   SimpleTest.waitForExplicitFinish();
 
   const video = document.getElementById("video");
   const ccBtn = getAnonElementWithinVideoByAttribute(video, "anonid", "closedCaptionButton");
   const ttList = getAnonElementWithinVideoByAttribute(video, "anonid", "textTrackList");
-  const testCases = [];
 
   add_task(async function wait_for_media_ready() {
     await new Promise(resolve => window.addEventListener("load", resolve, {once: true}));
     await SpecialPowers.pushPrefEnv({"set": [["media.cache_size", 40000]]});
     await new Promise(resolve => {
       video.src = "seek_with_sound.ogg";
       video.addEventListener("loadedmetadata", resolve);
     });
--- a/toolkit/content/widgets/videocontrols.xml
+++ b/toolkit/content/widgets/videocontrols.xml
@@ -1735,17 +1735,18 @@
         const clickToPlayViewRatio = 0.15;
         const clickToPlayScaledSize = Math.max(
         this.clickToPlay.minWidth, minVideoSideLength * clickToPlayViewRatio);
 
         if (clickToPlayScaledSize >= videoWidth ||
            (clickToPlayScaledSize + this.controlBarMinHeight / 2 >= videoHeight / 2 )) {
           this.clickToPlay.hideByAdjustment = true;
         } else {
-          if (this.clickToPlay.hidden && !this.video.played.length && this.video.paused) {
+          if (this.clickToPlay.hidden && !this.video.played.length &&
+              this.video.paused && this.clickToPlay.hideByAdjustment) {
             this.clickToPlay.hideByAdjustment = false;
           }
           this.clickToPlay.style.width = `${clickToPlayScaledSize}px`;
           this.clickToPlay.style.height = `${clickToPlayScaledSize}px`;
         }
       },
 
       init(binding) {