Diff
Modified: trunk/LayoutTests/ChangeLog (279799 => 279800)
--- trunk/LayoutTests/ChangeLog 2021-07-09 23:00:28 UTC (rev 279799)
+++ trunk/LayoutTests/ChangeLog 2021-07-10 01:03:03 UTC (rev 279800)
@@ -1,3 +1,23 @@
+2021-07-09 Ryosuke Niwa <rn...@webkit.org>
+
+ ResizeObserver / IntersectionObserver memory leak on detached & out of reference elements
+ https://bugs.webkit.org/show_bug.cgi?id=227194
+ <rdar://problem/79839851>
+
+ Reviewed by Chris Dumez.
+
+ Added regression tests for leaking nodes with IntersectionObserver and ResizeObsever.
+
+ Also rewrote intersection-observer/root-element-deleted.html since the test was previously asserting
+ that IntersectionObserver.root becomes null if it was no longer in the document, which is wrong.
+
+ * intersection-observer/intersection-observer-should-not-leak-observed-nodes-expected.txt: Added.
+ * intersection-observer/intersection-observer-should-not-leak-observed-nodes.html: Added.
+ * intersection-observer/root-element-deleted-expected.txt:
+ * intersection-observer/root-element-deleted.html:
+ * resize-observer/resize-observer-should-not-leak-observed-nodes-expected.txt: Added.
+ * resize-observer/resize-observer-should-not-leak-observed-nodes.html: Added.
+
2021-07-09 Ryan Haddad <ryanhad...@apple.com>
REGRESSION: [ macOS wk2 ] inspector/canvas/create-context-bitmaprenderer.html is flaky failing
Added: trunk/LayoutTests/intersection-observer/intersection-observer-should-not-leak-observed-nodes-expected.txt (0 => 279800)
--- trunk/LayoutTests/intersection-observer/intersection-observer-should-not-leak-observed-nodes-expected.txt (rev 0)
+++ trunk/LayoutTests/intersection-observer/intersection-observer-should-not-leak-observed-nodes-expected.txt 2021-07-10 01:03:03 UTC (rev 279800)
@@ -0,0 +1,4 @@
+This tests observing an element with an IntersectionObserver and removing the element from the document.
+The element should become eligible for GC at some point.
+
+PASS
Added: trunk/LayoutTests/intersection-observer/intersection-observer-should-not-leak-observed-nodes.html (0 => 279800)
--- trunk/LayoutTests/intersection-observer/intersection-observer-should-not-leak-observed-nodes.html (rev 0)
+++ trunk/LayoutTests/intersection-observer/intersection-observer-should-not-leak-observed-nodes.html 2021-07-10 01:03:03 UTC (rev 279800)
@@ -0,0 +1,51 @@
+<!DOCTYPE html><!-- webkit-test-runner [ IntersectionObserverEnabled=true ] -->
+<html>
+<body>
+<pre id="log">This tests observing an element with an IntersectionObserver and removing the element from the document.
+The element should become eligible for GC at some point.
+
+</pre>
+<script src=""
+<script>
+
+let initialNodeCount;
+function runTest()
+{
+ if (!window.testRunner || !window.internals) {
+ log.textContent += 'FAIL - This test requires internals';
+ return;
+ }
+ testRunner.dumpAsText();
+ testRunner.waitUntilDone();
+
+ initialNodeCount = internals.referencingNodeCount(document);
+
+ const intersectionObservers = [];
+ for (let i = 0; i < 100; ++i)
+ intersectionObservers.push(createIntersectionObserver());
+
+ requestAnimationFrame(() => {
+ setTimeout(() => {
+ gc();
+ log.textContent += internals.referencingNodeCount(document) < initialNodeCount + intersectionObservers.length * 0.8 ? 'PASS' : 'FAIL - Less than 20% of nodes were collected.'
+ testRunner.notifyDone();
+ }, 0);
+ });
+}
+
+function createIntersectionObserver()
+{
+ const element = document.createElement('div');
+ const intersectionObserver = new IntersectionObserver(() => { }).observe(element);
+ document.body.appendChild(element);
+ setTimeout(() => {
+ element.remove();
+ }, 0);
+ return intersectionObserver;
+}
+
+_onload_ = runTest;
+
+</script>
+</body>
+</html>
Modified: trunk/LayoutTests/intersection-observer/root-element-deleted-expected.txt (279799 => 279800)
--- trunk/LayoutTests/intersection-observer/root-element-deleted-expected.txt 2021-07-09 23:00:28 UTC (rev 279799)
+++ trunk/LayoutTests/intersection-observer/root-element-deleted-expected.txt 2021-07-10 01:03:03 UTC (rev 279800)
@@ -1,3 +1,25 @@
+This tests removing the root node of IntersectionObserver. The root node should be eligible for GC.
-PASS IntersectionObserver doesn't keep unreachable root alive
+Test 1: PASS
+Test 2: PASS
+Test 3: PASS
+Test 4: PASS
+Test 5: PASS
+Test 6: PASS
+Test 7: PASS
+Test 8: PASS
+Test 9: PASS
+Test 10: PASS
+Test 11: PASS
+Test 12: PASS
+Test 13: PASS
+Test 14: PASS
+Test 15: PASS
+Test 16: PASS
+Test 17: PASS
+Test 18: PASS
+Test 19: PASS
+Test 20: PASS
+Node count: PASS
+Intersection observer count: PASS
Modified: trunk/LayoutTests/intersection-observer/root-element-deleted.html (279799 => 279800)
--- trunk/LayoutTests/intersection-observer/root-element-deleted.html 2021-07-09 23:00:28 UTC (rev 279799)
+++ trunk/LayoutTests/intersection-observer/root-element-deleted.html 2021-07-10 01:03:03 UTC (rev 279800)
@@ -1,44 +1,95 @@
<!DOCTYPE html><!-- webkit-test-runner [ IntersectionObserverEnabled=true ] -->
<head>
-<script src=""
-<script src=""
+<script src=""
<body>
+<pre id="log">This tests removing the root node of IntersectionObserver. The root node should be eligible for GC.
-<div id="root" style="position:absolute">
- <div id="target" style="width: 100px; height: 100px; background-color: green"></div>
-</div>
+</pre>
+<script>
-<script>
- function observerShouldBeRemoved()
- {
- return new Promise(function(resolve) {
- handle = setInterval(function() {
- GCController.collect();
- if (internals && internals.numberOfIntersectionObservers(document) == 0) {
- clearInterval(handle);
- resolve();
- }
- }, 10);
- });
+function createRoot(rootId)
+{
+ const container = document.createElement('div');
+ container.innerHTML = `<div id="${rootId}" class="root" style="position:absolute"><div class="target" style="width: 100px; height: 100px; background-color: green"></div></div>`;
+ const root = container.getElementsByClassName('root')[0];
+ root.remove();
+ return root;
+}
+
+let initialNodeCount = 0;
+window._onload_ = () => {
+ if (!window.testRunner || !window.internals) {
+ log.textContent += 'FAIL - This test requires internals';
+ return;
}
+ if (internals.numberOfIntersectionObservers(document)) {
+ log.textContent += 'FAIL - Initial intersection observer count is not zero';
+ return;
+ }
+ initialNodeCount = internals.referencingNodeCount(document);
+ testRunner.dumpAsText();
+ testRunner.waitUntilDone();
+ setTimeout(() => testRunner.notifyDone(), 5000);
+ runTest(1);
+}
- async_test(function(t) {
- 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 starts out with non-null root');
- observer.observe(target);
- assert_equals(internals.numberOfIntersectionObservers(document), 1);
- root.parentNode.removeChild(root);
- root = null;
- target = null;
- requestAnimationFrame(function() {
- observer.takeRecords();
- observerShouldBeRemoved().then(t.step_func_done(function() {
- assert_equals(observer.root, null, 'Observer has null root after root element is destroyed');
- }));
- });
- }, "IntersectionObserver doesn't keep unreachable root alive");
+const totalTestCount = 20;
+function runTest(testNumber) {
+ log.textContent += `Test ${testNumber}: `;
+ const rootId = 'root-' + testNumber;
+ let root = createRoot(rootId);
+ root.alive = true;
+ document.body.appendChild(root);
+
+ let observer = new IntersectionObserver(() => { }, { root });
+ let target = root.getElementsByClassName('target')[0];
+ if (observer.root != root)
+ return testFailed(testNumber, 'observer.root != root after construction');
+ observer.observe(target);
+
+ root.remove();
+ root = null;
+ target = null;
+ requestAnimationFrame(function () {
+ observer.takeRecords();
+ gc();
+
+ if (!observer.root)
+ return testFailed(testNumber, 'observer.root is null');
+ if (observer.root.id != rootId)
+ return testFailed(testNumber, `observer.root.id (${observer.root.id}) != rootId (${rootId}) after running rAF and GC`);
+ if (!observer.root.alive)
+ return testFailed(testNumber, 'observer.alive is false');
+
+ observer = null;
+ gc();
+
+ log.textContent += 'PASS\n';
+
+ if (testNumber < totalTestCount)
+ setTimeout(() => runTest(testNumber + 1), 0);
+ else
+ setTimeout(() => finish(testNumber), 0);
+ });
+}
+
+function testFailed(testNumber, error)
+{
+ log.textContent += `FAIL: ${error}\n`;
+ finish(testNumber);
+}
+
+function finish(finishedTestCount)
+{
+ log.textContent += 'Node count: ';
+ log.textContent += (internals.referencingNodeCount(document) < initialNodeCount + 2 * finishedTestCount * 0.8) ? 'PASS' : 'FAIL - Less than 20% of the root nodes were collected';
+ log.textContent += '\n';
+ log.textContent += 'Intersection observer count: ';
+ log.textContent += (internals.numberOfIntersectionObservers(document) < finishedTestCount * 0.8) ? 'PASS' : 'FAIL - Less than 20% of the intersection observers were collected';
+ log.textContent += '\n';
+ testRunner.notifyDone();
+}
+
</script>
</body>
</html>
Added: trunk/LayoutTests/resize-observer/resize-observer-should-not-leak-observed-nodes-expected.txt (0 => 279800)
--- trunk/LayoutTests/resize-observer/resize-observer-should-not-leak-observed-nodes-expected.txt (rev 0)
+++ trunk/LayoutTests/resize-observer/resize-observer-should-not-leak-observed-nodes-expected.txt 2021-07-10 01:03:03 UTC (rev 279800)
@@ -0,0 +1,4 @@
+This tests observing an element with an ResizeObserver and removing the element from the document.
+The element should become eligible for GC at some point.
+
+PASS
Added: trunk/LayoutTests/resize-observer/resize-observer-should-not-leak-observed-nodes.html (0 => 279800)
--- trunk/LayoutTests/resize-observer/resize-observer-should-not-leak-observed-nodes.html (rev 0)
+++ trunk/LayoutTests/resize-observer/resize-observer-should-not-leak-observed-nodes.html 2021-07-10 01:03:03 UTC (rev 279800)
@@ -0,0 +1,47 @@
+<!DOCTYPE html><!-- webkit-test-runner [ ResizeObserverEnabled=true ] -->
+<html>
+<body>
+<pre id="log">This tests observing an element with an ResizeObserver and removing the element from the document.
+The element should become eligible for GC at some point.
+
+</pre>
+<script src=""
+<script>
+
+let initialNodeCount;
+function runTest()
+{
+ if (!window.testRunner || !window.internals) {
+ log.textContent += 'FAIL - This test requires internals';
+ return;
+ }
+ testRunner.dumpAsText();
+ testRunner.waitUntilDone();
+
+ initialNodeCount = internals.referencingNodeCount(document);
+
+ const resizeObserverCount = 100;
+ for (let i = 0; i < resizeObserverCount; ++i)
+ createResizeObserver();
+
+ requestAnimationFrame(() => {
+ gc();
+ setTimeout(() => {
+ gc();
+ log.textContent += internals.referencingNodeCount(document) < initialNodeCount + resizeObserverCount * 0.95 ? 'PASS' : 'FAIL - Less than 20% of nodes were collected.'
+ testRunner.notifyDone();
+ }, 0);
+ });
+}
+
+function createResizeObserver()
+{
+ const element = document.createElement('div');
+ const resizeObserver = new ResizeObserver(() => { }).observe(element);
+}
+
+_onload_ = runTest;
+
+</script>
+</body>
+</html>
Modified: trunk/Source/WebCore/ChangeLog (279799 => 279800)
--- trunk/Source/WebCore/ChangeLog 2021-07-09 23:00:28 UTC (rev 279799)
+++ trunk/Source/WebCore/ChangeLog 2021-07-10 01:03:03 UTC (rev 279800)
@@ -1,3 +1,93 @@
+2021-07-09 Ryosuke Niwa <rn...@webkit.org>
+
+ ResizeObserver / IntersectionObserver memory leak on detached & out of reference elements
+ https://bugs.webkit.org/show_bug.cgi?id=227194
+ <rdar://problem/79839851>
+
+ Reviewed by Chris Dumez.
+
+ The memory leak was caused by ResizeObserver and IntersectionObserver keeping their respective
+ JS wrapper objects alive so long as there are some observed elements by having pending
+ activity as ActiveDOMObjects. This is not the right GC model for these JS wrapper objects
+ since there is no obvious end to these activities. So long as the observed nodes are alive,
+ ResizeObserver and IntersectionObserver may get new observation entries queued later.
+
+ To address this issue, this patch reworks the way ResizeObserver and IntersectionObserver keep
+ their JS wrappers alive. Namely, they're no longer ActiveDOMObjects. Instead, their JS wrappers
+ would use their respective observed nodes as opaque roots. i.e. so long as any of the observed
+ nodes are kept alive by GC, then its observer's JS wrapper is kept alive.
+
+ ResizeObserver had an additional bug that every observed node was kept using GCReachableRef,
+ which obviously leaked every observed node until the observations were explicitly cleared.
+ This patch makes only the target elements of the active observations (i.e. there are entries
+ to be delivered to the observer callback) are kept alive with GCReachableRef as done with
+ IntersectionObserver and MutationObserver.
+
+ Finally, this patch fixes the bug that IntersectionObserver wasn't keeping its root node alive.
+ We even had a test which was testing this erroneously behavior. The test has been rewritten to
+ expect the new behavior.
+
+ Tests: intersection-observer/intersection-observer-should-not-leak-observed-nodes.html
+ intersection-observer/root-element-deleted.html
+ resize-observer/resize-observer-should-not-leak-observed-nodes.html
+
+ * bindings/js/JSIntersectionObserverCustom.cpp:
+ (WebCore::JSIntersectionObserver::visitAdditionalChildren): Add the root node as an opaque root.
+ This has an effect of keeping the root node alive so long as IntersectionObserver is alive.
+ (WebCore::JSIntersectionObserverOwner::isReachableFromOpaqueRoots): Added.
+ * bindings/js/JSResizeObserverCustom.cpp:
+ (WebCore::JSResizeObserverOwner::isReachableFromOpaqueRoots): Added.
+ * dom/Document.cpp:
+ (WebCore::Document::updateIntersectionObservations):
+ * html/LazyLoadFrameObserver.cpp:
+ (WebCore::LazyLoadFrameObserver::isObserved const):
+ * html/LazyLoadImageObserver.cpp:
+ (WebCore::LazyLoadImageObserver::isObserved const):
+ * page/IntersectionObserver.cpp:
+ (WebCore::IntersectionObserver::create):
+ (WebCore::IntersectionObserver::IntersectionObserver):
+ (WebCore::IntersectionObserver::~IntersectionObserver):
+ (WebCore::IntersectionObserver::isObserving const): Added.
+ (WebCore::IntersectionObserver::observe):
+ (WebCore::IntersectionObserver::virtualHasPendingActivity const): Deleted.
+ (WebCore::IntersectionObserver::activeDOMObjectName const): Deleted.
+ (WebCore::IntersectionObserver::stop): Deleted.
+ (WebCore::IntersectionObserver::isReachableFromOpaqueRoots const): Added.
+ * page/IntersectionObserver.h:
+ (WebCore::IntersectionObserver::root const):
+ (WebCore::IntersectionObserver::observationTargets const):
+ (WebCore::IntersectionObserver::hasObservationTargets const):
+ * page/IntersectionObserver.idl:
+ * page/ResizeObservation.cpp:
+ (WebCore::ResizeObservation::create):
+ (WebCore::ResizeObservation::ResizeObservation):
+ (WebCore::ResizeObservation::targetElementDepth const):
+ * page/ResizeObservation.h:
+ (WebCore::ResizeObservation::target const):
+ (WebCore::ResizeObservation): Renamed m_pendingTargets to m_activeObservationTargets to reflect
+ the new semantics.
+ * page/ResizeObserver.cpp:
+ (WebCore::ResizeObserver::ResizeObserver):
+ (WebCore::ResizeObserver::observe): Don't store the new observation target with GCReachableRef as
+ that would result in an immediate leak of this element.
+ (WebCore::ResizeObserver::gatherObservations): Now that we have an active observation (i.e. there
+ is an entry to be delivered to the JS callback), store the target element using GCReachableRef.
+ This keeps the JS wrapper of this target element alive between now and when the JS callback is
+ notified of this element's resize. Without this GCReachableRef, JS wrapper of the element can be
+ erroneously collected when there is no JS reference to the element and the element is no longer in
+ any live document. Note that this GC behavior is already tested by existing tests. This patch
+ simply delays the use of GCReachableRef from when ResizeObserver started observing this element
+ to when we created an active observation for the element; this is because GCReachableRef like
+ a pending activity of ActiveDOMObject is only appropriate when there is a definite end to it.
+ (WebCore::ResizeObserver::isReachableFromOpaqueRoots const): Added.
+ (WebCore::ResizeObserver::removeAllTargets):
+ (WebCore::ResizeObserver::removeObservation):
+ (WebCore::ResizeObserver::virtualHasPendingActivity const): Deleted.
+ (WebCore::ResizeObserver::activeDOMObjectName const): Deleted.
+ (WebCore::ResizeObserver::stop): Deleted.
+ * page/ResizeObserver.h:
+ * page/ResizeObserver.idl:
+
2021-07-09 Aditya Keerthi <akeer...@apple.com>
[iOS] Increase contrast for textfields, checkboxes, and radio buttons
Modified: trunk/Source/WebCore/bindings/js/JSIntersectionObserverCustom.cpp (279799 => 279800)
--- trunk/Source/WebCore/bindings/js/JSIntersectionObserverCustom.cpp 2021-07-09 23:00:28 UTC (rev 279799)
+++ trunk/Source/WebCore/bindings/js/JSIntersectionObserverCustom.cpp 2021-07-10 01:03:03 UTC (rev 279800)
@@ -27,6 +27,7 @@
#include "config.h"
#include "JSIntersectionObserver.h"
+#include "JSNodeCustom.h"
#include <_javascript_Core/JSCInlines.h>
namespace WebCore {
@@ -36,8 +37,19 @@
{
if (auto* callback = wrapped().callbackConcurrently())
callback->visitJSFunction(visitor);
+ visitor.addOpaqueRoot(root(wrapped().root()));
}
DEFINE_VISIT_ADDITIONAL_CHILDREN(JSIntersectionObserver);
+bool JSIntersectionObserverOwner::isReachableFromOpaqueRoots(JSC::Handle<JSC::Unknown> handle, void*, JSC::AbstractSlotVisitor& visitor, const char**reason)
+{
+ if (jsCast<JSIntersectionObserver*>(handle.slot()->asCell())->wrapped().isReachableFromOpaqueRoots(visitor)) {
+ if (UNLIKELY(reason))
+ *reason = "Reachable from observed nodes";
+ return true;
+ }
+ return false;
+}
+
} // namespace WebCore
Modified: trunk/Source/WebCore/bindings/js/JSResizeObserverCustom.cpp (279799 => 279800)
--- trunk/Source/WebCore/bindings/js/JSResizeObserverCustom.cpp 2021-07-09 23:00:28 UTC (rev 279799)
+++ trunk/Source/WebCore/bindings/js/JSResizeObserverCustom.cpp 2021-07-10 01:03:03 UTC (rev 279800)
@@ -41,4 +41,14 @@
DEFINE_VISIT_ADDITIONAL_CHILDREN(JSResizeObserver);
+bool JSResizeObserverOwner::isReachableFromOpaqueRoots(JSC::Handle<JSC::Unknown> handle, void*, JSC::AbstractSlotVisitor& visitor, const char**reason)
+{
+ if (jsCast<JSResizeObserver*>(handle.slot()->asCell())->wrapped().isReachableFromOpaqueRoots(visitor)) {
+ if (UNLIKELY(reason))
+ *reason = "Reachable from observed nodes";
+ return true;
+ }
+ return false;
+}
+
} // namespace WebCore
Modified: trunk/Source/WebCore/dom/Document.cpp (279799 => 279800)
--- trunk/Source/WebCore/dom/Document.cpp 2021-07-09 23:00:28 UTC (rev 279799)
+++ trunk/Source/WebCore/dom/Document.cpp 2021-07-10 01:03:03 UTC (rev 279800)
@@ -7912,7 +7912,7 @@
auto timestamp = observer->nowTimestamp();
if (!timestamp)
continue;
- for (Element* target : observer->observationTargets()) {
+ for (auto& target : observer->observationTargets()) {
auto& targetRegistrations = target->intersectionObserverDataIfExists()->registrations;
auto index = targetRegistrations.findMatching([observer](auto& registration) {
return registration.observer.get() == observer;
@@ -7969,7 +7969,7 @@
{ targetBoundingClientRect.x(), targetBoundingClientRect.y(), targetBoundingClientRect.width(), targetBoundingClientRect.height() },
{ clientIntersectionRect.x(), clientIntersectionRect.y(), clientIntersectionRect.width(), clientIntersectionRect.height() },
intersectionRatio,
- target,
+ target.get(),
thresholdIndex > 0,
}));
needNotify = true;
Modified: trunk/Source/WebCore/html/LazyLoadFrameObserver.cpp (279799 => 279800)
--- trunk/Source/WebCore/html/LazyLoadFrameObserver.cpp 2021-07-09 23:00:28 UTC (rev 279799)
+++ trunk/Source/WebCore/html/LazyLoadFrameObserver.cpp 2021-07-10 01:03:03 UTC (rev 279800)
@@ -103,7 +103,7 @@
bool LazyLoadFrameObserver::isObserved(Element& element) const
{
- return m_observer && m_observer->observationTargets().contains(&element);
+ return m_observer && m_observer->isObserving(element);
}
}
Modified: trunk/Source/WebCore/html/LazyLoadImageObserver.cpp (279799 => 279800)
--- trunk/Source/WebCore/html/LazyLoadImageObserver.cpp 2021-07-09 23:00:28 UTC (rev 279799)
+++ trunk/Source/WebCore/html/LazyLoadImageObserver.cpp 2021-07-10 01:03:03 UTC (rev 279800)
@@ -95,7 +95,7 @@
bool LazyLoadImageObserver::isObserved(Element& element) const
{
- return m_observer && m_observer->observationTargets().contains(&element);
+ return m_observer && m_observer->isObserving(element);
}
}
Modified: trunk/Source/WebCore/page/IntersectionObserver.cpp (279799 => 279800)
--- trunk/Source/WebCore/page/IntersectionObserver.cpp 2021-07-09 23:00:28 UTC (rev 279799)
+++ trunk/Source/WebCore/page/IntersectionObserver.cpp 2021-07-10 01:03:03 UTC (rev 279800)
@@ -37,6 +37,7 @@
#include "IntersectionObserverCallback.h"
#include "IntersectionObserverEntry.h"
#include "Performance.h"
+#include <_javascript_Core/AbstractSlotVisitorInlines.h>
#include <wtf/Vector.h>
namespace WebCore {
@@ -86,7 +87,7 @@
ExceptionOr<Ref<IntersectionObserver>> IntersectionObserver::create(Document& document, Ref<IntersectionObserverCallback>&& callback, IntersectionObserver::Init&& init)
{
- ContainerNode* root = nullptr;
+ RefPtr<ContainerNode> root;
if (init.root) {
WTF::switchOn(*init.root, [&root] (RefPtr<Element> element) {
root = element.get();
@@ -112,38 +113,34 @@
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), root, rootMarginOrException.releaseReturnValue(), WTFMove(thresholds)));
+ return adoptRef(*new IntersectionObserver(document, WTFMove(callback), root.get(), rootMarginOrException.releaseReturnValue(), WTFMove(thresholds)));
}
IntersectionObserver::IntersectionObserver(Document& document, Ref<IntersectionObserverCallback>&& callback, ContainerNode* root, LengthBox&& parsedRootMargin, Vector<double>&& thresholds)
- : ActiveDOMObject(callback->scriptExecutionContext())
- , m_root(root)
+ : m_root(makeWeakPtr(root))
, m_rootMargin(WTFMove(parsedRootMargin))
, m_thresholds(WTFMove(thresholds))
, m_callback(WTFMove(callback))
{
- if (is<Document>(m_root)) {
- auto& observerData = downcast<Document>(*m_root).ensureIntersectionObserverData();
+ if (is<Document>(root)) {
+ auto& observerData = downcast<Document>(*root).ensureIntersectionObserverData();
observerData.observers.append(makeWeakPtr(this));
- } else if (m_root) {
- ASSERT(is<Element>(m_root));
- auto& observerData = downcast<Element>(*m_root).ensureIntersectionObserverData();
+ } else if (root) {
+ auto& observerData = downcast<Element>(*root).ensureIntersectionObserverData();
observerData.observers.append(makeWeakPtr(this));
} else if (auto* frame = document.frame())
m_implicitRootDocument = makeWeakPtr(frame->mainFrame().document());
std::sort(m_thresholds.begin(), m_thresholds.end());
- suspendIfNeeded();
}
IntersectionObserver::~IntersectionObserver()
{
- if (is<Document>(m_root)) {
- downcast<Document>(*m_root).intersectionObserverDataIfExists()->observers.removeFirst(this);
- } else if (m_root) {
- ASSERT(is<Element>(m_root));
- downcast<Element>(*m_root).intersectionObserverDataIfExists()->observers.removeFirst(this);
- }
+ RefPtr root = m_root.get();
+ if (is<Document>(root))
+ downcast<Document>(*root).intersectionObserverDataIfExists()->observers.removeFirst(this);
+ else if (root)
+ downcast<Element>(*root).intersectionObserverDataIfExists()->observers.removeFirst(this);
disconnect();
}
@@ -157,14 +154,22 @@
return stringBuilder.toString();
}
+bool IntersectionObserver::isObserving(const Element& element) const
+{
+ return m_observationTargets.findMatching([&](auto& target) {
+ return target.get() == &element;
+ }) != notFound;
+}
+
void IntersectionObserver::observe(Element& target)
{
- if (!trackingDocument() || !m_callback || m_observationTargets.contains(&target))
+ if (!trackingDocument() || !m_callback || isObserving(target))
return;
target.ensureIntersectionObserverData().registrations.append({ makeWeakPtr(this), std::nullopt });
bool hadObservationTargets = hasObservationTargets();
- m_observationTargets.append(&target);
+ m_observationTargets.append(makeWeakPtr(target));
+
auto* document = trackingDocument();
if (!hadObservationTargets)
document->addIntersectionObserver(*this);
@@ -223,7 +228,7 @@
void IntersectionObserver::removeAllTargets()
{
- for (auto* target : m_observationTargets) {
+ for (auto& target : m_observationTargets) {
bool removed = removeTargetRegistration(*target);
ASSERT_UNUSED(removed, removed);
}
@@ -279,24 +284,15 @@
InspectorInstrumentation::didFireObserverCallback(*context);
}
-bool IntersectionObserver::virtualHasPendingActivity() const
+bool IntersectionObserver::isReachableFromOpaqueRoots(JSC::AbstractSlotVisitor& visitor) const
{
- return (hasObservationTargets() && trackingDocument()) || !m_queuedEntries.isEmpty();
+ for (auto& target : m_observationTargets) {
+ if (auto* element = target.get(); element && visitor.containsOpaqueRoot(element->opaqueRoot()))
+ return true;
+ }
+ return false;
}
-const char* IntersectionObserver::activeDOMObjectName() const
-{
- return "IntersectionObserver";
-}
-
-void IntersectionObserver::stop()
-{
- disconnect();
- m_callback = nullptr;
- m_queuedEntries.clear();
- m_pendingTargets.clear();
-}
-
} // namespace WebCore
#endif // ENABLE(INTERSECTION_OBSERVER)
Modified: trunk/Source/WebCore/page/IntersectionObserver.h (279799 => 279800)
--- trunk/Source/WebCore/page/IntersectionObserver.h 2021-07-09 23:00:28 UTC (rev 279799)
+++ trunk/Source/WebCore/page/IntersectionObserver.h 2021-07-10 01:03:03 UTC (rev 279800)
@@ -27,7 +27,6 @@
#if ENABLE(INTERSECTION_OBSERVER)
-#include "ActiveDOMObject.h"
#include "GCReachableRef.h"
#include "IntersectionObserverCallback.h"
#include "IntersectionObserverEntry.h"
@@ -37,6 +36,12 @@
#include <wtf/WeakPtr.h>
#include <wtf/text/WTFString.h>
+namespace JSC {
+
+class AbstractSlotVisitor;
+
+}
+
namespace WebCore {
class Document;
@@ -60,7 +65,7 @@
Vector<IntersectionObserverRegistration> registrations;
};
-class IntersectionObserver : public RefCounted<IntersectionObserver>, public ActiveDOMObject, public CanMakeWeakPtr<IntersectionObserver> {
+class IntersectionObserver : public RefCounted<IntersectionObserver>, public CanMakeWeakPtr<IntersectionObserver> {
public:
struct Init {
std::optional<Variant<RefPtr<Element>, RefPtr<Document>>> root;
@@ -74,11 +79,13 @@
Document* trackingDocument() const { return m_root ? &m_root->document() : m_implicitRootDocument.get(); }
- ContainerNode* root() const { return m_root; }
+ ContainerNode* root() const { return m_root.get(); }
String rootMargin() const;
const LengthBox& rootMarginBox() const { return m_rootMargin; }
const Vector<double>& thresholds() const { return m_thresholds; }
- const Vector<Element*> observationTargets() const { return m_observationTargets; }
+ const Vector<WeakPtr<Element>>& observationTargets() const { return m_observationTargets; }
+ bool hasObservationTargets() const { return m_observationTargets.size(); }
+ bool isObserving(const Element&) const;
void observe(Element&);
void unobserve(Element&);
@@ -91,7 +98,6 @@
TakenRecords takeRecords();
void targetDestroyed(Element&);
- bool hasObservationTargets() const { return m_observationTargets.size(); }
void rootDestroyed();
std::optional<ReducedResolutionSeconds> nowTimestamp() const;
@@ -100,24 +106,20 @@
void notify();
IntersectionObserverCallback* callbackConcurrently() { return m_callback.get(); }
+ bool isReachableFromOpaqueRoots(JSC::AbstractSlotVisitor&) const;
private:
IntersectionObserver(Document&, Ref<IntersectionObserverCallback>&&, ContainerNode* root, LengthBox&& parsedRootMargin, Vector<double>&& thresholds);
- // ActiveDOMObject.
- bool virtualHasPendingActivity() const override;
- const char* activeDOMObjectName() const override;
- void stop() override;
-
bool removeTargetRegistration(Element&);
void removeAllTargets();
WeakPtr<Document> m_implicitRootDocument;
- ContainerNode* m_root;
+ WeakPtr<ContainerNode> m_root;
LengthBox m_rootMargin;
Vector<double> m_thresholds;
RefPtr<IntersectionObserverCallback> m_callback;
- Vector<Element*> m_observationTargets;
+ Vector<WeakPtr<Element>> m_observationTargets;
Vector<GCReachableRef<Element>> m_pendingTargets;
Vector<Ref<IntersectionObserverEntry>> m_queuedEntries;
};
Modified: trunk/Source/WebCore/page/IntersectionObserver.idl (279799 => 279800)
--- trunk/Source/WebCore/page/IntersectionObserver.idl 2021-07-09 23:00:28 UTC (rev 279799)
+++ trunk/Source/WebCore/page/IntersectionObserver.idl 2021-07-10 01:03:03 UTC (rev 279800)
@@ -26,11 +26,12 @@
// https://wicg.github.io/IntersectionObserver/
[
- ActiveDOMObject,
Conditional=INTERSECTION_OBSERVER,
EnabledBySetting=IntersectionObserver,
Exposed=Window,
JSCustomMarkFunction,
+ CustomIsReachable,
+ ImplementationLacksVTable
] interface IntersectionObserver {
[CallWith=Document] constructor(IntersectionObserverCallback callback, optional IntersectionObserverInit options);
Modified: trunk/Source/WebCore/page/ResizeObservation.cpp (279799 => 279800)
--- trunk/Source/WebCore/page/ResizeObservation.cpp 2021-07-09 23:00:28 UTC (rev 279799)
+++ trunk/Source/WebCore/page/ResizeObservation.cpp 2021-07-10 01:03:03 UTC (rev 279800)
@@ -34,13 +34,13 @@
namespace WebCore {
-Ref<ResizeObservation> ResizeObservation::create(Element* target)
+Ref<ResizeObservation> ResizeObservation::create(Element& target)
{
return adoptRef(*new ResizeObservation(target));
}
-ResizeObservation::ResizeObservation(Element* element)
- : m_target(element)
+ResizeObservation::ResizeObservation(Element& element)
+ : m_target { makeWeakPtr(element) }
{
}
@@ -92,7 +92,7 @@
size_t ResizeObservation::targetElementDepth() const
{
unsigned depth = 0;
- for (Element* ownerElement = m_target; ownerElement; ownerElement = ownerElement->document().ownerElement()) {
+ for (Element* ownerElement = m_target.get(); ownerElement; ownerElement = ownerElement->document().ownerElement()) {
for (Element* parent = ownerElement; parent; parent = parent->parentElement())
++depth;
}
Modified: trunk/Source/WebCore/page/ResizeObservation.h (279799 => 279800)
--- trunk/Source/WebCore/page/ResizeObservation.h 2021-07-09 23:00:28 UTC (rev 279799)
+++ trunk/Source/WebCore/page/ResizeObservation.h 2021-07-10 01:03:03 UTC (rev 279800)
@@ -30,6 +30,7 @@
#include "LayoutSize.h"
#include <wtf/RefCounted.h>
+#include <wtf/WeakPtr.h>
namespace WebCore {
@@ -38,7 +39,7 @@
class ResizeObservation : public RefCounted<ResizeObservation> {
WTF_MAKE_FAST_ALLOCATED;
public:
- static Ref<ResizeObservation> create(Element* target);
+ static Ref<ResizeObservation> create(Element& target);
~ResizeObservation();
@@ -48,13 +49,13 @@
FloatRect computeContentRect() const;
bool elementSizeChanged(LayoutSize&) const;
- Element* target() const { return m_target; }
+ Element* target() const { return m_target.get(); }
size_t targetElementDepth() const;
private:
- ResizeObservation(Element* target);
+ ResizeObservation(Element& target);
- Element* m_target;
+ WeakPtr<Element> m_target;
LayoutSize m_lastObservationSize;
};
Modified: trunk/Source/WebCore/page/ResizeObserver.cpp (279799 => 279800)
--- trunk/Source/WebCore/page/ResizeObserver.cpp 2021-07-09 23:00:28 UTC (rev 279799)
+++ trunk/Source/WebCore/page/ResizeObserver.cpp 2021-07-10 01:03:03 UTC (rev 279800)
@@ -31,6 +31,7 @@
#include "Element.h"
#include "InspectorInstrumentation.h"
#include "ResizeObserverEntry.h"
+#include <_javascript_Core/AbstractSlotVisitorInlines.h>
namespace WebCore {
@@ -40,11 +41,9 @@
}
ResizeObserver::ResizeObserver(Document& document, Ref<ResizeObserverCallback>&& callback)
- : ActiveDOMObject(callback->scriptExecutionContext())
- , m_document(makeWeakPtr(document))
+ : m_document(makeWeakPtr(document))
, m_callback(WTFMove(callback))
{
- suspendIfNeeded();
}
ResizeObserver::~ResizeObserver()
@@ -69,8 +68,7 @@
auto& observerData = target.ensureResizeObserverData();
observerData.observers.append(makeWeakPtr(this));
- m_observations.append(ResizeObservation::create(&target));
- m_pendingTargets.append(target);
+ m_observations.append(ResizeObservation::create(target));
if (m_document) {
m_document->addResizeObserver(*this);
@@ -107,6 +105,7 @@
if (depth > deeperThan) {
observation->updateObservationSize(currentSize);
m_activeObservations.append(observation.get());
+ m_activeObservationTargets.append(*observation->target());
minObservedDepth = std::min(depth, minObservedDepth);
} else
m_hasSkippedObservations = true;
@@ -133,6 +132,15 @@
InspectorInstrumentation::didFireObserverCallback(*context);
}
+bool ResizeObserver::isReachableFromOpaqueRoots(JSC::AbstractSlotVisitor& visitor) const
+{
+ for (auto& observation : m_observations) {
+ if (auto* target = observation->target(); target && visitor.containsOpaqueRoot(target->opaqueRoot()))
+ return true;
+ }
+ return false;
+}
+
bool ResizeObserver::removeTarget(Element& target)
{
auto* observerData = target.resizeObserverData();
@@ -149,7 +157,7 @@
bool removed = removeTarget(*observation->target());
ASSERT_UNUSED(removed, removed);
}
- m_pendingTargets.clear();
+ m_activeObservationTargets.clear();
m_activeObservations.clear();
m_observations.clear();
}
@@ -156,7 +164,7 @@
bool ResizeObserver::removeObservation(const Element& target)
{
- m_pendingTargets.removeFirstMatching([&target](auto& pendingTarget) {
+ m_activeObservationTargets.removeFirstMatching([&target](auto& pendingTarget) {
return pendingTarget.ptr() == ⌖
});
@@ -169,22 +177,6 @@
});
}
-bool ResizeObserver::virtualHasPendingActivity() const
-{
- return (hasObservations() && m_document) || !m_activeObservations.isEmpty();
-}
-
-const char* ResizeObserver::activeDOMObjectName() const
-{
- return "ResizeObserver";
-}
-
-void ResizeObserver::stop()
-{
- disconnect();
- m_callback = nullptr;
-}
-
} // namespace WebCore
#endif // ENABLE(RESIZE_OBSERVER)
Modified: trunk/Source/WebCore/page/ResizeObserver.h (279799 => 279800)
--- trunk/Source/WebCore/page/ResizeObserver.h 2021-07-09 23:00:28 UTC (rev 279799)
+++ trunk/Source/WebCore/page/ResizeObserver.h 2021-07-10 01:03:03 UTC (rev 279800)
@@ -27,13 +27,19 @@
#if ENABLE(RESIZE_OBSERVER)
-#include "ActiveDOMObject.h"
#include "GCReachableRef.h"
#include "ResizeObservation.h"
#include "ResizeObserverCallback.h"
+#include <wtf/Lock.h>
#include <wtf/RefCounted.h>
#include <wtf/WeakPtr.h>
+namespace JSC {
+
+class AbstractSlotVisitor;
+
+}
+
namespace WebCore {
class Document;
@@ -44,8 +50,7 @@
Vector<WeakPtr<ResizeObserver>> observers;
};
-class ResizeObserver : public RefCounted<ResizeObserver>, public ActiveDOMObject, public CanMakeWeakPtr<ResizeObserver> {
- WTF_MAKE_FAST_ALLOCATED;
+class ResizeObserver : public RefCounted<ResizeObserver>, public CanMakeWeakPtr<ResizeObserver> {
public:
static Ref<ResizeObserver> create(Document&, Ref<ResizeObserverCallback>&&);
~ResizeObserver();
@@ -65,15 +70,11 @@
void setHasSkippedObservations(bool skipped) { m_hasSkippedObservations = skipped; }
ResizeObserverCallback* callbackConcurrently() { return m_callback.get(); }
+ bool isReachableFromOpaqueRoots(JSC::AbstractSlotVisitor&) const;
private:
ResizeObserver(Document&, Ref<ResizeObserverCallback>&&);
- // ActiveDOMObject.
- bool virtualHasPendingActivity() const override;
- const char* activeDOMObjectName() const override;
- void stop() override;
-
bool removeTarget(Element&);
void removeAllTargets();
bool removeObservation(const Element&);
@@ -83,7 +84,7 @@
Vector<Ref<ResizeObservation>> m_observations;
Vector<Ref<ResizeObservation>> m_activeObservations;
- Vector<GCReachableRef<Element>> m_pendingTargets;
+ Vector<GCReachableRef<Element>> m_activeObservationTargets;
bool m_hasSkippedObservations { false };
};
Modified: trunk/Source/WebCore/page/ResizeObserver.idl (279799 => 279800)
--- trunk/Source/WebCore/page/ResizeObserver.idl 2021-07-09 23:00:28 UTC (rev 279799)
+++ trunk/Source/WebCore/page/ResizeObserver.idl 2021-07-10 01:03:03 UTC (rev 279800)
@@ -26,11 +26,12 @@
// https://wicg.github.io/ResizeObserver/
[
- ActiveDOMObject,
Conditional=RESIZE_OBSERVER,
EnabledBySetting=ResizeObserver,
Exposed=Window,
JSCustomMarkFunction,
+ CustomIsReachable,
+ ImplementationLacksVTable
] interface ResizeObserver {
[CallWith=Document] constructor(ResizeObserverCallback callback);