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 DocumentIt 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 ElementWhen
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