Title: [247606] branches/safari-608-branch
Revision
247606
Author
kocsen_ch...@apple.com
Date
2019-07-18 13:24:40 -0700 (Thu, 18 Jul 2019)

Log Message

Cherry-pick r247530. rdar://problem/53229569

    Typing into a cell in a Google Sheet lags behind by one character
    https://bugs.webkit.org/show_bug.cgi?id=199587
    <rdar://problem/51616845>

    Reviewed by Brent Fulgham.

    Source/WebCore:

    Add a Google Sheets quirk. Put all DOM timers scheduled from keydown and keypress event listeners
    into a holding tank. The timers continue to tick, but are barred from executing their action until
    the next text insertion or deletion or 32 ms (on device) have elapsed, whichever is sooner. We only
    allocate a holding tank once per document, only if the quirk is active, and this allocation is done
    when the document schedules a timer on keydown or keypress. The holding tank lives for the lifetime
    of the document.

    The story behind the quirk:

    On keypress Google Sheets schedules timers and expects that a DOM update will occur (i.e. text
    will be inserted or deleted) within the same event loop iteration as the dispatched keypress. The
    UI Events spec. [1] makes no such guarantee of when a DOM update must occur in relation to the keypress
    event. It could happen in the same event loop iteration as the key press (as Google expects), the
    next iteration, 500ms later, 2 minutes later, etc. What the spec does guarantee is that by the time
    a DOM input event is dispatched that the DOM will be updated. And this is the solution to the problem
    Google Sheets is trying to solve, but is doing so using pre-IE 9 technology (though similar
    functionality was available via onpropertychange in IE < 9).

    See also <https://github.com/w3c/uievents/issues/238>, which is tracking a spec. text update for
    this quirk.

    Test: fast/events/ios/dom-update-on-keydown-quirk.html

    [1] <https://w3c.github.io/uievents/> (Editor's Draft, 14 October 2018)

    * SourcesCocoa.txt:
    * WebCore.xcodeproj/project.pbxproj:
    Add some files to the project.

    * dom/Document.cpp:
    (WebCore::Document::domTimerHoldingTank): Added.
    * dom/Document.h:
    (WebCore::Document::domTimerHoldingTankIfExists): Added.

    * page/DOMTimer.cpp:
    (WebCore::DOMTimer::install): Put the newly instantiated timer into the holding tank.
    (WebCore::DOMTimer::removeById): Remove the timer from the holding tank.
    (WebCore::DOMTimer::fired): Check if the timer is in the holding tank. If it is and it is a one-
    shot timer then schedule it for the next event loop iteration. If it's a repeating timer just
    let it continue ticking. Otherwise, do what we no now and execute the timer's action. The reason
    we do not suspend timers in the holding tank is because:
        1. Far out timers (Google Sheets registers timers as far out as 5 minutes!) are not penalized.
        Though smart supension logic could avoid this. See (3).

        2. Empirical observations indicate that the keyboard will perform the insertion or deletion
        reasonably quickly (not the same event loop iteration as the keydown, but within two iterations out).
        So, the timers in the holding tank are short-lived.

        3. Simplifies the code. There is no need to keep additional bookkeeping to track multiple timer
        suspension reasons (timers currently can only have one suspension reason) or alternatively defer
        scheduling a timer until a later time and computing a new "fair" firing time when scheduled.
    * page/EventHandler.cpp:
    (WebCore::EventHandler::internalKeyEvent): Place a token on the stack to put all DOM timers
    scheduled on keydown and keypress into the holding tank if the quirk is enabled.
    * page/Quirks.cpp:
    (WebCore::Quirks::needsDeferKeyDownAndKeyPressTimersUntilNextEditingCommand const): Added.
    * page/Quirks.h:
    * page/Settings.yaml: Added setting so that this quirk can be enabled from a layout test. This setting
    also lets us enable the quirk for all sites or for certain third-party apps if desired.
    * page/ios/DOMTimerHoldingTank.cpp: Added.
    (WebCore::DOMTimerHoldingTank::DOMTimerHoldingTank):
    (WebCore::DOMTimerHoldingTank::add):
    (WebCore::DOMTimerHoldingTank::remove):
    (WebCore::DOMTimerHoldingTank::contains):
    (WebCore::DOMTimerHoldingTank::removeAll):
    (WebCore::DOMTimerHoldingTank::stopExceededMaximumHoldTimer):
    * page/ios/DOMTimerHoldingTank.h: Added.
    (WebCore::DeferDOMTimersForScope::DeferDOMTimersForScope):
    (WebCore::DeferDOMTimersForScope::~DeferDOMTimersForScope):
    (WebCore::DeferDOMTimersForScope::isDeferring):

    Source/WebKit:

    Remove all timers from the holding tank on text insertion or deletion (represented as an
    editing command). Timers that were in the holding tank never stopped ticking and will now
    be able to execute their action.

    * WebProcess/WebPage/WebPage.cpp:
    (WebKit::WebPage::executeEditingCommand):
    (WebKit::WebPage::insertTextAsync):
    (WebKit::WebPage::setCompositionAsync):
    (WebKit::WebPage::confirmCompositionAsync):
    Call platformWillPerformEditingCommand().

    * WebProcess/WebPage/WebPage.h:
    (WebKit::WebPage::platformWillPerformEditingCommand): Added.
    * WebProcess/WebPage/ios/WebPageIOS.mm:
    (WebKit::WebPage::platformWillPerformEditingCommand): Remove all the timers from the holding
    tank if we have a holding tank.

    LayoutTests:

    Add a test that enables the quirk and ensures that the DOM is up-to-date on expiration of a
    zero timer scheduled from keydown, keypress, keyup, and input.

    * fast/events/ios/dom-update-on-keydown-quirk-expected.txt: Added.
    * fast/events/ios/dom-update-on-keydown-quirk.html: Added.

    git-svn-id: https://svn.webkit.org/repository/webkit/trunk@247530 268f45cc-cd09-0410-ab3c-d52691b4dbfc

Modified Paths

Added Paths

Diff

Modified: branches/safari-608-branch/LayoutTests/ChangeLog (247605 => 247606)


--- branches/safari-608-branch/LayoutTests/ChangeLog	2019-07-18 20:24:34 UTC (rev 247605)
+++ branches/safari-608-branch/LayoutTests/ChangeLog	2019-07-18 20:24:40 UTC (rev 247606)
@@ -1,5 +1,131 @@
 2019-07-17  Kocsen Chung  <kocsen_ch...@apple.com>
 
+        Cherry-pick r247530. rdar://problem/53229569
+
+    Typing into a cell in a Google Sheet lags behind by one character
+    https://bugs.webkit.org/show_bug.cgi?id=199587
+    <rdar://problem/51616845>
+    
+    Reviewed by Brent Fulgham.
+    
+    Source/WebCore:
+    
+    Add a Google Sheets quirk. Put all DOM timers scheduled from keydown and keypress event listeners
+    into a holding tank. The timers continue to tick, but are barred from executing their action until
+    the next text insertion or deletion or 32 ms (on device) have elapsed, whichever is sooner. We only
+    allocate a holding tank once per document, only if the quirk is active, and this allocation is done
+    when the document schedules a timer on keydown or keypress. The holding tank lives for the lifetime
+    of the document.
+    
+    The story behind the quirk:
+    
+    On keypress Google Sheets schedules timers and expects that a DOM update will occur (i.e. text
+    will be inserted or deleted) within the same event loop iteration as the dispatched keypress. The
+    UI Events spec. [1] makes no such guarantee of when a DOM update must occur in relation to the keypress
+    event. It could happen in the same event loop iteration as the key press (as Google expects), the
+    next iteration, 500ms later, 2 minutes later, etc. What the spec does guarantee is that by the time
+    a DOM input event is dispatched that the DOM will be updated. And this is the solution to the problem
+    Google Sheets is trying to solve, but is doing so using pre-IE 9 technology (though similar
+    functionality was available via onpropertychange in IE < 9).
+    
+    See also <https://github.com/w3c/uievents/issues/238>, which is tracking a spec. text update for
+    this quirk.
+    
+    Test: fast/events/ios/dom-update-on-keydown-quirk.html
+    
+    [1] <https://w3c.github.io/uievents/> (Editor's Draft, 14 October 2018)
+    
+    * SourcesCocoa.txt:
+    * WebCore.xcodeproj/project.pbxproj:
+    Add some files to the project.
+    
+    * dom/Document.cpp:
+    (WebCore::Document::domTimerHoldingTank): Added.
+    * dom/Document.h:
+    (WebCore::Document::domTimerHoldingTankIfExists): Added.
+    
+    * page/DOMTimer.cpp:
+    (WebCore::DOMTimer::install): Put the newly instantiated timer into the holding tank.
+    (WebCore::DOMTimer::removeById): Remove the timer from the holding tank.
+    (WebCore::DOMTimer::fired): Check if the timer is in the holding tank. If it is and it is a one-
+    shot timer then schedule it for the next event loop iteration. If it's a repeating timer just
+    let it continue ticking. Otherwise, do what we no now and execute the timer's action. The reason
+    we do not suspend timers in the holding tank is because:
+        1. Far out timers (Google Sheets registers timers as far out as 5 minutes!) are not penalized.
+        Though smart supension logic could avoid this. See (3).
+    
+        2. Empirical observations indicate that the keyboard will perform the insertion or deletion
+        reasonably quickly (not the same event loop iteration as the keydown, but within two iterations out).
+        So, the timers in the holding tank are short-lived.
+    
+        3. Simplifies the code. There is no need to keep additional bookkeeping to track multiple timer
+        suspension reasons (timers currently can only have one suspension reason) or alternatively defer
+        scheduling a timer until a later time and computing a new "fair" firing time when scheduled.
+    * page/EventHandler.cpp:
+    (WebCore::EventHandler::internalKeyEvent): Place a token on the stack to put all DOM timers
+    scheduled on keydown and keypress into the holding tank if the quirk is enabled.
+    * page/Quirks.cpp:
+    (WebCore::Quirks::needsDeferKeyDownAndKeyPressTimersUntilNextEditingCommand const): Added.
+    * page/Quirks.h:
+    * page/Settings.yaml: Added setting so that this quirk can be enabled from a layout test. This setting
+    also lets us enable the quirk for all sites or for certain third-party apps if desired.
+    * page/ios/DOMTimerHoldingTank.cpp: Added.
+    (WebCore::DOMTimerHoldingTank::DOMTimerHoldingTank):
+    (WebCore::DOMTimerHoldingTank::add):
+    (WebCore::DOMTimerHoldingTank::remove):
+    (WebCore::DOMTimerHoldingTank::contains):
+    (WebCore::DOMTimerHoldingTank::removeAll):
+    (WebCore::DOMTimerHoldingTank::stopExceededMaximumHoldTimer):
+    * page/ios/DOMTimerHoldingTank.h: Added.
+    (WebCore::DeferDOMTimersForScope::DeferDOMTimersForScope):
+    (WebCore::DeferDOMTimersForScope::~DeferDOMTimersForScope):
+    (WebCore::DeferDOMTimersForScope::isDeferring):
+    
+    Source/WebKit:
+    
+    Remove all timers from the holding tank on text insertion or deletion (represented as an
+    editing command). Timers that were in the holding tank never stopped ticking and will now
+    be able to execute their action.
+    
+    * WebProcess/WebPage/WebPage.cpp:
+    (WebKit::WebPage::executeEditingCommand):
+    (WebKit::WebPage::insertTextAsync):
+    (WebKit::WebPage::setCompositionAsync):
+    (WebKit::WebPage::confirmCompositionAsync):
+    Call platformWillPerformEditingCommand().
+    
+    * WebProcess/WebPage/WebPage.h:
+    (WebKit::WebPage::platformWillPerformEditingCommand): Added.
+    * WebProcess/WebPage/ios/WebPageIOS.mm:
+    (WebKit::WebPage::platformWillPerformEditingCommand): Remove all the timers from the holding
+    tank if we have a holding tank.
+    
+    LayoutTests:
+    
+    Add a test that enables the quirk and ensures that the DOM is up-to-date on expiration of a
+    zero timer scheduled from keydown, keypress, keyup, and input.
+    
+    * fast/events/ios/dom-update-on-keydown-quirk-expected.txt: Added.
+    * fast/events/ios/dom-update-on-keydown-quirk.html: Added.
+    
+    git-svn-id: https://svn.webkit.org/repository/webkit/trunk@247530 268f45cc-cd09-0410-ab3c-d52691b4dbfc
+
+    2019-07-17  Daniel Bates  <daba...@apple.com>
+
+            Typing into a cell in a Google Sheet lags behind by one character
+            https://bugs.webkit.org/show_bug.cgi?id=199587
+            <rdar://problem/51616845>
+
+            Reviewed by Brent Fulgham.
+
+            Add a test that enables the quirk and ensures that the DOM is up-to-date on expiration of a
+            zero timer scheduled from keydown, keypress, keyup, and input.
+
+            * fast/events/ios/dom-update-on-keydown-quirk-expected.txt: Added.
+            * fast/events/ios/dom-update-on-keydown-quirk.html: Added.
+
+2019-07-17  Kocsen Chung  <kocsen_ch...@apple.com>
+
         Cherry-pick r247484. rdar://problem/53229757
 
     [Text autosizing] [iPadOS] Paragraph text on the front page of LinkedIn.com is not boosted

Added: branches/safari-608-branch/LayoutTests/fast/events/ios/dom-update-on-keydown-quirk-expected.txt (0 => 247606)


--- branches/safari-608-branch/LayoutTests/fast/events/ios/dom-update-on-keydown-quirk-expected.txt	                        (rev 0)
+++ branches/safari-608-branch/LayoutTests/fast/events/ios/dom-update-on-keydown-quirk-expected.txt	2019-07-18 20:24:40 UTC (rev 247606)
@@ -0,0 +1,21 @@
+This tests that the value of the field is updated by the time any timer scheduled on keydown, keypress, or keyup fires. To run this test manually, focus the text field and press [.
+
+On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
+
+
+
+For keydown:
+PASS document.getElementById("input").value is "["
+
+For keypress:
+PASS document.getElementById("input").value is "["
+
+For input:
+PASS document.getElementById("input").value is "["
+
+For keyup:
+PASS document.getElementById("input").value is "["
+PASS successfullyParsed is true
+
+TEST COMPLETE
+

Added: branches/safari-608-branch/LayoutTests/fast/events/ios/dom-update-on-keydown-quirk.html (0 => 247606)


--- branches/safari-608-branch/LayoutTests/fast/events/ios/dom-update-on-keydown-quirk.html	                        (rev 0)
+++ branches/safari-608-branch/LayoutTests/fast/events/ios/dom-update-on-keydown-quirk.html	2019-07-18 20:24:40 UTC (rev 247606)
@@ -0,0 +1,53 @@
+<!DOCTYPE html>
+<html>
+<head>
+<script src=""
+<script src=""
+</head>
+<body>
+<input id="input">
+<script>
+const expectedValue = "["; // [ chosen to avoid having to handle or disable iOS auto capitalization. 
+
+let input = document.getElementById("input");
+
+function done()
+{
+    document.body.removeChild(input);
+    finishJSTest();
+}
+
+async function runTest()
+{
+    if (window.internals.settings)
+        internals.settings.setNeedsDeferKeyDownAndKeyPressTimersUntilNextEditingCommandQuirk(true);
+
+    if (window.testRunner)
+        await UIHelper.callFunctionAndWaitForEvent(() => UIHelper.activateElement(input), input, "focus");
+
+    function checkResult(event) {
+        // Note that scheduling a timer from an Input event handler is for aesthetics only: to make the
+        // logged Input event be ordered like the spec'ed DOM dispatch event order. By the time the Input
+        // event fires the DOM is guaranteed to have been updated. So, no timer is needed.
+        window.setTimeout(() => {
+            debug(`<br>For ${event.type}:`);
+            shouldBeEqualToString('document.getElementById("input").value', expectedValue);
+            if (event.type === "keyup")
+                done();
+        }, 0);
+    }
+
+    for (let eventName of ["keydown", "keypress", "keyup", "input"])
+        input.addEventListener(eventName, checkResult, { once: true });
+
+    if (window.testRunner)
+        UIHelper.keyDown(expectedValue);
+}
+
+window.jsTestIsAsync = true;
+description("This tests that the value of the field is updated by the time any timer scheduled on keydown, keypress, or keyup fires. To run this test manually, focus the text field and press <kbd>[</kbd>.");
+
+runTest();
+</script>
+</body>
+</html>
\ No newline at end of file

Modified: branches/safari-608-branch/Source/WebCore/ChangeLog (247605 => 247606)


--- branches/safari-608-branch/Source/WebCore/ChangeLog	2019-07-18 20:24:34 UTC (rev 247605)
+++ branches/safari-608-branch/Source/WebCore/ChangeLog	2019-07-18 20:24:40 UTC (rev 247606)
@@ -1,5 +1,196 @@
 2019-07-17  Kocsen Chung  <kocsen_ch...@apple.com>
 
+        Cherry-pick r247530. rdar://problem/53229569
+
+    Typing into a cell in a Google Sheet lags behind by one character
+    https://bugs.webkit.org/show_bug.cgi?id=199587
+    <rdar://problem/51616845>
+    
+    Reviewed by Brent Fulgham.
+    
+    Source/WebCore:
+    
+    Add a Google Sheets quirk. Put all DOM timers scheduled from keydown and keypress event listeners
+    into a holding tank. The timers continue to tick, but are barred from executing their action until
+    the next text insertion or deletion or 32 ms (on device) have elapsed, whichever is sooner. We only
+    allocate a holding tank once per document, only if the quirk is active, and this allocation is done
+    when the document schedules a timer on keydown or keypress. The holding tank lives for the lifetime
+    of the document.
+    
+    The story behind the quirk:
+    
+    On keypress Google Sheets schedules timers and expects that a DOM update will occur (i.e. text
+    will be inserted or deleted) within the same event loop iteration as the dispatched keypress. The
+    UI Events spec. [1] makes no such guarantee of when a DOM update must occur in relation to the keypress
+    event. It could happen in the same event loop iteration as the key press (as Google expects), the
+    next iteration, 500ms later, 2 minutes later, etc. What the spec does guarantee is that by the time
+    a DOM input event is dispatched that the DOM will be updated. And this is the solution to the problem
+    Google Sheets is trying to solve, but is doing so using pre-IE 9 technology (though similar
+    functionality was available via onpropertychange in IE < 9).
+    
+    See also <https://github.com/w3c/uievents/issues/238>, which is tracking a spec. text update for
+    this quirk.
+    
+    Test: fast/events/ios/dom-update-on-keydown-quirk.html
+    
+    [1] <https://w3c.github.io/uievents/> (Editor's Draft, 14 October 2018)
+    
+    * SourcesCocoa.txt:
+    * WebCore.xcodeproj/project.pbxproj:
+    Add some files to the project.
+    
+    * dom/Document.cpp:
+    (WebCore::Document::domTimerHoldingTank): Added.
+    * dom/Document.h:
+    (WebCore::Document::domTimerHoldingTankIfExists): Added.
+    
+    * page/DOMTimer.cpp:
+    (WebCore::DOMTimer::install): Put the newly instantiated timer into the holding tank.
+    (WebCore::DOMTimer::removeById): Remove the timer from the holding tank.
+    (WebCore::DOMTimer::fired): Check if the timer is in the holding tank. If it is and it is a one-
+    shot timer then schedule it for the next event loop iteration. If it's a repeating timer just
+    let it continue ticking. Otherwise, do what we no now and execute the timer's action. The reason
+    we do not suspend timers in the holding tank is because:
+        1. Far out timers (Google Sheets registers timers as far out as 5 minutes!) are not penalized.
+        Though smart supension logic could avoid this. See (3).
+    
+        2. Empirical observations indicate that the keyboard will perform the insertion or deletion
+        reasonably quickly (not the same event loop iteration as the keydown, but within two iterations out).
+        So, the timers in the holding tank are short-lived.
+    
+        3. Simplifies the code. There is no need to keep additional bookkeeping to track multiple timer
+        suspension reasons (timers currently can only have one suspension reason) or alternatively defer
+        scheduling a timer until a later time and computing a new "fair" firing time when scheduled.
+    * page/EventHandler.cpp:
+    (WebCore::EventHandler::internalKeyEvent): Place a token on the stack to put all DOM timers
+    scheduled on keydown and keypress into the holding tank if the quirk is enabled.
+    * page/Quirks.cpp:
+    (WebCore::Quirks::needsDeferKeyDownAndKeyPressTimersUntilNextEditingCommand const): Added.
+    * page/Quirks.h:
+    * page/Settings.yaml: Added setting so that this quirk can be enabled from a layout test. This setting
+    also lets us enable the quirk for all sites or for certain third-party apps if desired.
+    * page/ios/DOMTimerHoldingTank.cpp: Added.
+    (WebCore::DOMTimerHoldingTank::DOMTimerHoldingTank):
+    (WebCore::DOMTimerHoldingTank::add):
+    (WebCore::DOMTimerHoldingTank::remove):
+    (WebCore::DOMTimerHoldingTank::contains):
+    (WebCore::DOMTimerHoldingTank::removeAll):
+    (WebCore::DOMTimerHoldingTank::stopExceededMaximumHoldTimer):
+    * page/ios/DOMTimerHoldingTank.h: Added.
+    (WebCore::DeferDOMTimersForScope::DeferDOMTimersForScope):
+    (WebCore::DeferDOMTimersForScope::~DeferDOMTimersForScope):
+    (WebCore::DeferDOMTimersForScope::isDeferring):
+    
+    Source/WebKit:
+    
+    Remove all timers from the holding tank on text insertion or deletion (represented as an
+    editing command). Timers that were in the holding tank never stopped ticking and will now
+    be able to execute their action.
+    
+    * WebProcess/WebPage/WebPage.cpp:
+    (WebKit::WebPage::executeEditingCommand):
+    (WebKit::WebPage::insertTextAsync):
+    (WebKit::WebPage::setCompositionAsync):
+    (WebKit::WebPage::confirmCompositionAsync):
+    Call platformWillPerformEditingCommand().
+    
+    * WebProcess/WebPage/WebPage.h:
+    (WebKit::WebPage::platformWillPerformEditingCommand): Added.
+    * WebProcess/WebPage/ios/WebPageIOS.mm:
+    (WebKit::WebPage::platformWillPerformEditingCommand): Remove all the timers from the holding
+    tank if we have a holding tank.
+    
+    LayoutTests:
+    
+    Add a test that enables the quirk and ensures that the DOM is up-to-date on expiration of a
+    zero timer scheduled from keydown, keypress, keyup, and input.
+    
+    * fast/events/ios/dom-update-on-keydown-quirk-expected.txt: Added.
+    * fast/events/ios/dom-update-on-keydown-quirk.html: Added.
+    
+    git-svn-id: https://svn.webkit.org/repository/webkit/trunk@247530 268f45cc-cd09-0410-ab3c-d52691b4dbfc
+
+    2019-07-17  Daniel Bates  <daba...@apple.com>
+
+            Typing into a cell in a Google Sheet lags behind by one character
+            https://bugs.webkit.org/show_bug.cgi?id=199587
+            <rdar://problem/51616845>
+
+            Reviewed by Brent Fulgham.
+
+            Add a Google Sheets quirk. Put all DOM timers scheduled from keydown and keypress event listeners
+            into a holding tank. The timers continue to tick, but are barred from executing their action until
+            the next text insertion or deletion or 32 ms (on device) have elapsed, whichever is sooner. We only
+            allocate a holding tank once per document, only if the quirk is active, and this allocation is done
+            when the document schedules a timer on keydown or keypress. The holding tank lives for the lifetime
+            of the document.
+
+            The story behind the quirk:
+
+            On keypress Google Sheets schedules timers and expects that a DOM update will occur (i.e. text
+            will be inserted or deleted) within the same event loop iteration as the dispatched keypress. The
+            UI Events spec. [1] makes no such guarantee of when a DOM update must occur in relation to the keypress
+            event. It could happen in the same event loop iteration as the key press (as Google expects), the
+            next iteration, 500ms later, 2 minutes later, etc. What the spec does guarantee is that by the time
+            a DOM input event is dispatched that the DOM will be updated. And this is the solution to the problem
+            Google Sheets is trying to solve, but is doing so using pre-IE 9 technology (though similar
+            functionality was available via onpropertychange in IE < 9).
+
+            See also <https://github.com/w3c/uievents/issues/238>, which is tracking a spec. text update for
+            this quirk.
+
+            Test: fast/events/ios/dom-update-on-keydown-quirk.html
+
+            [1] <https://w3c.github.io/uievents/> (Editor's Draft, 14 October 2018)
+
+            * SourcesCocoa.txt:
+            * WebCore.xcodeproj/project.pbxproj:
+            Add some files to the project.
+
+            * dom/Document.cpp:
+            (WebCore::Document::domTimerHoldingTank): Added.
+            * dom/Document.h:
+            (WebCore::Document::domTimerHoldingTankIfExists): Added.
+
+            * page/DOMTimer.cpp:
+            (WebCore::DOMTimer::install): Put the newly instantiated timer into the holding tank.
+            (WebCore::DOMTimer::removeById): Remove the timer from the holding tank.
+            (WebCore::DOMTimer::fired): Check if the timer is in the holding tank. If it is and it is a one-
+            shot timer then schedule it for the next event loop iteration. If it's a repeating timer just
+            let it continue ticking. Otherwise, do what we no now and execute the timer's action. The reason
+            we do not suspend timers in the holding tank is because:
+                1. Far out timers (Google Sheets registers timers as far out as 5 minutes!) are not penalized.
+                Though smart supension logic could avoid this. See (3).
+
+                2. Empirical observations indicate that the keyboard will perform the insertion or deletion
+                reasonably quickly (not the same event loop iteration as the keydown, but within two iterations out).
+                So, the timers in the holding tank are short-lived.
+
+                3. Simplifies the code. There is no need to keep additional bookkeeping to track multiple timer
+                suspension reasons (timers currently can only have one suspension reason) or alternatively defer
+                scheduling a timer until a later time and computing a new "fair" firing time when scheduled.
+            * page/EventHandler.cpp:
+            (WebCore::EventHandler::internalKeyEvent): Place a token on the stack to put all DOM timers
+            scheduled on keydown and keypress into the holding tank if the quirk is enabled.
+            * page/Quirks.cpp:
+            (WebCore::Quirks::needsDeferKeyDownAndKeyPressTimersUntilNextEditingCommand const): Added.
+            * page/Quirks.h:
+            * page/Settings.yaml: Added setting so that this quirk can be enabled from a layout test. This setting
+            also lets us enable the quirk for all sites or for certain third-party apps if desired.
+            * page/ios/DOMTimerHoldingTank.cpp: Added.
+            (WebCore::DOMTimerHoldingTank::DOMTimerHoldingTank):
+            (WebCore::DOMTimerHoldingTank::add):
+            (WebCore::DOMTimerHoldingTank::remove):
+            (WebCore::DOMTimerHoldingTank::contains):
+            (WebCore::DOMTimerHoldingTank::removeAll):
+            (WebCore::DOMTimerHoldingTank::stopExceededMaximumHoldTimer):
+            * page/ios/DOMTimerHoldingTank.h: Added.
+            (WebCore::DeferDOMTimersForScope::DeferDOMTimersForScope):
+            (WebCore::DeferDOMTimersForScope::~DeferDOMTimersForScope):
+            (WebCore::DeferDOMTimersForScope::isDeferring):
+
+2019-07-17  Kocsen Chung  <kocsen_ch...@apple.com>
+
         Cherry-pick r247525. rdar://problem/53229719
 
     Hop to the main thread when doing logging in RealtimeIncomingVideoSourceCocoa

Modified: branches/safari-608-branch/Source/WebCore/SourcesCocoa.txt (247605 => 247606)


--- branches/safari-608-branch/Source/WebCore/SourcesCocoa.txt	2019-07-18 20:24:34 UTC (rev 247605)
+++ branches/safari-608-branch/Source/WebCore/SourcesCocoa.txt	2019-07-18 20:24:40 UTC (rev 247606)
@@ -131,6 +131,7 @@
 page/cocoa/SettingsBaseCocoa.mm
 
 page/ios/ContentChangeObserver.cpp
+page/ios/DOMTimerHoldingTank.cpp
 page/ios/EventHandlerIOS.mm
 page/ios/FrameIOS.mm
 page/ios/WebEventRegion.mm

Modified: branches/safari-608-branch/Source/WebCore/WebCore.xcodeproj/project.pbxproj (247605 => 247606)


--- branches/safari-608-branch/Source/WebCore/WebCore.xcodeproj/project.pbxproj	2019-07-18 20:24:34 UTC (rev 247605)
+++ branches/safari-608-branch/Source/WebCore/WebCore.xcodeproj/project.pbxproj	2019-07-18 20:24:40 UTC (rev 247606)
@@ -4191,6 +4191,7 @@
 		CE057FA61220731100A476D5 /* DocumentMarkerController.h in Headers */ = {isa = PBXBuildFile; fileRef = CE057FA41220731100A476D5 /* DocumentMarkerController.h */; settings = {ATTRIBUTES = (Private, ); }; };
 		CE08C3D2152B599A0021B8C2 /* AlternativeTextController.h in Headers */ = {isa = PBXBuildFile; fileRef = CE08C3D0152B599A0021B8C2 /* AlternativeTextController.h */; settings = {ATTRIBUTES = (); }; };
 		CE1866451F72E5B400A0CAB6 /* MarkedText.h in Headers */ = {isa = PBXBuildFile; fileRef = CE1866431F72E5B400A0CAB6 /* MarkedText.h */; settings = {ATTRIBUTES = (Private, ); }; };
+		CE1A501F22D5350900CBC927 /* DOMTimerHoldingTank.h in Headers */ = {isa = PBXBuildFile; fileRef = CE1A501D22D5350900CBC927 /* DOMTimerHoldingTank.h */; settings = {ATTRIBUTES = (Private, ); }; };
 		CE2849871CA360DF00B4A57F /* ContentSecurityPolicyDirectiveNames.h in Headers */ = {isa = PBXBuildFile; fileRef = CE2849861CA360DF00B4A57F /* ContentSecurityPolicyDirectiveNames.h */; };
 		CE5169E721F1B84700EA4F78 /* ColorIOS.h in Headers */ = {isa = PBXBuildFile; fileRef = CE5169E521F1B84700EA4F78 /* ColorIOS.h */; settings = {ATTRIBUTES = (Private, ); }; };
 		CE5FA255209E48C50051D700 /* ContentSecurityPolicyClient.h in Headers */ = {isa = PBXBuildFile; fileRef = CE5FA253209E48C50051D700 /* ContentSecurityPolicyClient.h */; settings = {ATTRIBUTES = (Private, ); }; };
@@ -13857,6 +13858,8 @@
 		CE08C3D0152B599A0021B8C2 /* AlternativeTextController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AlternativeTextController.h; sourceTree = "<group>"; };
 		CE1866421F72E5B400A0CAB6 /* MarkedText.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = MarkedText.cpp; sourceTree = "<group>"; };
 		CE1866431F72E5B400A0CAB6 /* MarkedText.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MarkedText.h; sourceTree = "<group>"; };
+		CE1A501D22D5350900CBC927 /* DOMTimerHoldingTank.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DOMTimerHoldingTank.h; sourceTree = "<group>"; };
+		CE1A501E22D5350900CBC927 /* DOMTimerHoldingTank.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = DOMTimerHoldingTank.cpp; sourceTree = "<group>"; };
 		CE2849861CA360DF00B4A57F /* ContentSecurityPolicyDirectiveNames.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ContentSecurityPolicyDirectiveNames.h; path = csp/ContentSecurityPolicyDirectiveNames.h; sourceTree = "<group>"; };
 		CE2849881CA3614600B4A57F /* ContentSecurityPolicyDirectiveNames.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = ContentSecurityPolicyDirectiveNames.cpp; path = csp/ContentSecurityPolicyDirectiveNames.cpp; sourceTree = "<group>"; };
 		CE5169E521F1B84700EA4F78 /* ColorIOS.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ColorIOS.h; sourceTree = "<group>"; };
@@ -16576,6 +16579,8 @@
 			children = (
 				6FE9F09222211035004C5082 /* ContentChangeObserver.cpp */,
 				6FB5E212221F2447003989CF /* ContentChangeObserver.h */,
+				CE1A501E22D5350900CBC927 /* DOMTimerHoldingTank.cpp */,
+				CE1A501D22D5350900CBC927 /* DOMTimerHoldingTank.h */,
 				FE6938B51045D67E008EABB6 /* EventHandlerIOS.mm */,
 				FED13D3B0CEA936A00D89466 /* FrameIOS.mm */,
 				225A16B30D5C11E900090295 /* WebEventRegion.h */,
@@ -29020,6 +29025,7 @@
 				C544274B11A57E7A0063A749 /* DOMStringList.h in Headers */,
 				BC64640A11D7F304006455B0 /* DOMStringMap.h in Headers */,
 				188604B40F2E654A000B6443 /* DOMTimer.h in Headers */,
+				CE1A501F22D5350900CBC927 /* DOMTimerHoldingTank.h in Headers */,
 				05FD69E012845D4300B2BEB3 /* DOMTimeStamp.h in Headers */,
 				76FC2B0C12370DA0006A991A /* DOMTokenList.h in Headers */,
 				2E37DFDB12DBAFB800A6B233 /* DOMURL.h in Headers */,

Modified: branches/safari-608-branch/Source/WebCore/dom/Document.cpp (247605 => 247606)


--- branches/safari-608-branch/Source/WebCore/dom/Document.cpp	2019-07-18 20:24:34 UTC (rev 247605)
+++ branches/safari-608-branch/Source/WebCore/dom/Document.cpp	2019-07-18 20:24:40 UTC (rev 247606)
@@ -262,6 +262,7 @@
 #if PLATFORM(IOS_FAMILY)
 #include "ContentChangeObserver.h"
 #include "CSSFontSelector.h"
+#include "DOMTimerHoldingTank.h"
 #include "DeviceMotionClientIOS.h"
 #include "DeviceMotionController.h"
 #include "DeviceOrientationClientIOS.h"
@@ -8186,6 +8187,7 @@
 #endif
 
 #if PLATFORM(IOS_FAMILY)
+
 ContentChangeObserver& Document::contentChangeObserver()
 {
     if (!m_contentChangeObserver)
@@ -8192,6 +8194,15 @@
         m_contentChangeObserver = std::make_unique<ContentChangeObserver>(*this);
     return *m_contentChangeObserver; 
 }
+
+DOMTimerHoldingTank& Document::domTimerHoldingTank()
+{
+    if (m_domTimerHoldingTank)
+        return *m_domTimerHoldingTank;
+    m_domTimerHoldingTank = std::make_unique<DOMTimerHoldingTank>();
+    return *m_domTimerHoldingTank;
+}
+
 #endif
 
 bool Document::hasEvaluatedUserAgentScripts() const

Modified: branches/safari-608-branch/Source/WebCore/dom/Document.h (247605 => 247606)


--- branches/safari-608-branch/Source/WebCore/dom/Document.h	2019-07-18 20:24:34 UTC (rev 247605)
+++ branches/safari-608-branch/Source/WebCore/dom/Document.h	2019-07-18 20:24:40 UTC (rev 247606)
@@ -106,6 +106,7 @@
 class ContentChangeObserver;
 class DOMImplementation;
 class DOMSelection;
+class DOMTimerHoldingTank;
 class DOMWindow;
 class DOMWrapperWorld;
 class Database;
@@ -883,6 +884,9 @@
     void processWebAppOrientations();
 
     WEBCORE_EXPORT ContentChangeObserver& contentChangeObserver();
+
+    DOMTimerHoldingTank* domTimerHoldingTankIfExists() { return m_domTimerHoldingTank.get(); }
+    DOMTimerHoldingTank& domTimerHoldingTank();
 #endif
     
     void processViewport(const String& features, ViewportArguments::Type origin);
@@ -2044,6 +2048,7 @@
     Ref<UndoManager> m_undoManager;
 #if PLATFORM(IOS_FAMILY)
     std::unique_ptr<ContentChangeObserver> m_contentChangeObserver;
+    std::unique_ptr<DOMTimerHoldingTank> m_domTimerHoldingTank;
 #endif
 
     HashMap<Element*, ElementIdentifier> m_identifiedElementsMap;

Modified: branches/safari-608-branch/Source/WebCore/page/DOMTimer.cpp (247605 => 247606)


--- branches/safari-608-branch/Source/WebCore/page/DOMTimer.cpp	2019-07-18 20:24:34 UTC (rev 247605)
+++ branches/safari-608-branch/Source/WebCore/page/DOMTimer.cpp	2019-07-18 20:24:40 UTC (rev 247606)
@@ -43,6 +43,7 @@
 
 #if PLATFORM(IOS_FAMILY)
 #include "ContentChangeObserver.h"
+#include "DOMTimerHoldingTank.h"
 #endif
 
 namespace WebCore {
@@ -196,8 +197,12 @@
     if (NestedTimersMap* nestedTimers = NestedTimersMap::instanceForContext(context))
         nestedTimers->add(timer->m_timeoutId, *timer);
 #if PLATFORM(IOS_FAMILY)
-    if (is<Document>(context))
-        downcast<Document>(context).contentChangeObserver().didInstallDOMTimer(*timer, timeout, singleShot);
+    if (is<Document>(context)) {
+        auto& document = downcast<Document>(context);
+        document.contentChangeObserver().didInstallDOMTimer(*timer, timeout, singleShot);
+        if (DeferDOMTimersForScope::isDeferring())
+            document.domTimerHoldingTank().add(*timer);
+    }
 #endif
     return timer->m_timeoutId;
 }
@@ -213,8 +218,11 @@
 #if PLATFORM(IOS_FAMILY)
     if (is<Document>(context)) {
         auto& document = downcast<Document>(context);
-        if (auto* timer = document.findTimeout(timeoutId))
+        if (auto* timer = document.findTimeout(timeoutId)) {
             document.contentChangeObserver().didRemoveDOMTimer(*timer);
+            if (auto* holdingTank = document.domTimerHoldingTankIfExists())
+                holdingTank->remove(*timer);
+        }
     }
 #endif
 
@@ -284,6 +292,17 @@
     ASSERT(scriptExecutionContext());
     ScriptExecutionContext& context = *scriptExecutionContext();
 
+#if PLATFORM(IOS_FAMILY)
+    if (is<Document>(context)) {
+        auto& document = downcast<Document>(context);
+        if (auto* holdingTank = document.domTimerHoldingTankIfExists(); holdingTank && holdingTank->contains(*this)) {
+            if (!repeatInterval())
+                startOneShot(0_s);
+            return;
+        }
+    }
+#endif
+
     DOMTimerFireState fireState(context, std::min(m_nestingLevel + 1, maxTimerNestingLevel));
 
     if (m_userGestureTokenToForward && m_userGestureTokenToForward->hasExpired(maxIntervalForUserGestureForwarding))

Modified: branches/safari-608-branch/Source/WebCore/page/EventHandler.cpp (247605 => 247606)


--- branches/safari-608-branch/Source/WebCore/page/EventHandler.cpp	2019-07-18 20:24:34 UTC (rev 247605)
+++ branches/safari-608-branch/Source/WebCore/page/EventHandler.cpp	2019-07-18 20:24:40 UTC (rev 247606)
@@ -131,6 +131,10 @@
 #include "RuntimeEnabledFeatures.h"
 #endif
 
+#if PLATFORM(IOS_FAMILY)
+#include "DOMTimerHoldingTank.h"
+#endif
+
 namespace WebCore {
 
 using namespace HTMLNames;
@@ -3341,6 +3345,10 @@
     if (accessibilityPreventsEventPropagation(keydown))
         keydown->stopPropagation();
 
+#if PLATFORM(IOS_FAMILY)
+    DeferDOMTimersForScope deferralScope { m_frame.document()->quirks().needsDeferKeyDownAndKeyPressTimersUntilNextEditingCommand() };
+#endif
+
     element->dispatchEvent(keydown);
     if (handledByInputMethod)
         return true;

Modified: branches/safari-608-branch/Source/WebCore/page/Quirks.cpp (247605 => 247606)


--- branches/safari-608-branch/Source/WebCore/page/Quirks.cpp	2019-07-18 20:24:34 UTC (rev 247605)
+++ branches/safari-608-branch/Source/WebCore/page/Quirks.cpp	2019-07-18 20:24:40 UTC (rev 247606)
@@ -317,6 +317,22 @@
     return false;
 }
 
+bool Quirks::needsDeferKeyDownAndKeyPressTimersUntilNextEditingCommand() const
+{
+#if PLATFORM(IOS_FAMILY)
+    if (m_document->settings().needsDeferKeyDownAndKeyPressTimersUntilNextEditingCommandQuirk())
+        return true;
+
+    if (!needsQuirks())
+        return false;
+
+    auto& url = ""
+    return equalLettersIgnoringASCIICase(url.host(), "docs.google.com") && url.path().startsWithIgnoringASCIICase("/spreadsheets/");
+#else
+    return false;
+#endif
+}
+
 // FIXME(<rdar://problem/50394969>): Remove after desmos.com adopts inputmode="none".
 bool Quirks::needsInputModeNoneImplicitly(const HTMLElement& element) const
 {

Modified: branches/safari-608-branch/Source/WebCore/page/Quirks.h (247605 => 247606)


--- branches/safari-608-branch/Source/WebCore/page/Quirks.h	2019-07-18 20:24:34 UTC (rev 247605)
+++ branches/safari-608-branch/Source/WebCore/page/Quirks.h	2019-07-18 20:24:40 UTC (rev 247606)
@@ -54,6 +54,7 @@
 #endif
     bool shouldDisablePointerEventsQuirk() const;
     bool needsInputModeNoneImplicitly(const HTMLElement&) const;
+    bool needsDeferKeyDownAndKeyPressTimersUntilNextEditingCommand() const;
 
     WEBCORE_EXPORT bool shouldDispatchSyntheticMouseEventsWhenModifyingSelection() const;
     WEBCORE_EXPORT bool shouldSuppressAutocorrectionAndAutocaptializationInHiddenEditableAreas() const;

Modified: branches/safari-608-branch/Source/WebCore/page/Settings.yaml (247605 => 247606)


--- branches/safari-608-branch/Source/WebCore/page/Settings.yaml	2019-07-18 20:24:34 UTC (rev 247605)
+++ branches/safari-608-branch/Source/WebCore/page/Settings.yaml	2019-07-18 20:24:40 UTC (rev 247606)
@@ -162,6 +162,15 @@
 needsKeyboardEventDisambiguationQuirks:
   initial: false
 
+# This is an iOS-specific quirk. Unlike Mac, keyboard operations are asynchronous and hence a DOM update as
+# a result of text insertion or deletion does not occur within the same event loop iteration as a dispatched
+# DOM keydown event. Some sites, notably Google Sheets, schedule timers on keypress and expect on a DOM update
+# to have occurred on expiration. When enabled, this quirk puts all such scheduled timers in a holding tank
+# until the keyboard performs the insertion or deletion. This gives Google Sheets the illusion that the DOM
+# update happened within the same event loop iteration that the keypress event was dispatched in.
+needsDeferKeyDownAndKeyPressTimersUntilNextEditingCommandQuirk:
+  initial: false
+
 treatsAnyTextCSSLinkAsStylesheet:
   initial: false
 shrinksStandaloneImagesToFit:

Added: branches/safari-608-branch/Source/WebCore/page/ios/DOMTimerHoldingTank.cpp (0 => 247606)


--- branches/safari-608-branch/Source/WebCore/page/ios/DOMTimerHoldingTank.cpp	                        (rev 0)
+++ branches/safari-608-branch/Source/WebCore/page/ios/DOMTimerHoldingTank.cpp	2019-07-18 20:24:40 UTC (rev 247606)
@@ -0,0 +1,82 @@
+/*
+ * Copyright (C) 2019 Apple Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "config.h"
+#include "DOMTimerHoldingTank.h"
+
+#if PLATFORM(IOS_FAMILY)
+
+#include <wtf/HashSet.h>
+
+namespace WebCore {
+
+#if PLATFORM(IOS_SIMULATOR)
+constexpr Seconds maximumHoldTimeLimit { 50_ms };
+#else
+constexpr Seconds maximumHoldTimeLimit { 32_ms };
+#endif
+
+bool DeferDOMTimersForScope::s_isDeferring { false };
+
+DOMTimerHoldingTank::DOMTimerHoldingTank()
+    : m_exceededMaximumHoldTimer { *this, &DOMTimerHoldingTank::removeAll }
+{
+}
+
+DOMTimerHoldingTank::~DOMTimerHoldingTank() = default;
+
+void DOMTimerHoldingTank::add(const DOMTimer& timer)
+{
+    m_timers.add(&timer);
+    if (!m_exceededMaximumHoldTimer.isActive())
+        m_exceededMaximumHoldTimer.startOneShot(maximumHoldTimeLimit);
+}
+
+void DOMTimerHoldingTank::remove(const DOMTimer& timer)
+{
+    stopExceededMaximumHoldTimer();
+    m_timers.remove(&timer);
+}
+
+bool DOMTimerHoldingTank::contains(const DOMTimer& timer)
+{
+    return m_timers.contains(&timer);
+}
+
+void DOMTimerHoldingTank::removeAll()
+{
+    stopExceededMaximumHoldTimer();
+    m_timers.clear();
+}
+
+inline void DOMTimerHoldingTank::stopExceededMaximumHoldTimer()
+{
+    if (m_exceededMaximumHoldTimer.isActive())
+        m_exceededMaximumHoldTimer.stop();
+}
+
+} // namespace WebCore
+
+#endif // PLATFORM(IOS_FAMILY)

Added: branches/safari-608-branch/Source/WebCore/page/ios/DOMTimerHoldingTank.h (0 => 247606)


--- branches/safari-608-branch/Source/WebCore/page/ios/DOMTimerHoldingTank.h	                        (rev 0)
+++ branches/safari-608-branch/Source/WebCore/page/ios/DOMTimerHoldingTank.h	2019-07-18 20:24:40 UTC (rev 247606)
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2019 Apple Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#if PLATFORM(IOS_FAMILY)
+
+#include "Timer.h"
+#include <wtf/Forward.h>
+
+namespace WebCore {
+
+class DOMTimer;
+
+class DOMTimerHoldingTank {
+public:
+    DOMTimerHoldingTank();
+    ~DOMTimerHoldingTank();
+
+    void add(const DOMTimer&);
+    void remove(const DOMTimer&);
+    bool contains(const DOMTimer&);
+    WEBCORE_EXPORT void removeAll();
+
+private:
+    void stopExceededMaximumHoldTimer();
+
+    HashSet<const DOMTimer*> m_timers;
+    Timer m_exceededMaximumHoldTimer;
+};
+
+class DeferDOMTimersForScope {
+public:
+    DeferDOMTimersForScope(bool enable)
+        : m_previousIsDeferring { s_isDeferring }
+    {
+        if (enable)
+            s_isDeferring = true;
+    }
+
+    ~DeferDOMTimersForScope() { s_isDeferring = m_previousIsDeferring; }
+
+    static bool isDeferring() { return s_isDeferring; }
+
+private:
+    bool m_previousIsDeferring;
+    static bool s_isDeferring;
+};
+
+} // namespace WebCore
+
+#endif // PLATFORM(IOS_FAMILY)

Modified: branches/safari-608-branch/Source/WebKit/ChangeLog (247605 => 247606)


--- branches/safari-608-branch/Source/WebKit/ChangeLog	2019-07-18 20:24:34 UTC (rev 247605)
+++ branches/safari-608-branch/Source/WebKit/ChangeLog	2019-07-18 20:24:40 UTC (rev 247606)
@@ -1,5 +1,142 @@
 2019-07-17  Kocsen Chung  <kocsen_ch...@apple.com>
 
+        Cherry-pick r247530. rdar://problem/53229569
+
+    Typing into a cell in a Google Sheet lags behind by one character
+    https://bugs.webkit.org/show_bug.cgi?id=199587
+    <rdar://problem/51616845>
+    
+    Reviewed by Brent Fulgham.
+    
+    Source/WebCore:
+    
+    Add a Google Sheets quirk. Put all DOM timers scheduled from keydown and keypress event listeners
+    into a holding tank. The timers continue to tick, but are barred from executing their action until
+    the next text insertion or deletion or 32 ms (on device) have elapsed, whichever is sooner. We only
+    allocate a holding tank once per document, only if the quirk is active, and this allocation is done
+    when the document schedules a timer on keydown or keypress. The holding tank lives for the lifetime
+    of the document.
+    
+    The story behind the quirk:
+    
+    On keypress Google Sheets schedules timers and expects that a DOM update will occur (i.e. text
+    will be inserted or deleted) within the same event loop iteration as the dispatched keypress. The
+    UI Events spec. [1] makes no such guarantee of when a DOM update must occur in relation to the keypress
+    event. It could happen in the same event loop iteration as the key press (as Google expects), the
+    next iteration, 500ms later, 2 minutes later, etc. What the spec does guarantee is that by the time
+    a DOM input event is dispatched that the DOM will be updated. And this is the solution to the problem
+    Google Sheets is trying to solve, but is doing so using pre-IE 9 technology (though similar
+    functionality was available via onpropertychange in IE < 9).
+    
+    See also <https://github.com/w3c/uievents/issues/238>, which is tracking a spec. text update for
+    this quirk.
+    
+    Test: fast/events/ios/dom-update-on-keydown-quirk.html
+    
+    [1] <https://w3c.github.io/uievents/> (Editor's Draft, 14 October 2018)
+    
+    * SourcesCocoa.txt:
+    * WebCore.xcodeproj/project.pbxproj:
+    Add some files to the project.
+    
+    * dom/Document.cpp:
+    (WebCore::Document::domTimerHoldingTank): Added.
+    * dom/Document.h:
+    (WebCore::Document::domTimerHoldingTankIfExists): Added.
+    
+    * page/DOMTimer.cpp:
+    (WebCore::DOMTimer::install): Put the newly instantiated timer into the holding tank.
+    (WebCore::DOMTimer::removeById): Remove the timer from the holding tank.
+    (WebCore::DOMTimer::fired): Check if the timer is in the holding tank. If it is and it is a one-
+    shot timer then schedule it for the next event loop iteration. If it's a repeating timer just
+    let it continue ticking. Otherwise, do what we no now and execute the timer's action. The reason
+    we do not suspend timers in the holding tank is because:
+        1. Far out timers (Google Sheets registers timers as far out as 5 minutes!) are not penalized.
+        Though smart supension logic could avoid this. See (3).
+    
+        2. Empirical observations indicate that the keyboard will perform the insertion or deletion
+        reasonably quickly (not the same event loop iteration as the keydown, but within two iterations out).
+        So, the timers in the holding tank are short-lived.
+    
+        3. Simplifies the code. There is no need to keep additional bookkeeping to track multiple timer
+        suspension reasons (timers currently can only have one suspension reason) or alternatively defer
+        scheduling a timer until a later time and computing a new "fair" firing time when scheduled.
+    * page/EventHandler.cpp:
+    (WebCore::EventHandler::internalKeyEvent): Place a token on the stack to put all DOM timers
+    scheduled on keydown and keypress into the holding tank if the quirk is enabled.
+    * page/Quirks.cpp:
+    (WebCore::Quirks::needsDeferKeyDownAndKeyPressTimersUntilNextEditingCommand const): Added.
+    * page/Quirks.h:
+    * page/Settings.yaml: Added setting so that this quirk can be enabled from a layout test. This setting
+    also lets us enable the quirk for all sites or for certain third-party apps if desired.
+    * page/ios/DOMTimerHoldingTank.cpp: Added.
+    (WebCore::DOMTimerHoldingTank::DOMTimerHoldingTank):
+    (WebCore::DOMTimerHoldingTank::add):
+    (WebCore::DOMTimerHoldingTank::remove):
+    (WebCore::DOMTimerHoldingTank::contains):
+    (WebCore::DOMTimerHoldingTank::removeAll):
+    (WebCore::DOMTimerHoldingTank::stopExceededMaximumHoldTimer):
+    * page/ios/DOMTimerHoldingTank.h: Added.
+    (WebCore::DeferDOMTimersForScope::DeferDOMTimersForScope):
+    (WebCore::DeferDOMTimersForScope::~DeferDOMTimersForScope):
+    (WebCore::DeferDOMTimersForScope::isDeferring):
+    
+    Source/WebKit:
+    
+    Remove all timers from the holding tank on text insertion or deletion (represented as an
+    editing command). Timers that were in the holding tank never stopped ticking and will now
+    be able to execute their action.
+    
+    * WebProcess/WebPage/WebPage.cpp:
+    (WebKit::WebPage::executeEditingCommand):
+    (WebKit::WebPage::insertTextAsync):
+    (WebKit::WebPage::setCompositionAsync):
+    (WebKit::WebPage::confirmCompositionAsync):
+    Call platformWillPerformEditingCommand().
+    
+    * WebProcess/WebPage/WebPage.h:
+    (WebKit::WebPage::platformWillPerformEditingCommand): Added.
+    * WebProcess/WebPage/ios/WebPageIOS.mm:
+    (WebKit::WebPage::platformWillPerformEditingCommand): Remove all the timers from the holding
+    tank if we have a holding tank.
+    
+    LayoutTests:
+    
+    Add a test that enables the quirk and ensures that the DOM is up-to-date on expiration of a
+    zero timer scheduled from keydown, keypress, keyup, and input.
+    
+    * fast/events/ios/dom-update-on-keydown-quirk-expected.txt: Added.
+    * fast/events/ios/dom-update-on-keydown-quirk.html: Added.
+    
+    git-svn-id: https://svn.webkit.org/repository/webkit/trunk@247530 268f45cc-cd09-0410-ab3c-d52691b4dbfc
+
+    2019-07-17  Daniel Bates  <daba...@apple.com>
+
+            Typing into a cell in a Google Sheet lags behind by one character
+            https://bugs.webkit.org/show_bug.cgi?id=199587
+            <rdar://problem/51616845>
+
+            Reviewed by Brent Fulgham.
+
+            Remove all timers from the holding tank on text insertion or deletion (represented as an
+            editing command). Timers that were in the holding tank never stopped ticking and will now
+            be able to execute their action.
+
+            * WebProcess/WebPage/WebPage.cpp:
+            (WebKit::WebPage::executeEditingCommand):
+            (WebKit::WebPage::insertTextAsync):
+            (WebKit::WebPage::setCompositionAsync):
+            (WebKit::WebPage::confirmCompositionAsync):
+            Call platformWillPerformEditingCommand().
+
+            * WebProcess/WebPage/WebPage.h:
+            (WebKit::WebPage::platformWillPerformEditingCommand): Added.
+            * WebProcess/WebPage/ios/WebPageIOS.mm:
+            (WebKit::WebPage::platformWillPerformEditingCommand): Remove all the timers from the holding
+            tank if we have a holding tank.
+
+2019-07-17  Kocsen Chung  <kocsen_ch...@apple.com>
+
         Cherry-pick r247524. rdar://problem/53229635
 
     [iOS] Option + Up or Down Arrow key doesn’t move cursor past paragraph boundaries in WebKit2

Modified: branches/safari-608-branch/Source/WebKit/WebProcess/WebPage/WebPage.cpp (247605 => 247606)


--- branches/safari-608-branch/Source/WebKit/WebProcess/WebPage/WebPage.cpp	2019-07-18 20:24:34 UTC (rev 247605)
+++ branches/safari-608-branch/Source/WebKit/WebProcess/WebPage/WebPage.cpp	2019-07-18 20:24:40 UTC (rev 247606)
@@ -1260,8 +1260,10 @@
 
 void WebPage::executeEditingCommand(const String& commandName, const String& argument)
 {
-    Frame& frame = m_page->focusController().focusedOrMainFrame();
+    platformWillPerformEditingCommand();
 
+    auto& frame = m_page->focusController().focusedOrMainFrame();
+
     if (PluginView* pluginView = focusedPluginViewForFrame(frame)) {
         pluginView->handleEditingCommand(commandName, argument);
         return;
@@ -5175,8 +5177,10 @@
 
 void WebPage::insertTextAsync(const String& text, const EditingRange& replacementEditingRange, InsertTextOptions&& options)
 {
-    Frame& frame = m_page->focusController().focusedOrMainFrame();
+    platformWillPerformEditingCommand();
 
+    auto& frame = m_page->focusController().focusedOrMainFrame();
+
     Ref<Frame> protector(frame);
 
     UserGestureIndicator gestureIndicator { options.processingUserGesture ? ProcessingUserGesture : NotProcessingUserGesture, frame.document() };
@@ -5249,8 +5253,10 @@
 
 void WebPage::setCompositionAsync(const String& text, const Vector<CompositionUnderline>& underlines, const EditingRange& selection, const EditingRange& replacementEditingRange)
 {
-    Frame& frame = m_page->focusController().focusedOrMainFrame();
+    platformWillPerformEditingCommand();
 
+    auto& frame = m_page->focusController().focusedOrMainFrame();
+
     if (frame.selection().selection().isContentEditable()) {
         RefPtr<Range> replacementRange;
         if (replacementEditingRange.location != notFound) {
@@ -5265,6 +5271,8 @@
 
 void WebPage::confirmCompositionAsync()
 {
+    platformWillPerformEditingCommand();
+
     Frame& frame = m_page->focusController().focusedOrMainFrame();
     frame.editor().confirmComposition();
 }

Modified: branches/safari-608-branch/Source/WebKit/WebProcess/WebPage/WebPage.h (247605 => 247606)


--- branches/safari-608-branch/Source/WebKit/WebProcess/WebPage/WebPage.h	2019-07-18 20:24:34 UTC (rev 247605)
+++ branches/safari-608-branch/Source/WebKit/WebProcess/WebPage/WebPage.h	2019-07-18 20:24:40 UTC (rev 247606)
@@ -1225,6 +1225,7 @@
     void platformReinitialize();
     void platformDetach();
     void platformEditorState(WebCore::Frame&, EditorState& result, IncludePostLayoutDataHint) const;
+    void platformWillPerformEditingCommand();
     void sendEditorStateUpdate();
 
 #if PLATFORM(COCOA)
@@ -1957,5 +1958,9 @@
 #endif
 };
 
+#if !PLATFORM(IOS_FAMILY)
+inline void WebPage::platformWillPerformEditingCommand() { }
+#endif
+
 } // namespace WebKit
 

Modified: branches/safari-608-branch/Source/WebKit/WebProcess/WebPage/ios/WebPageIOS.mm (247605 => 247606)


--- branches/safari-608-branch/Source/WebKit/WebProcess/WebPage/ios/WebPageIOS.mm	2019-07-18 20:24:34 UTC (rev 247605)
+++ branches/safari-608-branch/Source/WebKit/WebProcess/WebPage/ios/WebPageIOS.mm	2019-07-18 20:24:40 UTC (rev 247606)
@@ -63,6 +63,7 @@
 #import <WebCore/AutofillElements.h>
 #import <WebCore/Chrome.h>
 #import <WebCore/ContentChangeObserver.h>
+#import <WebCore/DOMTimerHoldingTank.h>
 #import <WebCore/DataDetection.h>
 #import <WebCore/DiagnosticLoggingClient.h>
 #import <WebCore/DiagnosticLoggingKeys.h>
@@ -275,6 +276,15 @@
     }
 }
 
+void WebPage::platformWillPerformEditingCommand()
+{
+    auto& frame = m_page->focusController().focusedOrMainFrame();
+    if (auto* document = frame.document()) {
+        if (auto* holdingTank = document->domTimerHoldingTankIfExists())
+            holdingTank->removeAll();
+    }
+}
+
 FloatSize WebPage::screenSize() const
 {
     return m_screenSize;
_______________________________________________
webkit-changes mailing list
webkit-changes@lists.webkit.org
https://lists.webkit.org/mailman/listinfo/webkit-changes

Reply via email to