Diff
Modified: branches/safari-612-branch/LayoutTests/ChangeLog (284801 => 284802)
--- branches/safari-612-branch/LayoutTests/ChangeLog 2021-10-25 19:09:31 UTC (rev 284801)
+++ branches/safari-612-branch/LayoutTests/ChangeLog 2021-10-25 19:09:38 UTC (rev 284802)
@@ -1,5 +1,71 @@
2021-10-25 Null <[email protected]>
+ Cherry-pick r283590. rdar://problem/80837616
+
+ ASSERT(m_callback->hasCallback()) under IntersectionObserver::notify()
+ https://bugs.webkit.org/show_bug.cgi?id=231235
+ <rdar://80837616>
+
+ Reviewed by Ryosuke Niwa.
+
+ Source/WebCore:
+
+ IntersectionObserver's JS callback stays alive as long as its JS wrapper and
+ its JS wrapper's lifetime relies on the IntersectionObserver::isReachableFromOpaqueRoots()
+ implementation. isReachableFromOpaqueRoots() keeps the wrapper alive as long
+ as the JS wrappers of observation / pending targets are alive. However, as per specification,
+ we always need to dispatch an observation for an observation target, even if that target
+ is not connected. Our code was already taking care of dispatching such observation. However,
+ there was nothing keeping the observation target alive in this case and thus nothing keeping
+ the JS callback alive either.
+
+ To address the issue, I am introducing a new m_targetsWaitingForFirstObservation data member
+ which holds a strong ref to the observation target until the next time we call notify().
+ This makes sure that the observation target (and its JS wrapper) stays alive long enough for
+ us to dispatch the first observation for it. I also updated isReachableFromOpaqueRoots() to
+ return true as long as m_targetsWaitingForFirstObservation is non-empty so that the
+ IntersectionObserver's JS wrapper (and thus the JS callback) stay alive long enough too.
+
+ Tests: intersection-observer/observe-disconnected-target-crash.html
+ intersection-observer/observe-disconnected-target.html
+
+ * page/IntersectionObserver.cpp:
+ (WebCore::IntersectionObserver::observe):
+ (WebCore::IntersectionObserver::unobserve):
+ (WebCore::IntersectionObserver::removeAllTargets):
+ (WebCore::IntersectionObserver::notify):
+ (WebCore::IntersectionObserver::isReachableFromOpaqueRoots const):
+ * page/IntersectionObserver.h:
+
+ LayoutTests:
+
+ Add layout test coverage both for the crash and the Web facing behavior.
+
+ * intersection-observer/observe-disconnected-target-crash-expected.txt: Added.
+ * intersection-observer/observe-disconnected-target-crash.html: Added.
+ * intersection-observer/observe-disconnected-target-expected.txt: Added.
+ * intersection-observer/observe-disconnected-target.html: Added.
+
+
+ git-svn-id: https://svn.webkit.org/repository/webkit/trunk@283590 268f45cc-cd09-0410-ab3c-d52691b4dbfc
+
+ 2021-10-05 Chris Dumez <[email protected]>
+
+ ASSERT(m_callback->hasCallback()) under IntersectionObserver::notify()
+ https://bugs.webkit.org/show_bug.cgi?id=231235
+ <rdar://80837616>
+
+ Reviewed by Ryosuke Niwa.
+
+ Add layout test coverage both for the crash and the Web facing behavior.
+
+ * intersection-observer/observe-disconnected-target-crash-expected.txt: Added.
+ * intersection-observer/observe-disconnected-target-crash.html: Added.
+ * intersection-observer/observe-disconnected-target-expected.txt: Added.
+ * intersection-observer/observe-disconnected-target.html: Added.
+
+2021-10-25 Null <[email protected]>
+
Cherry-pick r282984. rdar://problem/77587429
Web Inspector: Graphics: add instrumentation for new `CanvasRenderingContext2DSettings`
Added: branches/safari-612-branch/LayoutTests/intersection-observer/observe-disconnected-target-crash-expected.txt (0 => 284802)
--- branches/safari-612-branch/LayoutTests/intersection-observer/observe-disconnected-target-crash-expected.txt (rev 0)
+++ branches/safari-612-branch/LayoutTests/intersection-observer/observe-disconnected-target-crash-expected.txt 2021-10-25 19:09:38 UTC (rev 284802)
@@ -0,0 +1 @@
+This test passes if it doesn't crash.
Added: branches/safari-612-branch/LayoutTests/intersection-observer/observe-disconnected-target-crash.html (0 => 284802)
--- branches/safari-612-branch/LayoutTests/intersection-observer/observe-disconnected-target-crash.html (rev 0)
+++ branches/safari-612-branch/LayoutTests/intersection-observer/observe-disconnected-target-crash.html 2021-10-25 19:09:38 UTC (rev 284802)
@@ -0,0 +1,20 @@
+<p>This test passes if it doesn't crash.</p>
+<script>
+ if (window.testRunner) {
+ testRunner.dumpAsText();
+ testRunner.waitUntilDone();
+ }
+ _onload_ = async () => {
+ for (let i = 0; i < 8; i++) {
+ document.createElement('div');
+ }
+ for (let i = 0; i < 8; i++) {
+ new IntersectionObserver(() => {});
+ }
+ let intersectionObserver = new IntersectionObserver(() => {});
+ await intersectionObserver.observe(document.createElement('div'));
+ new ImageData(1000, 10000);
+ if (window.testRunner)
+ testRunner.notifyDone();
+ };
+</script>
Added: branches/safari-612-branch/LayoutTests/intersection-observer/observe-disconnected-target-expected.txt (0 => 284802)
--- branches/safari-612-branch/LayoutTests/intersection-observer/observe-disconnected-target-expected.txt (rev 0)
+++ branches/safari-612-branch/LayoutTests/intersection-observer/observe-disconnected-target-expected.txt 2021-10-25 19:09:38 UTC (rev 284802)
@@ -0,0 +1,15 @@
+Tests that an observation is received when calling IntersectionObserver.observe() with a disconnected target.
+
+On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
+
+
+PASS receivedObservations is false
+PASS observations.length is 1
+PASS observations[0].target.tagName is "DIV"
+PASS observations[0].isIntersecting is false
+PASS observations[0].intersectionRatio is 0.0
+PASS observations[0].target.foo is 1
+PASS successfullyParsed is true
+
+TEST COMPLETE
+
Added: branches/safari-612-branch/LayoutTests/intersection-observer/observe-disconnected-target.html (0 => 284802)
--- branches/safari-612-branch/LayoutTests/intersection-observer/observe-disconnected-target.html (rev 0)
+++ branches/safari-612-branch/LayoutTests/intersection-observer/observe-disconnected-target.html 2021-10-25 19:09:38 UTC (rev 284802)
@@ -0,0 +1,34 @@
+<!DOCTYPE html>
+<html>
+<body>
+<script src=""
+<script>
+ description("Tests that an observation is received when calling IntersectionObserver.observe() with a disconnected target.");
+ jsTestIsAsync = true;
+
+ let receivedObservations = false;
+ _onload_ = () => {
+ let intersectionObserver = new IntersectionObserver((_observations) => {
+ gc();
+ observations = _observations;
+
+ shouldBeFalse("receivedObservations");
+ receivedObservations = true;
+
+ shouldBe("observations.length", "1");
+ shouldBeEqualToString("observations[0].target.tagName", "DIV");
+ shouldBeFalse("observations[0].isIntersecting");
+ shouldBe("observations[0].intersectionRatio", "0.0");
+ shouldBe("observations[0].target.foo", "1");
+ setTimeout(finishJSTest, 100);
+ });
+ let div = document.createElement('div');
+ div.foo = 1;
+ intersectionObserver.observe(div);
+ div = null;
+ gc();
+ setTimeout(gc, 0);
+ };
+</script>
+</body>
+</html>
Added: branches/safari-612-branch/LayoutTests/intersection-observer/observe-then-disconnect-target-expected.txt (0 => 284802)
--- branches/safari-612-branch/LayoutTests/intersection-observer/observe-then-disconnect-target-expected.txt (rev 0)
+++ branches/safari-612-branch/LayoutTests/intersection-observer/observe-then-disconnect-target-expected.txt 2021-10-25 19:09:38 UTC (rev 284802)
@@ -0,0 +1,18 @@
+Tests that an observation is received after disconnecting a visible target
+
+On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
+
+
+PASS observations.length is 1
+PASS observations[0].target.tagName is "DIV"
+PASS observations[0].target.foo is 1
+PASS observations[0].isIntersecting is true
+* Removing target from document
+PASS observations.length is 1
+PASS observations[0].target.tagName is "DIV"
+PASS observations[0].target.foo is 1
+PASS observations[0].isIntersecting is false
+PASS successfullyParsed is true
+
+TEST COMPLETE
+
Added: branches/safari-612-branch/LayoutTests/intersection-observer/observe-then-disconnect-target.html (0 => 284802)
--- branches/safari-612-branch/LayoutTests/intersection-observer/observe-then-disconnect-target.html (rev 0)
+++ branches/safari-612-branch/LayoutTests/intersection-observer/observe-then-disconnect-target.html 2021-10-25 19:09:38 UTC (rev 284802)
@@ -0,0 +1,43 @@
+<!DOCTYPE html>
+<html>
+<body>
+<script src=""
+<div id="testDiv">test</div>
+<script>
+ description("Tests that an observation is received after disconnecting a visible target");
+ jsTestIsAsync = true;
+
+ let receivedInitialObservation = false;
+ _onload_ = () => {
+ let intersectionObserver = new IntersectionObserver((_observations) => {
+ gc();
+ observations = _observations;
+
+ shouldBe("observations.length", "1");
+ shouldBeEqualToString("observations[0].target.tagName", "DIV");
+ shouldBe("observations[0].target.foo", "1");
+
+ if (!receivedInitialObservation) {
+ receivedInitialObservation = true;
+ shouldBeTrue("observations[0].isIntersecting");
+ setTimeout(() => {
+ debug("* Removing target from document");
+ document.getElementById("testDiv").remove();
+ gc();
+ setTimeout(gc, 0);
+ }, 0);
+ } else {
+ shouldBeFalse("observations[0].isIntersecting");
+ setTimeout(finishJSTest, 100);
+ }
+ });
+ let div = document.getElementById("testDiv");
+ div.foo = 1;
+ intersectionObserver.observe(div);
+ div = null;
+ gc();
+ setTimeout(gc, 0);
+ };
+</script>
+</body>
+</html>
Modified: branches/safari-612-branch/Source/WebCore/ChangeLog (284801 => 284802)
--- branches/safari-612-branch/Source/WebCore/ChangeLog 2021-10-25 19:09:31 UTC (rev 284801)
+++ branches/safari-612-branch/Source/WebCore/ChangeLog 2021-10-25 19:09:38 UTC (rev 284802)
@@ -1,5 +1,91 @@
2021-10-25 Null <[email protected]>
+ Cherry-pick r283590. rdar://problem/80837616
+
+ ASSERT(m_callback->hasCallback()) under IntersectionObserver::notify()
+ https://bugs.webkit.org/show_bug.cgi?id=231235
+ <rdar://80837616>
+
+ Reviewed by Ryosuke Niwa.
+
+ Source/WebCore:
+
+ IntersectionObserver's JS callback stays alive as long as its JS wrapper and
+ its JS wrapper's lifetime relies on the IntersectionObserver::isReachableFromOpaqueRoots()
+ implementation. isReachableFromOpaqueRoots() keeps the wrapper alive as long
+ as the JS wrappers of observation / pending targets are alive. However, as per specification,
+ we always need to dispatch an observation for an observation target, even if that target
+ is not connected. Our code was already taking care of dispatching such observation. However,
+ there was nothing keeping the observation target alive in this case and thus nothing keeping
+ the JS callback alive either.
+
+ To address the issue, I am introducing a new m_targetsWaitingForFirstObservation data member
+ which holds a strong ref to the observation target until the next time we call notify().
+ This makes sure that the observation target (and its JS wrapper) stays alive long enough for
+ us to dispatch the first observation for it. I also updated isReachableFromOpaqueRoots() to
+ return true as long as m_targetsWaitingForFirstObservation is non-empty so that the
+ IntersectionObserver's JS wrapper (and thus the JS callback) stay alive long enough too.
+
+ Tests: intersection-observer/observe-disconnected-target-crash.html
+ intersection-observer/observe-disconnected-target.html
+
+ * page/IntersectionObserver.cpp:
+ (WebCore::IntersectionObserver::observe):
+ (WebCore::IntersectionObserver::unobserve):
+ (WebCore::IntersectionObserver::removeAllTargets):
+ (WebCore::IntersectionObserver::notify):
+ (WebCore::IntersectionObserver::isReachableFromOpaqueRoots const):
+ * page/IntersectionObserver.h:
+
+ LayoutTests:
+
+ Add layout test coverage both for the crash and the Web facing behavior.
+
+ * intersection-observer/observe-disconnected-target-crash-expected.txt: Added.
+ * intersection-observer/observe-disconnected-target-crash.html: Added.
+ * intersection-observer/observe-disconnected-target-expected.txt: Added.
+ * intersection-observer/observe-disconnected-target.html: Added.
+
+
+ git-svn-id: https://svn.webkit.org/repository/webkit/trunk@283590 268f45cc-cd09-0410-ab3c-d52691b4dbfc
+
+ 2021-10-05 Chris Dumez <[email protected]>
+
+ ASSERT(m_callback->hasCallback()) under IntersectionObserver::notify()
+ https://bugs.webkit.org/show_bug.cgi?id=231235
+ <rdar://80837616>
+
+ Reviewed by Ryosuke Niwa.
+
+ IntersectionObserver's JS callback stays alive as long as its JS wrapper and
+ its JS wrapper's lifetime relies on the IntersectionObserver::isReachableFromOpaqueRoots()
+ implementation. isReachableFromOpaqueRoots() keeps the wrapper alive as long
+ as the JS wrappers of observation / pending targets are alive. However, as per specification,
+ we always need to dispatch an observation for an observation target, even if that target
+ is not connected. Our code was already taking care of dispatching such observation. However,
+ there was nothing keeping the observation target alive in this case and thus nothing keeping
+ the JS callback alive either.
+
+ To address the issue, I am introducing a new m_targetsWaitingForFirstObservation data member
+ which holds a strong ref to the observation target until the next time we call notify().
+ This makes sure that the observation target (and its JS wrapper) stays alive long enough for
+ us to dispatch the first observation for it. I also updated isReachableFromOpaqueRoots() to
+ return true as long as m_targetsWaitingForFirstObservation is non-empty so that the
+ IntersectionObserver's JS wrapper (and thus the JS callback) stay alive long enough too.
+
+ Tests: intersection-observer/observe-disconnected-target-crash.html
+ intersection-observer/observe-disconnected-target.html
+
+ * page/IntersectionObserver.cpp:
+ (WebCore::IntersectionObserver::observe):
+ (WebCore::IntersectionObserver::unobserve):
+ (WebCore::IntersectionObserver::removeAllTargets):
+ (WebCore::IntersectionObserver::notify):
+ (WebCore::IntersectionObserver::isReachableFromOpaqueRoots const):
+ * page/IntersectionObserver.h:
+
+2021-10-25 Null <[email protected]>
+
Cherry-pick r282984. rdar://problem/77587429
Web Inspector: Graphics: add instrumentation for new `CanvasRenderingContext2DSettings`
Modified: branches/safari-612-branch/Source/WebCore/page/IntersectionObserver.cpp (284801 => 284802)
--- branches/safari-612-branch/Source/WebCore/page/IntersectionObserver.cpp 2021-10-25 19:09:31 UTC (rev 284801)
+++ branches/safari-612-branch/Source/WebCore/page/IntersectionObserver.cpp 2021-10-25 19:09:38 UTC (rev 284802)
@@ -170,6 +170,11 @@
bool hadObservationTargets = hasObservationTargets();
m_observationTargets.append(makeWeakPtr(target));
+ // Per the specification, we should dispatch at least one observation for the target. For this reason, we make sure to keep the
+ // target alive until this first observation. This, in turn, will keep the IntersectionObserver's JS wrapper alive via
+ // isReachableFromOpaqueRoots(), so the callback stays alive.
+ m_targetsWaitingForFirstObservation.append(target);
+
auto* document = trackingDocument();
if (!hadObservationTargets)
document->addIntersectionObserver(*this);
@@ -183,6 +188,7 @@
bool removed = m_observationTargets.removeFirst(&target);
ASSERT_UNUSED(removed, removed);
+ m_targetsWaitingForFirstObservation.removeFirstMatching([&](auto& pendingTarget) { return pendingTarget.ptr() == ⌖ });
if (!hasObservationTargets()) {
if (auto* document = trackingDocument())
@@ -208,6 +214,7 @@
void IntersectionObserver::targetDestroyed(Element& target)
{
m_observationTargets.removeFirst(&target);
+ m_targetsWaitingForFirstObservation.removeFirstMatching([&](auto& pendingTarget) { return pendingTarget.ptr() == ⌖ });
if (!hasObservationTargets()) {
if (auto* document = trackingDocument())
document->removeIntersectionObserver(*this);
@@ -233,6 +240,7 @@
ASSERT_UNUSED(removed, removed);
}
m_observationTargets.clear();
+ m_targetsWaitingForFirstObservation.clear();
}
void IntersectionObserver::rootDestroyed()
@@ -274,6 +282,7 @@
}
auto takenRecords = takeRecords();
+ auto targetsWaitingForFirstObservation = std::exchange(m_targetsWaitingForFirstObservation, { });
// FIXME: The JSIntersectionObserver wrapper should be kept alive as long as the intersection observer can fire events.
ASSERT(m_callback->hasCallback());
@@ -299,7 +308,7 @@
if (visitor.containsOpaqueRoot(target->opaqueRoot()))
return true;
}
- return false;
+ return !m_targetsWaitingForFirstObservation.isEmpty();
}
} // namespace WebCore
Modified: branches/safari-612-branch/Source/WebCore/page/IntersectionObserver.h (284801 => 284802)
--- branches/safari-612-branch/Source/WebCore/page/IntersectionObserver.h 2021-10-25 19:09:31 UTC (rev 284801)
+++ branches/safari-612-branch/Source/WebCore/page/IntersectionObserver.h 2021-10-25 19:09:38 UTC (rev 284802)
@@ -122,6 +122,7 @@
Vector<WeakPtr<Element>> m_observationTargets;
Vector<GCReachableRef<Element>> m_pendingTargets;
Vector<Ref<IntersectionObserverEntry>> m_queuedEntries;
+ Vector<GCReachableRef<Element>> m_targetsWaitingForFirstObservation;
};