Changeset 260800 in webkit


Ignore:
Timestamp:
Apr 27, 2020 5:22:49 PM (4 years ago)
Author:
commit-queue@webkit.org
Message:

Timestamps should be the same for all rendering update steps
https://bugs.webkit.org/show_bug.cgi?id=207153

Patch by Said Abou-Hallawa <sabouhallawa@apple.com> on 2020-04-27
Reviewed by Simon Fraser.

Source/WebCore:

The HTML 5 event loop sepcs states that timestamps should be the same for
all rendering update steps.

Specs link (step 9):

https://html.spec.whatwg.org/multipage/webappapis.html#event-loop-processing-model

This patch also fixes some issues in IntersectionObserver.

Test: intersection-observer/intersection-observer-callback-timestamp.html

  • dom/Document.cpp:

(WebCore::Document::updateIntersectionObservations):
(WebCore::Document::notifyIntersectionObserversTimerFired): Deleted.

  • dom/Document.h:

-- Handle the case when two floats are areEssentiallyEqual().
-- Execute the IntersectionObserver immediately and do not wait until the

next CFRunLoop event since this does not implement the specs.

  • page/DOMWindow.cpp:

(WebCore::DOMWindow::freezeNowTimestamp):
(WebCore::DOMWindow::unfreezeNowTimestamp):
(WebCore::DOMWindow::frozenNowTimestamp const):

  • page/DOMWindow.h:

Provide a frozen now() timestamp in seconds to be used internally only.

  • page/IntersectionObserver.cpp:

(WebCore::IntersectionObserver::nowTimestamp const):
Use the frozenNowTimestamp().

  • page/Page.cpp:

(WebCore::Page::updateRendering):
Freeze the timestamps while serving the rendering update steps.

LayoutTests:

  • animations/animation-callback-timestamp-expected.txt:
  • animations/animation-callback-timestamp.html:

Ensure the rAF callback timestamp is less than Performance.now().

  • animations/animation-multiple-callbacks-timestamp-expected.txt:
  • animations/animation-multiple-callbacks-timestamp.html:

Ensure the rAF callbacks receive the same timestamps.

  • intersection-observer/intersection-observer-callback-timestamp-expected.txt: Added.
  • intersection-observer/intersection-observer-callback-timestamp.html: Added.

A new test to ensure the IntersectionObsever and the rAF callbacks receive the same timestamps.

  • platform/ios-wk2/TestExpectations:
  • platform/mac-wk1/TestExpectations:
Location:
trunk
Files:
2 added
14 edited

Legend:

Unmodified
Added
Removed
  • trunk/LayoutTests/ChangeLog

    r260797 r260800  
     12020-04-27  Said Abou-Hallawa  <sabouhallawa@apple.com>
     2
     3        Timestamps should be the same for all rendering update steps
     4        https://bugs.webkit.org/show_bug.cgi?id=207153
     5
     6        Reviewed by Simon Fraser.
     7
     8        * animations/animation-callback-timestamp-expected.txt:
     9        * animations/animation-callback-timestamp.html:
     10        Ensure the rAF callback timestamp is less than Performance.now().
     11
     12        * animations/animation-multiple-callbacks-timestamp-expected.txt:
     13        * animations/animation-multiple-callbacks-timestamp.html:
     14        Ensure the rAF callbacks receive the same timestamps.
     15
     16        * intersection-observer/intersection-observer-callback-timestamp-expected.txt: Added.
     17        * intersection-observer/intersection-observer-callback-timestamp.html: Added.
     18        A new test to ensure the IntersectionObsever and the rAF callbacks receive the same timestamps.
     19
     20        * platform/ios-wk2/TestExpectations:
     21        * platform/mac-wk1/TestExpectations:
     22
    1232020-04-27  Ryan Haddad  <ryanhaddad@apple.com>
    224
  • trunk/LayoutTests/animations/animation-callback-timestamp-expected.txt

    r202399 r260800  
    1 PASS All the differences between requestAnimationFrame() callback timestamps and Performance.now() were within 3ms.
     1Test performance.now() is at least as the timestamp the rAF callback.
     2
     3On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
     4
     5
     6PASS Math.round(window.performance.now()) is >= Math.round(timestamp1)
     7PASS Math.round(window.performance.now()) is >= Math.round(timestamp1)
     8PASS Math.round(window.performance.now()) is >= Math.round(timestamp1)
     9PASS Math.round(window.performance.now()) is >= Math.round(timestamp1)
     10PASS Math.round(window.performance.now()) is >= Math.round(timestamp1)
     11PASS Math.round(window.performance.now()) is >= Math.round(timestamp1)
     12PASS Math.round(window.performance.now()) is >= Math.round(timestamp1)
     13PASS Math.round(window.performance.now()) is >= Math.round(timestamp1)
     14PASS Math.round(window.performance.now()) is >= Math.round(timestamp1)
     15PASS Math.round(window.performance.now()) is >= Math.round(timestamp1)
    216PASS successfullyParsed is true
    317
  • trunk/LayoutTests/animations/animation-callback-timestamp.html

    r236541 r260800  
    66<body>
    77    <script>
     8        var timestamp1 = 0;
    89        var currentFrame = 0;
    9         var failed = false;
     10        const MaxFrames = 10;
    1011
    11         function finishTest()
    12         {
    13             if (failed)
    14                 testFailed("Some of the requestAnimationFrame() callback timestamps were larger than Performance.now() by more than 3ms.");
    15             else
    16                 testPassed("All the differences between requestAnimationFrame() callback timestamps and Performance.now() were within 3ms.")
    17             finishJSTest();
    18         }
    19        
    2012        function doAnimation(timestamp)
    2113        {
    22             const Tolerance = 3;
    23             const WarmupFrames = 5;
     14            timestamp1 = timestamp;
     15            shouldBeGreaterThanOrEqual("Math.round(window.performance.now())", "Math.round(timestamp1)");
    2416
    25             var performanceNow = window.performance.now();
    26             if (++currentFrame > WarmupFrames && Math.abs(timestamp - performanceNow) >= Tolerance) {
    27                 debug("requestAnimationFrame() timestamp = " + timestamp + ", window.performance.now() = " + performanceNow);
    28                 failed = true;
     17            if (++currentFrame == MaxFrames) {
     18                finishJSTest();
     19                return;
    2920            }
    3021
    31             const MaxFrames = 25;
    32             if (currentFrame == MaxFrames)
    33                 finishTest();
    34             else
    35                 requestAnimationFrame(doAnimation);
     22            requestAnimationFrame(doAnimation);
    3623        }
    37        
     24
    3825        window.jsTestIsAsync = true;
     26
     27        description("Test performance.now() is at least as the timestamp the rAF callback.");
    3928        requestAnimationFrame(doAnimation);
    4029    </script>
  • trunk/LayoutTests/animations/animation-multiple-callbacks-timestamp-expected.txt

    r202399 r260800  
    1 PASS All the multiple requestAnimationFrame() callbacks, which were fired at the same time, received the same timestamp.
     1Test the timestamps of all the rAF callbacks are the same.
     2
     3On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
     4
     5
     6PASS Math.round(timestamp1) is Math.round(timestamp2)
     7PASS Math.round(timestamp1) is Math.round(timestamp2)
     8PASS Math.round(timestamp1) is Math.round(timestamp2)
     9PASS Math.round(timestamp1) is Math.round(timestamp2)
     10PASS Math.round(timestamp1) is Math.round(timestamp2)
     11PASS Math.round(timestamp1) is Math.round(timestamp2)
     12PASS Math.round(timestamp1) is Math.round(timestamp2)
     13PASS Math.round(timestamp1) is Math.round(timestamp2)
     14PASS Math.round(timestamp1) is Math.round(timestamp2)
     15PASS Math.round(timestamp1) is Math.round(timestamp2)
    216PASS successfullyParsed is true
    317
  • trunk/LayoutTests/animations/animation-multiple-callbacks-timestamp.html

    r244182 r260800  
    66<body>
    77    <script>
     8        var timestamp1 = 0;
     9        var timestamp2 = 0;
    810        var currentFrame = 0;
    9         var timestamp1 = 0;
    10         var failed = false;
    11 
    12         function finishTest()
    13         {
    14             if (failed)
    15                 testFailed("Some of the multiple requestAnimationFrame() callbacks, which were fired at the same time, received different timestamps.");
    16             else
    17                 testPassed("All the multiple requestAnimationFrame() callbacks, which were fired at the same time, received the same timestamp.");
    18             finishJSTest();
    19         }
     11        const MaxFrames = 10;
    2012
    2113        function doAnimation1(timestamp)
    2214        {
     15            if (++currentFrame > MaxFrames) {
     16                finishJSTest();
     17                return;
     18            }
     19
    2320            timestamp1 = timestamp;
    24            
    25             const MaxFrames = 25;
    26             if (currentFrame == MaxFrames)
    27                 finishTest();
    28             else {
    29                 requestAnimationFrame(doAnimation1);
    30                 requestAnimationFrame(doAnimation2);
    31             }
     21            requestAnimationFrame(doAnimation1);
     22            requestAnimationFrame(doAnimation2);
    3223        }
    3324
    3425        function doAnimation2(timestamp)
    3526        {
    36             const WarmupFrames = 5;
    37             if (++currentFrame > WarmupFrames && timestamp != timestamp1) {
    38                 testFailed("timestamp = " + timestamp + ", timestamp1 = " + timestamp1  + ", window.performance.now() = " + window.performance.now());
    39                 failed = true;
    40             }
     27            timestamp2 = timestamp;
     28            shouldBe("Math.round(timestamp1)", "Math.round(timestamp2)");
    4129        }
    42        
     30
    4331        window.jsTestIsAsync = true;
     32
     33        description("Test the timestamps of all the rAF callbacks are the same.");
    4434        requestAnimationFrame(doAnimation1);
     35        requestAnimationFrame(doAnimation2);
    4536    </script>
    4637    <script src="../resources/js-test-post.js"></script>
  • trunk/LayoutTests/platform/ios-wk2/TestExpectations

    r260767 r260800  
    13251325
    13261326webkit.org/b/205309 scrollingcoordinator/ios/scroll-position-after-reattach.html [ ImageOnlyFailure ]
    1327 
    1328 webkit.org/b/207153 animations/animation-callback-timestamp.html [ Pass Failure ]
    13291327
    13301328webkit.org/b/207156 [ Release ] http/tests/websocket/tests/hybi/workers/close.html [ Pass Failure ]
  • trunk/LayoutTests/platform/mac-wk1/TestExpectations

    r260471 r260800  
    902902webkit.org/b/207560 media/airplay-target-availability.html [ Pass Failure ]
    903903
    904 webkit.org/b/207153 [ Debug ] animations/animation-callback-timestamp.html [ Pass Failure ]
    905 
    906904webkit.org/b/207568 inspector/page/overrideUserAgent.html [ Pass Failure ]
    907905
  • trunk/Source/WebCore/ChangeLog

    r260789 r260800  
     12020-04-27  Said Abou-Hallawa  <sabouhallawa@apple.com>
     2
     3        Timestamps should be the same for all rendering update steps
     4        https://bugs.webkit.org/show_bug.cgi?id=207153
     5
     6        Reviewed by Simon Fraser.
     7
     8        The HTML 5 event loop sepcs states that timestamps should be the same for
     9        all rendering update steps.
     10
     11        Specs link (step 9):
     12            https://html.spec.whatwg.org/multipage/webappapis.html#event-loop-processing-model
     13
     14        This patch also fixes some issues in IntersectionObserver.
     15
     16        Test: intersection-observer/intersection-observer-callback-timestamp.html
     17
     18        * dom/Document.cpp:
     19        (WebCore::Document::updateIntersectionObservations):
     20        (WebCore::Document::notifyIntersectionObserversTimerFired): Deleted.
     21        * dom/Document.h:
     22        -- Handle the case when two floats are areEssentiallyEqual().
     23        -- Execute the IntersectionObserver immediately and do not wait until the
     24           next CFRunLoop event since this does not implement the specs.
     25
     26        * page/DOMWindow.cpp:
     27        (WebCore::DOMWindow::freezeNowTimestamp):
     28        (WebCore::DOMWindow::unfreezeNowTimestamp):
     29        (WebCore::DOMWindow::frozenNowTimestamp const):
     30        * page/DOMWindow.h:
     31        Provide a frozen now() timestamp in seconds to be used internally only.
     32
     33        * page/IntersectionObserver.cpp:
     34        (WebCore::IntersectionObserver::nowTimestamp const):
     35        Use the frozenNowTimestamp().
     36
     37        * page/Page.cpp:
     38        (WebCore::Page::updateRendering):
     39        Freeze the timestamps while serving the rendering update steps.
     40
    1412020-04-27  Dean Jackson  <dino@apple.com>
    242
  • trunk/Source/WebCore/dom/Document.cpp

    r260774 r260800  
    562562#endif
    563563#if ENABLE(INTERSECTION_OBSERVER)
    564     , m_intersectionObserversNotifyTimer(*this, &Document::notifyIntersectionObserversTimerFired)
    565564    , m_intersectionObserversInitialUpdateTimer(*this, &Document::scheduleTimedRenderingUpdate)
    566565#endif
     
    76157614        return;
    76167615
    7617     m_intersectionObserversInitialUpdateTimer.stop();
    7618 
    76197616    bool needsLayout = frameView->layoutContext().isLayoutPending() || (renderView() && renderView()->needsLayout());
    76207617    if (needsLayout || hasPendingStyleRecalc())
    76217618        return;
     7619
     7620    Vector<WeakPtr<IntersectionObserver>> intersectionObserversWithPendingNotifications;
    76227621
    76237622    for (const auto& observer : m_intersectionObservers) {
     
    76477646                        intersectionRatio = 1;
    76487647
    7649                     auto& thresholds = observer->thresholds();
    7650                     while (thresholdIndex < thresholds.size() && thresholds[thresholdIndex] <= intersectionRatio)
     7648                    for (auto threshold : observer->thresholds()) {
     7649                        if (!(threshold <= intersectionRatio || WTF::areEssentiallyEqual<float>(threshold, intersectionRatio)))
     7650                            break;
    76517651                        ++thresholdIndex;
     7652                    }
    76527653                }
    76537654            }
     
    76907691        }
    76917692        if (needNotify)
    7692             m_intersectionObserversWithPendingNotifications.append(makeWeakPtr(observer.get()));
    7693     }
    7694 
    7695     if (m_intersectionObserversWithPendingNotifications.size())
    7696         m_intersectionObserversNotifyTimer.startOneShot(0_s);
    7697 }
    7698 
    7699 void Document::notifyIntersectionObserversTimerFired()
    7700 {
    7701     for (const auto& observer : m_intersectionObserversWithPendingNotifications) {
     7693            intersectionObserversWithPendingNotifications.append(makeWeakPtr(observer.get()));
     7694    }
     7695
     7696    for (const auto& observer : intersectionObserversWithPendingNotifications) {
    77027697        if (observer)
    77037698            observer->notify();
    77047699    }
    7705     m_intersectionObserversWithPendingNotifications.clear();
    77067700}
    77077701
  • trunk/Source/WebCore/dom/Document.h

    r260736 r260800  
    16691669    bool isNavigationBlockedByThirdPartyIFrameRedirectBlocking(Frame& targetFrame, const URL& destinationURL);
    16701670
    1671 #if ENABLE(INTERSECTION_OBSERVER)
    1672     void notifyIntersectionObserversTimerFired();
    1673 #endif
    1674 
    16751671#if USE(QUICK_LOOK)
    16761672    bool shouldEnforceQuickLookSandbox() const;
     
    18451841    Vector<WeakPtr<IntersectionObserver>> m_intersectionObservers;
    18461842    Vector<WeakPtr<IntersectionObserver>> m_intersectionObserversWithPendingNotifications;
    1847     Timer m_intersectionObserversNotifyTimer;
    18481843    Timer m_intersectionObserversInitialUpdateTimer;
    18491844    // This is only non-null when this document is an explicit root.
  • trunk/Source/WebCore/page/DOMWindow.cpp

    r260736 r260800  
    740740}
    741741
     742void DOMWindow::freezeNowTimestamp()
     743{
     744    m_frozenNowTimestamp = nowTimestamp();
     745}
     746
     747void DOMWindow::unfreezeNowTimestamp()
     748{
     749    m_frozenNowTimestamp = WTF::nullopt;
     750}
     751
     752ReducedResolutionSeconds DOMWindow::frozenNowTimestamp() const
     753{
     754    return m_frozenNowTimestamp.valueOr(nowTimestamp());
     755}
     756
    742757Location& DOMWindow::location()
    743758{
  • trunk/Source/WebCore/page/DOMWindow.h

    r260736 r260800  
    352352    Performance& performance() const;
    353353    WEBCORE_EXPORT ReducedResolutionSeconds nowTimestamp() const;
     354    void freezeNowTimestamp();
     355    void unfreezeNowTimestamp();
     356    ReducedResolutionSeconds frozenNowTimestamp() const;
    354357
    355358#if PLATFORM(IOS_FAMILY)
     
    471474    mutable RefPtr<Performance> m_performance;
    472475
     476    Optional<ReducedResolutionSeconds> m_frozenNowTimestamp;
     477
    473478    // For the purpose of tracking user activation, each Window W has a last activation timestamp. This is a number indicating the last time W got
    474479    // an activation notification. It corresponds to a DOMHighResTimeStamp value except for two cases: positive infinity indicates that W has never
  • trunk/Source/WebCore/page/IntersectionObserver.cpp

    r260736 r260800  
    257257    auto& document = downcast<Document>(*context);
    258258    if (auto* window = document.domWindow())
    259         return window->nowTimestamp();
     259        return window->frozenNowTimestamp();
    260260   
    261261    return WTF::nullopt;
  • trunk/Source/WebCore/page/Page.cpp

    r260774 r260800  
    13421342    layoutIfNeeded();
    13431343
     1344    // Timestamps should not change while serving the rendering update steps.
     1345    Vector<WeakPtr<Document>> initialDocuments;
     1346    forEachDocument([&initialDocuments] (Document& document) {
     1347        document.domWindow()->freezeNowTimestamp();
     1348        initialDocuments.append(makeWeakPtr(&document));
     1349    });
     1350
    13441351    // Flush autofocus candidates
    13451352
     
    13591366        if (!document.domWindow())
    13601367            return;
    1361         auto timestamp = document.domWindow()->nowTimestamp();
     1368        auto timestamp = document.domWindow()->frozenNowTimestamp();
    13621369        if (auto* timelinesController = document.timelinesController())
    13631370            timelinesController->updateAnimationsAndSendEvents(timestamp);
     
    13861393        }
    13871394    });
     1395
     1396    for (auto& document : initialDocuments) {
     1397        if (document)
     1398            document->domWindow()->unfreezeNowTimestamp();
     1399    }
    13881400
    13891401    layoutIfNeeded();
Note: See TracChangeset for help on using the changeset viewer.