Bug 203088

Summary: WebAnimation should never prevent entering the back/forward cache
Product: WebKit Reporter: Chris Dumez <cdumez>
Component: WebCore Misc.Assignee: Antoine Quint <graouts>
Status: RESOLVED FIXED    
Severity: Normal CC: commit-queue, dino, graouts, koivisto, webkit-bug-importer
Priority: P2 Keywords: InRadar
Version: WebKit Nightly Build   
Hardware: Unspecified   
OS: Unspecified   
See Also: https://bugs.webkit.org/show_bug.cgi?id=203075
Bug Depends on:    
Bug Blocks: 202293    
Attachments:
Description Flags
Patch
none
Patch
none
Patch none

Description Chris Dumez 2019-10-17 09:41:06 PDT
WebAnimation should never prevent entering the back/forward cache.
Comment 1 Radar WebKit Bug Importer 2019-10-17 10:01:53 PDT
<rdar://problem/56374249>
Comment 2 Antoine Quint 2019-10-21 02:09:15 PDT
So to fix we need to:

- remove WebAnimation::shouldPreventEnteringBackForwardCache_DEPRECATED()
- subclass ActiveDOMObject::suspend() and ActiveDOMObject::resume()
- queue events between calls to suspend() and resume() or use a WindowEventLoop (though I'm not sure that is always possible since a WebAnimation may not be associated with a Document directly)
- don't resolve promises while the document is suspended

We might need something similar for DocumentTimeline, although it's possible that already happens.
Comment 3 Chris Dumez 2019-10-21 07:32:00 PDT
(In reply to Antoine Quint from comment #2)
> So to fix we need to:
> 
> - remove WebAnimation::shouldPreventEnteringBackForwardCache_DEPRECATED()
> - subclass ActiveDOMObject::suspend() and ActiveDOMObject::resume()
> - queue events between calls to suspend() and resume() or use a
> WindowEventLoop (though I'm not sure that is always possible since a
> WebAnimation may not be associated with a Document directly)

ActiveDOMObject need a script execution context when constructed. So presumably WebAnimation has one on construction.

If you cannot use WindowEventLoop, we have GenericEventQueue & SuspendableTaskQueue.
In your case, since you have both promises and events, SuspendableTaskQueue may be better. You can add such data member and then simply enqueue lambdas that either dispatch events or resolve promises. Those data types are PageCache aware and will delay things during suspension for you.

> - don't resolve promises while the document is suspended
> 
> We might need something similar for DocumentTimeline, although it's possible
> that already happens.
Comment 4 Antoine Quint 2019-10-21 09:45:25 PDT
(In reply to Chris Dumez from comment #3)
> (In reply to Antoine Quint from comment #2)
> > So to fix we need to:
> > 
> > - remove WebAnimation::shouldPreventEnteringBackForwardCache_DEPRECATED()
> > - subclass ActiveDOMObject::suspend() and ActiveDOMObject::resume()
> > - queue events between calls to suspend() and resume() or use a
> > WindowEventLoop (though I'm not sure that is always possible since a
> > WebAnimation may not be associated with a Document directly)
> 
> ActiveDOMObject need a script execution context when constructed. So
> presumably WebAnimation has one on construction.
> 
> If you cannot use WindowEventLoop, we have GenericEventQueue &
> SuspendableTaskQueue.
> In your case, since you have both promises and events, SuspendableTaskQueue
> may be better. You can add such data member and then simply enqueue lambdas
> that either dispatch events or resolve promises. Those data types are
> PageCache aware and will delay things during suspension for you.

Right, we have a Document at creation time. It may not necessarily match the animation's associated document, but for the purpose of the back/forward cache, that should be fine since they will all be associated with the same top-level navigation.

I will look into SuspendableTaskQueue, it seems to be the most appropriate for my use. Thanks for the tips.
Comment 5 Antoine Quint 2019-10-22 10:09:50 PDT
Created attachment 381558 [details]
Patch
Comment 6 Chris Dumez 2019-10-22 10:24:24 PDT
Comment on attachment 381558 [details]
Patch

View in context: https://bugs.webkit.org/attachment.cgi?id=381558&action=review

> Source/WebCore/ChangeLog:11
> +        We remove the Web Animation subclass of the deprecated method ActiveDOMObject::shouldPreventEnteringBackForwardCache_DEPRECATED()

s/subclass/override

> Source/WebCore/animation/WebAnimation.cpp:-616
> -        callOnMainThread([this, pendingActivity = makePendingActivity(*this), event = WTFMove(event)]() {

You likely want to add `|| m_taskQueue->hasPendingTasks()` to WebAnimation::hasPendingActivity(), to make sure that your JS wrapper does not get collected until the event actually fires.
You used to enforce this behavior via a `pendingActivity = makePendingActivity(*this)` here.

> Source/WebCore/animation/WebAnimation.cpp:618
> +            this->dispatchEvent(event);

this-> should not be needed.

> Source/WebCore/animation/WebAnimation.cpp:1163
> +    ActiveDOMObject::suspend(reasonForSuspension);

Not needed.

> Source/WebCore/animation/WebAnimation.cpp:1169
> +    ActiveDOMObject::resume();

Not needed.

> LayoutTests/webanimations/animation-page-cache.html:52
> +        // Load a new page, and let it go back after 500ms.

This says 500ms but your next line says 1 second.
Either way, this seems like a pretty slow test, it'd be good to shorten this if possible.
Comment 7 Antoine Quint 2019-10-22 10:58:30 PDT
(In reply to Chris Dumez from comment #6)
> Comment on attachment 381558 [details]
> Patch
> 
> View in context:
> https://bugs.webkit.org/attachment.cgi?id=381558&action=review
> 
> > LayoutTests/webanimations/animation-page-cache.html:52
> > +        // Load a new page, and let it go back after 500ms.
> 
> This says 500ms but your next line says 1 second.
> Either way, this seems like a pretty slow test, it'd be good to shorten this
> if possible.

It is tricky, there needs to be some real delay if we want to have a guarantee that we navigate forward and backward while the animation is live. I think we can shorten it a bit, which I will do in the next patch, but it is necessary for some time to elapse.
Comment 8 Antoine Quint 2019-10-22 11:00:24 PDT
Created attachment 381566 [details]
Patch
Comment 9 Antoine Quint 2019-10-22 11:07:50 PDT
(In reply to Chris Dumez from comment #6)
> Comment on attachment 381558 [details]
> Patch
> 
> View in context:
> https://bugs.webkit.org/attachment.cgi?id=381558&action=review
> 
> > Source/WebCore/ChangeLog:11
> > +        We remove the Web Animation subclass of the deprecated method ActiveDOMObject::shouldPreventEnteringBackForwardCache_DEPRECATED()
> 
> s/subclass/override
> 
> > Source/WebCore/animation/WebAnimation.cpp:-616
> > -        callOnMainThread([this, pendingActivity = makePendingActivity(*this), event = WTFMove(event)]() {
> 
> You likely want to add `|| m_taskQueue->hasPendingTasks()` to
> WebAnimation::hasPendingActivity(), to make sure that your JS wrapper does
> not get collected until the event actually fires.
> You used to enforce this behavior via a `pendingActivity =
> makePendingActivity(*this)` here.


I'll have to research this some more, but adding this check makes webanimations/leak-document-with-web-animation.html regress.
Comment 10 Chris Dumez 2019-10-29 13:06:29 PDT
I found the bug.
Comment 11 Chris Dumez 2019-10-29 13:07:52 PDT
Created attachment 382212 [details]
Patch
Comment 12 Chris Dumez 2019-10-29 13:08:29 PDT
Comment on attachment 382212 [details]
Patch

View in context: https://bugs.webkit.org/attachment.cgi?id=382212&action=review

> Source/WebCore/platform/SuspendableTaskQueue.h:61
> +    bool hasPendingTasks() const { return !m_pendingTasks.isEmpty(); }

This was the bug, wasn't your fault :)
Comment 13 WebKit Commit Bot 2019-10-29 16:07:23 PDT
Comment on attachment 382212 [details]
Patch

Clearing flags on attachment: 382212

Committed r251742: <https://trac.webkit.org/changeset/251742>
Comment 14 WebKit Commit Bot 2019-10-29 16:07:25 PDT
All reviewed patches have been landed.  Closing bug.
Comment 15 Antoine Quint 2019-10-30 01:50:22 PDT
Thanks for wrapping this up Chris!