{"id":7,"date":"2023-02-13T14:33:32","date_gmt":"2023-02-13T14:33:32","guid":{"rendered":"https:\/\/blogs.igalia.com\/cchen\/?p=7"},"modified":"2023-02-13T14:45:14","modified_gmt":"2023-02-13T14:45:14","slug":"how-does-resizeobserver-get-garbage-collected-in-webkit","status":"publish","type":"post","link":"https:\/\/blogs.igalia.com\/cchen\/2023\/02\/13\/how-does-resizeobserver-get-garbage-collected-in-webkit\/","title":{"rendered":"How does ResizeObserver get garbage collected in WebKit?"},"content":{"rendered":"\n<code>ResizeObserver<\/code> is an interface provided by browsers to detect the size change of a target. It would call the js callback when the size changes.\n\n\n\nIn the callback function, you can do anything including delete the target. So how <code>ResizeObserver <\/code>related object is managed?\n\n\n\n\n<h3 class=\"wp-block-heading\">ResizeObserver related objects<\/h3>\n\n\n\n\nLet&#8217;s take a look at a simple example.\n\n\n\n\n<pre class=\"wp-block-preformatted\"><code>&lt;div id=\"target\"&gt;&lt;\/div&gt;\n&lt;script&gt;\n{\n  var ro = new ResizeObserver( entries =&gt; {});\n  ro.observe(document.getElementById('target'));\n} \/\/ end of the scope\n&lt;\/script&gt;<\/code><\/pre>\n\n\n\n\n\n<ul class=\"wp-block-list\"><li>ro: a JSResizeObserver which is a js wrapper of ResizeObserver,<\/li><li>callback: JSResizeObserverCallback,<\/li><li>entries: JSResizeObserverEntry,<\/li><li>and observe() would create a ResizeObservation,<\/li><li>then document and the target.<\/li><\/ul>\n\n\n\n\nSo how these objects organized? Let&#8217;s take a look at the code.<br>&#8211; <code>ResizeObserver<\/code> and <code>Document<\/code><br>It needs <code>Document<\/code> to create <code>ResizeObserver<\/code>, and store document in <code>WeakPtr&lt;Document, WeakPtrImplWithEventTargetData&gt; m_document;<\/code>.<br>On the other hand, when <code>observe()<\/code>, <code>m_document-&gt;addResizeObserver(*this)<\/code>, <code>ResizeObserver<\/code> is stored in <code>Vector&lt;WeakPtr&lt;ResizeObserver&gt;&gt; m_resizeObservers;<\/code>.<br>So <code>ResizeObserver<\/code> and <code>Document<\/code> both hold each other by <code>WeakPtr<\/code>.<br>&#8211; <code>ResizeObserver<\/code> and <code>Element<\/code><br>When <code>observe()<\/code>, <code>ResizeObservation<\/code> is created, and it is stored in ResizeObserver by <code>Vector&lt;Ref&lt;ResizeObservation&gt;&gt; m_observations;<\/code>.<br><code>ResizeObservation<\/code> holds Element by <code>WeakPtr<\/code>, <code>WeakPtr&lt;Element, WeakPtrImplWithEventTargetData&gt; m_target<\/code>.<br>On the other hand, <code>target.ensureResizeObserverData()<\/code>, Element creates <code>ResizeObserverData<\/code>, which holds <code>ResizeObserver<\/code> by <code>WeakPtr<\/code>, <code>Vector&lt;WeakPtr&lt;ResizeObserver&gt;&gt; observers;<\/code>.<br>So the connection between <code>ResizeObserver<\/code> and <code>element<\/code> is through <code>WeakPtr<\/code>.\n\n\n\n\n<h3 class=\"wp-block-heading\">Keep JSResizeObserver alive<\/h3>\n\n\n\n\nBoth Document and Element hold ResizeObserver by <code>WeakPtr<\/code>, how do we keep ResizeObserver alive and get released properly?<br>In the example, what happens outside the scope?\n\n\n\nPer [1],\n\n\n\n\n<blockquote class=\"wp-block-quote is-layout-flow wp-block-quote-is-layout-flow\"><strong>Visit Children<\/strong>\u00a0&#8211; When JavaScriptCore\u2019s garbage collection visits some JS wrapper during the\u00a0<a href=\"https:\/\/en.wikipedia.org\/wiki\/Tracing_garbage_collection#Basic_algorithm\">marking phase<\/a>, visit another JS wrapper or JS object that needs to be kept alive.<br><strong>Reachable from Opaque Roots<\/strong>\u00a0&#8211; Tell JavaScriptCore\u2019s garbage collection that a JS wrapper is reachable from an opaque root which was added to the set of opaque roots during marking phase.<\/p><\/blockquote>\n\n\n\n\nTo keep <code>JSResizeObserver<\/code> itself alive, use the second mechanism &#8220;Reachable from Opaque Roots&#8221;, custom <code>isReachableFromOpaqueRoots<\/code>. It checks the target of <code>m_observations<\/code>, <code>m_activeObservationTargets<\/code>, and <code>m_targetsWaitingForFirstObservation<\/code>, if the targets <code>containsWebCoreOpaqueRoot<\/code>, the <code>JSResizeObserver<\/code> won&#8217;t be released. Note that it uses <code>GCReachableRef<\/code>, which means the targets won&#8217;t be released either. The timeline of <code>m_activeObservationTargets<\/code> is from <code>gatherObservations<\/code> to <code>deliverObservations<\/code>. And the timeline of <code>m_targetsWaitingForFirstObservation<\/code> is from <code>observe()<\/code> to the first time <code>deliverObservations<\/code>. So <code>JSResizeObserver<\/code> won&#8217;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.\n\n\n\n\n<h3 class=\"wp-block-heading\">ResizeObservation<\/h3>\n\n\n\n\nResizeObservation is owned by ResizeObserver, so it will be released if ResizeObserver is released.\n\n\n\n\n<h3 class=\"wp-block-heading\">Keep `JSCallbackDataWeak* m_data` in `JSResizeObserverCallback` alive<\/h3>\n\n\n\n\nThough <code>ResizeObserver<\/code> hold <code>ResizeObserverCallback<\/code> by <code>RefPtr<\/code>, it is a <code>IsWeakCallback<\/code>.<br><br><code>JSCallbackDataWeak* m_data;<\/code> in <code>JSResizeObserverCallback<\/code> does not keep align with <code>JSResizeObserver<\/code>.<br>Take a close look at <code>JSCallbackDataWeak<\/code>, there is <code>JSC::Weak&lt;JSC::JSObject&gt; m_callback;<\/code>.<br><br>To keep <code>JSResizeObserver<\/code> itself alive, <code>ResizeObserver<\/code> using the first mechanism &#8220;Visit Children&#8221;.<br>In <code>JSResizeObserver::visitAdditionalChildren<\/code>, it adds <code>m_callback<\/code> to <code>Visitor<\/code>, see:\n\n\n\n\n<pre class=\"wp-block-preformatted\">void JSCallbackDataWeak::visitJSFunction(Visitor&amp; visitor)\n{\n    visitor.append(m_callback);\n}<\/pre>\n\n\n\n\n\n<h3 class=\"wp-block-heading\">JSResizeObserverEntry<\/h3>\n\n\n\n\nLike <code>JSResizeObserver<\/code> and callback, <code>JSResizeObserverEntry<\/code> would make sure the target and <code>contentRect<\/code> won&#8217;t be released when it is alive.\n\n\n\n\n<pre class=\"wp-block-preformatted\">void JSResizeObserverEntry::visitAdditionalChildren(Visitor&amp; visitor)\n{\n    addWebCoreOpaqueRoot(visitor, wrapped().target());\n    addWebCoreOpaqueRoot(visitor, wrapped().contentRect());\n}<\/pre>\n\n\n\n\nResizeObserverEntry is RefCounted.\n\n\n\n\n<pre class=\"wp-block-preformatted\">class ResizeObserverEntry : public RefCounted&lt;ResizeObserverEntry&gt;<\/pre>\n\n\n\n\nIt is created in <code>ResizeObserver::deliverObservations<\/code>, and passed to the JS callback, if JS callback doesn&#8217;t keep it, it will be released when the function is finished.\n\n\n\n\n<pre class=\"wp-block-preformatted\">[1] https:\/\/github.com\/WebKit\/WebKit\/blob\/main\/Introduction.md#js-wrapper-lifecycle-management<\/pre>\n\n\n\n\n\n<pre class=\"wp-block-preformatted\"><\/pre>\n\n","protected":false},"excerpt":{"rendered":"<p>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&#8217;s take a look at a simple example. &hellip; <\/p>\n<p class=\"link-more\"><a href=\"https:\/\/blogs.igalia.com\/cchen\/2023\/02\/13\/how-does-resizeobserver-get-garbage-collected-in-webkit\/\" class=\"more-link\">Continue reading<span class=\"screen-reader-text\"> &#8220;How does ResizeObserver get garbage collected in WebKit?&#8221;<\/span><\/a><\/p>\n","protected":false},"author":59,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[1],"tags":[],"class_list":["post-7","post","type-post","status-publish","format-standard","hentry","category-uncategorized","entry"],"_links":{"self":[{"href":"https:\/\/blogs.igalia.com\/cchen\/wp-json\/wp\/v2\/posts\/7","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/blogs.igalia.com\/cchen\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/blogs.igalia.com\/cchen\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/blogs.igalia.com\/cchen\/wp-json\/wp\/v2\/users\/59"}],"replies":[{"embeddable":true,"href":"https:\/\/blogs.igalia.com\/cchen\/wp-json\/wp\/v2\/comments?post=7"}],"version-history":[{"count":11,"href":"https:\/\/blogs.igalia.com\/cchen\/wp-json\/wp\/v2\/posts\/7\/revisions"}],"predecessor-version":[{"id":19,"href":"https:\/\/blogs.igalia.com\/cchen\/wp-json\/wp\/v2\/posts\/7\/revisions\/19"}],"wp:attachment":[{"href":"https:\/\/blogs.igalia.com\/cchen\/wp-json\/wp\/v2\/media?parent=7"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/blogs.igalia.com\/cchen\/wp-json\/wp\/v2\/categories?post=7"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/blogs.igalia.com\/cchen\/wp-json\/wp\/v2\/tags?post=7"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}