Bug 1505471 - Map intersection observer rects to the right viewport. r=mstange
authorEmilio Cobos Álvarez <emilio@crisal.io>
Wed, 15 May 2019 17:29:33 +0000
changeset 535862 5f88dc67d75ba1e08dad4c4e9b39a8df593b2be6
parent 535861 23d241c9666593bd287ab3333a0227963bd28bcf
child 535863 44144136273adb24b1aad01ba4db492346bb7559
push id2082
push userffxbld-merge
push dateMon, 01 Jul 2019 08:34:18 +0000
treeherdermozilla-release@2fb19d0466d2 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmstange
bugs1505471
milestone68.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
Bug 1505471 - Map intersection observer rects to the right viewport. r=mstange targetFrame is modified during the intersection computation loop, so it's not the viewport you want if there are scrollframes around. The test is the same as iframe-no-root.html but with a wrapping scroller which triggers this bug. This code is quite subtle, so will refactor and clean it up in a followup. Differential Revision: https://phabricator.services.mozilla.com/D31147
dom/base/DOMIntersectionObserver.cpp
testing/web-platform/tests/intersection-observer/iframe-no-root-with-wrapping-scroller.html
--- a/dom/base/DOMIntersectionObserver.cpp
+++ b/dom/base/DOMIntersectionObserver.cpp
@@ -288,16 +288,17 @@ void DOMIntersectionObserver::Update(Doc
                                                             : rootRect.Width();
     rootMargin.Side(side) =
         nsLayoutUtils::ComputeCBDependentValue(basis, mRootMargin.Get(side));
   }
 
   for (size_t i = 0; i < mObservationTargets.Length(); ++i) {
     Element* target = mObservationTargets.ElementAt(i);
     nsIFrame* targetFrame = target->GetPrimaryFrame();
+    nsIFrame* originalTargetFrame = targetFrame;
     nsRect targetRect;
     Maybe<nsRect> intersectionRect;
     bool isSameDoc = root && root->GetComposedDoc() == target->GetComposedDoc();
 
     if (rootFrame && targetFrame) {
       // If mRoot is set we are testing intersection with a container element
       // instead of the implicit root.
       if (mRoot) {
@@ -369,17 +370,17 @@ void DOMIntersectionObserver::Update(Doc
       nsRect intersectionRectRelativeToRoot =
           nsLayoutUtils::TransformFrameRectToAncestor(
               targetFrame, intersectionRect.value(),
               nsLayoutUtils::GetContainingBlockForClientRect(rootFrame));
       intersectionRect = EdgeInclusiveIntersection(
           intersectionRectRelativeToRoot, rootIntersectionRect);
       if (intersectionRect.isSome() && !isSameDoc) {
         nsRect rect = intersectionRect.value();
-        nsPresContext* presContext = targetFrame->PresContext();
+        nsPresContext* presContext = originalTargetFrame->PresContext();
         nsIFrame* rootScrollFrame =
             presContext->PresShell()->GetRootScrollFrame();
         if (rootScrollFrame) {
           nsLayoutUtils::TransformRect(rootFrame, rootScrollFrame, rect);
         }
         intersectionRect = Some(rect);
       }
     }
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/intersection-observer/iframe-no-root-with-wrapping-scroller.html
@@ -0,0 +1,73 @@
+<!DOCTYPE html>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="./resources/intersection-observer-test-utils.js"></script>
+
+<style>
+pre, #log {
+  position: absolute;
+  top: 0;
+  left: 200px;
+}
+.spacer {
+  height: calc(100vh + 100px);
+}
+iframe {
+  height: 100px;
+  width: 150px;
+}
+</style>
+
+<div class="spacer"></div>
+<div style="overflow: hidden">
+  <iframe id="target-iframe" src="resources/iframe-no-root-subframe.html"></iframe>
+</div>
+<div class="spacer"></div>
+
+<script>
+var vw = document.documentElement.clientWidth;
+var vh = document.documentElement.clientHeight;
+
+var iframe = document.getElementById("target-iframe");
+var target;
+var entries = [];
+
+iframe.onload = function() {
+  runTestCycle(function() {
+    assert_true(!!iframe, "iframe exists");
+
+    target = iframe.contentDocument.getElementById("target");
+    assert_true(!!target, "Target element exists.");
+    var observer = new IntersectionObserver(function(changes) {
+      entries = entries.concat(changes)
+    });
+    observer.observe(target);
+    entries = entries.concat(observer.takeRecords());
+    assert_equals(entries.length, 0, "No initial notifications.");
+    runTestCycle(step0, "First rAF.");
+  }, "Observer with the implicit root; target in a same-origin iframe.");
+};
+
+function step0() {
+  document.scrollingElement.scrollTop = 200;
+  runTestCycle(step1, "document.scrollingElement.scrollTop = 200");
+  checkLastEntry(entries, 0, [8, 108, 208, 308, 0, 0, 0, 0, 0, vw, 0, vh, false]);
+}
+
+function step1() {
+  iframe.contentDocument.scrollingElement.scrollTop = 250;
+  runTestCycle(step2, "iframe.contentDocument.scrollingElement.scrollTop = 250");
+  assert_equals(entries.length, 1, "entries.length == 1");
+}
+
+function step2() {
+  document.scrollingElement.scrollTop = 100;
+  runTestCycle(step3, "document.scrollingElement.scrollTop = 100");
+  checkLastEntry(entries, 1, [8, 108, -42, 58, 8, 108, 0, 58, 0, vw, 0, vh, true]);
+}
+
+function step3() {
+  checkLastEntry(entries, 2, [8, 108, -42, 58, 0, 0, 0, 0, 0, vw, 0, vh, false]);
+  document.scrollingElement.scrollTop = 0;
+}
+</script>