Bug 190235

Summary: IntersectionObserver doesn't keep target's JS wrapper alive
Product: WebKit Reporter: Ryosuke Niwa <rniwa>
Component: Layout and RenderingAssignee: Ali Juma <ajuma>
Status: RESOLVED FIXED    
Severity: Normal CC: ajuma, bfulgham, cdumez, commit-queue, dino, esprehn+autocc, ews-watchlist, ggaren, graouts, kondapallykalyan, simon.fraser, webkit-bug-importer, zalan
Priority: P2 Keywords: InRadar
Version: WebKit Nightly Build   
Hardware: Unspecified   
OS: Unspecified   
Bug Depends on:    
Bug Blocks: 159475    
Attachments:
Description Flags
Patch
none
Patch
none
Patch
none
Patch
none
Patch for landing none

Ryosuke Niwa
Reported 2018-10-02 22:55:04 PDT
When an element is enqueued as an IntersectionObserverEntry and subsequently removed from the document, there is no way for JSC GC to know the JS wrapper of the element needs to be kept alive. As such, the JS wrapper of an element can be erroneously collected.
Attachments
Patch (15.41 KB, patch)
2018-10-04 10:53 PDT, Ali Juma
no flags
Patch (15.44 KB, patch)
2018-10-04 12:20 PDT, Ali Juma
no flags
Patch (19.58 KB, patch)
2018-10-22 14:38 PDT, Ali Juma
no flags
Patch (20.16 KB, patch)
2018-11-05 11:08 PST, Ali Juma
no flags
Patch for landing (29.26 KB, patch)
2018-11-06 11:52 PST, Ali Juma
no flags
Ryosuke Niwa
Comment 1 2018-10-02 23:02:32 PDT
We need to deploy GCReachableRef for every target kept alive in IntersectionObserver as I've done in https://trac.webkit.org/changeset/236781, and need to add the root(m_target) as an opaque root in JSIntersectionObserverEntry::visitAdditionalChildren. (Add JSCustomMarkFunction to the IDL file). IntersectionObserver itself has a bug that it doesn't keep its callback alive while there are pending records. We need to override its visitAdditionalChildren and isReachableFromOpaqueRoots as done in JSMutationObserverCustom.
Radar WebKit Bug Importer
Comment 2 2018-10-02 23:03:30 PDT
Ryosuke Niwa
Comment 3 2018-10-02 23:04:39 PDT
Note that this bug will have a dire consequence of turning a custom element into a HTMLUnknownElement so we MUST fix this bug before we can turn the feature on by default.
Ali Juma
Comment 4 2018-10-04 10:53:04 PDT
Ali Juma
Comment 5 2018-10-04 10:56:57 PDT
(In reply to Ryosuke Niwa from comment #1) > > IntersectionObserver itself has a bug that it doesn't keep its callback > alive while there are pending records. We need to override its > visitAdditionalChildren and isReachableFromOpaqueRoots as done in > JSMutationObserverCustom. MutationObserver uses a WeakCallback (see MutationCallback.idl), but IntersectionObserver does not. So the callback won't get GC'd, as long as we don't clear IntersectionObserver::m_callback. There was, however, a bug in IntersectionObserver::hasPendingActivity, since this didn't account for pending records (so the IntersectionObserver itself could have gotten GC'd while it still had pending records). That bug is fixed in the patch.
Geoffrey Garen
Comment 6 2018-10-04 12:00:40 PDT
Comment on attachment 351601 [details] Patch View in context: https://bugs.webkit.org/attachment.cgi?id=351601&action=review > Source/WebCore/bindings/js/JSIntersectionObserverEntryCustom.cpp:33 > + visitor.addOpaqueRoot(wrapped().target()); This looks wrong. The opaque root needs to be the root of the data structure. "root(wrapped().target())" will probably do what you need.
Ali Juma
Comment 7 2018-10-04 12:20:06 PDT
Ali Juma
Comment 8 2018-10-04 12:20:48 PDT
Comment on attachment 351601 [details] Patch View in context: https://bugs.webkit.org/attachment.cgi?id=351601&action=review >> Source/WebCore/bindings/js/JSIntersectionObserverEntryCustom.cpp:33 >> + visitor.addOpaqueRoot(wrapped().target()); > > This looks wrong. The opaque root needs to be the root of the data structure. "root(wrapped().target())" will probably do what you need. Ah, thanks! Fixed now.
Ryosuke Niwa
Comment 9 2018-10-04 22:36:33 PDT
Comment on attachment 351612 [details] Patch View in context: https://bugs.webkit.org/attachment.cgi?id=351612&action=review > Source/WebCore/ChangeLog:19 > + No new tests, since the races fixed here are difficult to test reliably. See r236801 and r236825 for similar > + fixes to MutationObserver that also had to land without tests. See my tests in https://trac.webkit.org/changeset/236850/webkit. We should be able to add similar tests.
Ali Juma
Comment 10 2018-10-22 14:38:45 PDT
Created attachment 352910 [details] Patch Added tests
Ali Juma
Comment 11 2018-11-05 11:08:07 PST
Simon Fraser (smfr)
Comment 12 2018-11-05 11:32:33 PST
Chris, could you review?
Ryosuke Niwa
Comment 13 2018-11-05 20:42:13 PST
Comment on attachment 353889 [details] Patch View in context: https://bugs.webkit.org/attachment.cgi?id=353889&action=review r=me with one additional test. > Source/WebCore/page/IntersectionObserverEntry.h:62 > RefPtr<DOMRectReadOnly> rootBounds() const { return m_rootBounds; } > RefPtr<DOMRectReadOnly> boundingClientRect() const { return m_boundingClientRect; } > RefPtr<DOMRectReadOnly> intersectionRect() const { return m_intersectionRect; } DOMRectReadOnly have the same issue. We need to visit these objects' wrappers as well. Otherwise, properties added on DOMRectReadOnly would be collected. But that could be addressed in a separate patch. > LayoutTests/intersection-observer/root-element-deleted.html:23 > requestAnimationFrame(t.step_func_done(function() { > + observer.takeRecords(); We should add another test which doesn't rely on takeRecords to make sure deliver() path results in the clearing of GCReachableRef as well.
Ali Juma
Comment 14 2018-11-06 11:52:29 PST
Created attachment 353982 [details] Patch for landing
Ali Juma
Comment 15 2018-11-06 11:54:03 PST
(In reply to Ryosuke Niwa from comment #13) > Comment on attachment 353889 [details] > Patch > > View in context: > https://bugs.webkit.org/attachment.cgi?id=353889&action=review > > r=me with one additional test. > > > Source/WebCore/page/IntersectionObserverEntry.h:62 > > RefPtr<DOMRectReadOnly> rootBounds() const { return m_rootBounds; } > > RefPtr<DOMRectReadOnly> boundingClientRect() const { return m_boundingClientRect; } > > RefPtr<DOMRectReadOnly> intersectionRect() const { return m_intersectionRect; } > > DOMRectReadOnly have the same issue. > We need to visit these objects' wrappers as well. > Otherwise, properties added on DOMRectReadOnly would be collected. > But that could be addressed in a separate patch. Thanks for pointing this out, I'll fix this in a follow-up patch. > > LayoutTests/intersection-observer/root-element-deleted.html:23 > > requestAnimationFrame(t.step_func_done(function() { > > + observer.takeRecords(); > > We should add another test which doesn't rely on takeRecords to make sure > deliver() path results in the clearing of GCReachableRef as well. Added a test.
Ryosuke Niwa
Comment 16 2018-11-06 11:59:24 PST
(In reply to Ali Juma from comment #15) > (In reply to Ryosuke Niwa from comment #13) > > Comment on attachment 353889 [details] > > Patch > > > > View in context: > > https://bugs.webkit.org/attachment.cgi?id=353889&action=review > > > > r=me with one additional test. > > > > > Source/WebCore/page/IntersectionObserverEntry.h:62 > > > RefPtr<DOMRectReadOnly> rootBounds() const { return m_rootBounds; } > > > RefPtr<DOMRectReadOnly> boundingClientRect() const { return m_boundingClientRect; } > > > RefPtr<DOMRectReadOnly> intersectionRect() const { return m_intersectionRect; } > > > > DOMRectReadOnly have the same issue. > > We need to visit these objects' wrappers as well. > > Otherwise, properties added on DOMRectReadOnly would be collected. > > But that could be addressed in a separate patch. > > Thanks for pointing this out, I'll fix this in a follow-up patch. Be sure to make these methods return a pointer instead when you do that since visitAdditionalChildren can be called from non-main threads.
WebKit Commit Bot
Comment 17 2018-11-06 12:52:59 PST
Comment on attachment 353982 [details] Patch for landing Clearing flags on attachment: 353982 Committed r237880: <https://trac.webkit.org/changeset/237880>
WebKit Commit Bot
Comment 18 2018-11-06 12:53:02 PST
All reviewed patches have been landed. Closing bug.
Note You need to log in before you can comment on or make changes to this bug.