ResizeObserver
is an interface provided by browsers to detect the size change of a target. It would call the js callback when the size changes.
In the callback function, you can do anything including delete the target. So how ResizeObserver
related object is managed?
ResizeObserver related objects
Let’s take a look at a simple example.<div id="target"></div>
<script>
{
var ro = new ResizeObserver( entries => {});
ro.observe(document.getElementById('target'));
} // end of the scope
</script>
- ro: a JSResizeObserver which is a js wrapper of ResizeObserver,
- callback: JSResizeObserverCallback,
- entries: JSResizeObserverEntry,
- and observe() would create a ResizeObservation,
- then document and the target.
–
ResizeObserver
and Document
It needs
Document
to create ResizeObserver
, and store document in WeakPtr<Document, WeakPtrImplWithEventTargetData> m_document;
.On the other hand, when
observe()
, m_document->addResizeObserver(*this)
, ResizeObserver
is stored in Vector<WeakPtr<ResizeObserver>> m_resizeObservers;
.So
ResizeObserver
and Document
both hold each other by WeakPtr
.–
ResizeObserver
and Element
When
observe()
, ResizeObservation
is created, and it is stored in ResizeObserver by Vector<Ref<ResizeObservation>> m_observations;
.ResizeObservation
holds Element by WeakPtr
, WeakPtr<Element, WeakPtrImplWithEventTargetData> m_target
.On the other hand,
target.ensureResizeObserverData()
, Element creates ResizeObserverData
, which holds ResizeObserver
by WeakPtr
, Vector<WeakPtr<ResizeObserver>> observers;
.So the connection between
ResizeObserver
and element
is through WeakPtr
.
Keep JSResizeObserver alive
Both Document and Element hold ResizeObserver byWeakPtr
, how do we keep ResizeObserver alive and get released properly?In the example, what happens outside the scope? Per [1],
Visit Children – When JavaScriptCore’s garbage collection visits some JS wrapper during the marking phase, visit another JS wrapper or JS object that needs to be kept alive.To keep
Reachable from Opaque Roots – Tell JavaScriptCore’s garbage collection that a JS wrapper is reachable from an opaque root which was added to the set of opaque roots during marking phase.
JSResizeObserver
itself alive, use the second mechanism “Reachable from Opaque Roots”, custom isReachableFromOpaqueRoots
. It checks the target of m_observations
, m_activeObservationTargets
, and m_targetsWaitingForFirstObservation
, if the targets containsWebCoreOpaqueRoot
, the JSResizeObserver
won’t be released. Note that it uses GCReachableRef
, which means the targets won’t be released either. The timeline of m_activeObservationTargets
is from gatherObservations
to deliverObservations
. And the timeline of m_targetsWaitingForFirstObservation
is from observe()
to the first time deliverObservations
. So JSResizeObserver
won’t be released if the observed targets are alive, or it has size changed observations not delivered, or it has any target not delivered at all.
ResizeObservation
ResizeObservation is owned by ResizeObserver, so it will be released if ResizeObserver is released.Keep `JSCallbackDataWeak* m_data` in `JSResizeObserverCallback` alive
ThoughResizeObserver
hold ResizeObserverCallback
by RefPtr
, it is a IsWeakCallback
.JSCallbackDataWeak* m_data;
in JSResizeObserverCallback
does not keep align with JSResizeObserver
.Take a close look at
JSCallbackDataWeak
, there is JSC::Weak<JSC::JSObject> m_callback;
.To keep
JSResizeObserver
itself alive, ResizeObserver
using the first mechanism “Visit Children”.In
JSResizeObserver::visitAdditionalChildren
, it adds m_callback
to Visitor
, see:
void JSCallbackDataWeak::visitJSFunction(Visitor& visitor) { visitor.append(m_callback); }
JSResizeObserverEntry
LikeJSResizeObserver
and callback, JSResizeObserverEntry
would make sure the target and contentRect
won’t be released when it is alive.
void JSResizeObserverEntry::visitAdditionalChildren(Visitor& visitor) { addWebCoreOpaqueRoot(visitor, wrapped().target()); addWebCoreOpaqueRoot(visitor, wrapped().contentRect()); }ResizeObserverEntry is RefCounted.
class ResizeObserverEntry : public RefCounted<ResizeObserverEntry>It is created in
ResizeObserver::deliverObservations
, and passed to the JS callback, if JS callback doesn’t keep it, it will be released when the function is finished.
[1] https://github.com/WebKit/WebKit/blob/main/Introduction.md#js-wrapper-lifecycle-management