WebKit Bugzilla
Attachment 346780 Details for
Bug 159475
: Implement IntersectionObserver
Home
|
New
|
Browse
|
Search
|
[?]
|
Reports
|
Requests
|
Help
|
New Account
|
Log In
Remember
[x]
|
Forgot Password
Login:
[x]
[patch]
WIP
bug-159475-20180808144635.patch (text/plain), 211.03 KB, created by
Ali Juma
on 2018-08-08 11:46:37 PDT
(
hide
)
Description:
WIP
Filename:
MIME Type:
Creator:
Ali Juma
Created:
2018-08-08 11:46:37 PDT
Size:
211.03 KB
patch
obsolete
>Subversion Revision: 234702 >diff --git a/Source/WebCore/ChangeLog b/Source/WebCore/ChangeLog >index 10657f39bf860b42f851126f873fabda3de8adba..e586144bc54278f8a436f84eb74931aa2e69a413 100644 >--- a/Source/WebCore/ChangeLog >+++ b/Source/WebCore/ChangeLog >@@ -1,3 +1,312 @@ >+2018-08-08 Ali Juma <ajuma@chromium.org> >+ >+ Implement IntersectionObserver >+ https://bugs.webkit.org/show_bug.cgi?id=159475 >+ >+ Reviewed by NOBODY (OOPS!). >+ >+ Based on a patch by Simon Fraser, with contributions by Ian Vollick. >+ >+ Tests: imported/w3c/web-platform-tests/intersection-observer/bounding-box.html >+ imported/w3c/web-platform-tests/intersection-observer/client-rect.html >+ imported/w3c/web-platform-tests/intersection-observer/containing-block.html >+ imported/w3c/web-platform-tests/intersection-observer/cross-origin-iframe.html >+ imported/w3c/web-platform-tests/intersection-observer/disconnect.html >+ imported/w3c/web-platform-tests/intersection-observer/display-none.html >+ imported/w3c/web-platform-tests/intersection-observer/edge-inclusive-intersection.html >+ imported/w3c/web-platform-tests/intersection-observer/iframe-no-root.html >+ imported/w3c/web-platform-tests/intersection-observer/isIntersecting-change-events.html >+ imported/w3c/web-platform-tests/intersection-observer/multiple-targets.html >+ imported/w3c/web-platform-tests/intersection-observer/multiple-thresholds.html >+ imported/w3c/web-platform-tests/intersection-observer/observer-attributes.html >+ imported/w3c/web-platform-tests/intersection-observer/observer-exceptions.html >+ imported/w3c/web-platform-tests/intersection-observer/observer-in-iframe.html >+ imported/w3c/web-platform-tests/intersection-observer/observer-without-js-reference.html >+ imported/w3c/web-platform-tests/intersection-observer/remove-element.html >+ imported/w3c/web-platform-tests/intersection-observer/root-margin.html >+ imported/w3c/web-platform-tests/intersection-observer/same-document-no-root.html >+ imported/w3c/web-platform-tests/intersection-observer/same-document-root.html >+ imported/w3c/web-platform-tests/intersection-observer/same-document-zero-size-target.html >+ imported/w3c/web-platform-tests/intersection-observer/shadow-content.html >+ imported/w3c/web-platform-tests/intersection-observer/timestamp.html >+ imported/w3c/web-platform-tests/intersection-observer/unclipped-root.html >+ imported/w3c/web-platform-tests/intersection-observer/zero-area-element-hidden.html >+ imported/w3c/web-platform-tests/intersection-observer/zero-area-element-visible.html >+ >+ * dom/Document.cpp: >+ (WebCore::Document::updateStyleIfNeeded): >+ (WebCore::Document::addToIntersectionObserverTargets): >+ (WebCore::Document::removeFromIntersectionObserverTargets): >+ (WebCore::Document::collectIntersectionObservers): >+ (WebCore::Document::updateIntersectionObservations): >+ (WebCore::floatValueForLengthInLayoutUnits): >+ (WebCore::computeAbsoluteTargetIntersection): >+ (WebCore::Document::updateIntersectionObserveration): >+ (WebCore::Document::notifyIntersectionObserversTimerFired): >+ (WebCore::Document::requestStorageAccess): >+ * dom/Document.h: >+ * dom/Element.cpp: >+ (WebCore::Element::~Element): >+ (WebCore::Element::boundingClientRect): >+ (WebCore::Element::boundingRectInClientCoordinates): >+ (WebCore::Element::didMoveToNewDocument): >+ (WebCore::Element::startObservingIntersections): >+ (WebCore::Element::endObservingIntersections): >+ (WebCore::Element::disconnectFromIntersectionObservers): >+ (WebCore::Element::intersectionObserverRegistrations): >+ * dom/Element.h: >+ * dom/ElementRareData.cpp: >+ * dom/ElementRareData.h: >+ (WebCore::ElementRareData::intersectionObserverRegistrations): >+ (WebCore::ElementRareData::setIntersectionObserverRegistrations): >+ * page/FrameView.cpp: >+ (WebCore::FrameView::viewportContentsChanged): >+ * page/IntersectionObserver.cpp: >+ (WebCore::isThresholdWithinRange): >+ (WebCore::parseRootMargin): >+ (WebCore::IntersectionObserver::create): >+ (WebCore::IntersectionObserver::IntersectionObserver): >+ (WebCore::IntersectionObserver::~IntersectionObserver): >+ (WebCore::IntersectionObserver::observe): >+ (WebCore::IntersectionObserver::unobserve): >+ (WebCore::IntersectionObserver::disconnect): >+ (WebCore::IntersectionObserver::takeRecords): >+ (WebCore::IntersectionObserver::addObservationTarget): >+ (WebCore::IntersectionObserver::removeObservationTarget): >+ (WebCore::IntersectionObserver::hasObservationTarget const): >+ (WebCore::IntersectionObserver::appendQueuedEntry): >+ (WebCore::IntersectionObserver::notify): >+ * page/IntersectionObserver.h: >+ (WebCore::IntersectionObserver::document): >+ (WebCore::IntersectionObserver::marginBox const): >+ (WebCore::IntersectionObserver:: const): >+ (WebCore::IntersectionObserver::hasObservationTargets const): >+ (WebCore::IntersectionObserver::create): Deleted. >+ * page/IntersectionObserver.idl: >+ * page/IntersectionObserverEntry.cpp: >+ (WebCore::IntersectionObserverEntry::IntersectionObserverEntry): >+ (WebCore::operator<<): >+ * page/IntersectionObserverEntry.h: >+ (WebCore::IntersectionObserverEntry::isIntersecting const): >+ * page/IntersectionObserverEntry.idl: >+ * platform/Logging.h: >+ * platform/graphics/FloatRect.h: >+ (WebCore::FloatRect::area const): >+ * rendering/RenderBlock.cpp: >+ (WebCore::RenderBlock::isContainingBlockAncestorFor const): >+ * rendering/RenderBlock.h: >+ >+ * dom/Document.cpp: >+ (WebCore::Document::updateStyleIfNeeded): >+ (WebCore::Document::addToIntersectionObserverTargets): >+ (WebCore::Document::removeFromIntersectionObserverTargets): >+ (WebCore::Document::collectIntersectionObservers): >+ (WebCore::Document::updateIntersectionObservations): >+ (WebCore::floatValueForLengthInLayoutUnits): >+ (WebCore::computeAbsoluteTargetIntersection): >+ (WebCore::Document::updateIntersectionObservation): >+ (WebCore::Document::notifyIntersectionObserversTimerFired): >+ (WebCore::Document::requestStorageAccess): >+ * dom/Document.h: >+ * dom/Element.cpp: >+ (WebCore::Element::~Element): >+ (WebCore::Element::boundingClientRect): >+ (WebCore::Element::boundingRectInClientCoordinates): >+ (WebCore::Element::didMoveToNewDocument): >+ (WebCore::Element::startObservingIntersections): >+ (WebCore::Element::endObservingIntersections): >+ (WebCore::Element::disconnectFromIntersectionObservers): >+ (WebCore::Element::intersectionObserverRegistrations): >+ * dom/Element.h: >+ * dom/ElementRareData.cpp: >+ * dom/ElementRareData.h: >+ (WebCore::ElementRareData::intersectionObserverRegistrations): >+ (WebCore::ElementRareData::setIntersectionObserverRegistrations): >+ * page/FrameView.cpp: >+ (WebCore::FrameView::flushCompositingStateIncludingSubframes): >+ * page/IntersectionObserver.cpp: >+ (WebCore::isThresholdWithinRange): >+ (WebCore::parseRootMargin): >+ (WebCore::IntersectionObserver::create): >+ (WebCore::IntersectionObserver::IntersectionObserver): >+ (WebCore::IntersectionObserver::~IntersectionObserver): >+ (WebCore::IntersectionObserver::observe): >+ (WebCore::IntersectionObserver::unobserve): >+ (WebCore::IntersectionObserver::disconnect): >+ (WebCore::IntersectionObserver::takeRecords): >+ (WebCore::IntersectionObserver::addObservationTarget): >+ (WebCore::IntersectionObserver::removeObservationTarget): >+ (WebCore::IntersectionObserver::hasObservationTarget const): >+ (WebCore::IntersectionObserver::appendQueuedEntry): >+ (WebCore::IntersectionObserver::notify): >+ * page/IntersectionObserver.h: >+ (WebCore::IntersectionObserver::document): >+ (WebCore::IntersectionObserver::marginBox const): >+ (WebCore::IntersectionObserver:: const): >+ (WebCore::IntersectionObserver::hasObservationTargets const): >+ (WebCore::IntersectionObserver::create): Deleted. >+ * page/IntersectionObserver.idl: >+ * page/IntersectionObserverEntry.cpp: >+ (WebCore::IntersectionObserverEntry::IntersectionObserverEntry): >+ (WebCore::operator<<): >+ * page/IntersectionObserverEntry.h: >+ (WebCore::IntersectionObserverEntry::isIntersecting const): >+ * page/IntersectionObserverEntry.idl: >+ * platform/Logging.h: >+ * platform/graphics/FloatRect.cpp: >+ (WebCore::FloatRect::inclusiveIntersect): >+ * platform/graphics/FloatRect.h: >+ (WebCore::FloatRect::area const): >+ * platform/graphics/LayoutRect.cpp: >+ (WebCore::LayoutRect::inclusiveIntersect): >+ * platform/graphics/LayoutRect.h: >+ * rendering/RenderBlock.cpp: >+ (WebCore::RenderBlock::isContainingBlockAncestorFor const): >+ * rendering/RenderBlock.h: >+ * rendering/RenderBox.cpp: >+ (WebCore::RenderBox::applyCachedClipAndScrollPositionForRepaint const): >+ (WebCore::RenderBox::computeRectForRepaint const): >+ * rendering/RenderBox.h: >+ * rendering/RenderInline.cpp: >+ (WebCore::RenderInline::clippedOverflowRectForRepaint const): >+ (WebCore::RenderInline::computeRectForRepaint const): >+ * rendering/RenderObject.cpp: >+ (WebCore::RenderObject::computeRectForRepaint const): >+ (WebCore::RenderObject::computeFloatRectForRepaint const): >+ * rendering/RenderObject.h: >+ (WebCore::RenderObject::RepaintContext::RepaintContext): >+ * rendering/svg/RenderSVGForeignObject.cpp: >+ (WebCore::RenderSVGForeignObject::computeFloatRectForRepaint const): >+ (WebCore::RenderSVGForeignObject::computeRectForRepaint const): >+ * rendering/svg/RenderSVGForeignObject.h: >+ * rendering/svg/RenderSVGInline.cpp: >+ (WebCore::RenderSVGInline::computeFloatRectForRepaint const): >+ * rendering/svg/RenderSVGInline.h: >+ * rendering/svg/RenderSVGModelObject.cpp: >+ (WebCore::RenderSVGModelObject::computeFloatRectForRepaint const): >+ * rendering/svg/RenderSVGModelObject.h: >+ * rendering/svg/RenderSVGRoot.cpp: >+ (WebCore::RenderSVGRoot::computeFloatRectForRepaint const): >+ * rendering/svg/RenderSVGRoot.h: >+ * rendering/svg/RenderSVGText.cpp: >+ (WebCore::RenderSVGText::computeRectForRepaint const): >+ (WebCore::RenderSVGText::computeFloatRectForRepaint const): >+ * rendering/svg/RenderSVGText.h: >+ * rendering/svg/SVGRenderSupport.cpp: >+ (WebCore::SVGRenderSupport::computeFloatRectForRepaint): >+ * rendering/svg/SVGRenderSupport.h: >+ >+ * dom/Document.cpp: >+ (WebCore::Document::~Document): >+ (WebCore::Document::activateIntersectionObserver): >+ (WebCore::Document::deactivateIntersectionObserver): >+ (WebCore::Document::addViewportIntersectionObserver): >+ (WebCore::Document::removeViewportIntersectionObserver): >+ (WebCore::Document::updateIntersectionObservations): >+ (WebCore::floatValueForLengthInLayoutUnits): >+ (WebCore::computeAbsoluteTargetIntersection): >+ (WebCore::Document::updateIntersectionObservation): >+ (WebCore::Document::notifyIntersectionObserversTimerFired): >+ (WebCore::Document::requestStorageAccess): >+ * dom/Document.h: >+ (WebCore::Document::numberOfActiveIntersectionObservers const): >+ * dom/Element.cpp: >+ (WebCore::Element::~Element): >+ (WebCore::Element::boundingClientRect): >+ (WebCore::Element::boundingRectInClientCoordinates): >+ (WebCore::Element::didMoveToNewDocument): >+ (WebCore::Element::startObservingIntersections): >+ (WebCore::Element::endObservingIntersections): >+ (WebCore::Element::disconnectFromIntersectionObservers): >+ (WebCore::Element::ensureIntersectionObserverData): >+ (WebCore::Element::intersectionObserverData): >+ * dom/Element.h: >+ * dom/ElementRareData.cpp: >+ * dom/ElementRareData.h: >+ (WebCore::ElementRareData::intersectionObserverData): >+ (WebCore::ElementRareData::setIntersectionObserverData): >+ * page/FrameView.cpp: >+ (WebCore::FrameView::flushCompositingStateIncludingSubframes): >+ * page/IntersectionObserver.cpp: >+ (WebCore::isThresholdWithinRange): >+ (WebCore::parseRootMargin): >+ (WebCore::IntersectionObserver::create): >+ (WebCore::IntersectionObserver::IntersectionObserver): >+ (WebCore::IntersectionObserver::~IntersectionObserver): >+ (WebCore::IntersectionObserver::observe): >+ (WebCore::IntersectionObserver::unobserve): >+ (WebCore::IntersectionObserver::disconnect): >+ (WebCore::IntersectionObserver::takeRecords): >+ (WebCore::IntersectionObserver::addObservationTarget): >+ (WebCore::IntersectionObserver::removeObservationTarget): >+ (WebCore::IntersectionObserver::hasObservationTarget const): >+ (WebCore::IntersectionObserver::createTimestamp const): >+ (WebCore::IntersectionObserver::rootDestroyed): >+ (WebCore::IntersectionObserver::implicitRootDocumentDestroyed): >+ (WebCore::IntersectionObserver::appendQueuedEntry): >+ (WebCore::IntersectionObserver::notify): >+ * page/IntersectionObserver.h: >+ (WebCore::IntersectionObserver::trackingDocument): >+ (WebCore::IntersectionObserver::root const): >+ (WebCore::IntersectionObserver::marginBox const): >+ (WebCore::IntersectionObserver:: const): >+ (WebCore::IntersectionObserver::hasObservationTargets const): >+ (WebCore::IntersectionObserver::create): Deleted. >+ * page/IntersectionObserver.idl: >+ * page/IntersectionObserverEntry.cpp: >+ (WebCore::IntersectionObserverEntry::IntersectionObserverEntry): >+ (WebCore::operator<<): >+ * page/IntersectionObserverEntry.h: >+ (WebCore::IntersectionObserverEntry::isIntersecting const): >+ * page/IntersectionObserverEntry.idl: >+ * platform/Logging.h: >+ * platform/graphics/FloatRect.cpp: >+ (WebCore::FloatRect::inclusiveIntersect): >+ * platform/graphics/FloatRect.h: >+ (WebCore::FloatRect::area const): >+ * platform/graphics/LayoutRect.cpp: >+ (WebCore::LayoutRect::inclusiveIntersect): >+ * platform/graphics/LayoutRect.h: >+ * rendering/RenderBlock.cpp: >+ (WebCore::RenderBlock::isContainingBlockAncestorFor const): >+ * rendering/RenderBlock.h: >+ * rendering/RenderBox.cpp: >+ (WebCore::RenderBox::applyCachedClipAndScrollPositionForRepaint const): >+ (WebCore::RenderBox::computeRectForRepaint const): >+ * rendering/RenderBox.h: >+ * rendering/RenderInline.cpp: >+ (WebCore::RenderInline::clippedOverflowRectForRepaint const): >+ (WebCore::RenderInline::computeRectForRepaint const): >+ * rendering/RenderObject.cpp: >+ (WebCore::RenderObject::computeRectForRepaint const): >+ (WebCore::RenderObject::computeFloatRectForRepaint const): >+ * rendering/RenderObject.h: >+ (WebCore::RenderObject::RepaintContext::RepaintContext): >+ * rendering/svg/RenderSVGForeignObject.cpp: >+ (WebCore::RenderSVGForeignObject::computeFloatRectForRepaint const): >+ (WebCore::RenderSVGForeignObject::computeRectForRepaint const): >+ * rendering/svg/RenderSVGForeignObject.h: >+ * rendering/svg/RenderSVGInline.cpp: >+ (WebCore::RenderSVGInline::computeFloatRectForRepaint const): >+ * rendering/svg/RenderSVGInline.h: >+ * rendering/svg/RenderSVGModelObject.cpp: >+ (WebCore::RenderSVGModelObject::computeFloatRectForRepaint const): >+ * rendering/svg/RenderSVGModelObject.h: >+ * rendering/svg/RenderSVGRoot.cpp: >+ (WebCore::RenderSVGRoot::computeFloatRectForRepaint const): >+ * rendering/svg/RenderSVGRoot.h: >+ * rendering/svg/RenderSVGText.cpp: >+ (WebCore::RenderSVGText::computeRectForRepaint const): >+ (WebCore::RenderSVGText::computeFloatRectForRepaint const): >+ * rendering/svg/RenderSVGText.h: >+ * rendering/svg/SVGRenderSupport.cpp: >+ (WebCore::SVGRenderSupport::computeFloatRectForRepaint): >+ * rendering/svg/SVGRenderSupport.h: >+ * testing/Internals.cpp: >+ (WebCore::Internals::numberOfActiveIntersectionObservers const): >+ * testing/Internals.h: >+ * testing/Internals.idl: >+ > 2018-08-08 Joseph Pecoraro <pecoraro@apple.com> > > Web Inspector: XHR content sometimes shows as error even though load succeeded >diff --git a/Source/WebKit/ChangeLog b/Source/WebKit/ChangeLog >index a8463f581e6dc46dbaeef030ce65bea97cc066d4..23a1f3c3baa84e7c559c94be07ed1a43e4ba38be 100644 >--- a/Source/WebKit/ChangeLog >+++ b/Source/WebKit/ChangeLog >@@ -1,3 +1,12 @@ >+2018-08-08 Ali Juma <ajuma@chromium.org> >+ >+ Implement IntersectionObserver >+ https://bugs.webkit.org/show_bug.cgi?id=159475 >+ >+ Reviewed by NOBODY (OOPS!). >+ >+ * Shared/WebPreferences.yaml: >+ > 2018-08-05 Darin Adler <darin@apple.com> > > [Cocoa] More tweaks and refactoring to prepare for ARC >diff --git a/Source/WebKitLegacy/mac/ChangeLog b/Source/WebKitLegacy/mac/ChangeLog >index 7ba849e8d610b84c22d72efa9f4066ae38d41f27..403db1878fc200698b135e016ca6578097f587d6 100644 >--- a/Source/WebKitLegacy/mac/ChangeLog >+++ b/Source/WebKitLegacy/mac/ChangeLog >@@ -1,3 +1,12 @@ >+2018-08-08 Ali Juma <ajuma@chromium.org> >+ >+ Implement IntersectionObserver >+ https://bugs.webkit.org/show_bug.cgi?id=159475 >+ >+ Reviewed by NOBODY (OOPS!). >+ >+ * WebView/WebPreferencesPrivate.h: >+ > 2018-08-08 Jonathan Bedard <jbedard@apple.com> > > Follow-up build fix for r234685 >diff --git a/Source/WebCore/dom/Document.cpp b/Source/WebCore/dom/Document.cpp >index 21cafd4eb1729218ec7b49c91cac338eaec6d955..ecfdbc54f49aa68439c7dde55f70588208771aff 100644 >--- a/Source/WebCore/dom/Document.cpp >+++ b/Source/WebCore/dom/Document.cpp >@@ -53,6 +53,7 @@ > #include "CustomElementRegistry.h" > #include "CustomEvent.h" > #include "DOMImplementation.h" >+#include "DOMRectInit.h" > #include "DOMWindow.h" > #include "DateComponents.h" > #include "DebugPageOverlays.h" >@@ -109,6 +110,7 @@ > #include "ImageBitmapRenderingContext.h" > #include "ImageLoader.h" > #include "InspectorInstrumentation.h" >+#include "IntersectionObserver.h" > #include "JSCustomElementInterface.h" > #include "JSDOMPromiseDeferred.h" > #include "JSLazyEventListener.h" >@@ -138,6 +140,7 @@ > #include "PageConsoleClient.h" > #include "PageGroup.h" > #include "PageTransitionEvent.h" >+#include "Performance.h" > #include "PlatformLocale.h" > #include "PlatformMediaSessionManager.h" > #include "PlatformScreen.h" >@@ -503,6 +506,9 @@ Document::Document(Frame* frame, const URL& url, unsigned documentClasses, unsig > , m_constantPropertyMap(std::make_unique<ConstantPropertyMap>(*this)) > , m_documentClasses(documentClasses) > , m_eventQueue(*this) >+#if ENABLE(INTERSECTION_OBSERVER) >+ , m_intersectionObserversNotifyTimer(*this, &Document::notifyIntersectionObserversTimerFired) >+#endif > , m_loadEventDelayTimer(*this, &Document::loadEventDelayTimerFired) > #if PLATFORM(IOS) > #if ENABLE(DEVICE_ORIENTATION) >@@ -645,6 +651,10 @@ Document::~Document() > if (hasRareData()) > clearRareData(); > >+ for (auto* observer : m_viewportIntersectionObservers) >+ observer->implicitRootDocumentDestroyed(); >+ m_viewportIntersectionObservers.clear(); >+ > RELEASE_ASSERT_WITH_SECURITY_IMPLICATION(m_listsInvalidatedAtDocument.isEmpty()); > RELEASE_ASSERT_WITH_SECURITY_IMPLICATION(m_collectionsInvalidatedAtDocument.isEmpty()); > RELEASE_ASSERT_WITH_SECURITY_IMPLICATION(m_svgUseElements.isEmpty()); >@@ -7413,6 +7423,330 @@ void Document::removeViewportDependentPicture(HTMLPictureElement& picture) > m_viewportDependentPictures.remove(&picture); > } > >+#if ENABLE(INTERSECTION_OBSERVER) >+ >+void Document::activateIntersectionObserver(IntersectionObserver& observer) >+{ >+ m_activeIntersectionObservers.add(&observer); >+} >+ >+bool Document::deactivateIntersectionObserver(IntersectionObserver& observer) >+{ >+ return m_activeIntersectionObservers.remove(&observer); >+} >+ >+void Document::addViewportIntersectionObserver(IntersectionObserver& observer) >+{ >+ m_viewportIntersectionObservers.add(&observer); >+} >+ >+void Document::removeViewportIntersectionObserver(IntersectionObserver& observer) >+{ >+ m_viewportIntersectionObservers.remove(&observer); >+} >+ >+void Document::updateIntersectionObservations() >+{ >+ if (m_activeIntersectionObservers.isEmpty()) >+ return; >+ >+ auto frameView = view(); >+ bool needsLayout = frameView && renderView() && (frameView->layoutContext().isLayoutPending() || renderView()->needsLayout()); >+ if (needsLayout || hasPendingStyleRecalc()) >+ return; >+ >+ LOG(IntersectionObserver, "Document %p updateIntersectionObservations(), %u observers", this, m_activeIntersectionObservers.size()); >+ >+ // 2. For each observer in observer list: >+ bool needNotify = false; >+ for (auto observer : m_activeIntersectionObservers) { >+ ASSERT(!observer->root() || &observer->root()->document() == this); >+ // FIXME: this timestamp should probably be shared with subsequent rAF. >+ DOMHighResTimeStamp timestamp; >+ if (!observer->createTimestamp(timestamp)) >+ continue; >+ if (updateIntersectionObservation(*observer, timestamp)) >+ needNotify = true; >+ } >+ >+ if (needNotify) >+ m_intersectionObserversNotifyTimer.startOneShot(0_s); >+} >+ >+static float floatValueForLengthInLayoutUnits(const Length& length, float extent, float cssToLayoutScale) >+{ >+ float unscaled = floatValueForLength(length, extent); >+ return length.isPercent() ? unscaled : unscaled * cssToLayoutScale; >+} >+ >+static bool computeAbsoluteTargetIntersection(FrameView& frameView, IntersectionObserver& observer, Element* target, FloatRect& absTargetRect, FloatRect& absRootBounds, FloatRect& absIntersectionRect, bool& isIntersecting) >+{ >+ if (!target->renderer()) >+ return false; >+ >+ RenderElement& rendererForTarget = *target->renderer(); >+ RenderBlock* rendererForRoot = nullptr; >+ >+ // 1. If the intersection root is not the implicit root and target is not a descendant of the intersection root >+ // in the containing block chain, skip further processing for target. >+ if (observer.root()) { >+ // 2. If the intersection root is not the implicit root, and target is not in the same Document as the intersection root, >+ // skip further processing for target. >+ if (observer.root()->document() != target->document()) >+ return false; >+ >+ if (!observer.root()->renderer()) >+ return false; >+ >+ RenderElement* rootRenderer = observer.root()->renderer(); >+ if (!is<RenderBlock>(rootRenderer)) >+ return false; >+ >+ rendererForRoot = downcast<RenderBlock>(rootRenderer); >+ >+ if (!rendererForRoot->isContainingBlockAncestorFor(rendererForTarget)) >+ return false; >+ } >+ >+ float cssToLayoutScale = 1.0f; >+ >+ // We compute the intersections in the local space of the coordinate root. >+ // See the return line at the end of this function where we transform from >+ // local to absolute space. >+ // >+ // The coordinate root is usually the root element, but in the implicit root >+ // case, it is the target frame's RenderView. >+ >+ // 1. Let rootBounds be observers root intersection rectangle. >+ FloatRect rootBounds; >+ RenderBlock* coordinateRoot = rendererForRoot; >+ if (rendererForRoot) { >+ // If the intersection root is a scrollable element, it's the element's content area. >+ if (rendererForRoot->hasOverflowClip()) { >+ // This is local/content space. See RenderBox::absoluteContentQuad. >+ rootBounds = rendererForRoot->contentBoxRect(); >+ } else { >+ // Otherwise, it's the result of running the getBoundingClientRect() algorithm on the intersection root. >+ // We just take the RenderBox's bounds in local coordinates (see RenderBox::absoluteQuads() which is what getBoundingClientRect() uses); >+ rootBounds = { { 0, 0 }, rendererForRoot->size() }; >+ } >+ cssToLayoutScale = frameView.frame().pageZoomFactor() * frameView.frame().frameScaleFactor(); >+ } else { >+ // If the intersection root is the implicit root, it's the viewport. >+ coordinateRoot = &rendererForTarget.view(); >+ FrameView& targetFrameView = rendererForTarget.view().frameView(); >+ if (targetFrameView.frame().isMainFrame()) >+ rootBounds = targetFrameView.layoutViewportRect(); >+ else >+ // FIXME: need a version of windowClipRect() that ignores paintsEntireContents() >+ // FIXME: Need a version of windowClipRect() that gets the layout viewport rect, but clipped >+ // by all the clips in between the frame and the root frame. >+ // FIXME: This won't correctly apply root margin. We need to apply that before dealing with clips. Add this to the new version of windowClipRect (that uses the layout viewport rect). >+ rootBounds = targetFrameView.windowToContents(targetFrameView.windowClipRect()); >+ } >+ >+ const LengthBox& rootMarginBox = observer.marginBox(); >+ // FIXME: handle writing modes. >+ FloatBoxExtent rootMarginBoxExtent( >+ floatValueForLengthInLayoutUnits(rootMarginBox.top(), rootBounds.height(), cssToLayoutScale), >+ floatValueForLengthInLayoutUnits(rootMarginBox.right(), rootBounds.width(), cssToLayoutScale), >+ floatValueForLengthInLayoutUnits(rootMarginBox.bottom(), rootBounds.height(), cssToLayoutScale), >+ floatValueForLengthInLayoutUnits(rootMarginBox.left(), rootBounds.width(), cssToLayoutScale)); >+ >+ rootBounds.move(-rootMarginBoxExtent.left(), -rootMarginBoxExtent.top()); >+ >+ // FIXME: The spec says to only do this expansion if root and target are similar-origin. Check what other browsers >+ // do. Also, for the cross-document case, this needs to be applied before applying any clips. See above. >+ rootBounds.expand(rootMarginBoxExtent.left() + rootMarginBoxExtent.right(), rootMarginBoxExtent.top() + rootMarginBoxExtent.bottom()); >+ >+ // Map rootBounds into absolute coords for the caller. >+ if (rendererForRoot) >+ absRootBounds = coordinateRoot->localToAbsoluteQuad(rootBounds).boundingBox(); >+ else { >+ // The caller needs unclipped root bounds. >+ ASSERT(frameView.frame().isMainFrame()); >+ FloatRect unclippedRootBounds = frameView.layoutViewportRect(); >+ unclippedRootBounds.move(-rootMarginBoxExtent.left(), -rootMarginBoxExtent.top()); >+ unclippedRootBounds.expand(rootMarginBoxExtent.left() + rootMarginBoxExtent.right(), rootMarginBoxExtent.top() + rootMarginBoxExtent.bottom()); >+ absRootBounds = frameView.renderView()->localToAbsoluteQuad(unclippedRootBounds).boundingBox(); >+ } >+ >+ // 1. Let intersectionRect be the result of running the getBoundingClientRect() algorithm on the target. >+ // This gets targetBounds in target-local coordinates. >+ LayoutRect targetBounds; >+ if (is<RenderBoxModelObject>(rendererForTarget)) >+ targetBounds = downcast<RenderBoxModelObject>(rendererForTarget).borderBoundingBox(); >+ else if (rendererForTarget.element() && rendererForTarget.element()->isSVGElement()) { >+ // Get the bounding rectangle from the SVG model. >+ SVGElement& svgElement = downcast<SVGElement>(*rendererForTarget.element()); >+ FloatRect localRect; >+ if (svgElement.getBoundingBox(localRect)) >+ targetBounds = LayoutRect(localRect); >+ } >+ >+ absTargetRect = rendererForTarget.localToAbsoluteQuad(FloatRect(targetBounds)).boundingBox(); >+ >+ // 2-3. Map to coordinateRoot, clipping along the way. >+ // FIXME: clippedOverflowRectForRepaint() may do some things we don't want. >+ // 4. Map intersectionRect to the coordinate space of the intersection root. >+ isIntersecting = true; >+ FloatRect intersectionRect = rendererForTarget.computeRectForRepaint(targetBounds, coordinateRoot, {false /* hasPositionFixedDescendant */, false /* dirtyRectIsFlipped */, true /* useInclusiveIntersection */, &isIntersecting}); >+ >+ LOG_WITH_STREAM(IntersectionObserver, stream << " target in root space " << intersectionRect << " root bounds " << rootBounds); >+ >+ if (isIntersecting) { >+ // 5. Update intersectionRect by intersecting it with the root intersection rectangle. >+ isIntersecting = intersectionRect.inclusiveIntersect(rootBounds); >+ >+ // 6. Map intersectionRect to the coordinate space of the viewport of the Document containing the target. >+ // We'll use absolute coordinates instead. >+ absIntersectionRect = coordinateRoot->localToAbsoluteQuad(intersectionRect).boundingBox(); >+ } >+ return true; >+} >+ >+bool Document::updateIntersectionObservation(IntersectionObserver& observer, double timeStamp) >+{ >+ FrameView* frameView = view(); >+ if (!frameView) >+ return false; >+ >+ bool anyNeedNotify = false; >+ >+ // 2. For each target in observer's internal [[ObservationTargets]] slot, >+ // processed in the same order that observe() was called on each target: >+ for (auto target : observer.observationTargets()) { >+ FloatRect absTargetRect; >+ FloatRect absRootBounds; >+ FloatRect absIntersectionRect; >+ bool isIntersecting; >+ >+ bool computedIntersection = computeAbsoluteTargetIntersection(*frameView, observer, target, absTargetRect, absRootBounds, absIntersectionRect, isIntersecting); >+ >+ LOG_WITH_STREAM(IntersectionObserver, stream << " observer " << &observer << " target" << target << " absTargetRect" << absTargetRect << " absIntersectionRect " << absIntersectionRect); >+ >+ // 5. Let targetArea be targetRect's area. >+ // 6. Let intersectionArea be intersectionRect's area. >+ // 7. Let intersectionRatio be intersectionArea divided by targetArea if targetArea is non-zero, and 0 otherwise. >+ float targetArea = absTargetRect.area(); >+ float intersectionRatio; >+ if (targetArea) >+ intersectionRatio = absIntersectionRect.area() / targetArea; >+ else if (isIntersecting) >+ intersectionRatio = 1; >+ else >+ intersectionRatio = 0; >+ >+ // 8. If intersectionRatio is equal to 0, let threshold be 0. If intersectionRatio is equal to 1, >+ // let threshold be the length of observer.thresholds. Otherwise, let threshold be the index of the first entry >+ // in observer.thresholds whose value is greater than intersectionRatio. >+ size_t thresholdIndex = 0; >+ if (!isIntersecting) >+ thresholdIndex = 0; >+ else if (intersectionRatio == 1) >+ thresholdIndex = observer.thresholds().size(); >+ else { >+ bool setIndex = false; >+ for (size_t i = 0; i < observer.thresholds().size(); ++i) { >+ if (observer.thresholds()[i] > intersectionRatio) { >+ thresholdIndex = i; >+ setIndex = true; >+ break; >+ } >+ } >+ if (!setIndex) >+ thresholdIndex = observer.thresholds().size(); >+ } >+ >+ // 9. Let intersectionObserverRegistration be the IntersectionObserverRegistration record in >+ // target's internal [[RegisteredIntersectionObservers]] slot whose observer property is equal to observer. >+ IntersectionObserverRegistration* registration = nullptr; >+ auto* observerData = target->intersectionObserverData(); >+ if (observerData) { >+ auto& registrations = observerData->registrations; >+ for (auto& currRegistration : registrations) { >+ if (currRegistration.observer == &observer) { >+ registration = &currRegistration; >+ break; >+ } >+ } >+ } >+ >+ if (!registration) { >+ ASSERT_NOT_REACHED(); >+ continue; >+ } >+ >+ // 10. Let previousThreshold be set to intersectionObserverRegistration's previousThreshold property. >+ // 11. If threshold does not equal previousThreshold, queue an IntersectionObserverEntry, >+ // passing in observer, time, rootBounds, boundingClientRect, intersectionRect and target. >+ LOG_WITH_STREAM(IntersectionObserver, stream << " registration->previousThresholdIndex " << registration->previousThresholdIndex << " thresholdIndex " << thresholdIndex); >+ >+ if (thresholdIndex != registration->previousThresholdIndex || isIntersecting != registration->previousIsIntersecting) { >+ // These two rects are in client coordinates in target's frame. >+ FloatRect rootBounds; >+ FloatRect targetClientRect; >+ FloatRect clientIntersectionRect; >+ >+ bool shouldReportRootBounds; >+ if (observer.root()) >+ shouldReportRootBounds = true; >+ else { >+ auto& targetDocument = target->document(); >+ shouldReportRootBounds = targetDocument.securityOrigin().canAccess(frameView->frame().document()->securityOrigin()); >+ } >+ if (computedIntersection) { >+ // FIXME: unclear if this needs to be reported in client coordinates: https://github.com/WICG/IntersectionObserver/issues/170 >+ if (observer.root()) { >+ // If there's an explicit root, we've already checked that the target's document is this, and the root and target are in >+ // the same document. >+ rootBounds = frameView->documentToClientRect(frameView->absoluteToDocumentRect(absRootBounds)); >+ >+ } else { >+ ASSERT(frameView->frame().isMainFrame()); >+ if (shouldReportRootBounds) >+ rootBounds = frameView->documentToClientRect(frameView->absoluteToDocumentRect(absRootBounds)); >+ } >+ >+ targetClientRect = target->boundingRectInClientCoordinates(); >+ if (isIntersecting) { >+ auto* targetFrameView = target->document().view(); >+ clientIntersectionRect = targetFrameView->contentsToView(enclosingIntRect(absIntersectionRect)); >+ } >+ } >+ >+ std::optional<DOMRectInit> entryRootBounds; >+ if (shouldReportRootBounds) >+ entryRootBounds = DOMRectInit({ rootBounds.x(), rootBounds.y(), rootBounds.width(), rootBounds.height() }); >+ observer.appendQueuedEntry(IntersectionObserverEntry::create({ >+ timeStamp, >+ entryRootBounds, >+ { targetClientRect.x(), targetClientRect.y(), targetClientRect.width(), targetClientRect.height() }, >+ { clientIntersectionRect.x(), clientIntersectionRect.y(), clientIntersectionRect.width(), clientIntersectionRect.height() }, >+ isIntersecting, >+ intersectionRatio, >+ target >+ })); >+ >+ // 12. Assign threshold to intersectionObserverRegistration's previousThreshold property. >+ registration->previousThresholdIndex = thresholdIndex; >+ registration->previousIsIntersecting = isIntersecting; >+ anyNeedNotify = true; >+ } >+ } >+ >+ return anyNeedNotify; >+} >+ >+void Document::notifyIntersectionObserversTimerFired() >+{ >+ LOG_WITH_STREAM(IntersectionObserver, stream << "notifyIntersectionObserversTimerFired"); >+ for (auto* observer : m_activeIntersectionObservers) >+ observer->notify(); >+} >+ >+#endif >+ > const AtomicString& Document::dir() const > { > auto* documentElement = this->documentElement(); >@@ -7700,7 +8034,7 @@ void Document::requestStorageAccess(Ref<DeferredPromise>&& promise) > Document* document = documentReference.get(); > if (!document) > return; >- >+ > if (wasGranted) { > document->setHasFrameSpecificStorageAccess(true); > MicrotaskQueue::mainThreadQueue().append(std::make_unique<VoidMicrotask>([documentReference = makeWeakPtr(*document)] () { >diff --git a/Source/WebCore/dom/Document.h b/Source/WebCore/dom/Document.h >index 2e3afc20b059a8b6b964b89b9b595f4b071c18c1..f933bdb5ac7f16dda156ddbe96f1c48a109da01b 100644 >--- a/Source/WebCore/dom/Document.h >+++ b/Source/WebCore/dom/Document.h >@@ -235,6 +235,10 @@ class MediaSession; > class HTMLAttachmentElement; > #endif > >+#if ENABLE(INTERSECTION_OBSERVER) >+class IntersectionObserver; >+#endif >+ > namespace Style { > class Scope; > }; >@@ -1359,6 +1363,15 @@ public: > void addViewportDependentPicture(HTMLPictureElement&); > void removeViewportDependentPicture(HTMLPictureElement&); > >+#if ENABLE(INTERSECTION_OBSERVER) >+ void activateIntersectionObserver(IntersectionObserver&); >+ bool deactivateIntersectionObserver(IntersectionObserver&); >+ unsigned numberOfActiveIntersectionObservers() const { return m_activeIntersectionObservers.size(); } >+ void addViewportIntersectionObserver(IntersectionObserver&); >+ void removeViewportIntersectionObserver(IntersectionObserver&); >+ void updateIntersectionObservations(); >+#endif >+ > #if ENABLE(MEDIA_STREAM) > void setHasCaptureMediaStreamTrack() { m_hasHadCaptureMediaStreamTrack = true; } > bool hasHadCaptureMediaStreamTrack() const { return m_hasHadCaptureMediaStreamTrack; } >@@ -1575,6 +1588,12 @@ private: > > void checkViewportDependentPictures(); > >+#if ENABLE(INTERSECTION_OBSERVER) >+ // Return true if any need notifying. >+ bool updateIntersectionObservation(IntersectionObserver&, double timeStamp); >+ void notifyIntersectionObserversTimerFired(); >+#endif >+ > #if USE(QUICK_LOOK) > bool shouldEnforceQuickLookSandbox() const; > void applyQuickLookSandbox(); >@@ -1757,6 +1776,12 @@ private: > > HashSet<HTMLPictureElement*> m_viewportDependentPictures; > >+#if ENABLE(INTERSECTION_OBSERVER) >+ HashSet<IntersectionObserver*> m_activeIntersectionObservers; >+ HashSet<IntersectionObserver*> m_viewportIntersectionObservers; >+ Timer m_intersectionObserversNotifyTimer; >+#endif >+ > Timer m_loadEventDelayTimer; > > ViewportArguments m_viewportArguments; >diff --git a/Source/WebCore/dom/Element.cpp b/Source/WebCore/dom/Element.cpp >index 69e6f90c1693dd7749384c2c7e2db719c7b3d206..fa4615c72fecbf91c6d8835e3c5d346545c890a7 100644 >--- a/Source/WebCore/dom/Element.cpp >+++ b/Source/WebCore/dom/Element.cpp >@@ -187,6 +187,10 @@ Element::~Element() > ASSERT(!beforePseudoElement()); > ASSERT(!afterPseudoElement()); > >+#if ENABLE(INTERSECTION_OBSERVER) >+ disconnectFromIntersectionObservers(); >+#endif >+ > removeShadowRoot(); > > if (hasSyntheticAttrChildNodes()) >@@ -1209,8 +1213,13 @@ Ref<DOMRectList> Element::getClientRects() > FloatRect Element::boundingClientRect() > { > document().updateLayoutIgnorePendingStylesheets(); >+ return boundingRectInClientCoordinates(); >+} > >+FloatRect Element::boundingRectInClientCoordinates() >+{ > RenderObject* renderer = this->renderer(); >+ > Vector<FloatQuad> quads; > if (isSVGElement() && renderer && !renderer->isSVGRoot()) { > // Get the bounding rectangle from the SVG model. >@@ -1642,8 +1651,19 @@ void Element::didMoveToNewDocument(Document& oldDocument, Document& newDocument) > attributeChanged(classAttr, nullAtom(), getAttribute(classAttr)); > } > >+ // FIXME: Do we need to update newDocument's intersection observer targets? >+ > if (UNLIKELY(isDefinedCustomElement())) > CustomElementReactionQueue::enqueueAdoptedCallbackIfNeeded(*this, oldDocument, newDocument); >+ >+#if ENABLE(INTERSECTION_OBSERVER) >+ if (auto* observerData = intersectionObserverData()) { >+ for (auto* observer : observerData->observers) { >+ if (oldDocument.deactivateIntersectionObserver(*observer)) >+ newDocument.activateIntersectionObserver(*observer); >+ } >+ } >+#endif > } > > bool Element::hasAttributes() const >@@ -3233,6 +3253,84 @@ void Element::requestPointerLock() > } > #endif > >+#if ENABLE(INTERSECTION_OBSERVER) >+void Element::startObservingIntersections(IntersectionObserver& observer) >+{ >+ auto* trackingDocument = observer.trackingDocument(); >+ if (!trackingDocument) >+ return; >+ auto& observerData = ensureIntersectionObserverData(); >+ auto& registrations = observerData.registrations; >+ >+ // If target is in this's internal [[ObservationTargets]] slot, return. >+ for (auto& registration : registrations) { >+ if (registration.observer == &observer) >+ return; >+ } >+ >+ // Let intersectionObserverRegistration be an IntersectionObserverRegistration record with an observer >+ // property set to this and a previousThreshold property set so that it's immediately differently from >+ // the current threshold, triggering an initial notification. >+ // Append intersectionObserverRegistration to targets internal [[RegisteredIntersectionObservers]] slot. >+ registrations.append({ &observer, std::numeric_limits<size_t>::max(), false }); >+ >+ // Add target to this's internal [[ObservationTargets]] slot. >+ observer.addObservationTarget(*this); >+} >+ >+void Element::endObservingIntersections(IntersectionObserver& observer) >+{ >+ // Remove the IntersectionObserverRegistration record whose observer property is equal to this from >+ // targets internal [[RegisteredIntersectionObservers]] slot. >+ auto* observerData = hasRareData() ? elementRareData()->intersectionObserverData() : nullptr; >+ if (!observerData) { >+ ASSERT(!observer.hasObservationTarget(*this)); >+ return; >+ } >+ >+ auto& registrations = observerData->registrations; >+ >+ for (size_t i = 0; i < registrations.size(); ++i) { >+ auto& observerRegistration = registrations.at(i); >+ if (observerRegistration.observer == &observer) { >+ registrations.remove(i); >+ break; >+ } >+ } >+ >+ // Remove target from this's internal [[ObservationTargets]] slot. >+ observer.removeObservationTarget(*this); >+} >+ >+void Element::disconnectFromIntersectionObservers() >+{ >+ auto* observerData = intersectionObserverData(); >+ if (!observerData) >+ return; >+ >+ for (auto& registration : observerData->registrations) >+ registration.observer->removeObservationTarget(*this); >+ observerData->registrations.clear(); >+ >+ for (auto* observer : observerData->observers) >+ observer->rootDestroyed(); >+ observerData->observers.clear(); >+} >+ >+IntersectionObserverData& Element::ensureIntersectionObserverData() >+{ >+ auto& data = ensureElementRareData(); >+ if (!data.intersectionObserverData()) >+ data.setIntersectionObserverData(std::make_unique<IntersectionObserverData>()); >+ return *data.intersectionObserverData(); >+} >+ >+IntersectionObserverData* Element::intersectionObserverData() >+{ >+ return hasRareData() ? elementRareData()->intersectionObserverData() : nullptr; >+} >+#endif >+ > SpellcheckAttributeState Element::spellcheckAttributeState() const > { > const AtomicString& value = attributeWithoutSynchronization(HTMLNames::spellcheckAttr); >diff --git a/Source/WebCore/dom/Element.h b/Source/WebCore/dom/Element.h >index 183c3fd117c2427fcca573ffee7951b94f15d02e..7f110825d8aad71ba91c3fe73d4e92048ff61d6e 100644 >--- a/Source/WebCore/dom/Element.h >+++ b/Source/WebCore/dom/Element.h >@@ -56,6 +56,11 @@ class RenderTreePosition; > class WebAnimation; > struct ElementStyle; > >+#if ENABLE(INTERSECTION_OBSERVER) >+class IntersectionObserver; >+struct IntersectionObserverData; >+#endif >+ > enum SpellcheckAttributeState { > SpellcheckAttributeTrue, > SpellcheckAttributeFalse, >@@ -175,15 +180,19 @@ public: > virtual int scrollWidth(); > virtual int scrollHeight(); > >- WEBCORE_EXPORT IntRect boundsInRootViewSpace(); >- >- WEBCORE_EXPORT FloatRect boundingClientRect(); >- > WEBCORE_EXPORT Ref<DOMRectList> getClientRects(); > Ref<DOMRect> getBoundingClientRect(); > >- // Returns the absolute bounding box translated into client coordinates. >+ // Same web-exposed client coordinate system as getBoundingClientRect, but does not update layout. >+ FloatRect boundingRectInClientCoordinates(); >+ WEBCORE_EXPORT FloatRect boundingClientRect(); // FIXME >+ >+ // Returns the absolute bounding box translated into root view coordinates. Result affected by top content inset. >+ // Does not update layout. > WEBCORE_EXPORT IntRect clientRect() const; >+ // Similar (idential?) behavior to clientRect(), but updates layout. >+ WEBCORE_EXPORT IntRect boundsInRootViewSpace(); >+ > // Returns the absolute bounding box translated into screen coordinates. > WEBCORE_EXPORT IntRect screenRect() const; > >@@ -567,6 +576,14 @@ public: > using ContainerNode::setAttributeEventListener; > void setAttributeEventListener(const AtomicString& eventType, const QualifiedName& attributeName, const AtomicString& value); > >+#if ENABLE(INTERSECTION_OBSERVER) >+ void startObservingIntersections(IntersectionObserver&); >+ void endObservingIntersections(IntersectionObserver&); >+ >+ IntersectionObserverData& ensureIntersectionObserverData(); >+ IntersectionObserverData* intersectionObserverData(); >+#endif >+ > Element* findAnchorElementForLink(String& outAnchorName); > > ExceptionOr<Ref<WebAnimation>> animate(JSC::ExecState&, JSC::Strong<JSC::JSObject>&&, std::optional<Variant<double, KeyframeAnimationOptions>>&&); >@@ -646,6 +663,10 @@ private: > void formatForDebugger(char* buffer, unsigned length) const override; > #endif > >+#if ENABLE(INTERSECTION_OBSERVER) >+ void disconnectFromIntersectionObservers(); >+#endif >+ > // The cloneNode function is private so that non-virtual cloneElementWith/WithoutChildren are used instead. > Ref<Node> cloneNodeInternal(Document&, CloningOperation) override; > virtual Ref<Element> cloneElementWithoutAttributesAndChildren(Document&); >diff --git a/Source/WebCore/dom/ElementRareData.cpp b/Source/WebCore/dom/ElementRareData.cpp >index 478a8887e0051acabb620e375ab6022528195bce..626e91e2345503bdea0b395d28955e196a9f5e76 100644 >--- a/Source/WebCore/dom/ElementRareData.cpp >+++ b/Source/WebCore/dom/ElementRareData.cpp >@@ -44,6 +44,9 @@ struct SameSizeAsElementRareData : NodeRareData { > LayoutSize sizeForResizing; > IntPoint savedLayerScrollPosition; > void* pointers[8]; >+#if ENABLE(INTERSECTION_OBSERVER) >+ void* intersectionObserverRegistrations; >+#endif > }; > > static_assert(sizeof(ElementRareData) == sizeof(SameSizeAsElementRareData), "ElementRareData should stay small"); >diff --git a/Source/WebCore/dom/ElementRareData.h b/Source/WebCore/dom/ElementRareData.h >index a0f301e874a4c0af143930adadde62c5ea8fbbff..8d0bc934882fb7f974e624304a060a79f53f08ec 100644 >--- a/Source/WebCore/dom/ElementRareData.h >+++ b/Source/WebCore/dom/ElementRareData.h >@@ -24,6 +24,7 @@ > #include "CustomElementReactionQueue.h" > #include "DOMTokenList.h" > #include "DatasetDOMStringMap.h" >+#include "IntersectionObserver.h" > #include "NamedNodeMap.h" > #include "NodeRareData.h" > #include "PseudoElement.h" >@@ -116,6 +117,11 @@ public: > bool hasCSSAnimation() const { return m_hasCSSAnimation; } > void setHasCSSAnimation(bool value) { m_hasCSSAnimation = value; } > >+#if ENABLE(INTERSECTION_OBSERVER) >+ IntersectionObserverData* intersectionObserverData() { return m_intersectionObserverData.get(); } >+ void setIntersectionObserverData(std::unique_ptr<IntersectionObserverData>&& data) { m_intersectionObserverData = WTFMove(data); } >+#endif >+ > private: > int m_tabIndex; > unsigned short m_childIndex; >@@ -154,6 +160,10 @@ private: > RefPtr<PseudoElement> m_afterPseudoElement; > > void releasePseudoElement(PseudoElement*); >+ >+#if ENABLE(INTERSECTION_OBSERVER) >+ std::unique_ptr<IntersectionObserverData> m_intersectionObserverData; >+#endif > }; > > inline IntSize defaultMinimumSizeForResizing() >diff --git a/Source/WebCore/page/FrameView.cpp b/Source/WebCore/page/FrameView.cpp >index e1ec7daf705089fe5cb229cea3d98d5ebff9ad6d..d54659a38fbe1fd0fb746ed6ddd4c11332902b61 100644 >--- a/Source/WebCore/page/FrameView.cpp >+++ b/Source/WebCore/page/FrameView.cpp >@@ -1171,12 +1171,17 @@ bool FrameView::flushCompositingStateIncludingSubframes() > #endif > > bool allFramesFlushed = flushCompositingStateForThisFrame(frame()); >+ // FIXME: Is this the right spot for this? Need to call this once per draw, after layout is updated. >+ if (auto* document = frame().document()) >+ document->updateIntersectionObservations(); > > for (Frame* child = frame().tree().firstRenderedChild(); child; child = child->tree().traverseNextRendered(m_frame.ptr())) { > if (!child->view()) > continue; > bool flushed = child->view()->flushCompositingStateForThisFrame(frame()); > allFramesFlushed &= flushed; >+ if (auto* document = child->document()) >+ document->updateIntersectionObservations(); > } > return allFramesFlushed; > } >diff --git a/Source/WebCore/page/IntersectionObserver.cpp b/Source/WebCore/page/IntersectionObserver.cpp >index 07fa8a889c4845b29a1d7a823b2665c5ffae56c4..6160f328880a01de4749e3777757b7df98f0d830 100644 >--- a/Source/WebCore/page/IntersectionObserver.cpp >+++ b/Source/WebCore/page/IntersectionObserver.cpp >@@ -28,41 +28,239 @@ > #if ENABLE(INTERSECTION_OBSERVER) > #include "IntersectionObserver.h" > >+#include "CSSParser.h" >+#include "CSSPropertyParser.h" >+#include "CSSPropertyParserHelpers.h" >+#include "CSSTokenizer.h" >+#include "DOMWindow.h" > #include "Element.h" >+#include "ExceptionCode.h" > #include "IntersectionObserverCallback.h" > #include "IntersectionObserverEntry.h" >+#include "Logging.h" >+#include "Performance.h" >+#include "StyleResolver.h" >+#include <algorithm> >+#include <functional> > #include <wtf/Vector.h> >+#include <wtf/text/TextStream.h> > > namespace WebCore { > >-IntersectionObserver::IntersectionObserver(Ref<IntersectionObserverCallback>&& callback, Init&& init) >- : m_root(init.root) >+static bool isThresholdWithinRange(double threshold) >+{ >+ return threshold >= 0 && threshold <= 1; >+} >+ >+static ExceptionOr<LengthBox> parseRootMargin(String& rootMargin) >+{ >+ CSSTokenizer tokenizer(rootMargin); >+ auto tokenRange = tokenizer.tokenRange(); >+ Vector<Length, 4> margins; >+ while (!tokenRange.atEnd()) { >+ if (margins.size() == 4) >+ return Exception { SyntaxError, "Failed to construct 'IntersectionObserver': Extra text found at the end of rootMargin." }; >+ RefPtr<CSSPrimitiveValue> parsedValue = CSSPropertyParserHelpers::consumeLengthOrPercent(tokenRange, HTMLStandardMode, ValueRangeAll); >+ if (!parsedValue || parsedValue->isCalculated()) >+ return Exception { SyntaxError, "Failed to construct 'IntersectionObserver': rootMargin must be specified in pixels or percent." }; >+ if (parsedValue->isPercentage()) >+ margins.append(Length(parsedValue->doubleValue(), Percent)); >+ else if (parsedValue->isPx()) >+ margins.append(Length(parsedValue->intValue(), Fixed)); >+ else >+ return Exception { SyntaxError, "Failed to construct 'IntersectionObserver': rootMargin must be specified in pixels or percent." }; >+ } >+ >+ Length top, right, bottom, left; >+ switch (margins.size()) { >+ case 0: >+ for (int i = 0; i < 4; ++i) >+ margins.append(Length()); >+ break; >+ case 1: >+ for (int i = 0; i < 3; ++i) >+ margins.append(margins[0]); >+ break; >+ case 2: >+ margins.append(margins[0]); >+ margins.append(margins[1]); >+ break; >+ case 3: >+ margins.append(margins[1]); >+ break; >+ case 4: >+ break; >+ default: >+ ASSERT_NOT_REACHED(); >+ } >+ >+ StringBuilder stringBuilder; >+ for (size_t i = 0; i < 4; ++i) { >+ stringBuilder.appendNumber(margins[i].intValue()); >+ if (margins[i].type() == Percent) >+ stringBuilder.append('%'); >+ else >+ stringBuilder.append("px", 2); >+ if (i < 3) >+ stringBuilder.append(' '); >+ } >+ rootMargin = stringBuilder.toString(); >+ >+ return LengthBox(WTFMove(margins[0]), WTFMove(margins[1]), WTFMove(margins[2]), WTFMove(margins[3])); >+} >+ >+ExceptionOr<Ref<IntersectionObserver>> IntersectionObserver::create(Document& document, Ref<IntersectionObserverCallback>&& callback, IntersectionObserver::Init&& init) >+{ >+ auto rootMarginOrException = parseRootMargin(init.rootMargin); >+ if (rootMarginOrException.hasException()) >+ return rootMarginOrException.releaseException(); >+ >+ bool foundOutOfRangeThreshold = false; >+ if (WTF::holds_alternative<double>(init.threshold)) >+ foundOutOfRangeThreshold = !isThresholdWithinRange(WTF::get<double>(init.threshold)); >+ else { >+ for (auto& threshold : WTF::get<Vector<double>>(init.threshold)) >+ foundOutOfRangeThreshold |= !isThresholdWithinRange(threshold); >+ } >+ if (foundOutOfRangeThreshold) >+ return Exception { RangeError, "Failed to construct 'IntersectionObserver': all thresholds must lie in the range [0.0, 1.0]." }; >+ >+ return adoptRef(*new IntersectionObserver(document, WTFMove(callback), WTFMove(init), rootMarginOrException.releaseReturnValue())); >+} >+ >+// FIXME: need to stop observing if the root is deleted. >+IntersectionObserver::IntersectionObserver(Document& document, Ref<IntersectionObserverCallback>&& callback, Init&& init, LengthBox&& marginBox) >+ : m_implicitRootDocument(nullptr) >+ , m_root(init.root) > , m_rootMargin(WTFMove(init.rootMargin)) > , m_callback(WTFMove(callback)) >+ , m_marginBox(marginBox) > { > if (WTF::holds_alternative<double>(init.threshold)) > m_thresholds.append(WTF::get<double>(init.threshold)); > else > m_thresholds = WTF::get<Vector<double>>(WTFMove(init.threshold)); >+ std::sort(m_thresholds.begin(), m_thresholds.end(), std::less<double>()); >+ >+ if (m_root) { >+ auto& observerData = m_root->ensureIntersectionObserverData(); >+ observerData.observers.append(this); >+ } else if (auto* frame = document.frame()) { >+ m_implicitRootDocument = frame->mainFrame().document(); >+ m_implicitRootDocument->addViewportIntersectionObserver(*this); >+ } > } > >-void IntersectionObserver::observe(Element&) >+IntersectionObserver::~IntersectionObserver() > { >+ disconnect(); >+ if (m_implicitRootDocument) >+ m_implicitRootDocument->removeViewportIntersectionObserver(*this); >+ else if (m_root) { >+ auto& elementObservers = m_root->intersectionObserverData()->observers; >+ for (size_t i = 0; i < elementObservers.size(); ++i) { >+ if (elementObservers.at(i) == this) { >+ elementObservers.remove(i); >+ break; >+ } >+ } >+ } > } > >-void IntersectionObserver::unobserve(Element&) >+void IntersectionObserver::observe(Element& element) > { >+ if (!trackingDocument()) >+ return; >+ element.startObservingIntersections(*this); >+} >+ >+void IntersectionObserver::unobserve(Element& element) >+{ >+ element.endObservingIntersections(*this); > } > > void IntersectionObserver::disconnect() > { >+ for (auto* target : m_observationTargets) >+ unobserve(*target); > } > >-Vector<RefPtr<IntersectionObserverEntry>> IntersectionObserver::takeRecords() >+Vector<Ref<IntersectionObserverEntry>> IntersectionObserver::takeRecords() > { >- return { }; >+ return WTFMove(m_queuedEntries); > } > >+void IntersectionObserver::addObservationTarget(Element& element) >+{ >+ ASSERT(!m_observationTargets.contains(&element)); >+ m_observationTargets.append(&element); >+ auto* document = trackingDocument(); >+ document->activateIntersectionObserver(*this); >+ document->postTask([&trackingDocument = *document] (ScriptExecutionContext&) mutable { >+ trackingDocument.updateIntersectionObservations(); >+ }); >+} >+ >+void IntersectionObserver::removeObservationTarget(Element& element) >+{ >+ m_observationTargets.removeFirst(&element); >+ if (!hasObservationTargets()) { >+ if (auto* document = trackingDocument()) >+ document->deactivateIntersectionObserver(*this); >+ } >+} >+ >+bool IntersectionObserver::hasObservationTarget(Element& element) const >+{ >+ return m_observationTargets.contains(&element); >+} >+ >+bool IntersectionObserver::createTimestamp(DOMHighResTimeStamp& timestamp) const >+{ >+ auto* context = m_callback->scriptExecutionContext(); >+ if (!context) >+ return false; >+ ASSERT(context->isDocument()); >+ auto& document = downcast<Document>(*context); >+ if (auto* window = document.domWindow()) { >+ if (auto* performance = window->performance()) { >+ timestamp = performance->now(); >+ return true; >+ } >+ } >+ return false; >+} >+ >+void IntersectionObserver::rootDestroyed() >+{ >+ ASSERT(m_root); >+ disconnect(); >+ m_root = nullptr; >+} >+ >+void IntersectionObserver::implicitRootDocumentDestroyed() >+{ >+ ASSERT(m_implicitRootDocument); >+ disconnect(); >+ m_implicitRootDocument = nullptr; >+} >+ >+void IntersectionObserver::appendQueuedEntry(Ref<IntersectionObserverEntry>&& entry) >+{ >+ LOG_WITH_STREAM(IntersectionObserver, stream << " adding queued entry " << entry.get()); >+ >+ m_queuedEntries.append(WTFMove(entry)); >+} >+ >+void IntersectionObserver::notify() >+{ >+ if (m_queuedEntries.isEmpty() || !m_callback->canInvokeCallback()) >+ return; >+ >+ LOG(IntersectionObserver, "IntersectionObserver %p callback(): %lu records", this, m_queuedEntries.size()); >+ >+ m_callback->handleEvent(takeRecords(), *this); >+} > > } // namespace WebCore > >diff --git a/Source/WebCore/page/IntersectionObserver.h b/Source/WebCore/page/IntersectionObserver.h >index 13d58f3cfc55edecd52e98542844018cd2fb431a..44ba76e13b69ab4cdf7399259c31eb95c827d3c5 100644 >--- a/Source/WebCore/page/IntersectionObserver.h >+++ b/Source/WebCore/page/IntersectionObserver.h >@@ -29,47 +29,82 @@ > > #include "IntersectionObserverCallback.h" > #include "IntersectionObserverEntry.h" >+#include "LengthBox.h" > #include <wtf/RefCounted.h> > #include <wtf/Variant.h> > #include <wtf/text/WTFString.h> > > namespace WebCore { > >+class Document; > class Element; > >+struct IntersectionObserverRegistration { >+ RefPtr<IntersectionObserver> observer; >+ size_t previousThresholdIndex { std::numeric_limits<size_t>::max() }; >+ bool previousIsIntersecting { false }; >+}; >+ >+struct IntersectionObserverData { >+ // IntersectionObservers for which the element that owns this IntersectionObserverData is the root. >+ Vector<IntersectionObserver*> observers; >+ >+ // Intersection observations for which the element that owns this IntersectionObserverData is the target. >+ Vector<IntersectionObserverRegistration> registrations; >+}; >+ > class IntersectionObserver : public RefCounted<IntersectionObserver> { > public: > struct Init { >- RefPtr<Element> root; >+ Element* root; > String rootMargin; > Variant<double, Vector<double>> threshold; > }; > >- static Ref<IntersectionObserver> create(Ref<IntersectionObserverCallback>&& callback, Init&& init) >- { >- return adoptRef(*new IntersectionObserver(WTFMove(callback), WTFMove(init))); >- } >- >- Element* root() const { return m_root.get(); } >+ static ExceptionOr<Ref<IntersectionObserver>> create(Document&, Ref<IntersectionObserverCallback>&&, Init&&); >+ >+ ~IntersectionObserver(); >+ >+ Document* trackingDocument() { return m_root ? &m_root->document() : m_implicitRootDocument; } >+ >+ Element* root() const { return m_root; } > String rootMargin() const { return m_rootMargin; } >+ const LengthBox& marginBox() const { return m_marginBox; } > const Vector<double>& thresholds() const { return m_thresholds; } >+ const Vector<Element*> observationTargets() const { return m_observationTargets; } > >+ // Called from JavaScript API > void observe(Element&); > void unobserve(Element&); > void disconnect(); > >- Vector<RefPtr<IntersectionObserverEntry>> takeRecords(); >+ Vector<Ref<IntersectionObserverEntry>> takeRecords(); >+ >+ // Called internally. >+ void addObservationTarget(Element&); >+ void removeObservationTarget(Element&); >+ bool hasObservationTarget(Element&) const; >+ bool hasObservationTargets() const { return m_observationTargets.size(); } >+ bool createTimestamp(DOMHighResTimeStamp&) const; >+ void rootDestroyed(); >+ void implicitRootDocumentDestroyed(); >+ >+ void appendQueuedEntry(Ref<IntersectionObserverEntry>&&); >+ void notify(); > > private: >- IntersectionObserver(Ref<IntersectionObserverCallback>&&, Init&&); >- >- RefPtr<Element> m_root; >+ IntersectionObserver(Document&, Ref<IntersectionObserverCallback>&&, Init&&, LengthBox&& marginBox); >+ >+ Document* m_implicitRootDocument; >+ Element* m_root; > String m_rootMargin; > Vector<double> m_thresholds; > Ref<IntersectionObserverCallback> m_callback; >+ LengthBox m_marginBox; >+ Vector<Element*> m_observationTargets; >+ Vector<Ref<IntersectionObserverEntry>> m_queuedEntries; > }; > >- > } // namespace WebCore > > #endif // ENABLE(INTERSECTION_OBSERVER) >diff --git a/Source/WebCore/page/IntersectionObserver.idl b/Source/WebCore/page/IntersectionObserver.idl >index 757de6ee1f02f4f1705e011658b64abfe4528e3b..f78772af74ec53d9109582365aac1c2f5c2fc852 100644 >--- a/Source/WebCore/page/IntersectionObserver.idl >+++ b/Source/WebCore/page/IntersectionObserver.idl >@@ -27,6 +27,8 @@ > > [ > Conditional=INTERSECTION_OBSERVER, >+ ConstructorCallWith=Document, >+ ConstructorMayThrowException, > Constructor(IntersectionObserverCallback callback, optional IntersectionObserverInit options), > ImplementationLacksVTable, > EnabledAtRuntime=IntersectionObserver >diff --git a/Source/WebCore/page/IntersectionObserverEntry.cpp b/Source/WebCore/page/IntersectionObserverEntry.cpp >index e3f9c3c9fc40908b6371bcaa909f0c759159c52e..b2432940702202e8e6f25de99c3311576357e34c 100644 >--- a/Source/WebCore/page/IntersectionObserverEntry.cpp >+++ b/Source/WebCore/page/IntersectionObserverEntry.cpp >@@ -29,18 +29,51 @@ > #include "IntersectionObserverEntry.h" > > #include "Element.h" >+#include <wtf/text/TextStream.h> > > namespace WebCore { > > IntersectionObserverEntry::IntersectionObserverEntry(const Init& init) > : m_time(init.time) >- , m_rootBounds(DOMRectReadOnly::fromRect(init.rootBounds)) > , m_boundingClientRect(DOMRectReadOnly::fromRect(init.boundingClientRect)) > , m_intersectionRect(DOMRectReadOnly::fromRect(init.intersectionRect)) >+ , m_isIntersecting(init.isIntersecting) >+ , m_intersectionRatio(init.intersectionRatio) > , m_target(init.target) > { >+ if (init.rootBounds) >+ m_rootBounds = DOMRectReadOnly::fromRect(*init.rootBounds); > } > >+TextStream& operator<<(TextStream& ts, const DOMRectInit& rect) >+{ >+ ts << "x: " << rect.x; >+ ts << "y: " << rect.y; >+ ts << "width: " << rect.width; >+ ts << "height: " << rect.height; >+ return ts; >+} >+ >+TextStream& operator<<(TextStream& ts, const DOMRectReadOnly& rect) >+{ >+ ts << "x: " << rect.x(); >+ ts << "y: " << rect.y(); >+ ts << "width: " << rect.width(); >+ ts << "height: " << rect.height(); >+ >+ return ts; >+} >+ >+TextStream& operator<<(TextStream& ts, const IntersectionObserverEntry& entry) >+{ >+ ts << entry.time(); >+ ts << entry.rootBounds().get(); >+ ts << entry.boundingClientRect().get(); >+ ts << entry.intersectionRect().get(); >+ ts << entry.target().get(); >+ ts << entry.intersectionRatio(); >+ return ts; >+} > > } // namespace WebCore > >diff --git a/Source/WebCore/page/IntersectionObserverEntry.h b/Source/WebCore/page/IntersectionObserverEntry.h >index 638d4b814dd4ddcb21dbca7b5b9abac4488bafd2..5fc4473b4ffbaa5cdf0558c3d860b3062c7ad19d 100644 >--- a/Source/WebCore/page/IntersectionObserverEntry.h >+++ b/Source/WebCore/page/IntersectionObserverEntry.h >@@ -33,6 +33,10 @@ > #include <wtf/Vector.h> > #include <wtf/text/WTFString.h> > >+namespace WTF { >+class TextStream; >+} >+ > namespace WebCore { > > class Element; >@@ -43,9 +47,11 @@ public: > > struct Init { > double time; >- DOMRectInit rootBounds; >+ std::optional<DOMRectInit> rootBounds; > DOMRectInit boundingClientRect; > DOMRectInit intersectionRect; >+ bool isIntersecting; >+ double intersectionRatio; > RefPtr<Element> target; > }; > >@@ -60,6 +66,7 @@ public: > RefPtr<DOMRectReadOnly> intersectionRect() const { return m_intersectionRect; } > RefPtr<Element> target() const { return m_target; } > >+ bool isIntersecting() const { return m_isIntersecting; } > double intersectionRatio() const { return m_intersectionRatio; } > > private: >@@ -69,10 +76,14 @@ private: > RefPtr<DOMRectReadOnly> m_rootBounds; > RefPtr<DOMRectReadOnly> m_boundingClientRect; > RefPtr<DOMRectReadOnly> m_intersectionRect; >+ bool m_isIntersecting { false }; > double m_intersectionRatio { 0 }; > RefPtr<Element> m_target; > }; > >+WTF::TextStream& operator<<(WTF::TextStream&, const DOMRectInit&); >+WTF::TextStream& operator<<(WTF::TextStream&, const DOMRectReadOnly&); >+WTF::TextStream& operator<<(WTF::TextStream&, const IntersectionObserverEntry&); > > } // namespace WebCore > >diff --git a/Source/WebCore/page/IntersectionObserverEntry.idl b/Source/WebCore/page/IntersectionObserverEntry.idl >index 1b4338b9830940c083f3473e7fc210fd74d7c14c..47612d0a79d096ca33be534fccd48da4bfd3b73a 100644 >--- a/Source/WebCore/page/IntersectionObserverEntry.idl >+++ b/Source/WebCore/page/IntersectionObserverEntry.idl >@@ -34,9 +34,10 @@ typedef double DOMHighResTimeStamp; > EnabledAtRuntime=IntersectionObserver > ] interface IntersectionObserverEntry { > readonly attribute DOMHighResTimeStamp time; >- readonly attribute DOMRectReadOnly rootBounds; >+ readonly attribute DOMRectReadOnly? rootBounds; > readonly attribute DOMRectReadOnly boundingClientRect; > readonly attribute DOMRectReadOnly intersectionRect; >+ readonly attribute boolean isIntersecting; > readonly attribute double intersectionRatio; > readonly attribute Element target; > }; >@@ -45,8 +46,10 @@ typedef double DOMHighResTimeStamp; > Conditional=INTERSECTION_OBSERVER, > ] dictionary IntersectionObserverEntryInit { > required DOMHighResTimeStamp time; >- required DOMRectInit rootBounds; >+ required DOMRectInit? rootBounds; > required DOMRectInit boundingClientRect; > required DOMRectInit intersectionRect; >+ required boolean isIntersecting; >+ required double intersectionRatio; > required Element target; > }; >diff --git a/Source/WebCore/platform/Logging.h b/Source/WebCore/platform/Logging.h >index b47cbf993991a2acad935c37fd2c55369ed656e4..41f87bc35f103634444c778ef82ce07b6c7dc4db 100644 >--- a/Source/WebCore/platform/Logging.h >+++ b/Source/WebCore/platform/Logging.h >@@ -59,6 +59,7 @@ namespace WebCore { > M(Images) \ > M(IndexedDB) \ > M(IndexedDBOperations) \ >+ M(IntersectionObserver) \ > M(Layers) \ > M(Layout) \ > M(FormattingContextLayout) \ >diff --git a/Source/WebCore/platform/graphics/FloatRect.cpp b/Source/WebCore/platform/graphics/FloatRect.cpp >index eb165182b7bec41c90d9a82e3dd5623f81439123..eb62f619b150fc1c6c859ce95684fce4cbd8fa2f 100644 >--- a/Source/WebCore/platform/graphics/FloatRect.cpp >+++ b/Source/WebCore/platform/graphics/FloatRect.cpp >@@ -93,6 +93,27 @@ void FloatRect::intersect(const FloatRect& other) > setLocationAndSizeFromEdges(l, t, r, b); > } > >+bool FloatRect::inclusiveIntersect(const FloatRect& other) >+{ >+ float left = std::max(x(), other.x()); >+ float top = std::max(y(), other.y()); >+ float right = std::min(maxX(), other.maxX()); >+ float bottom = std::min(maxY(), other.maxY()); >+ >+ // Return a clean empty rectangle for non-intersecting cases. >+ if (left > right || top > bottom) { >+ left = 0; >+ top = 0; >+ right = 0; >+ bottom = 0; >+ return false; >+ } >+ >+ setLocationAndSizeFromEdges(left, top, right, bottom); >+ return true; >+} >+ >+ > void FloatRect::unite(const FloatRect& other) > { > // Handle empty special cases first. >diff --git a/Source/WebCore/platform/graphics/FloatRect.h b/Source/WebCore/platform/graphics/FloatRect.h >index 4f3576610afb13c0716ec33ac9bbe7c852fafcb5..b4f9152f2f4d8bcc52161c6260abe80f1abf2895 100644 >--- a/Source/WebCore/platform/graphics/FloatRect.h >+++ b/Source/WebCore/platform/graphics/FloatRect.h >@@ -89,6 +89,8 @@ public: > float width() const { return m_size.width(); } > float height() const { return m_size.height(); } > >+ float area() const { return m_size.area(); } >+ > void setX(float x) { m_location.setX(x); } > void setY(float y) { m_location.setY(y); } > void setWidth(float width) { m_size.setWidth(width); } >@@ -142,6 +144,7 @@ public: > WEBCORE_EXPORT bool contains(const FloatPoint&, ContainsMode = InsideOrOnStroke) const; > > WEBCORE_EXPORT void intersect(const FloatRect&); >+ bool inclusiveIntersect(const FloatRect&); > WEBCORE_EXPORT void unite(const FloatRect&); > void uniteEvenIfEmpty(const FloatRect&); > void uniteIfNonZero(const FloatRect&); >diff --git a/Source/WebCore/platform/graphics/LayoutRect.cpp b/Source/WebCore/platform/graphics/LayoutRect.cpp >index fd0b6994c334d2569682e2340a1b51969119b341..7f449fba56243b1b4d0d14d517bdc75c266b93b1 100644 >--- a/Source/WebCore/platform/graphics/LayoutRect.cpp >+++ b/Source/WebCore/platform/graphics/LayoutRect.cpp >@@ -71,6 +71,22 @@ void LayoutRect::intersect(const LayoutRect& other) > m_size = newMaxPoint - newLocation; > } > >+bool LayoutRect::inclusiveIntersect(const LayoutRect& other) >+{ >+ LayoutPoint newLocation(std::max(x(), other.x()), std::max(y(), other.y())); >+ LayoutPoint newMaxPoint(std::min(maxX(), other.maxX()), std::min(maxY(), other.maxY())); >+ >+ if (newLocation.x() > newMaxPoint.x() || newLocation.y() > newMaxPoint.y()) { >+ newLocation = LayoutPoint(0, 0); >+ newMaxPoint = LayoutPoint(0, 0); >+ return false; >+ } >+ >+ m_location = newLocation; >+ m_size = newMaxPoint - newLocation; >+ return true; >+} >+ > void LayoutRect::unite(const LayoutRect& other) > { > // Handle empty special cases first. >diff --git a/Source/WebCore/platform/graphics/LayoutRect.h b/Source/WebCore/platform/graphics/LayoutRect.h >index d720b5649fbec55340cbfb45e8ba2f2f99a3ce73..b2ef9cb19902da817309fff654499c70fbbe69de 100644 >--- a/Source/WebCore/platform/graphics/LayoutRect.h >+++ b/Source/WebCore/platform/graphics/LayoutRect.h >@@ -145,6 +145,7 @@ public: > bool contains(const LayoutPoint& point) const { return contains(point.x(), point.y()); } > > void intersect(const LayoutRect&); >+ bool inclusiveIntersect(const LayoutRect&); > WEBCORE_EXPORT void unite(const LayoutRect&); > void uniteIfNonZero(const LayoutRect&); > bool checkedUnite(const LayoutRect&); >diff --git a/Source/WebCore/rendering/RenderBlock.cpp b/Source/WebCore/rendering/RenderBlock.cpp >index 3d08ac2465ff3034d6da6311307d4c825d63b903..c0cc6af3862aea9dd0759728c9ee78bc42ad3269 100644 >--- a/Source/WebCore/rendering/RenderBlock.cpp >+++ b/Source/WebCore/rendering/RenderBlock.cpp >@@ -409,6 +409,15 @@ void RenderBlock::removePositionedObjectsIfNeeded(const RenderStyle& oldStyle, c > } > } > >+bool RenderBlock::isContainingBlockAncestorFor(RenderObject& renderer) const >+{ >+ for (const auto* cb = renderer.containingBlock(); cb && !is<RenderView>(*cb); cb = cb->containingBlock()) { >+ if (cb == this) >+ return true; >+ } >+ return false; >+} >+ > void RenderBlock::styleWillChange(StyleDifference diff, const RenderStyle& newStyle) > { > const RenderStyle* oldStyle = hasInitializedStyle() ? &style() : nullptr; >diff --git a/Source/WebCore/rendering/RenderBlock.h b/Source/WebCore/rendering/RenderBlock.h >index ef1e7e389824a44977f71795700c2fb27e7e8775..5a6e457db70aa62c59013aa7fe4b5fd10b8dd54f 100644 >--- a/Source/WebCore/rendering/RenderBlock.h >+++ b/Source/WebCore/rendering/RenderBlock.h >@@ -88,6 +88,8 @@ public: > return objects && !objects->isEmpty(); > } > >+ bool isContainingBlockAncestorFor(RenderObject&) const; >+ > void addPercentHeightDescendant(RenderBox&); > static void removePercentHeightDescendant(RenderBox&); > TrackedRendererListHashSet* percentHeightDescendants() const; >diff --git a/Source/WebCore/rendering/RenderBox.cpp b/Source/WebCore/rendering/RenderBox.cpp >index f2be7392afe834a14aac621b8b6a9dc239e2bcc5..547601318333c4eea01566ae9d3d5160e3a71359 100644 >--- a/Source/WebCore/rendering/RenderBox.cpp >+++ b/Source/WebCore/rendering/RenderBox.cpp >@@ -967,7 +967,7 @@ LayoutSize RenderBox::cachedSizeForOverflowClip() const > return layer()->size(); > } > >-void RenderBox::applyCachedClipAndScrollPositionForRepaint(LayoutRect& paintRect) const >+void RenderBox::applyCachedClipAndScrollPositionForRepaint(LayoutRect& paintRect, bool useInclusiveIntersection, bool* intersects) const > { > flipForWritingMode(paintRect); > paintRect.moveBy(-scrollPosition()); // For overflow:auto/scroll/hidden. >@@ -982,7 +982,10 @@ void RenderBox::applyCachedClipAndScrollPositionForRepaint(LayoutRect& paintRect > // layer's size instead. Even if the layer's size is wrong, the layer itself will repaint > // anyway if its size does change. > LayoutRect clipRect(LayoutPoint(), cachedSizeForOverflowClip()); >- paintRect = intersection(paintRect, clipRect); >+ if (useInclusiveIntersection) >+ *intersects = paintRect.inclusiveIntersect(clipRect); >+ else >+ paintRect = intersection(paintRect, clipRect); > flipForWritingMode(paintRect); > } > >@@ -2161,8 +2164,12 @@ LayoutRect RenderBox::computeRectForRepaint(const LayoutRect& rect, const Render > > adjustedRect.moveBy(location()); > adjustedRect.move(layoutState->paintOffset()); >- if (layoutState->isClipped()) >- adjustedRect.intersect(layoutState->clipRect()); >+ if (layoutState->isClipped()) { >+ if (context.m_useInclusiveIntersection) >+ *context.m_intersects = adjustedRect.inclusiveIntersect(layoutState->clipRect()); >+ else >+ adjustedRect.intersect(layoutState->clipRect()); >+ } > return adjustedRect; > } > >@@ -2253,8 +2260,13 @@ LayoutRect RenderBox::computeRectForRepaint(const LayoutRect& rect, const Render > if (container->hasOverflowClip()) { > RenderBox& containerBox = downcast<RenderBox>(*container); > if (containerBox.shouldApplyClipAndScrollPositionForRepaint(repaintContainer)) { >- containerBox.applyCachedClipAndScrollPositionForRepaint(adjustedRect); >- if (adjustedRect.isEmpty()) >+ bool intersects = true; >+ containerBox.applyCachedClipAndScrollPositionForRepaint(adjustedRect, context.m_useInclusiveIntersection, &intersects); >+ if (context.m_useInclusiveIntersection) >+ *context.m_intersects = intersects; >+ else >+ intersects = !adjustedRect.isEmpty(); >+ if (!intersects) > return adjustedRect; > } > } >diff --git a/Source/WebCore/rendering/RenderBox.h b/Source/WebCore/rendering/RenderBox.h >index 81663f74d2b9add13be6d647b4325ecdac46cef5..af29a15d6eef63f0862a4440da0cb4f5dbaccf40 100644 >--- a/Source/WebCore/rendering/RenderBox.h >+++ b/Source/WebCore/rendering/RenderBox.h >@@ -569,7 +569,7 @@ public: > LayoutSize cachedSizeForOverflowClip() const; > > bool shouldApplyClipAndScrollPositionForRepaint(const RenderLayerModelObject* repaintContainer) const; >- void applyCachedClipAndScrollPositionForRepaint(LayoutRect& paintRect) const; >+ void applyCachedClipAndScrollPositionForRepaint(LayoutRect& paintRect, bool useInclusiveIntersection, bool* intersects) const; > > virtual bool hasRelativeDimensions() const; > virtual bool hasRelativeLogicalHeight() const; >diff --git a/Source/WebCore/rendering/RenderInline.cpp b/Source/WebCore/rendering/RenderInline.cpp >index f67ab86f6a9c77222dad548138189438977f21b8..a62ea3252560fad3cc8bef254a7af6914b1d289f 100644 >--- a/Source/WebCore/rendering/RenderInline.cpp >+++ b/Source/WebCore/rendering/RenderInline.cpp >@@ -846,7 +846,7 @@ LayoutRect RenderInline::clippedOverflowRectForRepaint(const RenderLayerModelObj > return repaintRect; > > if (containingBlock->hasOverflowClip() && containingBlock->shouldApplyClipAndScrollPositionForRepaint(repaintContainer)) >- containingBlock->applyCachedClipAndScrollPositionForRepaint(repaintRect); >+ containingBlock->applyCachedClipAndScrollPositionForRepaint(repaintRect, false, nullptr); > > repaintRect = containingBlock->computeRectForRepaint(repaintRect, repaintContainer); > >@@ -880,7 +880,9 @@ LayoutRect RenderInline::computeRectForRepaint(const LayoutRect& rect, const Ren > if (style().hasInFlowPosition() && layer()) > adjustedRect.move(layer()->offsetForInFlowPosition()); > adjustedRect.move(layoutState->paintOffset()); >- if (layoutState->isClipped()) >+ if (context.m_useInclusiveIntersection) >+ *context.m_intersects = adjustedRect.inclusiveIntersect(layoutState->clipRect()); >+ else > adjustedRect.intersect(layoutState->clipRect()); > return adjustedRect; > } >@@ -907,8 +909,14 @@ LayoutRect RenderInline::computeRectForRepaint(const LayoutRect& rect, const Ren > // its controlClipRect will be wrong. For overflow clip we use the values cached by the layer. > adjustedRect.setLocation(topLeft); > if (container->hasOverflowClip()) { >- downcast<RenderBox>(*container).applyCachedClipAndScrollPositionForRepaint(adjustedRect); >- if (adjustedRect.isEmpty()) >+ bool intersects = true; >+ downcast<RenderBox>(*container).applyCachedClipAndScrollPositionForRepaint(adjustedRect, context.m_useInclusiveIntersection, &intersects); >+ >+ if (context.m_useInclusiveIntersection) >+ *context.m_intersects = intersects; >+ else >+ intersects = !adjustedRect.isEmpty(); >+ if (!intersects) > return adjustedRect; > } > >diff --git a/Source/WebCore/rendering/RenderObject.cpp b/Source/WebCore/rendering/RenderObject.cpp >index cdb0262053bb4b7f4270f7aa2fca40af0405251d..45dc57e82792028e9e4bc0551f87f162ed0e4826 100644 >--- a/Source/WebCore/rendering/RenderObject.cpp >+++ b/Source/WebCore/rendering/RenderObject.cpp >@@ -972,14 +972,19 @@ LayoutRect RenderObject::computeRectForRepaint(const LayoutRect& rect, const Ren > > LayoutRect adjustedRect = rect; > if (parent->hasOverflowClip()) { >- downcast<RenderBox>(*parent).applyCachedClipAndScrollPositionForRepaint(adjustedRect); >- if (adjustedRect.isEmpty()) >+ bool intersects = true; >+ downcast<RenderBox>(*parent).applyCachedClipAndScrollPositionForRepaint(adjustedRect, context.m_useInclusiveIntersection, &intersects); >+ if (context.m_useInclusiveIntersection) >+ *context.m_intersects = intersects; >+ else >+ intersects = !adjustedRect.isEmpty(); >+ if (!intersects) > return adjustedRect; > } > return parent->computeRectForRepaint(adjustedRect, repaintContainer, context); > } > >-FloatRect RenderObject::computeFloatRectForRepaint(const FloatRect&, const RenderLayerModelObject*, bool) const >+FloatRect RenderObject::computeFloatRectForRepaint(const FloatRect&, const RenderLayerModelObject*, bool, bool, bool*) const > { > ASSERT_NOT_REACHED(); > return FloatRect(); >diff --git a/Source/WebCore/rendering/RenderObject.h b/Source/WebCore/rendering/RenderObject.h >index 25162583d56d11c2eb86e6cf1a469d6e3b2f6d5e..ba9793b16b461c4b33292c124906f0c28e820dd3 100644 >--- a/Source/WebCore/rendering/RenderObject.h >+++ b/Source/WebCore/rendering/RenderObject.h >@@ -676,16 +676,20 @@ public: > // Given a rect in the object's coordinate space, compute a rect suitable for repainting > // that rect in the coordinate space of repaintContainer. > struct RepaintContext { >- RepaintContext(bool hasPositionFixedDescendant = false, bool dirtyRectIsFlipped = false) >+ RepaintContext(bool hasPositionFixedDescendant = false, bool dirtyRectIsFlipped = false, bool useInclusiveIntersection = false, bool* intersects = nullptr) > : m_hasPositionFixedDescendant(hasPositionFixedDescendant) > , m_dirtyRectIsFlipped(dirtyRectIsFlipped) >+ , m_useInclusiveIntersection(useInclusiveIntersection) >+ , m_intersects(intersects) > { > } > bool m_hasPositionFixedDescendant; > bool m_dirtyRectIsFlipped; >+ bool m_useInclusiveIntersection; >+ bool* m_intersects; > }; > virtual LayoutRect computeRectForRepaint(const LayoutRect&, const RenderLayerModelObject* repaintContainer, RepaintContext = { }) const; >- virtual FloatRect computeFloatRectForRepaint(const FloatRect&, const RenderLayerModelObject* repaintContainer, bool fixed = false) const; >+ virtual FloatRect computeFloatRectForRepaint(const FloatRect&, const RenderLayerModelObject* repaintContainer, bool fixed = false, bool useInclusiveIntersection = false, bool* intersects = nullptr) const; > > virtual unsigned int length() const { return 1; } > >diff --git a/Source/WebCore/rendering/svg/RenderSVGForeignObject.cpp b/Source/WebCore/rendering/svg/RenderSVGForeignObject.cpp >index 8c73091b2ad679c7424189f564ef898e2f74c37e..7b3f4473503d8edf397a789e11643154d1c68fd2 100644 >--- a/Source/WebCore/rendering/svg/RenderSVGForeignObject.cpp >+++ b/Source/WebCore/rendering/svg/RenderSVGForeignObject.cpp >@@ -98,14 +98,14 @@ LayoutRect RenderSVGForeignObject::clippedOverflowRectForRepaint(const RenderLay > return SVGRenderSupport::clippedOverflowRectForRepaint(*this, repaintContainer); > } > >-FloatRect RenderSVGForeignObject::computeFloatRectForRepaint(const FloatRect& repaintRect, const RenderLayerModelObject* repaintContainer, bool fixed) const >+FloatRect RenderSVGForeignObject::computeFloatRectForRepaint(const FloatRect& repaintRect, const RenderLayerModelObject* repaintContainer, bool fixed, bool useInclusiveIntersection, bool* intersects) const > { >- return SVGRenderSupport::computeFloatRectForRepaint(*this, repaintRect, repaintContainer, fixed); >+ return SVGRenderSupport::computeFloatRectForRepaint(*this, repaintRect, repaintContainer, fixed, useInclusiveIntersection, intersects); > } > > LayoutRect RenderSVGForeignObject::computeRectForRepaint(const LayoutRect& repaintRect, const RenderLayerModelObject* repaintContainer, RepaintContext context) const > { >- return enclosingLayoutRect(computeFloatRectForRepaint(repaintRect, repaintContainer, context.m_hasPositionFixedDescendant)); >+ return enclosingLayoutRect(computeFloatRectForRepaint(repaintRect, repaintContainer, context.m_hasPositionFixedDescendant, context.m_useInclusiveIntersection, context.m_intersects)); > } > > const AffineTransform& RenderSVGForeignObject::localToParentTransform() const >diff --git a/Source/WebCore/rendering/svg/RenderSVGForeignObject.h b/Source/WebCore/rendering/svg/RenderSVGForeignObject.h >index 1d53ce02f0ff98233932ff4f7f434341634a3985..ba2e589aa5a1acad97bec8ab40be060b3fc39693 100644 >--- a/Source/WebCore/rendering/svg/RenderSVGForeignObject.h >+++ b/Source/WebCore/rendering/svg/RenderSVGForeignObject.h >@@ -40,7 +40,7 @@ public: > void paint(PaintInfo&, const LayoutPoint&) override; > > LayoutRect clippedOverflowRectForRepaint(const RenderLayerModelObject* repaintContainer) const override; >- FloatRect computeFloatRectForRepaint(const FloatRect&, const RenderLayerModelObject* repaintContainer, bool fixed = false) const override; >+ FloatRect computeFloatRectForRepaint(const FloatRect&, const RenderLayerModelObject* repaintContainer, bool fixed = false, bool useInclusiveIntersection = false, bool* intersects = nullptr) const override; > LayoutRect computeRectForRepaint(const LayoutRect&, const RenderLayerModelObject* repaintContainer, RepaintContext = { }) const override; > > bool requiresLayer() const override { return false; } >diff --git a/Source/WebCore/rendering/svg/RenderSVGInline.cpp b/Source/WebCore/rendering/svg/RenderSVGInline.cpp >index c3a5400d2cdacdeadef4ccb930f78c43cf5b4e8b..7cf51ded4c1ac7bbfd74cea223973f2774ffea83 100644 >--- a/Source/WebCore/rendering/svg/RenderSVGInline.cpp >+++ b/Source/WebCore/rendering/svg/RenderSVGInline.cpp >@@ -75,9 +75,9 @@ LayoutRect RenderSVGInline::clippedOverflowRectForRepaint(const RenderLayerModel > return SVGRenderSupport::clippedOverflowRectForRepaint(*this, repaintContainer); > } > >-FloatRect RenderSVGInline::computeFloatRectForRepaint(const FloatRect& repaintRect, const RenderLayerModelObject* repaintContainer, bool fixed) const >+FloatRect RenderSVGInline::computeFloatRectForRepaint(const FloatRect& repaintRect, const RenderLayerModelObject* repaintContainer, bool fixed, bool useInclusiveIntersection, bool* intersects) const > { >- return SVGRenderSupport::computeFloatRectForRepaint(*this, repaintRect, repaintContainer, fixed); >+ return SVGRenderSupport::computeFloatRectForRepaint(*this, repaintRect, repaintContainer, fixed, useInclusiveIntersection, intersects); > } > > void RenderSVGInline::mapLocalToContainer(const RenderLayerModelObject* repaintContainer, TransformState& transformState, MapCoordinatesFlags, bool* wasFixed) const >diff --git a/Source/WebCore/rendering/svg/RenderSVGInline.h b/Source/WebCore/rendering/svg/RenderSVGInline.h >index 549bac49ba17bb60a3041902c4da7b55fbd261b5..e4c3f0900218e8e0cff8eacd40501a266c141e3c 100644 >--- a/Source/WebCore/rendering/svg/RenderSVGInline.h >+++ b/Source/WebCore/rendering/svg/RenderSVGInline.h >@@ -51,7 +51,7 @@ private: > FloatRect repaintRectInLocalCoordinates() const final; > > LayoutRect clippedOverflowRectForRepaint(const RenderLayerModelObject* repaintContainer) const final; >- FloatRect computeFloatRectForRepaint(const FloatRect&, const RenderLayerModelObject* repaintContainer, bool fixed = false) const final; >+ FloatRect computeFloatRectForRepaint(const FloatRect&, const RenderLayerModelObject* repaintContainer, bool fixed = false, bool useInclusiveIntersection = false, bool* intersects = nullptr) const final; > void mapLocalToContainer(const RenderLayerModelObject* repaintContainer, TransformState&, MapCoordinatesFlags, bool* wasFixed) const final; > const RenderObject* pushMappingToContainer(const RenderLayerModelObject* ancestorToStopAt, RenderGeometryMap&) const final; > void absoluteQuads(Vector<FloatQuad>&, bool* wasFixed) const final; >diff --git a/Source/WebCore/rendering/svg/RenderSVGModelObject.cpp b/Source/WebCore/rendering/svg/RenderSVGModelObject.cpp >index 6a0b6691e7203ff675b5b1a40fde8308b98ec183..878ed8a2538ae92f93669324b74224f4f696f525 100644 >--- a/Source/WebCore/rendering/svg/RenderSVGModelObject.cpp >+++ b/Source/WebCore/rendering/svg/RenderSVGModelObject.cpp >@@ -53,9 +53,9 @@ LayoutRect RenderSVGModelObject::clippedOverflowRectForRepaint(const RenderLayer > return SVGRenderSupport::clippedOverflowRectForRepaint(*this, repaintContainer); > } > >-FloatRect RenderSVGModelObject::computeFloatRectForRepaint(const FloatRect& repaintRect, const RenderLayerModelObject* repaintContainer, bool fixed) const >+FloatRect RenderSVGModelObject::computeFloatRectForRepaint(const FloatRect& repaintRect, const RenderLayerModelObject* repaintContainer, bool fixed, bool useInclusiveIntersection, bool* intersects) const > { >- return SVGRenderSupport::computeFloatRectForRepaint(*this, repaintRect, repaintContainer, fixed); >+ return SVGRenderSupport::computeFloatRectForRepaint(*this, repaintRect, repaintContainer, fixed, useInclusiveIntersection, intersects); > } > > void RenderSVGModelObject::mapLocalToContainer(const RenderLayerModelObject* repaintContainer, TransformState& transformState, MapCoordinatesFlags, bool* wasFixed) const >diff --git a/Source/WebCore/rendering/svg/RenderSVGModelObject.h b/Source/WebCore/rendering/svg/RenderSVGModelObject.h >index 8e3a0e12f54daacb476fb34a9e99b17826cc07c8..f73ed696ac1487922ba9dffeac0bf1ea6e9d696a 100644 >--- a/Source/WebCore/rendering/svg/RenderSVGModelObject.h >+++ b/Source/WebCore/rendering/svg/RenderSVGModelObject.h >@@ -47,7 +47,7 @@ class RenderSVGModelObject : public RenderElement { > WTF_MAKE_ISO_ALLOCATED(RenderSVGModelObject); > public: > LayoutRect clippedOverflowRectForRepaint(const RenderLayerModelObject* repaintContainer) const override; >- FloatRect computeFloatRectForRepaint(const FloatRect&, const RenderLayerModelObject* repaintContainer, bool fixed = false) const final; >+ FloatRect computeFloatRectForRepaint(const FloatRect&, const RenderLayerModelObject* repaintContainer, bool fixed = false, bool useInclusiveIntersection = false, bool* intersects = nullptr) const final; > LayoutRect outlineBoundsForRepaint(const RenderLayerModelObject* repaintContainer, const RenderGeometryMap*) const final; > > void absoluteRects(Vector<IntRect>&, const LayoutPoint& accumulatedOffset) const final; >diff --git a/Source/WebCore/rendering/svg/RenderSVGRoot.cpp b/Source/WebCore/rendering/svg/RenderSVGRoot.cpp >index 7170a0e5f0fab415907d61c4c24fad1895597d3f..9728958b9a9d562d20478a3184efce5e45b59425 100644 >--- a/Source/WebCore/rendering/svg/RenderSVGRoot.cpp >+++ b/Source/WebCore/rendering/svg/RenderSVGRoot.cpp >@@ -342,7 +342,7 @@ LayoutRect RenderSVGRoot::clippedOverflowRectForRepaint(const RenderLayerModelOb > return RenderReplaced::computeRectForRepaint(enclosingIntRect(repaintRect), repaintContainer); > } > >-FloatRect RenderSVGRoot::computeFloatRectForRepaint(const FloatRect& repaintRect, const RenderLayerModelObject* repaintContainer, bool fixed) const >+FloatRect RenderSVGRoot::computeFloatRectForRepaint(const FloatRect& repaintRect, const RenderLayerModelObject* repaintContainer, bool fixed, bool useInclusiveIntersection, bool* intersects) const > { > // Apply our local transforms (except for x/y translation), then our shadow, > // and then call RenderBox's method to handle all the normal CSS Box model bits >@@ -362,7 +362,7 @@ FloatRect RenderSVGRoot::computeFloatRectForRepaint(const FloatRect& repaintRect > LayoutRect decoratedRepaintRect = unionRect(localSelectionRect(false), visualOverflowRect()); > adjustedRect.unite(decoratedRepaintRect); > } >- return RenderReplaced::computeRectForRepaint(enclosingIntRect(adjustedRect), repaintContainer, {fixed, false}); >+ return RenderReplaced::computeRectForRepaint(enclosingIntRect(adjustedRect), repaintContainer, {fixed, false, useInclusiveIntersection, intersects}); > } > > // This method expects local CSS box coordinates. >diff --git a/Source/WebCore/rendering/svg/RenderSVGRoot.h b/Source/WebCore/rendering/svg/RenderSVGRoot.h >index fd57cd01ba5851317bdc3140dda58c390fb0a5d3..9b3d442a19ed3a2396b5a1c752ea6333b9de976e 100644 >--- a/Source/WebCore/rendering/svg/RenderSVGRoot.h >+++ b/Source/WebCore/rendering/svg/RenderSVGRoot.h >@@ -96,7 +96,7 @@ private: > bool nodeAtPoint(const HitTestRequest&, HitTestResult&, const HitTestLocation& locationInContainer, const LayoutPoint& accumulatedOffset, HitTestAction) override; > > LayoutRect clippedOverflowRectForRepaint(const RenderLayerModelObject* repaintContainer) const override; >- FloatRect computeFloatRectForRepaint(const FloatRect&, const RenderLayerModelObject* repaintContainer, bool fixed) const override; >+ FloatRect computeFloatRectForRepaint(const FloatRect&, const RenderLayerModelObject* repaintContainer, bool fixed, bool useInclusiveIntersection, bool* intersects) const override; > > void mapLocalToContainer(const RenderLayerModelObject* repaintContainer, TransformState&, MapCoordinatesFlags, bool* wasFixed) const override; > const RenderObject* pushMappingToContainer(const RenderLayerModelObject* ancestorToStopAt, RenderGeometryMap&) const override; >diff --git a/Source/WebCore/rendering/svg/RenderSVGText.cpp b/Source/WebCore/rendering/svg/RenderSVGText.cpp >index 3a751b9cb42abd5ee43f532d9a6f291c8a287ad6..b852a7213b6dfa6896ff0ec23f97d33de9045b86 100644 >--- a/Source/WebCore/rendering/svg/RenderSVGText.cpp >+++ b/Source/WebCore/rendering/svg/RenderSVGText.cpp >@@ -94,12 +94,12 @@ LayoutRect RenderSVGText::clippedOverflowRectForRepaint(const RenderLayerModelOb > > LayoutRect RenderSVGText::computeRectForRepaint(const LayoutRect& rect, const RenderLayerModelObject* repaintContainer, RepaintContext context) const > { >- return enclosingLayoutRect(computeFloatRectForRepaint(rect, repaintContainer, context.m_hasPositionFixedDescendant)); >+ return enclosingLayoutRect(computeFloatRectForRepaint(rect, repaintContainer, context.m_hasPositionFixedDescendant, context.m_useInclusiveIntersection, context.m_intersects)); > } > >-FloatRect RenderSVGText::computeFloatRectForRepaint(const FloatRect& repaintRect, const RenderLayerModelObject* repaintContainer, bool fixed) const >+FloatRect RenderSVGText::computeFloatRectForRepaint(const FloatRect& repaintRect, const RenderLayerModelObject* repaintContainer, bool fixed, bool useInclusiveIntersection, bool* intersects) const > { >- return SVGRenderSupport::computeFloatRectForRepaint(*this, repaintRect, repaintContainer, fixed); >+ return SVGRenderSupport::computeFloatRectForRepaint(*this, repaintRect, repaintContainer, fixed, useInclusiveIntersection, intersects); > } > > void RenderSVGText::mapLocalToContainer(const RenderLayerModelObject* repaintContainer, TransformState& transformState, MapCoordinatesFlags, bool* wasFixed) const >diff --git a/Source/WebCore/rendering/svg/RenderSVGText.h b/Source/WebCore/rendering/svg/RenderSVGText.h >index a5d1a78b98b306575503fa872dbaac7f69e0772f..65cd28ce162bb8d3f134b513b070869374aa2cf5 100644 >--- a/Source/WebCore/rendering/svg/RenderSVGText.h >+++ b/Source/WebCore/rendering/svg/RenderSVGText.h >@@ -79,7 +79,7 @@ private: > > LayoutRect clippedOverflowRectForRepaint(const RenderLayerModelObject* repaintContainer) const override; > LayoutRect computeRectForRepaint(const LayoutRect&, const RenderLayerModelObject* repaintContainer, RepaintContext = { }) const override; >- FloatRect computeFloatRectForRepaint(const FloatRect&, const RenderLayerModelObject* repaintContainer, bool fixed = false) const override; >+ FloatRect computeFloatRectForRepaint(const FloatRect&, const RenderLayerModelObject* repaintContainer, bool fixed = false, bool useInclusiveIntersection = false, bool* intersects = nullptr) const override; > > void mapLocalToContainer(const RenderLayerModelObject* repaintContainer, TransformState&, MapCoordinatesFlags, bool* wasFixed) const override; > const RenderObject* pushMappingToContainer(const RenderLayerModelObject* ancestorToStopAt, RenderGeometryMap&) const override; >diff --git a/Source/WebCore/rendering/svg/SVGRenderSupport.cpp b/Source/WebCore/rendering/svg/SVGRenderSupport.cpp >index 971cb1a7144ccfb0dd890dd5dd8c7462a35d1f76..810be1944b834bc774bb3bf369eb87420b5c30bb 100644 >--- a/Source/WebCore/rendering/svg/SVGRenderSupport.cpp >+++ b/Source/WebCore/rendering/svg/SVGRenderSupport.cpp >@@ -72,7 +72,7 @@ LayoutRect SVGRenderSupport::clippedOverflowRectForRepaint(const RenderElement& > return enclosingLayoutRect(renderer.computeFloatRectForRepaint(repaintRect, repaintContainer)); > } > >-FloatRect SVGRenderSupport::computeFloatRectForRepaint(const RenderElement& renderer, const FloatRect& repaintRect, const RenderLayerModelObject* repaintContainer, bool fixed) >+FloatRect SVGRenderSupport::computeFloatRectForRepaint(const RenderElement& renderer, const FloatRect& repaintRect, const RenderLayerModelObject* repaintContainer, bool fixed, bool useInclusiveIntersection, bool* intersects) > { > FloatRect adjustedRect = repaintRect; > const SVGRenderStyle& svgStyle = renderer.style().svgStyle(); >@@ -82,7 +82,7 @@ FloatRect SVGRenderSupport::computeFloatRectForRepaint(const RenderElement& rend > > // Translate to coords in our parent renderer, and then call computeFloatRectForRepaint() on our parent. > adjustedRect = renderer.localToParentTransform().mapRect(adjustedRect); >- return renderer.parent()->computeFloatRectForRepaint(adjustedRect, repaintContainer, fixed); >+ return renderer.parent()->computeFloatRectForRepaint(adjustedRect, repaintContainer, fixed, useInclusiveIntersection, intersects); > } > > const RenderElement& SVGRenderSupport::localToParentTransform(const RenderElement& renderer, AffineTransform &transform) >diff --git a/Source/WebCore/rendering/svg/SVGRenderSupport.h b/Source/WebCore/rendering/svg/SVGRenderSupport.h >index b3533395f220466a4db4d1b3edd3343f35860a49..c05e05b25ed91c1eebd3387e911dc222c74bc419 100644 >--- a/Source/WebCore/rendering/svg/SVGRenderSupport.h >+++ b/Source/WebCore/rendering/svg/SVGRenderSupport.h >@@ -68,7 +68,7 @@ public: > // Important functions used by nearly all SVG renderers centralizing coordinate transformations / repaint rect calculations > static FloatRect repaintRectForRendererInLocalCoordinatesExcludingSVGShadow(const RenderElement&); > static LayoutRect clippedOverflowRectForRepaint(const RenderElement&, const RenderLayerModelObject* repaintContainer); >- static FloatRect computeFloatRectForRepaint(const RenderElement&, const FloatRect&, const RenderLayerModelObject* repaintContainer, bool fixed); >+ static FloatRect computeFloatRectForRepaint(const RenderElement&, const FloatRect&, const RenderLayerModelObject* repaintContainer, bool fixed, bool useInclusiveIntersection, bool* intersects); > static const RenderElement& localToParentTransform(const RenderElement&, AffineTransform &); > static void mapLocalToContainer(const RenderElement&, const RenderLayerModelObject* repaintContainer, TransformState&, bool* wasFixed); > static const RenderElement* pushMappingToContainer(const RenderElement&, const RenderLayerModelObject* ancestorToStopAt, RenderGeometryMap&); >diff --git a/Source/WebCore/testing/Internals.cpp b/Source/WebCore/testing/Internals.cpp >index 0b348e80199a68ab89d307c0fe9e462dfd9433b9..f54b09a3296c8be86560b159412cf4462b5a57b1 100644 >--- a/Source/WebCore/testing/Internals.cpp >+++ b/Source/WebCore/testing/Internals.cpp >@@ -2310,6 +2310,11 @@ unsigned Internals::referencingNodeCount(const Document& document) const > return document.referencingNodeCount(); > } > >+unsigned Internals::numberOfActiveIntersectionObservers(const Document& document) const >+{ >+ return document.numberOfActiveIntersectionObservers(); >+} >+ > uint64_t Internals::documentIdentifier(const Document& document) const > { > return document.identifier().toUInt64(); >diff --git a/Source/WebCore/testing/Internals.h b/Source/WebCore/testing/Internals.h >index 3884efde17cdbd26af07f96f58a134145db5d004..8adf227727f82a5d8abd73e109302ad2b96d047b 100644 >--- a/Source/WebCore/testing/Internals.h >+++ b/Source/WebCore/testing/Internals.h >@@ -364,6 +364,8 @@ public: > unsigned numberOfLiveDocuments() const; > unsigned referencingNodeCount(const Document&) const; > >+ unsigned numberOfActiveIntersectionObservers(const Document&) const; >+ > uint64_t documentIdentifier(const Document&) const; > bool isDocumentAlive(uint64_t documentIdentifier) const; > >diff --git a/Source/WebCore/testing/Internals.idl b/Source/WebCore/testing/Internals.idl >index 750c6f9519dafbf9177bd567f2133bef6ee28842..940f3d579341bc666b3fcbfdd417411092881adb 100644 >--- a/Source/WebCore/testing/Internals.idl >+++ b/Source/WebCore/testing/Internals.idl >@@ -359,6 +359,7 @@ enum CompositingPolicy { > unsigned long numberOfLiveNodes(); > unsigned long numberOfLiveDocuments(); > unsigned long referencingNodeCount(Document document); >+ unsigned long numberOfActiveIntersectionObservers(Document document); > WindowProxy? openDummyInspectorFrontend(DOMString url); > void closeDummyInspectorFrontend(); > [MayThrowException] void setInspectorIsUnderTest(boolean isUnderTest); >diff --git a/Source/WebKit/Shared/WebPreferences.yaml b/Source/WebKit/Shared/WebPreferences.yaml >index dc4f68c05fa578c86ced91d47157a3baa9f1c8e7..5b8ac5152607c82980b946202295dc56ab36a72c 100644 >--- a/Source/WebKit/Shared/WebPreferences.yaml >+++ b/Source/WebKit/Shared/WebPreferences.yaml >@@ -655,14 +655,6 @@ MediaPreloadingEnabled: > defaultValue: false > webcoreBinding: RuntimeEnabledFeatures > >-IntersectionObserverEnabled: >- type: bool >- defaultValue: false >- humanReadableName: "Intersection Observer" >- humanReadableDescription: "Enable Intersection Observer support" >- webcoreBinding: RuntimeEnabledFeatures >- condition: ENABLE(INTERSECTION_OBSERVER) >- > InteractiveFormValidationEnabled: > type: bool > defaultValue: true >@@ -1138,6 +1130,15 @@ SpringTimingFunctionEnabled: > humanReadableDescription: "CSS Spring Animation prototype" > category: experimental > >+IntersectionObserverEnabled: >+ type: bool >+ defaultValue: DEFAULT_EXPERIMENTAL_FEATURES_ENABLED >+ humanReadableName: "Intersection Observer" >+ humanReadableDescription: "Enable Intersection Observer support" >+ condition: ENABLE(INTERSECTION_OBSERVER) >+ category: experimental >+ webcoreBinding: RuntimeEnabledFeatures >+ > ImageBitmapOffscreenCanvasEnabled: > type: bool > defaultValue: DEFAULT_EXPERIMENTAL_FEATURES_ENABLED >diff --git a/Source/WebKitLegacy/mac/WebView/WebPreferencesPrivate.h b/Source/WebKitLegacy/mac/WebView/WebPreferencesPrivate.h >index 05a121dbc31e784d1369d431fb35cb644755f749..32c47cb62e217a1fe5df39d6459d0e9191e02a22 100644 >--- a/Source/WebKitLegacy/mac/WebView/WebPreferencesPrivate.h >+++ b/Source/WebKitLegacy/mac/WebView/WebPreferencesPrivate.h >@@ -573,6 +573,9 @@ extern NSString *WebPreferencesCacheModelChangedInternalNotification WEBKIT_DEPR > - (void)setWebAnimationsCSSIntegrationEnabled:(BOOL)flag; > - (BOOL)webAnimationsCSSIntegrationEnabled; > >+- (void)setIntersectionObserverEnabled:(BOOL)flag; >+- (BOOL)intersectionObserverEnabled; >+ > - (void)setWebAuthenticationEnabled:(BOOL)flag; > - (BOOL)webAuthenticationEnabled; > >diff --git a/Tools/ChangeLog b/Tools/ChangeLog >index 5beae2a31bd37f2cfddf518eb8684bae1b306128..2e3efa6fa7240f379b64da33205c96b436057813 100644 >--- a/Tools/ChangeLog >+++ b/Tools/ChangeLog >@@ -1,3 +1,19 @@ >+2018-08-08 Ali Juma <ajuma@chromium.org> >+ >+ Implement IntersectionObserver >+ https://bugs.webkit.org/show_bug.cgi?id=159475 >+ >+ Reviewed by NOBODY (OOPS!). >+ >+ * DumpRenderTree/TestOptions.cpp: >+ (TestOptions::TestOptions): >+ * DumpRenderTree/TestOptions.h: >+ * DumpRenderTree/mac/DumpRenderTree.mm: >+ (enableExperimentalFeatures): >+ (setWebPreferencesForTestOptions): >+ * WebKitTestRunner/TestController.cpp: >+ (WTR::TestController::resetPreferencesToConsistentValues): >+ > 2018-08-08 Wenson Hsieh <wenson_hsieh@apple.com> > > [iOS] fast/events/ios/contenteditable-autocapitalize.html is a flaky failure >diff --git a/Tools/DumpRenderTree/TestOptions.cpp b/Tools/DumpRenderTree/TestOptions.cpp >index 4495942a133c2b598eb8d3907ae4dfac2d7b6c36..918529f34632c97aedb422f977b631bee60c1d2b 100644 >--- a/Tools/DumpRenderTree/TestOptions.cpp >+++ b/Tools/DumpRenderTree/TestOptions.cpp >@@ -77,8 +77,6 @@ TestOptions::TestOptions(const std::string& pathOrURL, const std::string& absolu > enableAttachmentElement = parseBooleanTestHeaderValue(value); > if (key == "useAcceleratedDrawing") > useAcceleratedDrawing = parseBooleanTestHeaderValue(value); >- else if (key == "enableIntersectionObserver") >- enableIntersectionObserver = parseBooleanTestHeaderValue(value); > else if (key == "enableMenuItemElement") > enableMenuItemElement = parseBooleanTestHeaderValue(value); > else if (key == "enableModernMediaControls") >diff --git a/Tools/DumpRenderTree/TestOptions.h b/Tools/DumpRenderTree/TestOptions.h >index 3ecaf22993cc4ae7d64e35ef1df965a7f3e97f60..3d783c6dffcc8f9801a4f3c38498c8b8f123cee4 100644 >--- a/Tools/DumpRenderTree/TestOptions.h >+++ b/Tools/DumpRenderTree/TestOptions.h >@@ -31,7 +31,6 @@ struct TestOptions { > bool enableAttachmentElement { false }; > bool enableWebAnimationsCSSIntegration { false }; > bool useAcceleratedDrawing { false }; >- bool enableIntersectionObserver { false }; > bool enableMenuItemElement { false }; > bool enableModernMediaControls { true }; > bool enablePointerLock { false }; >diff --git a/Tools/DumpRenderTree/mac/DumpRenderTree.mm b/Tools/DumpRenderTree/mac/DumpRenderTree.mm >index d9b84fc2009f2c976bae3dace8118e97241e2e60..5d52905736a36bb9b4b6b50a8f458c528a352a41 100644 >--- a/Tools/DumpRenderTree/mac/DumpRenderTree.mm >+++ b/Tools/DumpRenderTree/mac/DumpRenderTree.mm >@@ -867,6 +867,7 @@ static void enableExperimentalFeatures(WebPreferences* preferences) > [preferences setColorFilterEnabled:YES]; > [preferences setCrossOriginWindowPolicySupportEnabled:YES]; > [preferences setServerTimingEnabled:YES]; >+ [preferences setIntersectionObserverEnabled:YES]; > } > > // Called before each test. >@@ -996,7 +997,6 @@ static void setWebPreferencesForTestOptions(const TestOptions& options) > > preferences.attachmentElementEnabled = options.enableAttachmentElement; > preferences.acceleratedDrawingEnabled = options.useAcceleratedDrawing; >- preferences.intersectionObserverEnabled = options.enableIntersectionObserver; > preferences.menuItemElementEnabled = options.enableMenuItemElement; > preferences.modernMediaControlsEnabled = options.enableModernMediaControls; > preferences.webAuthenticationEnabled = options.enableWebAuthentication; >diff --git a/Tools/WebKitTestRunner/TestController.cpp b/Tools/WebKitTestRunner/TestController.cpp >index 0e7d8e6a48f3dac164914854bb6031ff0cd9df01..01203ce19db46cf22073b45c0ed3399baa035084 100644 >--- a/Tools/WebKitTestRunner/TestController.cpp >+++ b/Tools/WebKitTestRunner/TestController.cpp >@@ -711,7 +711,6 @@ void TestController::resetPreferencesToConsistentValues(const TestOptions& optio > WKPreferencesSetMockScrollbarsEnabled(preferences, options.useMockScrollbars); > WKPreferencesSetNeedsSiteSpecificQuirks(preferences, options.needsSiteSpecificQuirks); > WKPreferencesSetAttachmentElementEnabled(preferences, options.enableAttachmentElement); >- WKPreferencesSetIntersectionObserverEnabled(preferences, options.enableIntersectionObserver); > WKPreferencesSetMenuItemElementEnabled(preferences, options.enableMenuItemElement); > WKPreferencesSetModernMediaControlsEnabled(preferences, options.enableModernMediaControls); > WKPreferencesSetWebAuthenticationEnabled(preferences, options.enableWebAuthentication); >diff --git a/LayoutTests/ChangeLog b/LayoutTests/ChangeLog >index dbdba41392b35fb77887ffb0279670fb95e39ed7..6bf483fcf36f18d7abaf224ea8aa0ebdf97b2b49 100644 >--- a/LayoutTests/ChangeLog >+++ b/LayoutTests/ChangeLog >@@ -1,3 +1,19 @@ >+2018-08-08 Ali Juma <ajuma@chromium.org> >+ >+ Implement IntersectionObserver >+ https://bugs.webkit.org/show_bug.cgi?id=159475 >+ >+ Reviewed by NOBODY (OOPS!). >+ >+ * intersection-observer/intersection-observer-entry-interface-expected.txt: >+ * intersection-observer/intersection-observer-entry-interface.html: >+ * intersection-observer/intersection-observer-interface.html: >+ * intersection-observer/root-element-deleted-expected.txt: Added. >+ * intersection-observer/root-element-deleted.html: Added. >+ * intersection-observer/root-element-moved-expected.txt: Added. >+ * intersection-observer/root-element-moved.html: Added. >+ * platform/mac-wk1/TestExpectations: >+ > 2018-08-08 Joseph Pecoraro <pecoraro@apple.com> > > Web Inspector: XHR content sometimes shows as error even though load succeeded >diff --git a/LayoutTests/imported/w3c/ChangeLog b/LayoutTests/imported/w3c/ChangeLog >index 094a3226b48ecc4c0295d5fa8296391d3737eb40..5ebd232823bcf06da8e67b406a3df3d078a6654a 100644 >--- a/LayoutTests/imported/w3c/ChangeLog >+++ b/LayoutTests/imported/w3c/ChangeLog >@@ -1,3 +1,77 @@ >+2018-08-08 Ali Juma <ajuma@chromium.org> >+ >+ Implement IntersectionObserver >+ https://bugs.webkit.org/show_bug.cgi?id=159475 >+ >+ Reviewed by NOBODY (OOPS!). >+ >+ * web-platform-tests/intersection-observer/bounding-box-expected.txt: Added. >+ * web-platform-tests/intersection-observer/bounding-box.html: Added. >+ * web-platform-tests/intersection-observer/client-rect-expected.txt: Added. >+ * web-platform-tests/intersection-observer/client-rect.html: Added. >+ * web-platform-tests/intersection-observer/containing-block-expected.txt: Added. >+ * web-platform-tests/intersection-observer/containing-block.html: Added. >+ * web-platform-tests/intersection-observer/cross-origin-iframe-expected.txt: Added. >+ * web-platform-tests/intersection-observer/cross-origin-iframe.html: Added. >+ * web-platform-tests/intersection-observer/disconnect-expected.txt: Added. >+ * web-platform-tests/intersection-observer/disconnect.html: Added. >+ * web-platform-tests/intersection-observer/display-none-expected.txt: Added. >+ * web-platform-tests/intersection-observer/display-none.html: Added. >+ * web-platform-tests/intersection-observer/edge-inclusive-intersection-expected.txt: Added. >+ * web-platform-tests/intersection-observer/edge-inclusive-intersection.html: Added. >+ * web-platform-tests/intersection-observer/iframe-no-root-expected.txt: Added. >+ * web-platform-tests/intersection-observer/iframe-no-root.html: Added. >+ * web-platform-tests/intersection-observer/isIntersecting-change-events-expected.txt: Added. >+ * web-platform-tests/intersection-observer/isIntersecting-change-events.html: Added. >+ * web-platform-tests/intersection-observer/multiple-targets-expected.txt: Added. >+ * web-platform-tests/intersection-observer/multiple-targets.html: Added. >+ * web-platform-tests/intersection-observer/multiple-thresholds-expected.txt: Added. >+ * web-platform-tests/intersection-observer/multiple-thresholds.html: Added. >+ * web-platform-tests/intersection-observer/observer-attributes-expected.txt: Added. >+ * web-platform-tests/intersection-observer/observer-attributes.html: Added. >+ * web-platform-tests/intersection-observer/observer-exceptions-expected.txt: Added. >+ * web-platform-tests/intersection-observer/observer-exceptions.html: Added. >+ * web-platform-tests/intersection-observer/observer-in-iframe.html: Added. >+ * web-platform-tests/intersection-observer/observer-without-js-reference-expected.txt: Added. >+ * web-platform-tests/intersection-observer/observer-without-js-reference.html: Added. >+ * web-platform-tests/intersection-observer/remove-element-expected.txt: Added. >+ * web-platform-tests/intersection-observer/remove-element.html: Added. >+ * web-platform-tests/intersection-observer/resources/cross-origin-subframe.html: Added. >+ * web-platform-tests/intersection-observer/resources/iframe-no-root-subframe.html: Added. >+ * web-platform-tests/intersection-observer/resources/intersection-observer-test-utils.js: Added. >+ (waitForNotification): >+ (runTestCycle): >+ (contentBounds): >+ (borderBoxBounds): >+ (clientBounds): >+ (rectArea): >+ (checkRect): >+ (checkLastEntry): >+ (checkJsonEntry): >+ (checkJsonEntries): >+ * web-platform-tests/intersection-observer/resources/observer-in-iframe-subframe.html: Added. >+ * web-platform-tests/intersection-observer/resources/timestamp-subframe.html: Added. >+ * web-platform-tests/intersection-observer/root-margin-expected.txt: Added. >+ * web-platform-tests/intersection-observer/root-margin.html: Added. >+ * web-platform-tests/intersection-observer/same-document-no-root-expected.txt: Added. >+ * web-platform-tests/intersection-observer/same-document-no-root.html: Added. >+ * web-platform-tests/intersection-observer/same-document-root-expected.txt: Added. >+ * web-platform-tests/intersection-observer/same-document-root.html: Added. >+ * web-platform-tests/intersection-observer/same-document-zero-size-target-expected.txt: Added. >+ * web-platform-tests/intersection-observer/same-document-zero-size-target.html: Added. >+ * web-platform-tests/intersection-observer/shadow-content-expected.txt: Added. >+ * web-platform-tests/intersection-observer/shadow-content.html: Added. >+ * web-platform-tests/intersection-observer/text-target-expected.txt: Added. >+ * web-platform-tests/intersection-observer/text-target.html: Added. >+ * web-platform-tests/intersection-observer/timestamp-expected.txt: Added. >+ * web-platform-tests/intersection-observer/timestamp.html: Added. >+ * web-platform-tests/intersection-observer/unclipped-root-expected.txt: Added. >+ * web-platform-tests/intersection-observer/unclipped-root.html: Added. >+ * web-platform-tests/intersection-observer/zero-area-element-hidden-expected.txt: Added. >+ * web-platform-tests/intersection-observer/zero-area-element-hidden.html: Added. >+ * web-platform-tests/intersection-observer/zero-area-element-visible-expected.txt: Added. >+ * web-platform-tests/intersection-observer/zero-area-element-visible.html: Added. >+ > 2018-08-08 Charlie Turner <cturner@igalia.com> > > Add CENC sanitization >diff --git a/LayoutTests/imported/w3c/web-platform-tests/intersection-observer/bounding-box-expected.txt b/LayoutTests/imported/w3c/web-platform-tests/intersection-observer/bounding-box-expected.txt >new file mode 100644 >index 0000000000000000000000000000000000000000..848114318477a25f5d7c6c609ec56acc93969bb4 >--- /dev/null >+++ b/LayoutTests/imported/w3c/web-platform-tests/intersection-observer/bounding-box-expected.txt >@@ -0,0 +1,5 @@ >+ >+PASS Test that the target's border bounding box is used to calculate intersection. >+PASS First rAF. >+PASS target.style.transform = 'translateY(195px)' >+ >diff --git a/LayoutTests/imported/w3c/web-platform-tests/intersection-observer/bounding-box.html b/LayoutTests/imported/w3c/web-platform-tests/intersection-observer/bounding-box.html >new file mode 100644 >index 0000000000000000000000000000000000000000..69052b11ce6c40c6a56fe2b723c70c49ddc36dd9 >--- /dev/null >+++ b/LayoutTests/imported/w3c/web-platform-tests/intersection-observer/bounding-box.html >@@ -0,0 +1,61 @@ >+<!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; >+} >+#root { >+ overflow: visible; >+ height: 200px; >+ width: 160px; >+ border: 7px solid black; >+} >+#target { >+ margin: 10px; >+ width: 100px; >+ height: 100px; >+ padding: 10px; >+ background-color: green; >+} >+</style> >+ >+<div id="root"> >+ <div id="target" style="transform: translateY(300px)"></div> >+</div> >+ >+<script> >+var entries = []; >+var target; >+ >+runTestCycle(function() { >+ target = document.getElementById("target"); >+ assert_true(!!target, "target exists"); >+ var root = document.getElementById("root"); >+ assert_true(!!root, "root exists"); >+ var observer = new IntersectionObserver(function(changes) { >+ entries = entries.concat(changes) >+ }, {root: root}); >+ observer.observe(target); >+ entries = entries.concat(observer.takeRecords()); >+ assert_equals(entries.length, 0, "No initial notifications."); >+ runTestCycle(step0, "First rAF."); >+}, "Test that the target's border bounding box is used to calculate intersection."); >+ >+function step0() { >+ var targetBounds = clientBounds(target); >+ target.style.transform = "translateY(195px)"; >+ runTestCycle(step1, "target.style.transform = 'translateY(195px)'"); >+ checkLastEntry(entries, 0, targetBounds.concat(0, 0, 0, 0, 8, 182, 8, 222, false)); >+} >+ >+function step1() { >+ var targetBounds = clientBounds(target); >+ target.style.transform = ""; >+ checkLastEntry(entries, 1, targetBounds.concat(25, 145, 220, 222, 8, 182, 8, 222, true)); >+} >+</script> >diff --git a/LayoutTests/imported/w3c/web-platform-tests/intersection-observer/client-rect-expected.txt b/LayoutTests/imported/w3c/web-platform-tests/intersection-observer/client-rect-expected.txt >new file mode 100644 >index 0000000000000000000000000000000000000000..a639216fff38455c07a4e8ca2f22ebf8b1835592 >--- /dev/null >+++ b/LayoutTests/imported/w3c/web-platform-tests/intersection-observer/client-rect-expected.txt >@@ -0,0 +1,4 @@ >+ >+PASS IntersectionObserverEntry.boundingClientRect should match target.boundingClientRect() >+PASS First rAF should generate notification. >+ >diff --git a/LayoutTests/imported/w3c/web-platform-tests/intersection-observer/client-rect.html b/LayoutTests/imported/w3c/web-platform-tests/intersection-observer/client-rect.html >new file mode 100644 >index 0000000000000000000000000000000000000000..6c50fdb14ac4e094ca8e42a4bfafb83f36e15a59 >--- /dev/null >+++ b/LayoutTests/imported/w3c/web-platform-tests/intersection-observer/client-rect.html >@@ -0,0 +1,45 @@ >+<!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; >+} >+iframe { >+ width: 180px; >+ height: 100px; >+} >+</style> >+ >+<iframe id="iframe" srcdoc="<div id='target' style='margin:0.5px;width:1000px;height:1000px;'></div>"></iframe> >+ >+<script> >+var target; >+var entries = []; >+var observer; >+var iframe = document.getElementById("iframe"); >+ >+iframe.onload = function() { >+ runTestCycle(function() { >+ target = iframe.contentDocument.getElementById("target"); >+ assert_true(!!target, "Target element exists."); >+ 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(test0, "First rAF should generate notification."); >+ }, "IntersectionObserverEntry.boundingClientRect should match target.boundingClientRect()"); >+}; >+ >+function test0() { >+ assert_equals(entries.length, 1, "One notification."); >+ var bcr = target.getBoundingClientRect(); >+ checkLastEntry(entries, 0, [bcr.left, bcr.right, bcr.top, bcr.bottom]); >+} >+</script> >diff --git a/LayoutTests/imported/w3c/web-platform-tests/intersection-observer/containing-block-expected.txt b/LayoutTests/imported/w3c/web-platform-tests/intersection-observer/containing-block-expected.txt >new file mode 100644 >index 0000000000000000000000000000000000000000..0f1f801f3e178bb97409aeddce9561a213ec627c >--- /dev/null >+++ b/LayoutTests/imported/w3c/web-platform-tests/intersection-observer/containing-block-expected.txt >@@ -0,0 +1,7 @@ >+ >+PASS IntersectionObserver should only report intersections if root is a containing block ancestor of target. >+PASS In containing block and intersecting. >+PASS In containing block and not intersecting. >+PASS Not in containing block and intersecting. >+PASS Not in containing block and not intersecting. >+ >diff --git a/LayoutTests/imported/w3c/web-platform-tests/intersection-observer/containing-block.html b/LayoutTests/imported/w3c/web-platform-tests/intersection-observer/containing-block.html >new file mode 100644 >index 0000000000000000000000000000000000000000..d4f46b0fa77d8ed930f5bac19c3f5854d4950ed0 >--- /dev/null >+++ b/LayoutTests/imported/w3c/web-platform-tests/intersection-observer/containing-block.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; >+} >+#root { >+ width: 170px; >+ height: 200px; >+ overflow-y: scroll; >+} >+#target { >+ width: 100px; >+ height: 100px; >+ background-color: green; >+ position: absolute; >+} >+</style> >+ >+<div id="root" style="position: absolute"> >+ <div id="target" style="left: 50px; top: 250px"></div> >+</div> >+ >+<script> >+var entries = []; >+var root, target; >+ >+runTestCycle(function() { >+ root = document.getElementById("root"); >+ assert_true(!!root, "root element exists."); >+ target = document.getElementById("target"); >+ assert_true(!!target, "target element exists."); >+ var observer = new IntersectionObserver(function(changes) { >+ entries = entries.concat(changes); >+ }, { root: root }); >+ observer.observe(target); >+ entries = entries.concat(observer.takeRecords()); >+ assert_equals(entries.length, 0, "No initial notifications."); >+ target.style.top = "10px"; >+ runTestCycle(test1, "In containing block and intersecting."); >+}, "IntersectionObserver should only report intersections if root is a containing block ancestor of target."); >+ >+function test1() { >+ runTestCycle(test2, "In containing block and not intersecting."); >+ var rootBounds = contentBounds(root); >+ checkLastEntry(entries, 0, [58, 158, 18, 118, 58, 158, 18, 118].concat(rootBounds)); >+ target.style.top = "250px"; >+} >+ >+function test2() { >+ runTestCycle(test3, "Not in containing block and intersecting."); >+ var rootBounds = contentBounds(root); >+ checkLastEntry(entries, 1, [58, 158, 258, 358, 0, 0, 0, 0].concat(rootBounds)); >+ root.style.position = "static"; >+ target.style.top = "10px"; >+} >+ >+function test3() { >+ runTestCycle(test4, "Not in containing block and not intersecting."); >+ checkLastEntry(entries, 1); >+ target.style.top = "250px"; >+} >+ >+function test4() { >+ checkLastEntry(entries, 1); >+ target.style.top = "0"; >+} >+</script> >diff --git a/LayoutTests/imported/w3c/web-platform-tests/intersection-observer/cross-origin-iframe-expected.txt b/LayoutTests/imported/w3c/web-platform-tests/intersection-observer/cross-origin-iframe-expected.txt >new file mode 100644 >index 0000000000000000000000000000000000000000..109a41e960243c2db03c7fd99f4127f4cfe5a7bf >--- /dev/null >+++ b/LayoutTests/imported/w3c/web-platform-tests/intersection-observer/cross-origin-iframe-expected.txt >@@ -0,0 +1,8 @@ >+ >+ >+PASS Intersection observer test with no explicit root and target in a cross-origin iframe. >+PASS First rAF >+PASS topDocument.scrollingElement.scrollTop = 200 >+PASS iframeDocument.scrollingElement.scrollTop = 250 >+PASS topDocument.scrollingElement.scrollTop = 100 >+ >diff --git a/LayoutTests/imported/w3c/web-platform-tests/intersection-observer/cross-origin-iframe.html b/LayoutTests/imported/w3c/web-platform-tests/intersection-observer/cross-origin-iframe.html >new file mode 100644 >index 0000000000000000000000000000000000000000..2c9c4bcec69666f0e37509618e05a6b741f5533d >--- /dev/null >+++ b/LayoutTests/imported/w3c/web-platform-tests/intersection-observer/cross-origin-iframe.html >@@ -0,0 +1,52 @@ >+<!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; >+} >+iframe { >+ width: 160px; >+ height: 100px; >+ overflow-y: scroll; >+} >+.spacer { >+ height: calc(100vh + 100px); >+} >+</style> >+ >+<div class="spacer"></div> >+<iframe src="resources/cross-origin-subframe.html" sandbox="allow-scripts"></iframe> >+<div class="spacer"></div> >+ >+<script> >+async_test(function(t) { >+ var iframe = document.querySelector("iframe"); >+ >+ function handleMessage(event) { >+ if (event.data.hasOwnProperty('scrollTo')) { >+ document.scrollingElement.scrollTop = event.data.scrollTo; >+ waitForNotification(t, function() { iframe.contentWindow.postMessage("", "*"); }, >+ "document.scrollingElement.scrollTop = " + event.data.scrollTo); >+ } else if (event.data.hasOwnProperty('actual')) { >+ checkJsonEntries(event.data.actual, event.data.expected, event.data.description); >+ } else if (event.data.hasOwnProperty('DONE')) { >+ document.scrollingElement.scrollTop = 0; >+ t.done(); >+ } else { >+ var description = event.data.description; >+ waitForNotification(t, function() { iframe.contentWindow.postMessage("", "*"); }, description); >+ } >+ } >+ >+ window.addEventListener("message", t.step_func(handleMessage)); >+ >+ iframe.onload = t.step_func(function() { >+ waitForNotification(t, function() { iframe.contentWindow.postMessage("", "*") }, "setup"); >+ }); >+}, "Intersection observer test with no explicit root and target in a cross-origin iframe."); >+</script> >diff --git a/LayoutTests/imported/w3c/web-platform-tests/intersection-observer/disconnect-expected.txt b/LayoutTests/imported/w3c/web-platform-tests/intersection-observer/disconnect-expected.txt >new file mode 100644 >index 0000000000000000000000000000000000000000..f88c12c28008e77d08aac8cb06c97a9caa6e6c2f >--- /dev/null >+++ b/LayoutTests/imported/w3c/web-platform-tests/intersection-observer/disconnect-expected.txt >@@ -0,0 +1,5 @@ >+ >+PASS IntersectionObserver should not deliver pending notifications after disconnect(). >+PASS First rAF. >+PASS observer.disconnect() >+ >diff --git a/LayoutTests/imported/w3c/web-platform-tests/intersection-observer/disconnect.html b/LayoutTests/imported/w3c/web-platform-tests/intersection-observer/disconnect.html >new file mode 100644 >index 0000000000000000000000000000000000000000..0abfbc4e8aafa3e5f073f8899188f409af21e566 >--- /dev/null >+++ b/LayoutTests/imported/w3c/web-platform-tests/intersection-observer/disconnect.html >@@ -0,0 +1,53 @@ >+<!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); >+} >+#target { >+ width: 100px; >+ height: 100px; >+ background-color: green; >+} >+</style> >+ >+<div class="spacer"></div> >+<div id="target"></div> >+<div class="spacer"></div> >+ >+<script> >+var entries = []; >+var observer; >+var target; >+ >+runTestCycle(function() { >+ target = document.getElementById("target"); >+ assert_true(!!target, "target exists"); >+ 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."); >+}, "IntersectionObserver should not deliver pending notifications after disconnect()."); >+ >+function step0() { >+ runTestCycle(step1, "observer.disconnect()"); >+ document.scrollingElement.scrollTop = 300; >+ observer.disconnect(); >+ assert_equals(entries.length, 1, "Initial notification."); >+} >+ >+function step1() { >+ assert_equals(entries.length, 1, "No new notifications."); >+} >+</script> >diff --git a/LayoutTests/imported/w3c/web-platform-tests/intersection-observer/display-none-expected.txt b/LayoutTests/imported/w3c/web-platform-tests/intersection-observer/display-none-expected.txt >new file mode 100644 >index 0000000000000000000000000000000000000000..175d78afbd79cef175e10231ae43ac5879f07e05 >--- /dev/null >+++ b/LayoutTests/imported/w3c/web-platform-tests/intersection-observer/display-none-expected.txt >@@ -0,0 +1,6 @@ >+ >+PASS IntersectionObserver should send a not-intersecting notification for a target that gets display:none. >+PASS Intersecting notification after first rAF. >+PASS Not-intersecting notification after setting display:none on target. >+PASS Intersecting notification after removing display:none on target. >+ >diff --git a/LayoutTests/imported/w3c/web-platform-tests/intersection-observer/display-none.html b/LayoutTests/imported/w3c/web-platform-tests/intersection-observer/display-none.html >new file mode 100644 >index 0000000000000000000000000000000000000000..7cebc5633ef91228129d2a1c0478262615ad3ba1 >--- /dev/null >+++ b/LayoutTests/imported/w3c/web-platform-tests/intersection-observer/display-none.html >@@ -0,0 +1,54 @@ >+<!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; >+} >+#target { >+ background-color: green; >+ width: 100px; >+ height: 100px; >+} >+</style> >+ >+<div id="target"></div> >+ >+<script> >+var vw = document.documentElement.clientWidth; >+var vh = document.documentElement.clientHeight; >+ >+var entries = []; >+ >+runTestCycle(function() { >+ var target = document.getElementById("target"); >+ var root = document.getElementById("root"); >+ 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, "Intersecting notification after first rAF."); >+}, "IntersectionObserver should send a not-intersecting notification for a target that gets display:none."); >+ >+function step0() { >+ runTestCycle(step1, "Not-intersecting notification after setting display:none on target."); >+ checkLastEntry(entries, 0, [8, 108, 8, 108, 8, 108, 8, 108, 0, vw, 0, vh, true]); >+ target.style.display = "none"; >+} >+ >+function step1() { >+ runTestCycle(step2, "Intersecting notification after removing display:none on target."); >+ checkLastEntry(entries, 1, [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, false]); >+ target.style.display = ""; >+} >+ >+function step2() { >+ checkLastEntry(entries, 2, [8, 108, 8, 108, 8, 108, 8, 108, 0, vw, 0, vh, true]); >+} >+</script> >diff --git a/LayoutTests/imported/w3c/web-platform-tests/intersection-observer/edge-inclusive-intersection-expected.txt b/LayoutTests/imported/w3c/web-platform-tests/intersection-observer/edge-inclusive-intersection-expected.txt >new file mode 100644 >index 0000000000000000000000000000000000000000..bdc59c463bd6cdbda9e33e3ffd36c3ed43ef4a26 >--- /dev/null >+++ b/LayoutTests/imported/w3c/web-platform-tests/intersection-observer/edge-inclusive-intersection-expected.txt >@@ -0,0 +1,7 @@ >+ >+PASS IntersectionObserver should detect and report edge-adjacent and zero-area intersections. >+PASS First rAF. >+PASS Set transform=translateY(200px) on target. >+PASS Set transform=translateY(201px) on target. >+PASS Set transform=translateY(185px) on target. >+ >diff --git a/LayoutTests/imported/w3c/web-platform-tests/intersection-observer/edge-inclusive-intersection.html b/LayoutTests/imported/w3c/web-platform-tests/intersection-observer/edge-inclusive-intersection.html >new file mode 100644 >index 0000000000000000000000000000000000000000..b9fa24b87808abc158307e8c69afeeb5ce3c1907 >--- /dev/null >+++ b/LayoutTests/imported/w3c/web-platform-tests/intersection-observer/edge-inclusive-intersection.html >@@ -0,0 +1,66 @@ >+<!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; >+} >+#root { >+ width: 200px; >+ height: 200px; >+ overflow: visible; >+} >+#target { >+ background-color: green; >+} >+</style> >+ >+<div id="root"> >+ <div id="target" style="width: 100px; height: 100px; transform: translateY(250px)"></div> >+</div> >+ >+<script> >+var entries = []; >+ >+runTestCycle(function() { >+ var root = document.getElementById('root'); >+ assert_true(!!root, "root element exists."); >+ var target = document.getElementById('target'); >+ assert_true(!!target, "target element exists."); >+ var observer = new IntersectionObserver(function(changes) { >+ entries = entries.concat(changes); >+ }, { root: root }); >+ observer.observe(target); >+ entries = entries.concat(observer.takeRecords()); >+ assert_equals(entries.length, 0, "No initial notifications."); >+ runTestCycle(step0, "First rAF."); >+}, "IntersectionObserver should detect and report edge-adjacent and zero-area intersections."); >+ >+function step0() { >+ runTestCycle(step1, "Set transform=translateY(200px) on target."); >+ checkLastEntry(entries, 0, [8, 108, 258, 358, 0, 0, 0, 0, 8, 208, 8, 208, false]); >+ target.style.transform = "translateY(200px)"; >+} >+ >+function step1() { >+ runTestCycle(step2, "Set transform=translateY(201px) on target."); >+ checkLastEntry(entries, 1, [8, 108, 208, 308, 8, 108, 208, 208, 8, 208, 8, 208, true]); >+ target.style.transform = "translateY(201px)"; >+} >+ >+function step2() { >+ runTestCycle(step3, "Set transform=translateY(185px) on target."); >+ checkLastEntry(entries, 2); >+ target.style.height = "0px"; >+ target.style.width = "300px"; >+ target.style.transform = "translateY(185px)"; >+} >+ >+function step3() { >+ checkLastEntry(entries, 3, [8, 308, 193, 193, 8, 208, 193, 193, 8, 208, 8, 208, true]); >+} >+</script> >diff --git a/LayoutTests/imported/w3c/web-platform-tests/intersection-observer/iframe-no-root-expected.txt b/LayoutTests/imported/w3c/web-platform-tests/intersection-observer/iframe-no-root-expected.txt >new file mode 100644 >index 0000000000000000000000000000000000000000..5b064562d12895f9f6e57ef183d351a90e875dc3 >--- /dev/null >+++ b/LayoutTests/imported/w3c/web-platform-tests/intersection-observer/iframe-no-root-expected.txt >@@ -0,0 +1,8 @@ >+ >+ >+PASS Observer with the implicit root; target in a same-origin iframe. >+PASS First rAF. >+PASS document.scrollingElement.scrollTop = 200 >+PASS iframe.contentDocument.scrollingElement.scrollTop = 250 >+PASS document.scrollingElement.scrollTop = 100 >+ >diff --git a/LayoutTests/imported/w3c/web-platform-tests/intersection-observer/iframe-no-root.html b/LayoutTests/imported/w3c/web-platform-tests/intersection-observer/iframe-no-root.html >new file mode 100644 >index 0000000000000000000000000000000000000000..e37aeac5530f348328274f9f8a2eeae9f08609ff >--- /dev/null >+++ b/LayoutTests/imported/w3c/web-platform-tests/intersection-observer/iframe-no-root.html >@@ -0,0 +1,71 @@ >+<!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> >+<iframe id="target-iframe" src="resources/iframe-no-root-subframe.html"></iframe> >+<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> >diff --git a/LayoutTests/imported/w3c/web-platform-tests/intersection-observer/isIntersecting-change-events-expected.txt b/LayoutTests/imported/w3c/web-platform-tests/intersection-observer/isIntersecting-change-events-expected.txt >new file mode 100644 >index 0000000000000000000000000000000000000000..f797af317896a23033c890d081ddaac184bb6624 >--- /dev/null >+++ b/LayoutTests/imported/w3c/web-platform-tests/intersection-observer/isIntersecting-change-events-expected.txt >@@ -0,0 +1,7 @@ >+ >+PASS isIntersecting changes should trigger notifications. >+PASS Rects in initial notifications should report initial positions. >+PASS Set scrollTop=100 and check for no new notifications. >+PASS Add 4th target. >+PASS Set scrollTop=100 and check for one new notification. >+ >diff --git a/LayoutTests/imported/w3c/web-platform-tests/intersection-observer/isIntersecting-change-events.html b/LayoutTests/imported/w3c/web-platform-tests/intersection-observer/isIntersecting-change-events.html >new file mode 100644 >index 0000000000000000000000000000000000000000..f9362c3024c2003ac03c1e789c54486830710c92 >--- /dev/null >+++ b/LayoutTests/imported/w3c/web-platform-tests/intersection-observer/isIntersecting-change-events.html >@@ -0,0 +1,112 @@ >+<!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; >+} >+#root { >+ position: absolute; >+ top: 0; >+ left: 0; >+ width: 150px; >+ height: 200px; >+ overflow-y: scroll; >+} >+#target1, #target2, #target3, #target4 { >+ width: 100px; >+ height: 100px; >+} >+#target1 { >+ background-color: green; >+} >+#target2 { >+ background-color: red; >+} >+#target3 { >+ background-color: blue; >+} >+#target4 { >+ background-color: yellow; >+} >+</style> >+ >+<div id="root"> >+ <div id="target1"></div> >+ <div id="target2"></div> >+ <div id="target3"></div> >+</div> >+ >+<script> >+var entries = []; >+var observer; >+ >+runTestCycle(function() { >+ var root = document.getElementById('root'); >+ var target1 = document.getElementById('target1'); >+ var target2 = document.getElementById('target2'); >+ var target3 = document.getElementById('target3'); >+ assert_true(!!root, "root element exists."); >+ assert_true(!!target1, "target1 element exists."); >+ assert_true(!!target2, "target2 element exists."); >+ assert_true(!!target3, "target3 element exists."); >+ observer = new IntersectionObserver(function(changes) { >+ entries = entries.concat(changes); >+ }, { root: root }); >+ observer.observe(target1); >+ observer.observe(target2); >+ observer.observe(target3); >+ entries = entries.concat(observer.takeRecords()); >+ assert_equals(entries.length, 0, "No initial notifications."); >+ runTestCycle(step0, "Rects in initial notifications should report initial positions."); >+}, "isIntersecting changes should trigger notifications."); >+ >+function step0() { >+ assert_equals(entries.length, 3, "Has 3 initial notifications."); >+ checkRect(entries[0].boundingClientRect, [0, 100, 0, 100], "Check 1st entry rect"); >+ assert_equals(entries[0].target.id, 'target1', "Check 1st entry target id."); >+ checkIsIntersecting(entries, 0, true); >+ checkRect(entries[1].boundingClientRect, [0, 100, 100, 200], "Check 2nd entry rect"); >+ assert_equals(entries[1].target.id, 'target2', "Check 2nd entry target id."); >+ checkIsIntersecting(entries, 1, true); >+ checkRect(entries[2].boundingClientRect, [0, 100, 200, 300], "Check 3rd entry rect"); >+ assert_equals(entries[2].target.id, 'target3', "Check 3rd entry target id."); >+ checkIsIntersecting(entries, 2, true); >+ runTestCycle(step1, "Set scrollTop=100 and check for no new notifications."); >+ root.scrollTop = 100; >+} >+ >+function step1() { >+ assert_equals(entries.length, 3, "Has 3 total notifications because isIntersecting did not change."); >+ runTestCycle(step2, "Add 4th target."); >+ root.scrollTop = 0; >+ var target4 = document.createElement('div'); >+ target4.setAttribute('id', 'target4'); >+ root.appendChild(target4); >+ observer.observe(target4); >+} >+ >+function step2() { >+ assert_equals(entries.length, 4, "Has 3 total notifications because 4th element was added."); >+ checkRect(entries[3].boundingClientRect, [0, 100, 300, 400], "Check 4th entry rect"); >+ assert_equals(entries[3].target.id, 'target4', "Check 4th entry target id."); >+ checkIsIntersecting(entries, 3, false); >+ assert_equals(entries[3].intersectionRatio, 0, 'target4 initially has intersectionRatio of 0.'); >+ runTestCycle(step3, "Set scrollTop=100 and check for one new notification."); >+ root.scrollTop = 100; >+} >+ >+function step3() { >+ assert_equals(entries.length, 5, "Has 5 total notifications."); >+ checkRect(entries[4].boundingClientRect, [0, 100, 200, 300], "Check 5th entry rect"); >+ assert_equals(entries[4].target.id, 'target4', "Check 5th entry target id."); >+ checkIsIntersecting(entries, 4, true); >+ assert_equals(entries[4].intersectionRatio, 0, 'target4 still has intersectionRatio of 0.'); >+ root.scrollTop = 0; // reset to make it easier to refresh and run the test >+} >+ >+</script> >diff --git a/LayoutTests/imported/w3c/web-platform-tests/intersection-observer/multiple-targets-expected.txt b/LayoutTests/imported/w3c/web-platform-tests/intersection-observer/multiple-targets-expected.txt >new file mode 100644 >index 0000000000000000000000000000000000000000..b838960d2c5fd5c83e06ca5581e867a4ae2d4ea6 >--- /dev/null >+++ b/LayoutTests/imported/w3c/web-platform-tests/intersection-observer/multiple-targets-expected.txt >@@ -0,0 +1,7 @@ >+ >+PASS One observer with multiple targets. >+PASS First rAF. >+PASS document.scrollingElement.scrollTop = 150 >+PASS document.scrollingElement.scrollTop = 10000 >+PASS document.scrollingElement.scrollTop = 0 >+ >diff --git a/LayoutTests/imported/w3c/web-platform-tests/intersection-observer/multiple-targets.html b/LayoutTests/imported/w3c/web-platform-tests/intersection-observer/multiple-targets.html >new file mode 100644 >index 0000000000000000000000000000000000000000..525c5a699d1cc51080bebc650ae377b26c2fe7eb >--- /dev/null >+++ b/LayoutTests/imported/w3c/web-platform-tests/intersection-observer/multiple-targets.html >@@ -0,0 +1,80 @@ >+<!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); >+} >+.target { >+ width: 100px; >+ height: 100px; >+ margin: 10px; >+ background-color: green; >+} >+</style> >+ >+<div class="spacer"></div> >+<div id="target1" class="target"></div> >+<div id="target2" class="target"></div> >+<div id="target3" class="target"></div> >+ >+<script> >+var entries = []; >+var target1, target2, target3; >+ >+runTestCycle(function() { >+ target1 = document.getElementById("target1"); >+ assert_true(!!target1, "target1 exists."); >+ target2 = document.getElementById("target2"); >+ assert_true(!!target2, "target2 exists."); >+ target3 = document.getElementById("target3"); >+ assert_true(!!target3, "target3 exists."); >+ var observer = new IntersectionObserver(function(changes) { >+ entries = entries.concat(changes) >+ }); >+ observer.observe(target1); >+ observer.observe(target2); >+ observer.observe(target3); >+ entries = entries.concat(observer.takeRecords()); >+ assert_equals(entries.length, 0, "No initial notifications."); >+ runTestCycle(step0, "First rAF."); >+}, "One observer with multiple targets."); >+ >+function step0() { >+ document.scrollingElement.scrollTop = 150; >+ runTestCycle(step1, "document.scrollingElement.scrollTop = 150"); >+ assert_equals(entries.length, 3, "Three initial notifications."); >+ assert_equals(entries[0].target, target1, "entries[0].target === target1"); >+ assert_equals(entries[1].target, target2, "entries[1].target === target2"); >+ assert_equals(entries[2].target, target3, "entries[2].target === target3"); >+} >+ >+function step1() { >+ document.scrollingElement.scrollTop = 10000; >+ runTestCycle(step2, "document.scrollingElement.scrollTop = 10000"); >+ assert_equals(entries.length, 4, "Four notifications."); >+ assert_equals(entries[3].target, target1, "entries[3].target === target1"); >+} >+ >+function step2() { >+ document.scrollingElement.scrollTop = 0; >+ runTestCycle(step3, "document.scrollingElement.scrollTop = 0"); >+ assert_equals(entries.length, 6, "Six notifications."); >+ assert_equals(entries[4].target, target2, "entries[4].target === target2"); >+ assert_equals(entries[5].target, target3, "entries[5].target === target3"); >+} >+ >+function step3() { >+ assert_equals(entries.length, 9, "Nine notifications."); >+ assert_equals(entries[6].target, target1, "entries[6].target === target1"); >+ assert_equals(entries[7].target, target2, "entries[7].target === target2"); >+ assert_equals(entries[8].target, target3, "entries[8].target === target3"); >+} >+</script> >diff --git a/LayoutTests/imported/w3c/web-platform-tests/intersection-observer/multiple-thresholds-expected.txt b/LayoutTests/imported/w3c/web-platform-tests/intersection-observer/multiple-thresholds-expected.txt >new file mode 100644 >index 0000000000000000000000000000000000000000..9119f28ca9ad27c5dbdf3f0ae7bb1e42db96f0a4 >--- /dev/null >+++ b/LayoutTests/imported/w3c/web-platform-tests/intersection-observer/multiple-thresholds-expected.txt >@@ -0,0 +1,12 @@ >+ >+PASS Observer with multiple thresholds. >+PASS First rAF. >+PASS document.scrollingElement.scrollTop = 120 >+PASS document.scrollingElement.scrollTop = 160 >+PASS document.scrollingElement.scrollTop = 200 >+PASS document.scrollingElement.scrollTop = 240 >+PASS document.scrollingElement.scrollTop = window.innerHeight + 140 >+PASS document.scrollingElement.scrollTop = window.innerHeight + 160 >+PASS document.scrollingElement.scrollTop = window.innerHeight + 200 >+PASS document.scrollingElement.scrollTop = window.innerHeight + 220 >+ >diff --git a/LayoutTests/imported/w3c/web-platform-tests/intersection-observer/multiple-thresholds.html b/LayoutTests/imported/w3c/web-platform-tests/intersection-observer/multiple-thresholds.html >new file mode 100644 >index 0000000000000000000000000000000000000000..6450068941ce1d132e3bba6970b366a25a7dbc4a >--- /dev/null >+++ b/LayoutTests/imported/w3c/web-platform-tests/intersection-observer/multiple-thresholds.html >@@ -0,0 +1,96 @@ >+<!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); >+} >+#target { >+ width: 100px; >+ height: 100px; >+ background-color: green; >+} >+</style> >+ >+<div class="spacer"></div> >+<div id="target"></div> >+<div class="spacer"></div> >+ >+<script> >+var vw = document.documentElement.clientWidth; >+var vh = document.documentElement.clientHeight; >+ >+var entries = []; >+var target; >+ >+runTestCycle(function() { >+ target = document.getElementById("target"); >+ var observer = new IntersectionObserver(function(changes) { >+ entries = entries.concat(changes) >+ }, { threshold: [0, 0.25, 0.5, 0.75, 1] }); >+ observer.observe(target); >+ entries = entries.concat(observer.takeRecords()); >+ assert_equals(entries.length, 0, "No initial notifications."); >+ runTestCycle(step0, "First rAF."); >+}, "Observer with multiple thresholds."); >+ >+function step0() { >+ document.scrollingElement.scrollTop = 120; >+ runTestCycle(step1, "document.scrollingElement.scrollTop = 120"); >+ checkLastEntry(entries, 0, [8, 108, vh + 108, vh + 208, 0, 0, 0, 0, 0, vw, 0, vh, false]); >+} >+ >+function step1() { >+ document.scrollingElement.scrollTop = 160; >+ runTestCycle(step2, "document.scrollingElement.scrollTop = 160"); >+ checkLastEntry(entries, 1, [8, 108, vh - 12, vh + 88, 8, 108, vh - 12, vh, 0, vw, 0, vh, true]); >+} >+ >+function step2() { >+ document.scrollingElement.scrollTop = 200; >+ runTestCycle(step3, "document.scrollingElement.scrollTop = 200"); >+ checkLastEntry(entries, 2, [8, 108, vh - 52, vh + 48, 8, 108, vh - 52, vh, 0, vw, 0, vh, true]); >+} >+ >+function step3() { >+ document.scrollingElement.scrollTop = 240; >+ runTestCycle(step4, "document.scrollingElement.scrollTop = 240"); >+ checkLastEntry(entries, 3, [8, 108, vh - 92, vh + 8, 8, 108, vh - 92, vh, 0, vw, 0, vh, true]); >+} >+ >+function step4() { >+ document.scrollingElement.scrollTop = vh + 140; >+ runTestCycle(step5, "document.scrollingElement.scrollTop = window.innerHeight + 140"); >+ checkLastEntry(entries, 4, [8, 108, vh - 132, vh - 32, 8, 108, vh - 132, vh - 32, 0, vw, 0, vh, true]); >+} >+ >+function step5() { >+ document.scrollingElement.scrollTop = vh + 160; >+ runTestCycle(step6, "document.scrollingElement.scrollTop = window.innerHeight + 160"); >+ checkLastEntry(entries, 5, [8, 108, -32, 68, 8, 108, 0, 68, 0, vw, 0, vh, true]); >+} >+ >+function step6() { >+ document.scrollingElement.scrollTop = vh + 200; >+ runTestCycle(step7, "document.scrollingElement.scrollTop = window.innerHeight + 200"); >+ checkLastEntry(entries, 6, [8, 108, -52, 48, 8, 108, 0, 48, 0, vw, 0, vh, true]); >+} >+ >+function step7() { >+ checkLastEntry(entries, 7, [8, 108, -92, 8, 8, 108, 0, 8, 0, vw, 0, vh, true]); >+ document.scrollingElement.scrollTop = vh + 220; >+ runTestCycle(step8, "document.scrollingElement.scrollTop = window.innerHeight + 220"); >+} >+ >+function step8() { >+ checkLastEntry(entries, 8, [8, 108, -112, -12, 0, 0, 0, 0, 0, vw, 0, vh, false]); >+ document.scrollingElement.scrollTop = 0; >+} >+</script> >diff --git a/LayoutTests/imported/w3c/web-platform-tests/intersection-observer/observer-attributes-expected.txt b/LayoutTests/imported/w3c/web-platform-tests/intersection-observer/observer-attributes-expected.txt >new file mode 100644 >index 0000000000000000000000000000000000000000..9461517f99f603fd8cec27c24ccd7fb322df9f93 >--- /dev/null >+++ b/LayoutTests/imported/w3c/web-platform-tests/intersection-observer/observer-attributes-expected.txt >@@ -0,0 +1,9 @@ >+ >+PASS Observer attribute getters. >+PASS observer.root >+PASS observer.thresholds >+PASS observer.rootMargin >+PASS set observer.root >+PASS set observer.thresholds >+PASS set observer.rootMargin >+ >diff --git a/LayoutTests/imported/w3c/web-platform-tests/intersection-observer/observer-attributes.html b/LayoutTests/imported/w3c/web-platform-tests/intersection-observer/observer-attributes.html >new file mode 100644 >index 0000000000000000000000000000000000000000..ffca95ded3160b887adc6bbfada2e5b366efac24 >--- /dev/null >+++ b/LayoutTests/imported/w3c/web-platform-tests/intersection-observer/observer-attributes.html >@@ -0,0 +1,31 @@ >+<!DOCTYPE html> >+<script src="/resources/testharness.js"></script> >+<script src="/resources/testharnessreport.js"></script> >+ >+<div id="root"></div> >+ >+<script> >+test(function() { >+ var observer = new IntersectionObserver(function(e) {}, {}); >+ test(function() { assert_equals(observer.root, null) }, >+ "observer.root"); >+ test(function() { assert_array_equals(observer.thresholds, [0]) }, >+ "observer.thresholds"); >+ test(function() { assert_equals(observer.rootMargin, "0px 0px 0px 0px") }, >+ "observer.rootMargin"); >+ >+ var rootDiv = document.getElementById("root"); >+ observer = new IntersectionObserver(function(e) {}, { >+ root: rootDiv, >+ threshold: [0, 0.25, 0.5, 1.0], >+ rootMargin: "10% 20px" >+ }); >+ test(function() { assert_equals(observer.root, rootDiv) }, >+ "set observer.root"); >+ test(function() { assert_array_equals(observer.thresholds, [0, 0.25, 0.5, 1.0]) }, >+ "set observer.thresholds"); >+ test(function() { assert_equals(observer.rootMargin, "10% 20px 10% 20px") }, >+ "set observer.rootMargin"); >+}, "Observer attribute getters."); >+ >+</script> >diff --git a/LayoutTests/imported/w3c/web-platform-tests/intersection-observer/observer-exceptions-expected.txt b/LayoutTests/imported/w3c/web-platform-tests/intersection-observer/observer-exceptions-expected.txt >new file mode 100644 >index 0000000000000000000000000000000000000000..f500b02d3f3af07df2545c9af3c9400673fa2722 >--- /dev/null >+++ b/LayoutTests/imported/w3c/web-platform-tests/intersection-observer/observer-exceptions-expected.txt >@@ -0,0 +1,11 @@ >+ >+PASS IntersectionObserver constructor with { threshold: [1.1] } >+PASS IntersectionObserver constructor with { threshold: ["foo"] } >+PASS IntersectionObserver constructor witth { rootMargin: "1" } >+PASS IntersectionObserver constructor with { rootMargin: "2em" } >+PASS IntersectionObserver constructor with { rootMargin: "auto" } >+PASS IntersectionObserver constructor with { rootMargin: "calc(1px + 2px)" } >+PASS IntersectionObserver constructor with { rootMargin: "1px !important" } >+PASS IntersectionObserver constructor with { rootMargin: "1px 1px 1px 1px 1px" } >+PASS IntersectionObserver.observe("foo") >+ >diff --git a/LayoutTests/imported/w3c/web-platform-tests/intersection-observer/observer-exceptions.html b/LayoutTests/imported/w3c/web-platform-tests/intersection-observer/observer-exceptions.html >new file mode 100644 >index 0000000000000000000000000000000000000000..d4f178be51c394ba98225f359eb02cd78f45259e >--- /dev/null >+++ b/LayoutTests/imported/w3c/web-platform-tests/intersection-observer/observer-exceptions.html >@@ -0,0 +1,60 @@ >+<!DOCTYPE html> >+<script src="/resources/testharness.js"></script> >+<script src="/resources/testharnessreport.js"></script> >+ >+<script> >+test(function () { >+ assert_throws(RangeError(), function() { >+ new IntersectionObserver(e => {}, {threshold: [1.1]}) >+ }) >+}, "IntersectionObserver constructor with { threshold: [1.1] }"); >+ >+test(function () { >+ assert_throws(TypeError(), function() { >+ new IntersectionObserver(e => {}, {threshold: ["foo"]}) >+ }) >+}, 'IntersectionObserver constructor with { threshold: ["foo"] }'); >+ >+test(function () { >+ assert_throws("SYNTAX_ERR", function() { >+ new IntersectionObserver(e => {}, {rootMargin: "1"}) >+ }) >+}, 'IntersectionObserver constructor witth { rootMargin: "1" }'); >+ >+test(function () { >+ assert_throws("SYNTAX_ERR", function() { >+ new IntersectionObserver(e => {}, {rootMargin: "2em"}) >+ }) >+}, 'IntersectionObserver constructor with { rootMargin: "2em" }'); >+ >+test(function () { >+ assert_throws("SYNTAX_ERR", function() { >+ new IntersectionObserver(e => {}, {rootMargin: "auto"}) >+ }) >+}, 'IntersectionObserver constructor with { rootMargin: "auto" }'); >+ >+test(function () { >+ assert_throws("SYNTAX_ERR", function() { >+ new IntersectionObserver(e => {}, {rootMargin: "calc(1px + 2px)"}) >+ }) >+}, 'IntersectionObserver constructor with { rootMargin: "calc(1px + 2px)" }'); >+ >+test(function () { >+ assert_throws("SYNTAX_ERR", function() { >+ new IntersectionObserver(e => {}, {rootMargin: "1px !important"}) >+ }) >+}, 'IntersectionObserver constructor with { rootMargin: "1px !important" }'); >+ >+test(function () { >+ assert_throws("SYNTAX_ERR", function() { >+ new IntersectionObserver(e => {}, {rootMargin: "1px 1px 1px 1px 1px"}) >+ }) >+}, 'IntersectionObserver constructor with { rootMargin: "1px 1px 1px 1px 1px" }'); >+ >+test(function () { >+ assert_throws(TypeError(), function() { >+ let observer = new IntersectionObserver(c => {}, {}); >+ observer.observe("foo"); >+ }) >+}, 'IntersectionObserver.observe("foo")'); >+</script> >diff --git a/LayoutTests/imported/w3c/web-platform-tests/intersection-observer/observer-in-iframe.html b/LayoutTests/imported/w3c/web-platform-tests/intersection-observer/observer-in-iframe.html >new file mode 100644 >index 0000000000000000000000000000000000000000..f4aa387de2a80eb7309b3d457eb605d87eae6046 >--- /dev/null >+++ b/LayoutTests/imported/w3c/web-platform-tests/intersection-observer/observer-in-iframe.html >@@ -0,0 +1,9 @@ >+<!DOCTYPE html> >+<style> >+pre, #log { >+ position: absolute; >+ top: 0; >+ left: 200px; >+} >+</style> >+<iframe id="target-iframe" src="resources/observer-in-iframe-subframe.html" width="150px" height="150px"></iframe> >diff --git a/LayoutTests/imported/w3c/web-platform-tests/intersection-observer/observer-without-js-reference-expected.txt b/LayoutTests/imported/w3c/web-platform-tests/intersection-observer/observer-without-js-reference-expected.txt >new file mode 100644 >index 0000000000000000000000000000000000000000..c576d041370174430499f068979e5d409137bc08 >--- /dev/null >+++ b/LayoutTests/imported/w3c/web-platform-tests/intersection-observer/observer-without-js-reference-expected.txt >@@ -0,0 +1,5 @@ >+ >+PASS IntersectionObserver that is unreachable in js should still generate notifications. >+PASS First rAF >+PASS document.scrollingElement.scrollTop = 300 >+ >diff --git a/LayoutTests/imported/w3c/web-platform-tests/intersection-observer/observer-without-js-reference.html b/LayoutTests/imported/w3c/web-platform-tests/intersection-observer/observer-without-js-reference.html >new file mode 100644 >index 0000000000000000000000000000000000000000..3214345b61052cfc0ab3720f688416faf23a5585 >--- /dev/null >+++ b/LayoutTests/imported/w3c/web-platform-tests/intersection-observer/observer-without-js-reference.html >@@ -0,0 +1,50 @@ >+<!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); >+} >+#target { >+ width: 100px; >+ height: 100px; >+ background-color: green; >+} >+</style> >+<div class="spacer"></div> >+<div id="target"></div> >+<div class="spacer"></div> >+ >+<script> >+var entries = []; >+ >+runTestCycle(function() { >+ var target = document.getElementById("target"); >+ assert_true(!!target, "Target exists"); >+ function createObserver() { >+ new IntersectionObserver(function(changes) { >+ entries = entries.concat(changes) >+ }).observe(target); >+ } >+ createObserver(); >+ runTestCycle(step0, "First rAF"); >+}, "IntersectionObserver that is unreachable in js should still generate notifications."); >+ >+function step0() { >+ document.scrollingElement.scrollTop = 300; >+ runTestCycle(step1, "document.scrollingElement.scrollTop = 300"); >+ assert_equals(entries.length, 1, "One notification."); >+} >+ >+function step1() { >+ document.scrollingElement.scrollTop = 0; >+ assert_equals(entries.length, 2, "Two notifications."); >+} >+</script> >diff --git a/LayoutTests/imported/w3c/web-platform-tests/intersection-observer/remove-element-expected.txt b/LayoutTests/imported/w3c/web-platform-tests/intersection-observer/remove-element-expected.txt >new file mode 100644 >index 0000000000000000000000000000000000000000..e51f12bd127a6698eb158b19cb9095d1d7b233a4 >--- /dev/null >+++ b/LayoutTests/imported/w3c/web-platform-tests/intersection-observer/remove-element-expected.txt >@@ -0,0 +1,8 @@ >+ >+PASS Verify that not-intersecting notifications are sent when a target is removed from the DOM tree. >+PASS First rAF >+PASS root.scrollTop = 150 >+PASS root.removeChild(target). >+PASS root.insertBefore(target, trailingSpace). >+PASS root.scrollTop = 150 after reinserting target. >+ >diff --git a/LayoutTests/imported/w3c/web-platform-tests/intersection-observer/remove-element.html b/LayoutTests/imported/w3c/web-platform-tests/intersection-observer/remove-element.html >new file mode 100644 >index 0000000000000000000000000000000000000000..3b6a65e2d0e42513aefd4568409b1a2f510b2db9 >--- /dev/null >+++ b/LayoutTests/imported/w3c/web-platform-tests/intersection-observer/remove-element.html >@@ -0,0 +1,82 @@ >+<!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; >+} >+#root { >+ display: inline-block; >+ overflow-y: scroll; >+ height: 200px; >+ border: 3px solid black; >+} >+#target { >+ width: 100px; >+ height: 100px; >+ background-color: green; >+} >+.spacer { >+ height: 300px; >+} >+</style> >+ >+<div id="root"> >+ <div id="leading-space" class="spacer"></div> >+ <div id="target"></div> >+ <div id="trailing-space" class="spacer"</div> >+</div> >+ >+<script> >+var entries = []; >+var root, target, trailingSpace; >+ >+runTestCycle(function() { >+ target = document.getElementById("target"); >+ assert_true(!!target, "Target exists"); >+ trailingSpace = document.getElementById("trailing-space"); >+ assert_true(!!trailingSpace, "TrailingSpace exists"); >+ root = document.getElementById("root"); >+ assert_true(!!root, "Root exists"); >+ var observer = new IntersectionObserver(function(changes) { >+ entries = entries.concat(changes) >+ }, {root: root}); >+ observer.observe(target); >+ entries = entries.concat(observer.takeRecords()); >+ assert_equals(entries.length, 0, "No initial notifications."); >+ runTestCycle(step0, "First rAF"); >+}, "Verify that not-intersecting notifications are sent when a target is removed from the DOM tree."); >+ >+function step0() { >+ root.scrollTop = 150; >+ runTestCycle(step1, "root.scrollTop = 150"); >+ checkLastEntry(entries, 0, [11, 111, 311, 411, 0, 0, 0, 0, 11, 111, 11, 211, false]); >+} >+ >+function step1() { >+ root.removeChild(target); >+ runTestCycle(step2, "root.removeChild(target)."); >+ checkLastEntry(entries, 1, [11, 111, 161, 261, 11, 111, 161, 211, 11, 111, 11, 211, true]); >+} >+ >+function step2() { >+ root.scrollTop = 0; >+ root.insertBefore(target, trailingSpace); >+ runTestCycle(step3, "root.insertBefore(target, trailingSpace)."); >+ checkLastEntry(entries, 2, [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, false]); >+} >+ >+function step3() { >+ root.scrollTop = 150; >+ runTestCycle(step4, "root.scrollTop = 150 after reinserting target."); >+ checkLastEntry(entries, 2); >+} >+ >+function step4() { >+ checkLastEntry(entries, 3, [11, 111, 161, 261, 11, 111, 161, 211, 11, 111, 11, 211, true]); >+} >+</script> >diff --git a/LayoutTests/imported/w3c/web-platform-tests/intersection-observer/resources/cross-origin-subframe.html b/LayoutTests/imported/w3c/web-platform-tests/intersection-observer/resources/cross-origin-subframe.html >new file mode 100644 >index 0000000000000000000000000000000000000000..0cc117cb3a69d0f2ed19505f7d14f824cbad6d71 >--- /dev/null >+++ b/LayoutTests/imported/w3c/web-platform-tests/intersection-observer/resources/cross-origin-subframe.html >@@ -0,0 +1,125 @@ >+<!DOCTYPE html> >+<div style="height: 200px; width: 100px;"></div> >+<div id="target" style="background-color: green; width:100px; height:100px"></div> >+<div style="height: 200px; width: 100px;"></div> >+ >+<script> >+var port; >+var entries = []; >+var target = document.getElementById('target'); >+var scroller = document.scrollingElement; >+var nextStep; >+ >+function clientRectToJson(rect) { >+ if (!rect) >+ return "null"; >+ return { >+ top: rect.top, >+ right: rect.right, >+ bottom: rect.bottom, >+ left: rect.left >+ }; >+} >+ >+function entryToJson(entry) { >+ return { >+ boundingClientRect: clientRectToJson(entry.boundingClientRect), >+ intersectionRect: clientRectToJson(entry.intersectionRect), >+ rootBounds: clientRectToJson(entry.rootBounds), >+ target: entry.target.id >+ }; >+} >+ >+function coordinatesToClientRectJson(top, right, bottom, left) { >+ return { >+ top: top, >+ right: right, >+ bottom: bottom, >+ left: left >+ }; >+} >+ >+// Note that we never use RAF in this code, because this frame might get render-throttled. >+// Instead of RAF-ing, we just post an empty message to the parent window, which will >+// RAF when it is received, and then send us a message to cause the next step to run. >+ >+// Use a rootMargin here, and verify it does NOT get applied for the cross-origin case. >+var observer = new IntersectionObserver(function(changes) { >+ entries = entries.concat(changes) >+}, { rootMargin: "7px" }); >+observer.observe(target); >+ >+function step0() { >+ entries = entries.concat(observer.takeRecords()); >+ nextStep = step1; >+ var expected = [{ >+ boundingClientRect: coordinatesToClientRectJson(8, 208, 108, 308), >+ intersectionRect: coordinatesToClientRectJson(0, 0, 0, 0), >+ rootBounds: "null", >+ target: target.id >+ }]; >+ port.postMessage({ >+ actual: entries.map(entryToJson), >+ expected: expected, >+ description: "First rAF" >+ }, "*"); >+ entries = []; >+ port.postMessage({scrollTo: 200}, "*"); >+} >+ >+function step1() { >+ entries = entries.concat(observer.takeRecords()); >+ port.postMessage({ >+ actual: entries.map(entryToJson), >+ expected: [], >+ description: "topDocument.scrollingElement.scrollTop = 200" >+ }, "*"); >+ entries = []; >+ scroller.scrollTop = 250; >+ nextStep = step2; >+ port.postMessage({}, "*"); >+} >+ >+function step2() { >+ entries = entries.concat(observer.takeRecords()); >+ var expected = [{ >+ boundingClientRect: coordinatesToClientRectJson(-42, 108, 58, 8), >+ intersectionRect: coordinatesToClientRectJson(0, 108, 58, 8), >+ rootBounds: "null", >+ target: target.id >+ }]; >+ port.postMessage({ >+ actual: entries.map(entryToJson), >+ expected: expected, >+ description: "iframeDocument.scrollingElement.scrollTop = 250" >+ }, "*"); >+ entries = []; >+ nextStep = step3; >+ port.postMessage({scrollTo: 100}, "*"); >+} >+ >+function step3() { >+ entries = entries.concat(observer.takeRecords()); >+ var expected = [{ >+ boundingClientRect: coordinatesToClientRectJson(-42, 108, 58, 8), >+ intersectionRect: coordinatesToClientRectJson(0, 0, 0, 0), >+ rootBounds: "null", >+ target: target.id >+ }]; >+ port.postMessage({ >+ actual: entries.map(entryToJson), >+ expected: expected, >+ description: "topDocument.scrollingElement.scrollTop = 100" >+ }, "*"); >+ port.postMessage({DONE: 1}, "*"); >+} >+ >+function handleMessage(event) >+{ >+ port = event.source; >+ nextStep(); >+} >+ >+nextStep = step0; >+window.addEventListener("message", handleMessage); >+</script> >diff --git a/LayoutTests/imported/w3c/web-platform-tests/intersection-observer/resources/iframe-no-root-subframe.html b/LayoutTests/imported/w3c/web-platform-tests/intersection-observer/resources/iframe-no-root-subframe.html >new file mode 100644 >index 0000000000000000000000000000000000000000..ee63a06ca0ff30eb6bc82d28350cf8d85313251b >--- /dev/null >+++ b/LayoutTests/imported/w3c/web-platform-tests/intersection-observer/resources/iframe-no-root-subframe.html >@@ -0,0 +1,4 @@ >+<!DOCTYPE html> >+<div style="height: 200px; width: 100px;"></div> >+<div id="target" style="background-color: green; width:100px; height:100px"></div> >+<div style="height: 200px; width: 100px;"></div> >diff --git a/LayoutTests/imported/w3c/web-platform-tests/intersection-observer/resources/intersection-observer-test-utils.js b/LayoutTests/imported/w3c/web-platform-tests/intersection-observer/resources/intersection-observer-test-utils.js >new file mode 100644 >index 0000000000000000000000000000000000000000..0f48262a80269fc4108164a2c40e82caf3e7ad15 >--- /dev/null >+++ b/LayoutTests/imported/w3c/web-platform-tests/intersection-observer/resources/intersection-observer-test-utils.js >@@ -0,0 +1,127 @@ >+// Here's how waitForNotification works: >+// >+// - myTestFunction0() >+// - waitForNotification(myTestFunction1) >+// - requestAnimationFrame() >+// - Modify DOM in a way that should trigger an IntersectionObserver callback. >+// - BeginFrame >+// - requestAnimationFrame handler runs >+// - First step_timeout() >+// - Style, layout, paint >+// - IntersectionObserver generates new notifications >+// - Posts a task to deliver notifications >+// - First step_timeout handler runs >+// - Second step_timeout() >+// - Task to deliver IntersectionObserver notifications runs >+// - IntersectionObserver callbacks run >+// - Second step_timeout handler runs >+// - myTestFunction1() >+// - [optional] waitForNotification(myTestFunction2) >+// - requestAnimationFrame() >+// - Verify newly-arrived IntersectionObserver notifications >+// - [optional] Modify DOM to trigger new notifications >+function waitForNotification(t, f) { >+ requestAnimationFrame(function() { >+ t.step_timeout(function() { t.step_timeout(f); }); >+ }); >+} >+ >+// The timing of when runTestCycle is called is important. It should be >+// called: >+// >+// - Before or during the window load event, or >+// - Inside of a prior runTestCycle callback, *before* any assert_* methods >+// are called. >+// >+// Following these rules will ensure that the test suite will not abort before >+// all test steps have run. >+function runTestCycle(f, description) { >+ async_test(function(t) { >+ waitForNotification(t, t.step_func_done(f)); >+ }, description); >+} >+ >+// Root bounds for a root with an overflow clip as defined by: >+// http://wicg.github.io/IntersectionObserver/#intersectionobserver-root-intersection-rectangle >+function contentBounds(root) { >+ var left = root.offsetLeft + root.clientLeft; >+ var right = left + root.clientWidth; >+ var top = root.offsetTop + root.clientTop; >+ var bottom = top + root.clientHeight; >+ return [left, right, top, bottom]; >+} >+ >+// Root bounds for a root without an overflow clip as defined by: >+// http://wicg.github.io/IntersectionObserver/#intersectionobserver-root-intersection-rectangle >+function borderBoxBounds(root) { >+ var left = root.offsetLeft; >+ var right = left + root.offsetWidth; >+ var top = root.offsetTop; >+ var bottom = top + root.offsetHeight; >+ return [left, right, top, bottom]; >+} >+ >+function clientBounds(element) { >+ var rect = element.getBoundingClientRect(); >+ return [rect.left, rect.right, rect.top, rect.bottom]; >+} >+ >+function rectArea(rect) { >+ return (rect.left - rect.right) * (rect.bottom - rect.top); >+} >+ >+function checkRect(actual, expected, description, all) { >+ if (!expected.length) >+ return; >+ assert_equals(actual.left | 0, expected[0] | 0, description + '.left'); >+ assert_equals(actual.right | 0, expected[1] | 0, description + '.right'); >+ assert_equals(actual.top | 0, expected[2] | 0, description + '.top'); >+ assert_equals(actual.bottom | 0, expected[3] | 0, description + '.bottom'); >+} >+ >+function checkLastEntry(entries, i, expected) { >+ assert_equals(entries.length, i + 1, 'entries.length'); >+ if (expected) { >+ checkRect( >+ entries[i].boundingClientRect, expected.slice(0, 4), >+ 'entries[' + i + '].boundingClientRect', entries[i]); >+ checkRect( >+ entries[i].intersectionRect, expected.slice(4, 8), >+ 'entries[' + i + '].intersectionRect', entries[i]); >+ checkRect( >+ entries[i].rootBounds, expected.slice(8, 12), >+ 'entries[' + i + '].rootBounds', entries[i]); >+ if (expected.length > 12) { >+ assert_equals( >+ entries[i].isIntersecting, expected[12], >+ 'entries[' + i + '].isIntersecting'); >+ } >+ } >+} >+ >+function checkJsonEntry(actual, expected) { >+ checkRect( >+ actual.boundingClientRect, expected.boundingClientRect, >+ 'entry.boundingClientRect'); >+ checkRect( >+ actual.intersectionRect, expected.intersectionRect, >+ 'entry.intersectionRect'); >+ if (actual.rootBounds == 'null') >+ assert_equals(expected.rootBounds, 'null', 'rootBounds is null'); >+ else >+ checkRect(actual.rootBounds, expected.rootBounds, 'entry.rootBounds'); >+ assert_equals(actual.target, expected.target); >+} >+ >+function checkJsonEntries(actual, expected, description) { >+ test(function() { >+ assert_equals(actual.length, expected.length); >+ for (var i = 0; i < actual.length; i++) >+ checkJsonEntry(actual[i], expected[i]); >+ }, description); >+} >+ >+function checkIsIntersecting(entries, i, expected) { >+ assert_equals(entries[i].isIntersecting, expected, >+ 'entries[' + i + '].target.isIntersecting equals ' + expected); >+} >diff --git a/LayoutTests/imported/w3c/web-platform-tests/intersection-observer/resources/observer-in-iframe-subframe.html b/LayoutTests/imported/w3c/web-platform-tests/intersection-observer/resources/observer-in-iframe-subframe.html >new file mode 100644 >index 0000000000000000000000000000000000000000..9d0769ae44a1bb4a6195c006999b0959f706330c >--- /dev/null >+++ b/LayoutTests/imported/w3c/web-platform-tests/intersection-observer/resources/observer-in-iframe-subframe.html >@@ -0,0 +1,65 @@ >+<!DOCTYPE html> >+<script src="/resources/testharness.js"></script> >+<script src="/resources/testharnessreport.js"></script> >+<script src="./intersection-observer-test-utils.js"></script> >+ >+<style> >+#root { >+ width: 200px; >+ height: 200px; >+} >+#scroller { >+ width: 160px; >+ height: 200px; >+ overflow-y: scroll; >+} >+#target { >+ width: 100px; >+ height: 100px; >+ background-color: green; >+} >+.spacer { >+ height: 300px; >+} >+</style> >+ >+<div id="root"> >+ <div id="scroller"> >+ <div class="spacer"></div> >+ <div id="target"></div> >+ <div class="spacer"></div> >+ </div> >+</div> >+ >+<script> >+setup({message_events: [], output_document: window.parent.document}); >+ >+var entries = []; >+var root, scroller, target; >+ >+runTestCycle(function() { >+ root = document.getElementById("root"); >+ assert_true(!!root, "Root element exists."); >+ scroller = document.getElementById("scroller"); >+ assert_true(!!scroller, "Scroller element exists."); >+ target = document.getElementById("target"); >+ assert_true(!!target, "Target element exists."); >+ var observer = new IntersectionObserver(function(changes) { >+ entries = entries.concat(changes) >+ }, {root: root}); >+ observer.observe(target); >+ entries = entries.concat(observer.takeRecords()); >+ assert_equals(entries.length, 0, "No initial notifications.") >+ runTestCycle(step1, "First rAF."); >+}, "IntersectionObserver in iframe with explicit root."); >+ >+function step1() { >+ scroller.scrollTop = 250; >+ runTestCycle(step2, "scroller.scrollTop = 250"); >+ checkLastEntry(entries, 0, [8, 108, 308, 408, 0, 0, 0, 0, 8, 208, 8, 208, false]); >+} >+ >+function step2() { >+ checkLastEntry(entries, 1, [8, 108, 58, 158, 8, 108, 58, 158, 8, 208, 8, 208, true]); >+} >+</script> >diff --git a/LayoutTests/imported/w3c/web-platform-tests/intersection-observer/resources/timestamp-subframe.html b/LayoutTests/imported/w3c/web-platform-tests/intersection-observer/resources/timestamp-subframe.html >new file mode 100644 >index 0000000000000000000000000000000000000000..143e4f6e23a7688949420a07ccd20e3c211a6f6b >--- /dev/null >+++ b/LayoutTests/imported/w3c/web-platform-tests/intersection-observer/resources/timestamp-subframe.html >@@ -0,0 +1,28 @@ >+<!DOCTYPE html> >+<style> >+#target { >+ width: 100px; >+ height: 100px; >+ background-color: green; >+} >+.spacer { >+ width: height: 100px >+} >+</style> >+ >+<div class="spacer"></div> >+<div id="target"></div> >+<div class="spacer"></div> >+ >+<script> >+document.createObserverCallback = function(entries) { >+ return function(newEntries) { >+ for (var i in newEntries) { >+ entries.push(newEntries[i]); >+ } >+ }; >+} >+document.createObserver = function(callback) { >+ return new IntersectionObserver(callback, {}); >+}; >+</script> >diff --git a/LayoutTests/imported/w3c/web-platform-tests/intersection-observer/root-margin-expected.txt b/LayoutTests/imported/w3c/web-platform-tests/intersection-observer/root-margin-expected.txt >new file mode 100644 >index 0000000000000000000000000000000000000000..9d9ab2aba1f7d866476f73c35fb183807ba6039a >--- /dev/null >+++ b/LayoutTests/imported/w3c/web-platform-tests/intersection-observer/root-margin-expected.txt >@@ -0,0 +1,8 @@ >+ >+ >+PASS Root margin tests >+PASS First rAF. >+PASS document.scrollingElement.scrollLeft = 100 >+PASS document.scrollingElement.scrollTop = document.documentElement.clientHeight + 200 >+PASS document.scrollingElement.scrollTop = document.documentElement.clientHeight + 300 >+ >diff --git a/LayoutTests/imported/w3c/web-platform-tests/intersection-observer/root-margin.html b/LayoutTests/imported/w3c/web-platform-tests/intersection-observer/root-margin.html >new file mode 100644 >index 0000000000000000000000000000000000000000..c1fffec02becc50ccaad1f8fdaa9ffd16afef11c >--- /dev/null >+++ b/LayoutTests/imported/w3c/web-platform-tests/intersection-observer/root-margin.html >@@ -0,0 +1,86 @@ >+<!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; >+} >+#target { >+ display: inline-block; >+ width: 100px; >+ height: 100px; >+ background-color: green; >+} >+.vertical-spacer { >+ height: calc(100vh + 100px); >+} >+.horizontal-spacer { >+ display: inline-block; >+ width: 120vw; >+} >+</style> >+ >+<div class="vertical-spacer"></div> >+<div style="white-space:nowrap;"> >+ <div class="horizontal-spacer"></div> >+ <div id="target"></div> >+ <div class="horizontal-spacer"></div> >+</div> >+<div class="vertical-spacer"></div> >+ >+<script> >+var vw = document.documentElement.clientWidth; >+var vh = document.documentElement.clientHeight; >+ >+var entries = []; >+var target; >+ >+runTestCycle(function() { >+ target = document.getElementById("target"); >+ assert_true(!!target, "Target exists"); >+ var observer = new IntersectionObserver(function(changes) { >+ entries = entries.concat(changes) >+ }, { rootMargin: "10px 20% 40% 30px" }); >+ observer.observe(target); >+ entries = entries.concat(observer.takeRecords()); >+ assert_equals(entries.length, 0, "No initial notifications."); >+ runTestCycle(step0, "First rAF."); >+}, "Root margin tests"); >+ >+function step0() { >+ var targetBounds = clientBounds(target); >+ document.scrollingElement.scrollLeft = 100; >+ runTestCycle(step1, "document.scrollingElement.scrollLeft = 100"); >+ checkLastEntry(entries, 0, targetBounds.concat(0, 0, 0, 0, -30, vw * 1.2, -10, vh * 1.4, false)); >+} >+ >+function step1() { >+ var targetBounds = clientBounds(target); >+ var sw = window.innerWidth - document.documentElement.clientWidth; >+ var sh = window.innerHeight - document.documentElement.clientHeight; >+ document.scrollingElement.scrollTop = vh + 200; >+ runTestCycle(step2, "document.scrollingElement.scrollTop = document.documentElement.clientHeight + 200"); >+ checkLastEntry(entries, 1, targetBounds.concat( >+ targetBounds[0], Math.min(targetBounds[1], vw * 1.2), vh + 108 + sh, Math.min(vh + 208 + sw, vh * 1.4), >+ -30, vw * 1.2, -10, vh * 1.4, >+ true >+ )); >+} >+ >+function step2() { >+ document.scrollingElement.scrollTop = vh + 300; >+ runTestCycle(step3, "document.scrollingElement.scrollTop = document.documentElement.clientHeight + 300"); >+ checkLastEntry(entries, 1); >+} >+ >+function step3() { >+ var targetBounds = clientBounds(target); >+ document.scrollingElement.scrollLeft = 0; >+ document.scrollingElement.scrollTop = 0; >+ checkLastEntry(entries, 2, targetBounds.concat(0, 0, 0, 0, -30, vw * 1.2, -10, vh * 1.4, false)); >+} >+</script> >diff --git a/LayoutTests/imported/w3c/web-platform-tests/intersection-observer/same-document-no-root-expected.txt b/LayoutTests/imported/w3c/web-platform-tests/intersection-observer/same-document-no-root-expected.txt >new file mode 100644 >index 0000000000000000000000000000000000000000..d463af72669325653ec5edb153653c485a93b656 >--- /dev/null >+++ b/LayoutTests/imported/w3c/web-platform-tests/intersection-observer/same-document-no-root-expected.txt >@@ -0,0 +1,6 @@ >+ >+PASS IntersectionObserver in a single document using the implicit root. >+PASS First rAF. >+PASS document.scrollingElement.scrollTop = 300 >+PASS document.scrollingElement.scrollTop = 100 >+ >diff --git a/LayoutTests/imported/w3c/web-platform-tests/intersection-observer/same-document-no-root.html b/LayoutTests/imported/w3c/web-platform-tests/intersection-observer/same-document-no-root.html >new file mode 100644 >index 0000000000000000000000000000000000000000..783880888abdcdd870e2edb2358f619d434ad51f >--- /dev/null >+++ b/LayoutTests/imported/w3c/web-platform-tests/intersection-observer/same-document-no-root.html >@@ -0,0 +1,61 @@ >+<!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); >+} >+#target { >+ width: 100px; >+ height: 100px; >+ background-color: green; >+} >+</style> >+ >+<div class="spacer"></div> >+<div id="target"></div> >+<div class="spacer"></div> >+ >+<script> >+var vw = document.documentElement.clientWidth; >+var vh = document.documentElement.clientHeight; >+ >+var entries = []; >+var target; >+ >+runTestCycle(function() { >+ target = document.getElementById("target"); >+ assert_true(!!target, "target 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."); >+}, "IntersectionObserver in a single document using the implicit root."); >+ >+function step0() { >+ document.scrollingElement.scrollTop = 300; >+ runTestCycle(step1, "document.scrollingElement.scrollTop = 300"); >+ checkLastEntry(entries, 0, [8, 108, vh + 108, vh + 208, 0, 0, 0, 0, 0, vw, 0, vh, false]); >+} >+ >+function step1() { >+ document.scrollingElement.scrollTop = 100; >+ runTestCycle(step2, "document.scrollingElement.scrollTop = 100"); >+ checkLastEntry(entries, 1, [8, 108, vh - 192, vh - 92, 8, 108, vh - 192, vh - 92, 0, vw, 0, vh, true]); >+} >+ >+function step2() { >+ document.scrollingElement.scrollTop = 0; >+ checkLastEntry(entries, 2, [8, 108, vh + 8, vh + 108, 0, 0, 0, 0, 0, vw, 0, vh, false]); >+} >+</script> >diff --git a/LayoutTests/imported/w3c/web-platform-tests/intersection-observer/same-document-root-expected.txt b/LayoutTests/imported/w3c/web-platform-tests/intersection-observer/same-document-root-expected.txt >new file mode 100644 >index 0000000000000000000000000000000000000000..9e1fc864dc910f042c3ea05fb01a8affcd6bf9ef >--- /dev/null >+++ b/LayoutTests/imported/w3c/web-platform-tests/intersection-observer/same-document-root-expected.txt >@@ -0,0 +1,9 @@ >+ >+PASS IntersectionObserver in a single document with explicit root. >+PASS First rAF >+PASS document.scrollingElement.scrollTop = window.innerHeight. >+PASS root.scrollTop = 150 with root scrolled into view. >+PASS document.scrollingElement.scrollTop = 0. >+PASS root.scrollTop = 0 >+PASS root.scrollTop = 150 with root scrolled out of view. >+ >diff --git a/LayoutTests/imported/w3c/web-platform-tests/intersection-observer/same-document-root.html b/LayoutTests/imported/w3c/web-platform-tests/intersection-observer/same-document-root.html >new file mode 100644 >index 0000000000000000000000000000000000000000..40467be72b4f298bb9d8704cbb938391e395a3ef >--- /dev/null >+++ b/LayoutTests/imported/w3c/web-platform-tests/intersection-observer/same-document-root.html >@@ -0,0 +1,90 @@ >+<!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); >+} >+#root { >+ display: inline-block; >+ overflow-y: scroll; >+ height: 200px; >+ border: 3px solid black; >+} >+#target { >+ width: 100px; >+ height: 100px; >+ background-color: green; >+} >+</style> >+ >+<div class="spacer"></div> >+<div id="root"> >+ <div style="height: 300px;"></div> >+ <div id="target"></div> >+</div> >+<div class="spacer"></div> >+ >+<script> >+var vw = document.documentElement.clientWidth; >+var vh = document.documentElement.clientHeight; >+ >+var entries = []; >+var root, target; >+ >+runTestCycle(function() { >+ target = document.getElementById("target"); >+ assert_true(!!target, "target exists"); >+ root = document.getElementById("root"); >+ assert_true(!!root, "root exists"); >+ var observer = new IntersectionObserver(function(changes) { >+ entries = entries.concat(changes) >+ }, { root: root }); >+ observer.observe(target); >+ entries = entries.concat(observer.takeRecords()); >+ assert_equals(entries.length, 0, "No initial notifications."); >+ runTestCycle(step0, "First rAF"); >+}, "IntersectionObserver in a single document with explicit root."); >+ >+function step0() { >+ document.scrollingElement.scrollTop = vh; >+ runTestCycle(step1, "document.scrollingElement.scrollTop = window.innerHeight."); >+ checkLastEntry(entries, 0, [ 11, 111, vh + 411, vh + 511, 0, 0, 0, 0, 11, 111, vh + 111, vh + 311, false]); >+} >+ >+function step1() { >+ root.scrollTop = 150; >+ runTestCycle(step2, "root.scrollTop = 150 with root scrolled into view."); >+ assert_equals(entries.length, 1, "No notifications after scrolling frame."); >+} >+ >+function step2() { >+ document.scrollingElement.scrollTop = 0; >+ runTestCycle(step3, "document.scrollingElement.scrollTop = 0."); >+ checkLastEntry(entries, 1, [11, 111, 261, 361, 11, 111, 261, 311, 11, 111, 111, 311, true]); >+} >+ >+function step3() { >+ root.scrollTop = 0; >+ runTestCycle(step4, "root.scrollTop = 0"); >+ checkLastEntry(entries, 1); >+} >+ >+function step4() { >+ root.scrollTop = 150; >+ runTestCycle(step5, "root.scrollTop = 150 with root scrolled out of view."); >+ checkLastEntry(entries, 2, [11, 111, vh + 411, vh + 511, 0, 0, 0, 0, 11, 111, vh + 111, vh + 311, false]); >+} >+ >+// This tests that notifications are generated even when the root element is off screen. >+function step5() { >+ checkLastEntry(entries, 3, [11, 111, vh + 261, vh + 361, 11, 111, vh + 261, vh + 311, 11, 111, vh + 111, vh + 311, true]); >+} >+</script> >diff --git a/LayoutTests/imported/w3c/web-platform-tests/intersection-observer/same-document-zero-size-target-expected.txt b/LayoutTests/imported/w3c/web-platform-tests/intersection-observer/same-document-zero-size-target-expected.txt >new file mode 100644 >index 0000000000000000000000000000000000000000..802e8e47fc9680fabfb14aeb38e552579d16401a >--- /dev/null >+++ b/LayoutTests/imported/w3c/web-platform-tests/intersection-observer/same-document-zero-size-target-expected.txt >@@ -0,0 +1,6 @@ >+ >+PASS Observing a zero-area target. >+PASS First rAF >+PASS document.scrollingElement.scrollTop = 300 >+PASS document.scrollingElement.scrollTop = 100 >+ >diff --git a/LayoutTests/imported/w3c/web-platform-tests/intersection-observer/same-document-zero-size-target.html b/LayoutTests/imported/w3c/web-platform-tests/intersection-observer/same-document-zero-size-target.html >new file mode 100644 >index 0000000000000000000000000000000000000000..d835b40634d33d0c7dd912ea81d4ef6d42af7c1b >--- /dev/null >+++ b/LayoutTests/imported/w3c/web-platform-tests/intersection-observer/same-document-zero-size-target.html >@@ -0,0 +1,61 @@ >+<!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); >+} >+#target { >+ width: 0px; >+ height: 0px; >+ background-color: green; >+} >+</style> >+ >+<div class="spacer"></div> >+<div id="target"></div> >+<div class="spacer"></div> >+ >+<script> >+var vw = document.documentElement.clientWidth; >+var vh = document.documentElement.clientHeight; >+ >+var entries = []; >+var target; >+ >+runTestCycle(function() { >+ target = document.getElementById("target"); >+ assert_true(!!target, "Target 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"); >+}, "Observing a zero-area target."); >+ >+function step0() { >+ document.scrollingElement.scrollTop = 300; >+ runTestCycle(step1, "document.scrollingElement.scrollTop = 300"); >+ checkLastEntry(entries, 0, [8, 8, vh + 108, vh + 108, 0, 0, 0, 0, 0, vw, 0, vh, false]); >+} >+ >+function step1() { >+ document.scrollingElement.scrollTop = 100; >+ runTestCycle(step2, "document.scrollingElement.scrollTop = 100"); >+ checkLastEntry(entries, 1, [8, 8, vh - 192, vh - 192, 8, 8, vh - 192, vh - 192, 0, vw, 0, vh, true]); >+} >+ >+function step2() { >+ document.scrollingElement.scrollTop = 0; >+ checkLastEntry(entries, 2, [8, 8, vh + 8, vh + 8, 0, 0, 0, 0, 0, vw, 0, vh, false]); >+} >+</script> >diff --git a/LayoutTests/imported/w3c/web-platform-tests/intersection-observer/shadow-content-expected.txt b/LayoutTests/imported/w3c/web-platform-tests/intersection-observer/shadow-content-expected.txt >new file mode 100644 >index 0000000000000000000000000000000000000000..8da2c487e51903e2cb7fa287878d98bcf2725811 >--- /dev/null >+++ b/LayoutTests/imported/w3c/web-platform-tests/intersection-observer/shadow-content-expected.txt >@@ -0,0 +1,4 @@ >+ >+PASS Observing a target inside shadow DOM. >+PASS First rAF after creating shadow DOM. >+ >diff --git a/LayoutTests/imported/w3c/web-platform-tests/intersection-observer/shadow-content.html b/LayoutTests/imported/w3c/web-platform-tests/intersection-observer/shadow-content.html >new file mode 100644 >index 0000000000000000000000000000000000000000..d049c70919e129fa6c335e8601f6ce6a6cd73a9d >--- /dev/null >+++ b/LayoutTests/imported/w3c/web-platform-tests/intersection-observer/shadow-content.html >@@ -0,0 +1,44 @@ >+<!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; >+} >+</style> >+ >+<div id="host"></div> >+ >+<script> >+var vw = document.documentElement.clientWidth; >+var vh = document.documentElement.clientHeight; >+ >+var entries = []; >+var target; >+ >+runTestCycle(function() { >+ var shadowHost = document.getElementById("host"); >+ assert_true(!!shadowHost, "Host exists"); >+ var shadowRoot = shadowHost.attachShadow({ mode: "open" }); >+ assert_true(!!shadowRoot, "Shadow root exists"); >+ shadowRoot.innerHTML = "<div id='target' style='width: 100px; height: 100px; background-color: green;'></div>"; >+ target = shadowRoot.getElementById("target"); >+ assert_true(!!target, "target 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 after creating shadow DOM."); >+}, "Observing a target inside shadow DOM."); >+ >+function step0() { >+ checkLastEntry(entries, 0, [8, 108, 8, 108, 8, 108, 8, 108, 0, vw, 0, vh, true]); >+} >+</script> >diff --git a/LayoutTests/imported/w3c/web-platform-tests/intersection-observer/text-target-expected.txt b/LayoutTests/imported/w3c/web-platform-tests/intersection-observer/text-target-expected.txt >new file mode 100644 >index 0000000000000000000000000000000000000000..2c088282440c744f344bc3a3bcb97fb9d2e9858c >--- /dev/null >+++ b/LayoutTests/imported/w3c/web-platform-tests/intersection-observer/text-target-expected.txt >@@ -0,0 +1,7 @@ >+ >+ >+PASS IntersectionObserver observing a br element. >+PASS First rAF. >+PASS document.scrollingElement.scrollTop = 300 >+PASS document.scrollingElement.scrollTop = 100 >+ >diff --git a/LayoutTests/imported/w3c/web-platform-tests/intersection-observer/text-target.html b/LayoutTests/imported/w3c/web-platform-tests/intersection-observer/text-target.html >new file mode 100644 >index 0000000000000000000000000000000000000000..13dc3abea2d8b0438ac76e9da974718778bcbb12 >--- /dev/null >+++ b/LayoutTests/imported/w3c/web-platform-tests/intersection-observer/text-target.html >@@ -0,0 +1,62 @@ >+<!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); >+} >+</style> >+ >+<div class="spacer"></div> >+<br id="target"> >+<div class="spacer"></div> >+ >+<script> >+var vw = document.documentElement.clientWidth; >+var vh = document.documentElement.clientHeight; >+ >+var entries = []; >+var target; >+var tw, th; >+ >+runTestCycle(function() { >+ target = document.getElementById("target"); >+ let target_rect = target.getBoundingClientRect(); >+ tw = target_rect.width; >+ th = target_rect.height; >+ assert_true(!!target, "target 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."); >+}, "IntersectionObserver observing a br element."); >+ >+function step0() { >+ document.scrollingElement.scrollTop = 300; >+ runTestCycle(step1, "document.scrollingElement.scrollTop = 300"); >+ // The numbers in brackets are target client rect; intersection rect; >+ // and root bounds. >+ checkLastEntry(entries, 0, [8, 8 + tw, vh + 108, vh + 108 + th, 0, 0, 0, 0, 0, vw, 0, vh, false]); >+} >+ >+function step1() { >+ document.scrollingElement.scrollTop = 100; >+ runTestCycle(step2, "document.scrollingElement.scrollTop = 100"); >+ checkLastEntry(entries, 1, [8, 8 + tw, vh - 192, vh - 192 + th, 8, 8 + tw, vh - 192, vh - 192 + th, 0, vw, 0, vh, true]); >+} >+ >+function step2() { >+ document.scrollingElement.scrollTop = 0; >+ checkLastEntry(entries, 2, [8, 8 + tw, vh + 8, vh + 8 + th, 0, 0, 0, 0, 0, vw, 0, vh, false]); >+} >+</script> >diff --git a/LayoutTests/imported/w3c/web-platform-tests/intersection-observer/timestamp-expected.txt b/LayoutTests/imported/w3c/web-platform-tests/intersection-observer/timestamp-expected.txt >new file mode 100644 >index 0000000000000000000000000000000000000000..3fa50aa1e0911dbc0a36cb07df68dde47f309370 >--- /dev/null >+++ b/LayoutTests/imported/w3c/web-platform-tests/intersection-observer/timestamp-expected.txt >@@ -0,0 +1,6 @@ >+ >+ >+PASS Check that timestamps correspond to the to execution context that created the observer. >+PASS First rAF after iframe is loaded. >+PASS Generate notifications. >+ >diff --git a/LayoutTests/imported/w3c/web-platform-tests/intersection-observer/timestamp.html b/LayoutTests/imported/w3c/web-platform-tests/intersection-observer/timestamp.html >new file mode 100644 >index 0000000000000000000000000000000000000000..be1980040d298b4d6d0055976ed42c202dec9888 >--- /dev/null >+++ b/LayoutTests/imported/w3c/web-platform-tests/intersection-observer/timestamp.html >@@ -0,0 +1,99 @@ >+<!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); >+} >+ >+</style> >+<div id="leading-space" class="spacer"></div> >+<div id="trailing-space" class="spacer"></div> >+ >+<script> >+// Pick this number to be comfortably greater than the length of two frames at 60Hz. >+var timeSkew = 40; >+ >+var topWindowEntries = []; >+var iframeWindowEntries = []; >+var targetIframe; >+var topWindowTimeBeforeNotification; >+var iframeWindowTimeBeforeNotification; >+ >+async_test(function(t) { >+ t.step_timeout(function() { >+ targetIframe = document.createElement("iframe"); >+ assert_true(!!targetIframe, "iframe exists"); >+ targetIframe.src = "resources/timestamp-subframe.html"; >+ var trailingSpace = document.getElementById("trailing-space"); >+ assert_true(!!trailingSpace, "trailing-space exists"); >+ trailingSpace.parentNode.insertBefore(targetIframe, trailingSpace); >+ targetIframe.onload = function() { >+ var target = targetIframe.contentDocument.getElementById("target"); >+ var iframeScroller = targetIframe.contentDocument.scrollingElement; >+ >+ // Observer created here, callback created in iframe context. Timestamps should be >+ // from this window. >+ var observer = new IntersectionObserver( >+ targetIframe.contentDocument.createObserverCallback(topWindowEntries), {}); >+ assert_true(!!observer, "Observer exists"); >+ observer.observe(target); >+ >+ // Callback created here, observer created in iframe. Timestamps should be >+ // from iframe window. >+ observer = targetIframe.contentDocument.createObserver(function(newEntries) { >+ iframeWindowEntries = iframeWindowEntries.concat(newEntries); >+ }); >+ observer.observe(target); >+ runTestCycle(step1, "First rAF after iframe is loaded."); >+ t.done(); >+ }; >+ }, timeSkew); >+}, "Check that timestamps correspond to the to execution context that created the observer."); >+ >+function step1() { >+ document.scrollingElement.scrollTop = 200; >+ targetIframe.contentDocument.scrollingElement.scrollTop = 250; >+ topWindowTimeBeforeNotification = performance.now(); >+ iframeWindowTimeBeforeNotification = targetIframe.contentWindow.performance.now(); >+ runTestCycle(step2, "Generate notifications."); >+ assert_equals(topWindowEntries.length, 1, "One notification to top window observer."); >+ assert_equals(iframeWindowEntries.length, 1, "One notification to iframe observer."); >+} >+ >+function step2() { >+ document.scrollingElement.scrollTop = 0; >+ var topWindowTimeAfterNotification = performance.now(); >+ var iframeWindowTimeAfterNotification = targetIframe.contentWindow.performance.now(); >+ >+ assert_approx_equals( >+ topWindowEntries[1].time - topWindowTimeBeforeNotification, >+ iframeWindowEntries[1].time - iframeWindowTimeBeforeNotification, >+ // Since all intersections are computed in a tight loop between 2 frames, >+ // an epsilon of 16ms (the length of one frame at 60Hz) turned out to be >+ // reliable, even at slow frame rates. >+ 16, >+ "Notification times are relative to the expected time origins"); >+ >+ assert_equals(topWindowEntries.length, 2, "Top window observer has two notifications."); >+ assert_between_inclusive( >+ topWindowEntries[1].time, >+ topWindowTimeBeforeNotification, >+ topWindowTimeAfterNotification, >+ "Notification to top window observer is within the expected range."); >+ >+ assert_equals(iframeWindowEntries.length, 2, "Iframe observer has two notifications."); >+ assert_between_inclusive( >+ iframeWindowEntries[1].time, >+ iframeWindowTimeBeforeNotification, >+ iframeWindowTimeAfterNotification, >+ "Notification to iframe observer is within the expected range."); >+} >+</script> >diff --git a/LayoutTests/imported/w3c/web-platform-tests/intersection-observer/unclipped-root-expected.txt b/LayoutTests/imported/w3c/web-platform-tests/intersection-observer/unclipped-root-expected.txt >new file mode 100644 >index 0000000000000000000000000000000000000000..4729502374358176a7873cbb4a81686d02b5ed86 >--- /dev/null >+++ b/LayoutTests/imported/w3c/web-platform-tests/intersection-observer/unclipped-root-expected.txt >@@ -0,0 +1,5 @@ >+ >+PASS Test that border bounding box is used to calculate intersection with a non-scrolling root. >+PASS First rAF. >+PASS target.style.transform = 'translateY(195px)' >+ >diff --git a/LayoutTests/imported/w3c/web-platform-tests/intersection-observer/unclipped-root.html b/LayoutTests/imported/w3c/web-platform-tests/intersection-observer/unclipped-root.html >new file mode 100644 >index 0000000000000000000000000000000000000000..24ae01cedc2ef95f6dd04a722d47aefa30e8b777 >--- /dev/null >+++ b/LayoutTests/imported/w3c/web-platform-tests/intersection-observer/unclipped-root.html >@@ -0,0 +1,57 @@ >+<!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; >+} >+#root { >+ overflow: visible; >+ height: 200px; >+ width: 160px; >+ border: 7px solid black; >+} >+#target { >+ width: 100px; >+ height: 100px; >+ background-color: green; >+} >+</style> >+ >+<div id="root"> >+ <div id="target" style="transform: translateY(300px)"></div> >+</div> >+ >+<script> >+var entries = []; >+var target; >+ >+runTestCycle(function() { >+ target = document.getElementById("target"); >+ assert_true(!!target, "target exists"); >+ var root = document.getElementById("root"); >+ assert_true(!!root, "root exists"); >+ var observer = new IntersectionObserver(function(changes) { >+ entries = entries.concat(changes) >+ }, {root: root}); >+ observer.observe(target); >+ entries = entries.concat(observer.takeRecords()); >+ assert_equals(entries.length, 0, "No initial notifications."); >+ runTestCycle(step0, "First rAF."); >+}, "Test that border bounding box is used to calculate intersection with a non-scrolling root."); >+ >+function step0() { >+ target.style.transform = "translateY(195px)"; >+ runTestCycle(step1, "target.style.transform = 'translateY(195px)'"); >+ checkLastEntry(entries, 0, [15, 115, 315, 415, 0, 0, 0, 0, 8, 182, 8, 222, false]); >+} >+ >+function step1() { >+ target.style.transform = ""; >+ checkLastEntry(entries, 1, [15, 115, 210, 310, 15, 115, 210, 222, 8, 182, 8, 222, true]); >+} >+</script> >diff --git a/LayoutTests/imported/w3c/web-platform-tests/intersection-observer/zero-area-element-hidden-expected.txt b/LayoutTests/imported/w3c/web-platform-tests/intersection-observer/zero-area-element-hidden-expected.txt >new file mode 100644 >index 0000000000000000000000000000000000000000..29fd5f25a5b18570d857f14bf1b674a92e259702 >--- /dev/null >+++ b/LayoutTests/imported/w3c/web-platform-tests/intersection-observer/zero-area-element-hidden-expected.txt >@@ -0,0 +1,4 @@ >+ >+PASS A zero-area hidden target should not be intersecting. >+PASS First rAF. >+ >diff --git a/LayoutTests/imported/w3c/web-platform-tests/intersection-observer/zero-area-element-hidden.html b/LayoutTests/imported/w3c/web-platform-tests/intersection-observer/zero-area-element-hidden.html >new file mode 100644 >index 0000000000000000000000000000000000000000..e007040c8fe2e25bfca8fbe450b06a26934ad186 >--- /dev/null >+++ b/LayoutTests/imported/w3c/web-platform-tests/intersection-observer/zero-area-element-hidden.html >@@ -0,0 +1,43 @@ >+<!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; >+} >+#target { >+ width: 0px; >+ height: 0px; >+ position: fixed; >+ top: -1000px; >+} >+</style> >+ >+<div id='target'></div> >+ >+<script> >+var vw = document.documentElement.clientWidth; >+var vh = document.documentElement.clientHeight; >+ >+var entries = []; >+ >+runTestCycle(function() { >+ var target = document.getElementById('target'); >+ assert_true(!!target, "target 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."); >+}, "A zero-area hidden target should not be intersecting."); >+ >+function step0() { >+ checkLastEntry(entries, 0, [8, 8, -1000, -1000, 0, 0, 0, 0, 0, vw, 0, vh, false]); >+} >+</script> >diff --git a/LayoutTests/imported/w3c/web-platform-tests/intersection-observer/zero-area-element-visible-expected.txt b/LayoutTests/imported/w3c/web-platform-tests/intersection-observer/zero-area-element-visible-expected.txt >new file mode 100644 >index 0000000000000000000000000000000000000000..042b881e11cca769c6360585df57e28c838279ef >--- /dev/null >+++ b/LayoutTests/imported/w3c/web-platform-tests/intersection-observer/zero-area-element-visible-expected.txt >@@ -0,0 +1,4 @@ >+ >+PASS Ensure that a zero-area target intersecting root generates a notification with intersectionRatio == 1 >+PASS First rAF should generate a notification. >+ >diff --git a/LayoutTests/imported/w3c/web-platform-tests/intersection-observer/zero-area-element-visible.html b/LayoutTests/imported/w3c/web-platform-tests/intersection-observer/zero-area-element-visible.html >new file mode 100644 >index 0000000000000000000000000000000000000000..6bf1297fe453dce1d748d047f54ec427289d8df4 >--- /dev/null >+++ b/LayoutTests/imported/w3c/web-platform-tests/intersection-observer/zero-area-element-visible.html >@@ -0,0 +1,39 @@ >+<!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; >+} >+#target { >+ width: 0px; >+ height: 0px; >+} >+</style> >+ >+<div id='target'></div> >+ >+<script> >+var entries = []; >+ >+runTestCycle(function() { >+ var target = document.getElementById('target'); >+ assert_true(!!target, "target 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 should generate a notification."); >+}, "Ensure that a zero-area target intersecting root generates a notification with intersectionRatio == 1"); >+ >+function step0() { >+ assert_equals(entries.length, 1, "One notification."); >+ assert_equals(entries[0].intersectionRatio, 1, "intersectionRatio == 1"); >+} >+</script> >diff --git a/LayoutTests/intersection-observer/intersection-observer-entry-interface-expected.txt b/LayoutTests/intersection-observer/intersection-observer-entry-interface-expected.txt >index 25d93e2416c0eaae9a32252d2ba40196ea6dc99b..1c89becf61ee2ccc989eb7ec37504d0164327891 100644 >--- a/LayoutTests/intersection-observer/intersection-observer-entry-interface-expected.txt >+++ b/LayoutTests/intersection-observer/intersection-observer-entry-interface-expected.txt >@@ -4,5 +4,7 @@ PASS ConstructorTime > PASS ConstructorRootBounds > PASS ConstructorBoundingClientRect > PASS ConstructorIntersectionRect >-PASS ConstructorTime >+PASS ConstructorIsIntersecting >+PASS ConstructorIntersectionRatio >+PASS ConstructorTarget > >diff --git a/LayoutTests/intersection-observer/intersection-observer-entry-interface.html b/LayoutTests/intersection-observer/intersection-observer-entry-interface.html >index ac815a575bdae4d626eedbdb2d1fb9a30e093823..f6ff3c8e5d3377e315fb88877aeb3941ce3a3914 100644 >--- a/LayoutTests/intersection-observer/intersection-observer-entry-interface.html >+++ b/LayoutTests/intersection-observer/intersection-observer-entry-interface.html >@@ -7,7 +7,6 @@ > <script src="../resources/testharnessreport.js"></script> > </head> > <body> >-<div id="log"></div> > <script> > > var entryInit = { >@@ -15,6 +14,8 @@ > rootBounds: { x: 10, y: 12.5, width: 130, height: 140 }, > boundingClientRect: { x: 110, y: 112.7, width: 1130, height: 1140 }, > intersectionRect: { x: 210, y: 212, width: 2130, height: 2140 }, >+ isIntersecting: true, >+ intersectionRatio: 0.35, > target: document.body > }; > >@@ -40,10 +41,18 @@ > assert_class_string(entry.intersectionRect, 'DOMRectReadOnly'); > assert_equals(JSON.stringify(entry.intersectionRect), '{"x":210,"y":212,"width":2130,"height":2140,"top":212,"right":2340,"bottom":2352,"left":210}'); > },'ConstructorIntersectionRect'); >+ test(function() { >+ var entry = new IntersectionObserverEntry(entryInit); >+ assert_equals(entry.isIntersecting, true); >+ },'ConstructorIsIntersecting'); >+ test(function() { >+ var entry = new IntersectionObserverEntry(entryInit); >+ assert_equals(entry.intersectionRatio, 0.35); >+ },'ConstructorIntersectionRatio'); > test(function() { > var entry = new IntersectionObserverEntry(entryInit); > assert_equals(entry.target, document.body); >- },'ConstructorTime'); >+ },'ConstructorTarget'); > > </script> > </body> >diff --git a/LayoutTests/intersection-observer/intersection-observer-interface.html b/LayoutTests/intersection-observer/intersection-observer-interface.html >index 4a299060655677c00976c0e78b517e0ab41b471b..1b938c8e2454328e493447b40b8ee995b80823fb 100644 >--- a/LayoutTests/intersection-observer/intersection-observer-interface.html >+++ b/LayoutTests/intersection-observer/intersection-observer-interface.html >@@ -7,7 +7,6 @@ > <script src="../resources/testharnessreport.js"></script> > </head> > <body> >-<div id="log"></div> > <script> > > test(function() { >@@ -15,7 +14,7 @@ > },'Constructor0'); > test(function() { > var observer = new IntersectionObserver(function() {}); >- assert_equals(observer.rootMargin, '0px'); >+ assert_equals(observer.rootMargin, '0px 0px 0px 0px'); > },'DefaultRootMargin'); > test(function() { > var observer = new IntersectionObserver(function() {}); >@@ -26,8 +25,8 @@ > assert_array_equals(observer.thresholds, [0]); > },'DefaultThresholds'); > test(function() { >- var observer = new IntersectionObserver(function() {}, { rootMargin: '33em 10px -120px 3pt' }); >- assert_equals(observer.rootMargin, '33em 10px -120px 3pt'); >+ var observer = new IntersectionObserver(function() {}, { rootMargin: '33% 10px -120px 3%' }); >+ assert_equals(observer.rootMargin, '33% 10px -120px 3%'); > },'ExplicitRootMargin'); > test(function() { > var observer = new IntersectionObserver(function() {}, { root: document.body }); >diff --git a/LayoutTests/intersection-observer/root-element-deleted-expected.txt b/LayoutTests/intersection-observer/root-element-deleted-expected.txt >new file mode 100644 >index 0000000000000000000000000000000000000000..7df05d6abca7a6ba1f759bd91f913e1f7b768bb8 >--- /dev/null >+++ b/LayoutTests/intersection-observer/root-element-deleted-expected.txt >@@ -0,0 +1,3 @@ >+ >+PASS IntersectionObserver doesn't keep unreachable root alive >+ >diff --git a/LayoutTests/intersection-observer/root-element-deleted.html b/LayoutTests/intersection-observer/root-element-deleted.html >new file mode 100644 >index 0000000000000000000000000000000000000000..652ecd982ea462647bcd85fc15255b9232e5bcbf >--- /dev/null >+++ b/LayoutTests/intersection-observer/root-element-deleted.html >@@ -0,0 +1,28 @@ >+<!DOCTYPE html> >+<head> >+<script src="../resources/testharness.js"></script> >+<script src="../resources/testharnessreport.js"></script> >+<body> >+ >+<div id="root" style="position:absolute"> >+ <div id="target" style="width: 100px; height: 100px; background-color: green"></div> >+</div> >+ >+<script> >+ test(function() { >+ var root = document.getElementById('root'); >+ var observer = new IntersectionObserver(function() {}, { root: document.getElementById('root') });; >+ var target = document.getElementById('target'); >+ assert_equals(observer.root, root); >+ observer.observe(target); >+ assert_equals(internals.numberOfActiveIntersectionObservers(document), 1); >+ root.parentNode.removeChild(root); >+ root = null; >+ target = null; >+ GCController.collect(); >+ assert_equals(observer.root, null); >+ assert_equals(internals.numberOfActiveIntersectionObservers(document), 0); >+ }, "IntersectionObserver doesn't keep unreachable root alive"); >+</script> >+</body> >+</html> >diff --git a/LayoutTests/intersection-observer/root-element-moved-expected.txt b/LayoutTests/intersection-observer/root-element-moved-expected.txt >new file mode 100644 >index 0000000000000000000000000000000000000000..6552be4143b4acb992be76f540315d6652dc40b0 >--- /dev/null >+++ b/LayoutTests/intersection-observer/root-element-moved-expected.txt >@@ -0,0 +1,3 @@ >+ >+PASS Document's active intersection observers get updated >+ >diff --git a/LayoutTests/intersection-observer/root-element-moved.html b/LayoutTests/intersection-observer/root-element-moved.html >new file mode 100644 >index 0000000000000000000000000000000000000000..3ba877163a585c71a9e8738767ca8dff6526b239 >--- /dev/null >+++ b/LayoutTests/intersection-observer/root-element-moved.html >@@ -0,0 +1,31 @@ >+<!DOCTYPE html> >+<head> >+<script src="../resources/testharness.js"></script> >+<script src="../resources/testharnessreport.js"></script> >+<body> >+ >+<div id="root" style="position:absolute"> >+ <div id="target" style="width: 100px; height: 100px; background-color: green"></div> >+</div> >+ >+<script> >+ var observer; >+ var root = document.getElementById('root'); >+ var target = document.getElementById('target'); >+ test(function() { >+ observer = new IntersectionObserver(function() {}, { root: document.getElementById('root') });; >+ assert_equals(observer.root, root); >+ observer.observe(target); >+ assert_equals(internals.numberOfActiveIntersectionObservers(document), 1); >+ document.body.appendChild(target); >+ var newDoc = document.implementation.createHTMLDocument('New document'); >+ newDoc.body.appendChild(root); >+ assert_equals(internals.numberOfActiveIntersectionObservers(document), 0); >+ assert_equals(internals.numberOfActiveIntersectionObservers(newDoc), 1); >+ document.body.appendChild(root); >+ assert_equals(internals.numberOfActiveIntersectionObservers(document), 1); >+ assert_equals(internals.numberOfActiveIntersectionObservers(newDoc), 0); >+ },"Document's active intersection observers get updated"); >+</script> >+</body> >+</html> >diff --git a/LayoutTests/platform/mac-wk1/TestExpectations b/LayoutTests/platform/mac-wk1/TestExpectations >index 83b18b67e5df1c260b29dac51d351f2ee6c49e78..6b0abe0c2799d08fd0cc130ebc1c3e9e5c88b177 100644 >--- a/LayoutTests/platform/mac-wk1/TestExpectations >+++ b/LayoutTests/platform/mac-wk1/TestExpectations >@@ -547,6 +547,9 @@ http/wpt/credential-management/ [ Skip ] > http/wpt/webauthn/ [ Skip ] > imported/w3c/web-platform-tests/credential-management/ [ Skip ] > >+# IntersectionObserver isn't supported on WK1. >+imported/w3c/web-platform-tests/intersection-observer [ Skip ] >+ > webkit.org/b/182554 transitions/transition-display-property.html [ Pass ImageOnlyFailure ] > webkit.org/b/182554 legacy-animation-engine/transitions/transition-display-property.html [ Pass ImageOnlyFailure ] >
You cannot view the attachment while viewing its details because your browser does not support IFRAMEs.
View the attachment on a separate page
.
View Attachment As Diff
View Attachment As Raw
Actions:
View
|
Formatted Diff
|
Diff
Attachments on
bug 159475
:
293262
|
293271
|
293278
|
333008
|
344885
|
344897
|
344900
|
344903
|
344904
|
344913
|
345769
|
345776
|
345779
|
345780
|
346780
|
346789
|
351917
|
351931