Source/WebCore/ChangeLog

 12018-10-31 Ali Juma <ajuma@chromium.org>
 2
 3 Allow cross-document intersection observing
 4 https://bugs.webkit.org/show_bug.cgi?id=165746
 5
 6 Reviewed by NOBODY (OOPS!).
 7
 8 Add logic to compute the intersection between the viewport and an element in a
 9 subframe.
 10
 11 Add a FloatRect version of ScrollView::rootViewToContents, and FloatRect versions
 12 of the methods it calls.
 13
 14 Covered by rebased tests in imported/w3c/web-platform-tests/intersection-observer.
 15
 16 * dom/Document.cpp:
 17 (WebCore::computeClippedRectInRootContentsSpace):
 18 (WebCore::computeIntersectionState):
 19 (WebCore::Document::updateIntersectionObservations):
 20 * page/FrameView.cpp:
 21 (WebCore::FrameView::viewportContentsChanged):
 22 (WebCore::FrameView::convertFromContainingViewToRenderer const):
 23 (WebCore::FrameView::convertFromContainingView const):
 24 * page/FrameView.h:
 25 * platform/ScrollView.cpp:
 26 (WebCore::ScrollView::viewToContents const):
 27 (WebCore::ScrollView::contentsToView const):
 28 (WebCore::ScrollView::rootViewToContents const):
 29 * platform/ScrollView.h:
 30 * platform/Widget.cpp:
 31 (WebCore::Widget::convertFromRootView const):
 32 (WebCore::Widget::convertFromContainingView const):
 33 * platform/Widget.h:
 34
1352018-10-30 Andy Estes <aestes@apple.com>
236
337 [Payment Request] Implement PaymentResponse.retry()

Source/WebCore/dom/Document.cpp

@@static void expandRootBoundsWithRootMargin(FloatRect& localRootBounds, const Len
76017601 localRootBounds.expand(rootMarginFloatBox);
76027602}
76037603
 7604static std::optional<LayoutRect> computeClippedRectInRootContentsSpace(const LayoutRect& rect, const RenderElement* renderer)
 7605{
 7606 OptionSet<RenderObject::VisibleRectContextOption> visibleRectOptions = { RenderObject::VisibleRectContextOption::UseEdgeInclusiveIntersection, RenderObject::VisibleRectContextOption::ApplyCompositedClips, RenderObject::VisibleRectContextOption::ApplyCompositedContainerScrolls };
 7607 std::optional<LayoutRect> rectInFrameAbsoluteSpace = renderer->computeVisibleRectInContainer(rect, &renderer->view(), {false /* hasPositionFixedDescendant */, false /* dirtyRectIsFlipped */, visibleRectOptions });
 7608 if (!rectInFrameAbsoluteSpace || renderer->frame().isMainFrame())
 7609 return rectInFrameAbsoluteSpace;
 7610
 7611 bool intersects = rectInFrameAbsoluteSpace->edgeInclusiveIntersect(renderer->view().frameView().layoutViewportRect());
 7612 if (!intersects)
 7613 return std::nullopt;
 7614
 7615 LayoutRect rectInFrameViewSpace(renderer->view().frameView().contentsToView(snappedIntRect(*rectInFrameAbsoluteSpace)));
 7616 auto* ownerRenderer = renderer->frame().ownerRenderer();
 7617 if (!ownerRenderer)
 7618 return std::nullopt;
 7619
 7620 rectInFrameViewSpace.moveBy(ownerRenderer->contentBoxLocation());
 7621 return computeClippedRectInRootContentsSpace(rectInFrameViewSpace, ownerRenderer);
 7622}
 7623
76047624struct IntersectionObservationState {
76057625 FloatRect absoluteTargetRect;
76067626 FloatRect absoluteRootBounds;

@@struct IntersectionObservationState {
76087628 bool isIntersecting { false };
76097629};
76107630
7611 static std::optional<IntersectionObservationState> computeIntersectionState(FrameView& frameView, const IntersectionObserver& observer, Element& target)
 7631static std::optional<IntersectionObservationState> computeIntersectionState(FrameView& frameView, const IntersectionObserver& observer, Element& target, bool applyRootMargin)
76127632{
7613  // FIXME: Implement intersection computation for the cross-document case.
7614  if (observer.trackingDocument() != &target.document())
7615  return std::nullopt;
7616 
76177633 auto* targetRenderer = target.renderer();
76187634 if (!targetRenderer)
76197635 return std::nullopt;

@@static std::optional<IntersectionObservationState> computeIntersectionState(Fram
76217637 FloatRect localRootBounds;
76227638 RenderBlock* rootRenderer;
76237639 if (observer.root()) {
 7640 if (observer.trackingDocument() != &target.document())
 7641 return std::nullopt;
 7642
76247643 if (!observer.root()->renderer() || !is<RenderBlock>(observer.root()->renderer()))
76257644 return std::nullopt;
76267645

@@static std::optional<IntersectionObservationState> computeIntersectionState(Fram
76347653 localRootBounds = { FloatPoint(), rootRenderer->size() };
76357654 } else {
76367655 ASSERT(frameView.frame().isMainFrame());
 7656 // FIXME: Handle the case of an implicit-root observer that has a target in a different frame tree.
 7657 if (&targetRenderer->frame().mainFrame() != &frameView.frame())
 7658 return std::nullopt;
76377659 rootRenderer = frameView.renderView();
76387660 localRootBounds = frameView.layoutViewportRect();
76397661 }
76407662
7641  expandRootBoundsWithRootMargin(localRootBounds, observer.rootMarginBox());
 7663 if (applyRootMargin)
 7664 expandRootBoundsWithRootMargin(localRootBounds, observer.rootMarginBox());
76427665
76437666 LayoutRect localTargetBounds;
76447667 if (is<RenderBox>(*targetRenderer))

@@static std::optional<IntersectionObservationState> computeIntersectionState(Fram
76487671 else if (is<RenderLineBreak>(targetRenderer))
76497672 localTargetBounds = downcast<RenderLineBreak>(targetRenderer)->linesBoundingBox();
76507673
7651  OptionSet<RenderObject::VisibleRectContextOption> visibleRectOptions = { RenderObject::VisibleRectContextOption::UseEdgeInclusiveIntersection, RenderObject::VisibleRectContextOption::ApplyCompositedClips, RenderObject::VisibleRectContextOption::ApplyCompositedContainerScrolls };
7652  std::optional<LayoutRect> rootLocalTargetRect = targetRenderer->computeVisibleRectInContainer(localTargetBounds, rootRenderer, { false /* hasPositionFixedDescendant */, false /* dirtyRectIsFlipped */, visibleRectOptions });
 7674 std::optional<LayoutRect> rootLocalTargetRect;
 7675 if (observer.root()) {
 7676 OptionSet<RenderObject::VisibleRectContextOption> visibleRectOptions = { RenderObject::VisibleRectContextOption::UseEdgeInclusiveIntersection, RenderObject::VisibleRectContextOption::ApplyCompositedClips, RenderObject::VisibleRectContextOption::ApplyCompositedContainerScrolls };
 7677 rootLocalTargetRect = targetRenderer->computeVisibleRectInContainer(localTargetBounds, rootRenderer, { false /* hasPositionFixedDescendant */, false /* dirtyRectIsFlipped */, visibleRectOptions });
 7678 } else
 7679 rootLocalTargetRect = computeClippedRectInRootContentsSpace(localTargetBounds, targetRenderer);
 7680
76537681 FloatRect rootLocalIntersectionRect = localRootBounds;
76547682
76557683 IntersectionObservationState intersectionState;
76567684 intersectionState.isIntersecting = rootLocalTargetRect && rootLocalIntersectionRect.edgeInclusiveIntersect(*rootLocalTargetRect);
76577685
7658  if (intersectionState.isIntersecting)
7659  intersectionState.absoluteIntersectionRect = rootRenderer->localToAbsoluteQuad(rootLocalIntersectionRect).boundingBox();
 7686 if (intersectionState.isIntersecting) {
 7687 FloatRect rootAbsoluteIntersectionRect = rootRenderer->localToAbsoluteQuad(rootLocalIntersectionRect).boundingBox();
 7688 if (&targetRenderer->frame() == &rootRenderer->frame())
 7689 intersectionState.absoluteIntersectionRect = rootAbsoluteIntersectionRect;
 7690 else {
 7691 FloatRect rootViewIntersectionRect = frameView.delegatesScrolling() ? rootAbsoluteIntersectionRect : frameView.contentsToView(rootAbsoluteIntersectionRect);
 7692 intersectionState.absoluteIntersectionRect = targetRenderer->view().frameView().rootViewToContents(rootViewIntersectionRect);
 7693 }
 7694 }
76607695
76617696 intersectionState.absoluteTargetRect = targetRenderer->localToAbsoluteQuad(FloatRect(localTargetBounds)).boundingBox();
76627697 intersectionState.absoluteRootBounds = rootRenderer->localToAbsoluteQuad(localRootBounds).boundingBox();

@@void Document::updateIntersectionObservations()
76887723 ASSERT(index != notFound);
76897724 auto& registration = targetRegistrations[index];
76907725
7691  auto intersectionState = computeIntersectionState(*frameView, *observer, *target);
 7726 bool isSameOriginObservation = &target->document() == this || target->document().securityOrigin().canAccess(securityOrigin());
 7727 auto intersectionState = computeIntersectionState(*frameView, *observer, *target, isSameOriginObservation);
76927728
76937729 float intersectionRatio = 0;
76947730 size_t thresholdIndex = 0;

@@void Document::updateIntersectionObservations()
77067742 }
77077743 }
77087744
7709 
77107745 if (!registration.previousThresholdIndex || thresholdIndex != registration.previousThresholdIndex) {
77117746 FloatRect targetBoundingClientRect;
77127747 FloatRect clientIntersectionRect;
77137748 FloatRect clientRootBounds;
77147749 if (intersectionState) {
7715  targetBoundingClientRect = frameView->absoluteToClientRect(intersectionState->absoluteTargetRect);
 7750 auto* targetFrameView = target->document().view();
 7751 targetBoundingClientRect = targetFrameView->absoluteToClientRect(intersectionState->absoluteTargetRect);
77167752 clientRootBounds = frameView->absoluteToClientRect(intersectionState->absoluteRootBounds);
77177753 if (intersectionState->isIntersecting)
7718  clientIntersectionRect = frameView->absoluteToClientRect(intersectionState->absoluteIntersectionRect);
 7754 clientIntersectionRect = targetFrameView->absoluteToClientRect(intersectionState->absoluteIntersectionRect);
77197755 }
77207756
7721  // FIXME: Once cross-document observation is implemented, only report root bounds if the target document and
7722  // the root document are similar-origin.
7723  std::optional<DOMRectInit> reportedRootBounds = DOMRectInit({
7724  clientRootBounds.x(),
7725  clientRootBounds.y(),
7726  clientRootBounds.width(),
7727  clientRootBounds.height()
7728  });
 7757 std::optional<DOMRectInit> reportedRootBounds;
 7758 if (isSameOriginObservation) {
 7759 reportedRootBounds = DOMRectInit({
 7760 clientRootBounds.x(),
 7761 clientRootBounds.y(),
 7762 clientRootBounds.width(),
 7763 clientRootBounds.height()
 7764 });
 7765 }
77297766
77307767 observer->appendQueuedEntry(IntersectionObserverEntry::create({
77317768 timestamp,

Source/WebCore/page/FrameView.cpp

@@void FrameView::viewportContentsChanged()
20092009
20102010#if ENABLE(INTERSECTION_OBSERVER)
20112011 if (auto* document = frame().document()) {
2012  if (document->numberOfIntersectionObservers()) {
2013  if (auto* page = frame().page())
 2012 if (auto* page = frame().page()) {
 2013 if (document->numberOfIntersectionObservers())
20142014 page->addDocumentNeedingIntersectionObservationUpdate(*document);
 2015 if (!frame().isMainFrame()) {
 2016 if (auto* mainDocument = frame().mainFrame().document()) {
 2017 if (mainDocument->numberOfIntersectionObservers())
 2018 page->addDocumentNeedingIntersectionObservationUpdate(*mainDocument);
 2019 }
 2020 }
20152021 }
20162022 }
20172023#endif

@@IntRect FrameView::convertFromContainingViewToRenderer(const RenderElement* rend
45544560 return rect;
45554561}
45564562
 4563FloatRect FrameView::convertFromContainingViewToRenderer(const RenderElement* renderer, const FloatRect& viewRect) const
 4564{
 4565 FloatRect rect = viewRect;
 4566
 4567 // Convert from FrameView coords into page ("absolute") coordinates.
 4568 if (!delegatesScrolling())
 4569 rect = viewToContents(rect);
 4570
 4571 return (renderer->absoluteToLocalQuad(rect)).boundingBox();
 4572}
 4573
45574574IntPoint FrameView::convertFromRendererToContainingView(const RenderElement* renderer, const IntPoint& rendererPoint) const
45584575{
45594576 IntPoint point = roundedIntPoint(renderer->localToAbsolute(rendererPoint, UseTransforms));

@@IntRect FrameView::convertFromContainingView(const IntRect& parentRect) const
46194636 return parentRect;
46204637}
46214638
 4639FloatRect FrameView::convertFromContainingView(const FloatRect& parentRect) const
 4640{
 4641 if (const ScrollView* parentScrollView = parent()) {
 4642 if (is<FrameView>(*parentScrollView)) {
 4643 const FrameView& parentView = downcast<FrameView>(*parentScrollView);
 4644
 4645 // Get our renderer in the parent view
 4646 RenderWidget* renderer = frame().ownerRenderer();
 4647 if (!renderer)
 4648 return parentRect;
 4649
 4650 auto rect = parentView.convertFromContainingViewToRenderer(renderer, parentRect);
 4651 rect.moveBy(-renderer->contentBoxLocation());
 4652 return rect;
 4653 }
 4654
 4655 return Widget::convertFromContainingView(parentRect);
 4656 }
 4657
 4658 return parentRect;
 4659}
 4660
46224661IntPoint FrameView::convertToContainingView(const IntPoint& localPoint) const
46234662{
46244663 if (const ScrollView* parentScrollView = parent()) {

Source/WebCore/page/FrameView.h

@@public:
459459 // Methods to convert points and rects between the coordinate space of the renderer, and this view.
460460 WEBCORE_EXPORT IntRect convertFromRendererToContainingView(const RenderElement*, const IntRect&) const;
461461 WEBCORE_EXPORT IntRect convertFromContainingViewToRenderer(const RenderElement*, const IntRect&) const;
 462 WEBCORE_EXPORT FloatRect convertFromContainingViewToRenderer(const RenderElement*, const FloatRect&) const;
462463 WEBCORE_EXPORT IntPoint convertFromRendererToContainingView(const RenderElement*, const IntPoint&) const;
463464 WEBCORE_EXPORT IntPoint convertFromContainingViewToRenderer(const RenderElement*, const IntPoint&) const;
464465
465466 // Override ScrollView methods to do point conversion via renderers, in order to take transforms into account.
466467 IntRect convertToContainingView(const IntRect&) const final;
467468 IntRect convertFromContainingView(const IntRect&) const final;
 469 FloatRect convertFromContainingView(const FloatRect&) const final;
468470 IntPoint convertToContainingView(const IntPoint&) const final;
469471 IntPoint convertFromContainingView(const IntPoint&) const final;
470472

Source/WebCore/platform/ScrollView.cpp

@@IntRect ScrollView::viewToContents(IntRect rect) const
829829 return rect;
830830}
831831
 832FloatRect ScrollView::viewToContents(FloatRect rect) const
 833{
 834 rect.moveBy(documentScrollPositionRelativeToViewOrigin());
 835 return rect;
 836}
 837
832838IntRect ScrollView::contentsToView(IntRect rect) const
833839{
834840 rect.moveBy(-documentScrollPositionRelativeToViewOrigin());
835841 return rect;
836842}
837843
 844FloatRect ScrollView::contentsToView(FloatRect rect) const
 845{
 846 rect.moveBy(-documentScrollPositionRelativeToViewOrigin());
 847 return rect;
 848}
 849
838850IntPoint ScrollView::contentsToContainingViewContents(const IntPoint& point) const
839851{
840852 if (const ScrollView* parentScrollView = parent()) {

@@IntRect ScrollView::rootViewToContents(const IntRect& rootViewRect) const
882894 return viewToContents(convertFromRootView(rootViewRect));
883895}
884896
 897FloatRect ScrollView::rootViewToContents(const FloatRect& rootViewRect) const
 898{
 899 if (delegatesScrolling())
 900 return convertFromRootView(rootViewRect);
 901
 902 return viewToContents(convertFromRootView(rootViewRect));
 903}
 904
885905IntPoint ScrollView::rootViewToTotalContents(const IntPoint& rootViewPoint) const
886906{
887907 if (delegatesScrolling())

Source/WebCore/platform/ScrollView.h

@@public:
283283 WEBCORE_EXPORT IntPoint contentsToRootView(const IntPoint&) const;
284284 WEBCORE_EXPORT IntRect rootViewToContents(const IntRect&) const;
285285 WEBCORE_EXPORT IntRect contentsToRootView(const IntRect&) const;
 286 WEBCORE_EXPORT FloatRect rootViewToContents(const FloatRect&) const;
286287
287288 IntPoint viewToContents(const IntPoint&) const;
288289 IntPoint contentsToView(const IntPoint&) const;

@@public:
290291 IntRect viewToContents(IntRect) const;
291292 IntRect contentsToView(IntRect) const;
292293
 294 FloatRect viewToContents(FloatRect) const;
 295 FloatRect contentsToView(FloatRect) const;
 296
293297 IntPoint contentsToContainingViewContents(const IntPoint&) const;
294298 IntRect contentsToContainingViewContents(IntRect) const;
295299

Source/WebCore/platform/Widget.cpp

@@IntRect Widget::convertFromRootView(const IntRect& rootRect) const
7676 return rootRect;
7777}
7878
 79FloatRect Widget::convertFromRootView(const FloatRect& rootRect) const
 80{
 81 if (const ScrollView* parentScrollView = parent()) {
 82 FloatRect parentRect = parentScrollView->convertFromRootView(rootRect);
 83 return convertFromContainingView(parentRect);
 84 }
 85 return rootRect;
 86}
 87
7988IntRect Widget::convertToRootView(const IntRect& localRect) const
8089{
8190 if (const ScrollView* parentScrollView = parent()) {

@@IntRect Widget::convertFromContainingView(const IntRect& parentRect) const
182191 return parentRect;
183192}
184193
 194FloatRect Widget::convertFromContainingView(const FloatRect& parentRect) const
 195{
 196 return convertFromContainingView(IntRect(parentRect));
 197}
 198
185199IntPoint Widget::convertToContainingView(const IntPoint& localPoint) const
186200{
187201 if (const ScrollView* parentScrollView = parent())

Source/WebCore/platform/Widget.h

@@public:
150150 WEBCORE_EXPORT IntRect convertToRootView(const IntRect&) const;
151151 IntRect convertFromRootView(const IntRect&) const;
152152
 153 FloatRect convertFromRootView(const FloatRect&) const;
 154
153155 IntPoint convertToRootView(const IntPoint&) const;
154156 IntPoint convertFromRootView(const IntPoint&) const;
155157

@@public:
184186 // Virtual methods to convert points to/from the containing ScrollView
185187 WEBCORE_EXPORT virtual IntRect convertToContainingView(const IntRect&) const;
186188 WEBCORE_EXPORT virtual IntRect convertFromContainingView(const IntRect&) const;
 189 WEBCORE_EXPORT virtual FloatRect convertFromContainingView(const FloatRect&) const;
187190 WEBCORE_EXPORT virtual IntPoint convertToContainingView(const IntPoint&) const;
188191 WEBCORE_EXPORT virtual IntPoint convertFromContainingView(const IntPoint&) const;
189192

LayoutTests/ChangeLog

 12018-10-31 Ali Juma <ajuma@chromium.org>
 2
 3 Allow cross-document intersection observing
 4 https://bugs.webkit.org/show_bug.cgi?id=165746
 5
 6 Reviewed by NOBODY (OOPS!).
 7
 8 Add platform-specific baselines for tests involving iframe scrolling, which isn't supported on iOS.
 9
 10 * platform/ios/imported/w3c/web-platform-tests/intersection-observer/cross-origin-iframe-expected.txt: Copied from LayoutTests/imported/w3c/web-platform-tests/intersection-observer/cross-origin-iframe-expected.txt.
 11 * platform/ios/imported/w3c/web-platform-tests/intersection-observer/iframe-no-root-expected.txt: Copied from LayoutTests/imported/w3c/web-platform-tests/intersection-observer/iframe-no-root-expected.txt.
 12
1132018-10-30 Andy Estes <aestes@apple.com>
214
315 [Payment Request] Implement PaymentResponse.retry()

LayoutTests/imported/w3c/ChangeLog

 12018-10-31 Ali Juma <ajuma@chromium.org>
 2
 3 Allow cross-document intersection observing
 4 https://bugs.webkit.org/show_bug.cgi?id=165746
 5
 6 Reviewed by NOBODY (OOPS!).
 7
 8 Update expectations for tests that now pass.
 9
 10 * web-platform-tests/intersection-observer/client-rect-expected.txt:
 11 * web-platform-tests/intersection-observer/cross-origin-iframe-expected.txt:
 12 * web-platform-tests/intersection-observer/iframe-no-root-expected.txt:
 13 * web-platform-tests/intersection-observer/timestamp-expected.txt:
 14
1152018-10-30 Sihui Liu <sihui_liu@apple.com>
216
317 IndexedDB: iteration of cursors skip records if updated or deleted

LayoutTests/imported/w3c/web-platform-tests/intersection-observer/client-rect-expected.txt

11
22PASS IntersectionObserverEntry.boundingClientRect should match target.boundingClientRect()
3 FAIL First rAF should generate notification. assert_equals: entries[0].boundingClientRect.left expected 8 but got 0
 3PASS First rAF should generate notification.
44

LayoutTests/imported/w3c/web-platform-tests/intersection-observer/cross-origin-iframe-expected.txt

33PASS Intersection observer test with no explicit root and target in a cross-origin iframe.
44PASS First rAF
55PASS topDocument.scrollingElement.scrollTop = 200
6 FAIL iframeDocument.scrollingElement.scrollTop = 250 assert_equals: expected 1 but got 0
7 FAIL topDocument.scrollingElement.scrollTop = 100 assert_equals: expected 1 but got 0
 6PASS iframeDocument.scrollingElement.scrollTop = 250
 7PASS topDocument.scrollingElement.scrollTop = 100
88

LayoutTests/imported/w3c/web-platform-tests/intersection-observer/iframe-no-root-expected.txt

11
22
33PASS Observer with the implicit root; target in a same-origin iframe.
4 FAIL First rAF. assert_equals: entries[0].boundingClientRect.left expected 8 but got 0
 4PASS First rAF.
55PASS document.scrollingElement.scrollTop = 200
6 FAIL iframe.contentDocument.scrollingElement.scrollTop = 250 assert_equals: entries.length expected 2 but got 1
7 FAIL document.scrollingElement.scrollTop = 100 assert_equals: entries.length expected 3 but got 1
 6PASS iframe.contentDocument.scrollingElement.scrollTop = 250
 7PASS document.scrollingElement.scrollTop = 100
88

LayoutTests/imported/w3c/web-platform-tests/intersection-observer/timestamp-expected.txt

22
33PASS Check that timestamps correspond to the to execution context that created the observer.
44PASS First rAF after iframe is loaded.
5 FAIL Generate notifications. undefined is not an object (evaluating 'topWindowEntries[1].time')
 5PASS Generate notifications.
66

LayoutTests/platform/ios/imported/w3c/web-platform-tests/intersection-observer/cross-origin-iframe-expected.txt

 1
 2
 3PASS Intersection observer test with no explicit root and target in a cross-origin iframe.
 4PASS First rAF
 5PASS topDocument.scrollingElement.scrollTop = 200
 6FAIL iframeDocument.scrollingElement.scrollTop = 250 assert_equals: expected 1 but got 0
 7FAIL topDocument.scrollingElement.scrollTop = 100 assert_equals: expected 1 but got 0
 8

LayoutTests/platform/ios/imported/w3c/web-platform-tests/intersection-observer/iframe-no-root-expected.txt

 1
 2
 3PASS Observer with the implicit root; target in a same-origin iframe.
 4PASS First rAF.
 5PASS document.scrollingElement.scrollTop = 200
 6FAIL iframe.contentDocument.scrollingElement.scrollTop = 250 assert_equals: entries.length expected 2 but got 1
 7FAIL document.scrollingElement.scrollTop = 100 assert_equals: entries.length expected 3 but got 1
 8