Changeset 275187 in webkit


Ignore:
Timestamp:
Mar 29, 2021 3:55:49 PM (16 months ago)
Author:
Devin Rousso
Message:

Propagate user gestures through requestAnimationFrame just like setTimeout
https://bugs.webkit.org/show_bug.cgi?id=223775
<rdar://problem/75860868>

Reviewed by Geoffrey Garen.

Source/WebCore:

setTimeout and requestAnimationFrame are used somewhat interchangeably on the web.
There should be similar features/affordances for both so that if a developer decides to use
a display-linked animation "loop" instead of a strictly time-based delay (or even a "loop")
they're able to do similar things in the callback/handler.

Test: fast/animation/request-animation-frame-propagate-user-gesture.html

  • dom/ScriptedAnimationController.h:
  • dom/ScriptedAnimationController.cpp:

(WebCore::ScriptedAnimationController::resume):
(WebCore::ScriptedAnimationController::registerCallback):
(WebCore::ScriptedAnimationController::cancelCallback):
(WebCore::ScriptedAnimationController::serviceRequestAnimationFrameCallbacks):
Create a private struct for holding more data in the list of callbacks than just the
callback itself. For now, the only other data saved is a RefPtr<UserGestureToken>.

  • testing/Internals.idl:
  • testing/Internals.h:
  • testing/Internals.cpp:

(WebCore::Internals::withoutUserGesture): Added.
Add a way for tests to guaranteed run arbitrary code without a user gesture.

LayoutTests:

  • editing/pasteboard/dom-paste/dom-paste-requires-user-gesture.html:
  • fast/animation/request-animation-frame-propagate-user-gesture.html: Added.
  • fast/animation/request-animation-frame-propagate-user-gesture-expected.txt: Added.
Location:
trunk
Files:
2 added
8 edited

Legend:

Unmodified
Added
Removed
  • trunk/LayoutTests/ChangeLog

    r275179 r275187  
     12021-03-29  Devin Rousso  <drousso@apple.com>
     2
     3        Propagate user gestures through `requestAnimationFrame` just like `setTimeout`
     4        https://bugs.webkit.org/show_bug.cgi?id=223775
     5        <rdar://problem/75860868>
     6
     7        Reviewed by Geoffrey Garen.
     8
     9        * editing/pasteboard/dom-paste/dom-paste-requires-user-gesture.html:
     10        * fast/animation/request-animation-frame-propagate-user-gesture.html: Added.
     11        * fast/animation/request-animation-frame-propagate-user-gesture-expected.txt: Added.
     12
    1132021-03-29  Robert Jenner  <jenner@apple.com>
    214
  • trunk/LayoutTests/editing/pasteboard/dom-paste/dom-paste-requires-user-gesture.html

    r268400 r275187  
    5555    editor.focus();
    5656    shouldBe("document.execCommand('Paste')", "true");
    57     requestAnimationFrame(() => {
     57    internals.withoutUserGesture(() => {
    5858        shouldBe("document.execCommand('Paste')", "false")
    5959        shouldBeEqualToString("editor.textContent", "Click here to copy");
  • trunk/Source/WebCore/ChangeLog

    r275185 r275187  
     12021-03-29  Devin Rousso  <drousso@apple.com>
     2
     3        Propagate user gestures through `requestAnimationFrame` just like `setTimeout`
     4        https://bugs.webkit.org/show_bug.cgi?id=223775
     5        <rdar://problem/75860868>
     6
     7        Reviewed by Geoffrey Garen.
     8
     9        `setTimeout` and `requestAnimationFrame` are used somewhat interchangeably on the web.
     10        There should be similar features/affordances for both so that if a developer decides to use
     11        a display-linked animation "loop" instead of a strictly time-based delay (or even a "loop")
     12        they're able to do similar things in the callback/handler.
     13
     14        Test: fast/animation/request-animation-frame-propagate-user-gesture.html
     15
     16        * dom/ScriptedAnimationController.h:
     17        * dom/ScriptedAnimationController.cpp:
     18        (WebCore::ScriptedAnimationController::resume):
     19        (WebCore::ScriptedAnimationController::registerCallback):
     20        (WebCore::ScriptedAnimationController::cancelCallback):
     21        (WebCore::ScriptedAnimationController::serviceRequestAnimationFrameCallbacks):
     22        Create a private struct for holding more data in the list of callbacks than just the
     23        callback itself. For now, the only other data saved is a `RefPtr<UserGestureToken>`.
     24
     25        * testing/Internals.idl:
     26        * testing/Internals.h:
     27        * testing/Internals.cpp:
     28        (WebCore::Internals::withoutUserGesture): Added.
     29        Add a way for tests to guaranteed run arbitrary code without a user gesture.
     30
    1312021-03-29  Said Abou-Hallawa  <said@apple.com>
    232
  • trunk/Source/WebCore/dom/ScriptedAnimationController.cpp

    r273773 r275187  
    3333#include "RequestAnimationFrameCallback.h"
    3434#include "Settings.h"
     35#include "UserGestureIndicator.h"
    3536#include <wtf/Ref.h>
    3637#include <wtf/SystemTracing.h>
     
    6263        --m_suspendCount;
    6364
    64     if (!m_suspendCount && m_callbacks.size())
     65    if (!m_suspendCount && m_callbackDataList.size())
    6566        scheduleAnimation();
    6667}
     
    112113    callback->m_firedOrCancelled = false;
    113114    callback->m_id = callbackId;
    114     m_callbacks.append(WTFMove(callback));
     115    m_callbackDataList.append({ WTFMove(callback), UserGestureIndicator::currentUserGesture() });
    115116
    116117    if (m_document)
     
    124125void ScriptedAnimationController::cancelCallback(CallbackId callbackId)
    125126{
    126     bool cancelled = m_callbacks.removeFirstMatching([callbackId](auto& callback) {
    127         if (callback->m_id != callbackId)
     127    bool cancelled = m_callbackDataList.removeFirstMatching([callbackId](auto& data) {
     128        if (data.callback->m_id != callbackId)
    128129            return false;
    129         callback->m_firedOrCancelled = true;
     130        data.callback->m_firedOrCancelled = true;
    130131        return true;
    131132    });
     
    137138void ScriptedAnimationController::serviceRequestAnimationFrameCallbacks(ReducedResolutionSeconds timestamp)
    138139{
    139     if (!m_callbacks.size() || m_suspendCount || !requestAnimationFrameEnabled())
     140    if (!m_callbackDataList.size() || m_suspendCount || !requestAnimationFrameEnabled())
    140141        return;
    141142
     
    153154    // First, generate a list of callbacks to consider.  Callbacks registered from this point
    154155    // on are considered only for the "next" frame, not this one.
    155     CallbackList callbacks(m_callbacks);
     156    Vector<CallbackData> callbackDataList(m_callbackDataList);
    156157
    157158    // Invoking callbacks may detach elements from our document, which clears the document's
     
    160161    Ref<Document> protectedDocument(*m_document);
    161162
    162     for (auto& callback : callbacks) {
     163    for (auto& [callback, userGestureTokenToForward] : callbackDataList) {
    163164        if (callback->m_firedOrCancelled)
    164165            continue;
    165166        callback->m_firedOrCancelled = true;
     167
     168        if (userGestureTokenToForward && userGestureTokenToForward->hasExpired(UserGestureToken::maximumIntervalForUserGestureForwarding))
     169            userGestureTokenToForward = nullptr;
     170        UserGestureIndicator gestureIndicator(userGestureTokenToForward);
    166171
    167172        InspectorInstrumentation::willFireAnimationFrame(protectedDocument, callback->m_id);
     
    171176
    172177    // Remove any callbacks we fired from the list of pending callbacks.
    173     m_callbacks.removeAllMatching([](auto& callback) {
    174         return callback->m_firedOrCancelled;
     178    m_callbackDataList.removeAllMatching([](auto& data) {
     179        return data.callback->m_firedOrCancelled;
    175180    });
    176181
    177182    m_lastAnimationFrameTimestamp = timestamp;
    178183
    179     if (m_callbacks.size())
     184    if (m_callbackDataList.size())
    180185        scheduleAnimation();
    181186}
  • trunk/Source/WebCore/dom/ScriptedAnimationController.h

    r261113 r275187  
    4040class Page;
    4141class RequestAnimationFrameCallback;
     42class UserGestureToken;
    4243
    4344class ScriptedAnimationController : public RefCounted<ScriptedAnimationController>
     
    7576    void scheduleAnimation();
    7677
    77     using CallbackList = Vector<RefPtr<RequestAnimationFrameCallback>>;
    78     CallbackList m_callbacks;
     78    struct CallbackData {
     79        Ref<RequestAnimationFrameCallback> callback;
     80        RefPtr<UserGestureToken> userGestureTokenToForward;
     81    };
     82    Vector<CallbackData> m_callbackDataList;
    7983
    8084    WeakPtr<Document> m_document;
  • trunk/Source/WebCore/testing/Internals.cpp

    r275176 r275187  
    48534853}
    48544854
     4855void Internals::withoutUserGesture(RefPtr<VoidCallback>&& callback)
     4856{
     4857    UserGestureIndicator gestureIndicator(NotProcessingUserGesture, contextDocument());
     4858    callback->handleEvent();
     4859}
     4860
    48554861bool Internals::userIsInteracting()
    48564862{
  • trunk/Source/WebCore/testing/Internals.h

    r275151 r275187  
    746746
    747747    void withUserGesture(RefPtr<VoidCallback>&&);
     748    void withoutUserGesture(RefPtr<VoidCallback>&&);
    748749
    749750    bool userIsInteracting();
  • trunk/Source/WebCore/testing/Internals.idl

    r275151 r275187  
    783783
    784784    undefined withUserGesture(VoidCallback callback);
     785    undefined withoutUserGesture(VoidCallback callback);
    785786
    786787    boolean userIsInteracting();
Note: See TracChangeset for help on using the changeset viewer.