Title: [217063] trunk
Revision
217063
Author
[email protected]
Date
2017-05-18 13:32:01 -0700 (Thu, 18 May 2017)

Log Message

Allow nested timers to propagate user gestures so long as the total nested interval is less than 1s.
https://bugs.webkit.org/show_bug.cgi?id=172173

Reviewed by Andy Estes.

Source/WebCore:

Test: media/restricted-audio-playback-with-multiple-settimeouts.html

Store the current nested timer interval in DOMTimerFireState, and use that value to propagate the
nested interval through multiple invocations of setTimeout().

Drive-by fix: instead of manually resetting the nesting level in DOMTimer::fired(), add the
nesting level to the DOMTimerFireState, and reset the nesting level on the state's destruction.
This fixes one place in DOMTimer::fire() where an early return lead to the timer's nesting level
not being reset.

* page/DOMTimer.cpp:
(WebCore::DOMTimerFireState::DOMTimerFireState):
(WebCore::DOMTimerFireState::~DOMTimerFireState):
(WebCore::DOMTimerFireState::nestedTimerInterval):
(WebCore::shouldForwardUserGesture):
(WebCore::userGestureTokenToForward):
(WebCore::currentNestedTimerInterval):
(WebCore::DOMTimer::DOMTimer):
(WebCore::DOMTimer::fired):
* page/DOMTimer.h:

LayoutTests:

* fast/events/popup-blocked-from-untrusted-mouse-click.html:
* fast/events/popup-blocking-timers4-expected.txt: Removed.
* fast/events/popup-blocking-timers4.html: Removed.
* media/restricted-audio-playback-with-multiple-settimeouts-expected.txt: Added.
* media/restricted-audio-playback-with-multiple-settimeouts.html: Added.
* platform/ios/TestExpectations:

Modified Paths

Added Paths

Removed Paths

Diff

Modified: trunk/LayoutTests/ChangeLog (217062 => 217063)


--- trunk/LayoutTests/ChangeLog	2017-05-18 19:55:01 UTC (rev 217062)
+++ trunk/LayoutTests/ChangeLog	2017-05-18 20:32:01 UTC (rev 217063)
@@ -1,3 +1,17 @@
+2017-05-18  Jer Noble  <[email protected]>
+
+        Allow nested timers to propagate user gestures so long as the total nested interval is less than 1s.
+        https://bugs.webkit.org/show_bug.cgi?id=172173
+
+        Reviewed by Andy Estes.
+
+        * fast/events/popup-blocked-from-untrusted-mouse-click.html:
+        * fast/events/popup-blocking-timers4-expected.txt: Removed.
+        * fast/events/popup-blocking-timers4.html: Removed.
+        * media/restricted-audio-playback-with-multiple-settimeouts-expected.txt: Added.
+        * media/restricted-audio-playback-with-multiple-settimeouts.html: Added.
+        * platform/ios/TestExpectations:
+
 2017-05-18  Daniel Bates  <[email protected]>
 
         Evaluating window named element may return wrong result

Modified: trunk/LayoutTests/fast/events/popup-blocked-from-untrusted-mouse-click.html (217062 => 217063)


--- trunk/LayoutTests/fast/events/popup-blocked-from-untrusted-mouse-click.html	2017-05-18 19:55:01 UTC (rev 217062)
+++ trunk/LayoutTests/fast/events/popup-blocked-from-untrusted-mouse-click.html	2017-05-18 20:32:01 UTC (rev 217063)
@@ -38,7 +38,7 @@
     evt.initMouseEvent("click", true, true, window,
         0, 0, 0, 0, 0, false, false, false, false, 0, null);
     var cb = document.getElementById("anchor"); 
-    setTimeout(dispatchEvent(cb, evt), 100);
+    Promise.resolve().then(dispatchEvent(cb, evt));
 }
 
 function openWindow(evt) {
@@ -56,7 +56,7 @@
         return;
     }
     // Go to next test stage.
-    window.setTimeout(nextTestStage, 0);
+    Promise.resolve().then(nextTestStage);
 }
 
 function doClick() {
@@ -76,7 +76,7 @@
 }
 
 </script>
-<body _onload_="window.setTimeout(test, 0);">
+<body _onload_="test();">
 <input type="button" _onclick_="simulateClick();" value="click me" id="btn"><br>
 <a _onclick_="openWindow(event)" id="anchor"> open a new window </a><br>
 The _javascript_ created (untrusted) event inside a user-initiated (trusted) event should not cache the UserGesture status. This test is for bug https://bugs.webkit.org/show_bug.cgi?id=50508.

Deleted: trunk/LayoutTests/fast/events/popup-blocking-timers4-expected.txt (217062 => 217063)


--- trunk/LayoutTests/fast/events/popup-blocking-timers4-expected.txt	2017-05-18 19:55:01 UTC (rev 217062)
+++ trunk/LayoutTests/fast/events/popup-blocking-timers4-expected.txt	2017-05-18 20:32:01 UTC (rev 217063)
@@ -1,4 +0,0 @@
-Click Here
-Test calling window.open() in a nested call to setTimeout(). A popup should not be allowed.
-PASS newWindow is null
-

Deleted: trunk/LayoutTests/fast/events/popup-blocking-timers4.html (217062 => 217063)


--- trunk/LayoutTests/fast/events/popup-blocking-timers4.html	2017-05-18 19:55:01 UTC (rev 217062)
+++ trunk/LayoutTests/fast/events/popup-blocking-timers4.html	2017-05-18 20:32:01 UTC (rev 217063)
@@ -1,42 +0,0 @@
-<!DOCTYPE html>
-<head>
-    <script src=""
-    <script>
-        var newWindow;
-        
-        if (window.testRunner) {
-            testRunner.dumpAsText();
-            testRunner.setCanOpenWindows();
-            testRunner.waitUntilDone();
-            testRunner.setPopupBlockingEnabled(true);
-        }
-    
-        function clickHandler() {
-            setTimeout(function() {
-                setTimeout(function() {
-                    newWindow = window.open("about:blank");
-                    self.focus();
-                    debug("Test calling window.open() in a nested call to setTimeout(). A popup should not be allowed.")
-                    shouldBeNull("newWindow");
-                    if (window.testRunner)
-                        testRunner.notifyDone();
-                }, 0);
-            }, 300);
-        }
-        
-        function clickButton() {
-            var button = document.getElementById("test");
-            var buttonX = button.offsetLeft + button.offsetWidth / 2;
-            var buttonY = button.offsetTop + button.offsetHeight / 2;
-            if (window.eventSender) {
-                eventSender.mouseMoveTo(buttonX, buttonY);
-                eventSender.mouseDown();
-                eventSender.mouseUp();
-            }
-        }        
-    </script>
-</head>
-<body _onload_="clickButton()">
-    <button id="test" _onclick_="clickHandler()">Click Here</button>
-    <div id="console"></div>
-</body>

Added: trunk/LayoutTests/media/restricted-audio-playback-with-multiple-settimeouts-expected.txt (0 => 217063)


--- trunk/LayoutTests/media/restricted-audio-playback-with-multiple-settimeouts-expected.txt	                        (rev 0)
+++ trunk/LayoutTests/media/restricted-audio-playback-with-multiple-settimeouts-expected.txt	2017-05-18 20:32:01 UTC (rev 217063)
@@ -0,0 +1,30 @@
+Test that, when RequireUserGestureForAudioRateChange is set along with MediaUserGestureInheritsForDocument, nested setTimeout() calls of less than 1s will not block play() from succeeding.
+
+
+RUN(mediaElement.src = "" 'content/test'))
+EVENT(canplaythrough)
+Test > 1s in single step
+setTimeout 1, 1.1s
+RUN(shouldReject(mediaElement.play()).then(next, next))
+Promise rejected correctly OK
+
+RUN(mediaElement.src = "" 'content/test'))
+EVENT(canplaythrough)
+Test > 1s in multiple steps
+setTimeout 1, 0.6s
+setTimeout 1, 0.6s
+RUN(shouldReject(mediaElement.play()).then(next, next))
+Promise rejected correctly OK
+
+RUN(mediaElement.src = "" 'content/test'))
+EVENT(canplaythrough)
+Test < 1s total.
+setTimeout 1, 0.1s
+setTimeout 2, 0.1s
+setTimeout 3, 0.1s
+setTimeout 4, 0.1s
+setTimeout 5, 0.1s
+RUN(shouldResolve(mediaElement.play()).then(next, next))
+Promise resolved OK
+END OF TEST
+

Added: trunk/LayoutTests/media/restricted-audio-playback-with-multiple-settimeouts.html (0 => 217063)


--- trunk/LayoutTests/media/restricted-audio-playback-with-multiple-settimeouts.html	                        (rev 0)
+++ trunk/LayoutTests/media/restricted-audio-playback-with-multiple-settimeouts.html	2017-05-18 20:32:01 UTC (rev 217063)
@@ -0,0 +1,79 @@
+<!DOCTYPE html>
+<html>
+<head>
+    <title>restricted-audio-playback-with-document-gesture</title>
+    <script src=""
+    <script src=""
+
+    <script>
+    function runTest()
+    {
+        next();
+    }
+
+    function next() {
+        if (!tests.length) {
+            endTest()
+            return;
+        }
+
+        mediaElement = document.createElement('audio');
+        if (window.internals)
+            internals.setMediaElementRestrictions(mediaElement, "RequireUserGestureForAudioRateChange");
+
+        var nextTest = tests.shift();
+        consoleWrite('');
+        run("mediaElement.src = "" 'content/test')");
+        waitForEvent('canplaythrough', event => { runWithKeyDown(nextTest) });
+    }
+
+    var tests = [
+        function() {
+            consoleWrite('Test > 1s in single step');
+            consoleWrite('setTimeout 1, 1.1s');
+            setTimeout(() => {
+                run("shouldReject(mediaElement.play()).then(next, next)");
+            }, 1100);
+        },
+
+
+        function() {
+            consoleWrite('Test > 1s in multiple steps');
+            consoleWrite('setTimeout 1, 0.6s');
+            setTimeout(() => {
+                consoleWrite('setTimeout 1, 0.6s');
+                setTimeout(() => {
+                    run("shouldReject(mediaElement.play()).then(next, next)");
+                }, 600);
+            }, 600);
+        },
+
+        function() {
+            consoleWrite('Test < 1s total.')
+            consoleWrite('setTimeout 1, 0.1s');
+            setTimeout(() => {
+                consoleWrite('setTimeout 2, 0.1s');
+                setTimeout(() => {
+                    consoleWrite('setTimeout 3, 0.1s');
+                    setTimeout(() => {
+                        consoleWrite('setTimeout 4, 0.1s');
+                        setTimeout(() => {
+                            consoleWrite('setTimeout 5, 0.1s');
+                            setTimeout(() => {
+                                run("shouldResolve(mediaElement.play()).then(next, next)");
+                            }, 100);
+                        }, 100);
+                    }, 100);
+                }, 100);
+            }, 100);
+        },
+    ];
+    </script>
+</head>
+
+<body _onload_="runTest()">
+    <p>Test that, when RequireUserGestureForAudioRateChange is set along with MediaUserGestureInheritsForDocument, 
+    nested setTimeout() calls of less than 1s will not block play() from succeeding.</p>
+</body>
+</html>
+

Modified: trunk/LayoutTests/platform/ios/TestExpectations (217062 => 217063)


--- trunk/LayoutTests/platform/ios/TestExpectations	2017-05-18 19:55:01 UTC (rev 217062)
+++ trunk/LayoutTests/platform/ios/TestExpectations	2017-05-18 20:32:01 UTC (rev 217063)
@@ -382,6 +382,7 @@
 http/tests/navigation/keyboard-events-during-provisional-subframe-navigation.html [ Skip ]
 media/audio-dealloc-crash.html [ Skip ]
 media/restricted-audio-playback-with-document-gesture.html [ Skip ]
+media/restricted-audio-playback-with-multiple-settimeouts.html [ Skip ]
 
 # Tests that use EventSender's mouseMoveTo, mouseUp and mouseDown
 fast/forms/range/disabled-while-dragging.html [ Skip ]

Modified: trunk/Source/WebCore/ChangeLog (217062 => 217063)


--- trunk/Source/WebCore/ChangeLog	2017-05-18 19:55:01 UTC (rev 217062)
+++ trunk/Source/WebCore/ChangeLog	2017-05-18 20:32:01 UTC (rev 217063)
@@ -1,3 +1,31 @@
+2017-05-18  Jer Noble  <[email protected]>
+
+        Allow nested timers to propagate user gestures so long as the total nested interval is less than 1s.
+        https://bugs.webkit.org/show_bug.cgi?id=172173
+
+        Reviewed by Andy Estes.
+
+        Test: media/restricted-audio-playback-with-multiple-settimeouts.html
+
+        Store the current nested timer interval in DOMTimerFireState, and use that value to propagate the
+        nested interval through multiple invocations of setTimeout().
+
+        Drive-by fix: instead of manually resetting the nesting level in DOMTimer::fired(), add the
+        nesting level to the DOMTimerFireState, and reset the nesting level on the state's destruction.
+        This fixes one place in DOMTimer::fire() where an early return lead to the timer's nesting level
+        not being reset.
+
+        * page/DOMTimer.cpp:
+        (WebCore::DOMTimerFireState::DOMTimerFireState):
+        (WebCore::DOMTimerFireState::~DOMTimerFireState):
+        (WebCore::DOMTimerFireState::nestedTimerInterval):
+        (WebCore::shouldForwardUserGesture):
+        (WebCore::userGestureTokenToForward):
+        (WebCore::currentNestedTimerInterval):
+        (WebCore::DOMTimer::DOMTimer):
+        (WebCore::DOMTimer::fired):
+        * page/DOMTimer.h:
+
 2017-05-18  Youenn Fablet  <[email protected]>
 
         RealtimeOutgoingAudioSource should use the source sample rate

Modified: trunk/Source/WebCore/page/DOMTimer.cpp (217062 => 217063)


--- trunk/Source/WebCore/page/DOMTimer.cpp	2017-05-18 19:55:01 UTC (rev 217062)
+++ trunk/Source/WebCore/page/DOMTimer.cpp	2017-05-18 20:32:01 UTC (rev 217063)
@@ -58,8 +58,9 @@
 
 class DOMTimerFireState {
 public:
-    explicit DOMTimerFireState(ScriptExecutionContext& context)
+    DOMTimerFireState(ScriptExecutionContext& context, int nestingLevel, const Seconds& nestedTimerInterval)
         : m_context(context)
+        , m_nestedTimerInterval(nestedTimerInterval)
         , m_contextIsDocument(is<Document>(m_context))
     {
         // For worker threads, don't update the current DOMTimerFireState.
@@ -69,6 +70,8 @@
             m_previous = current;
             current = this;
         }
+
+        m_context.setTimerNestingLevel(nestingLevel);
     }
 
     ~DOMTimerFireState()
@@ -75,10 +78,13 @@
     {
         if (m_contextIsDocument)
             current = m_previous;
+        m_context.setTimerNestingLevel(0);
     }
 
     Document* contextDocument() const { return m_contextIsDocument ? &downcast<Document>(m_context) : nullptr; }
 
+    const Seconds& nestedTimerInterval() const { return m_nestedTimerInterval; }
+
     void setScriptMadeUserObservableChanges() { m_scriptMadeUserObservableChanges = true; }
     void setScriptMadeNonUserObservableChanges() { m_scriptMadeNonUserObservableChanges = true; }
 
@@ -97,6 +103,7 @@
 
 private:
     ScriptExecutionContext& m_context;
+    Seconds m_nestedTimerInterval;
     uint64_t m_initialDOMTreeVersion;
     DOMTimerFireState* m_previous;
     bool m_contextIsDocument;
@@ -161,21 +168,27 @@
 
 bool NestedTimersMap::isTrackingNestedTimers = false;
 
-static inline bool shouldForwardUserGesture(Seconds interval, int nestingLevel)
+static inline bool shouldForwardUserGesture(Seconds interval)
 {
     return UserGestureIndicator::processingUserGesture()
-        && interval <= maxIntervalForUserGestureForwarding
-        && !nestingLevel; // Gestures should not be forwarded to nested timers.
+        && interval <= maxIntervalForUserGestureForwarding;
 }
 
-static inline RefPtr<UserGestureToken> userGestureTokenToForward(Seconds interval, int nestingLevel)
+static inline RefPtr<UserGestureToken> userGestureTokenToForward(Seconds interval)
 {
-    if (!shouldForwardUserGesture(interval, nestingLevel))
+    if (!shouldForwardUserGesture(interval))
         return nullptr;
 
     return UserGestureIndicator::currentUserGesture();
 }
 
+static inline Seconds currentNestedTimerInterval()
+{
+    if (DOMTimerFireState::current)
+        return DOMTimerFireState::current->nestedTimerInterval();
+    return { };
+}
+
 DOMTimer::DOMTimer(ScriptExecutionContext& context, std::unique_ptr<ScheduledAction> action, Seconds interval, bool singleShot)
     : SuspendableTimer(context)
     , m_nestingLevel(context.timerNestingLevel())
@@ -183,7 +196,8 @@
     , m_originalInterval(interval)
     , m_throttleState(Undetermined)
     , m_currentTimerInterval(intervalClampedToMinimum())
-    , m_userGestureTokenToForward(userGestureTokenToForward(interval, m_nestingLevel))
+    , m_nestedTimerInterval(currentNestedTimerInterval())
+    , m_userGestureTokenToForward(userGestureTokenToForward(m_nestedTimerInterval + m_currentTimerInterval))
 {
     RefPtr<DOMTimer> reference = adoptRef(this);
 
@@ -302,10 +316,8 @@
     ASSERT(scriptExecutionContext());
     ScriptExecutionContext& context = *scriptExecutionContext();
 
-    DOMTimerFireState fireState(context);
+    DOMTimerFireState fireState(context, std::min(m_nestingLevel + 1, maxTimerNestingLevel), m_nestedTimerInterval + m_currentTimerInterval);
 
-    context.setTimerNestingLevel(std::min(m_nestingLevel + 1, maxTimerNestingLevel));
-
     ASSERT(!isSuspended());
     ASSERT(!context.activeDOMObjectsAreSuspended());
     UserGestureIndicator gestureIndicator(m_userGestureTokenToForward);
@@ -378,8 +390,6 @@
         }
         nestedTimers->stopTracking();
     }
-
-    context.setTimerNestingLevel(0);
 }
 
 void DOMTimer::didStop()

Modified: trunk/Source/WebCore/page/DOMTimer.h (217062 => 217063)


--- trunk/Source/WebCore/page/DOMTimer.h	2017-05-18 19:55:01 UTC (rev 217062)
+++ trunk/Source/WebCore/page/DOMTimer.h	2017-05-18 20:32:01 UTC (rev 217063)
@@ -92,6 +92,7 @@
     Seconds m_originalInterval;
     TimerThrottleState m_throttleState;
     Seconds m_currentTimerInterval;
+    Seconds m_nestedTimerInterval;
     RefPtr<UserGestureToken> m_userGestureTokenToForward;
 };
 
_______________________________________________
webkit-changes mailing list
[email protected]
https://lists.webkit.org/mailman/listinfo/webkit-changes

Reply via email to