Changeset 247530 in webkit
- Timestamp:
- Jul 17, 2019 1:06:04 PM (5 years ago)
- Location:
- trunk
- Files:
-
- 4 added
- 15 edited
Legend:
- Unmodified
- Added
- Removed
-
trunk/LayoutTests/ChangeLog
r247528 r247530 1 2019-07-17 Daniel Bates <dabates@apple.com> 2 3 Typing into a cell in a Google Sheet lags behind by one character 4 https://bugs.webkit.org/show_bug.cgi?id=199587 5 <rdar://problem/51616845> 6 7 Reviewed by Brent Fulgham. 8 9 Add a test that enables the quirk and ensures that the DOM is up-to-date on expiration of a 10 zero timer scheduled from keydown, keypress, keyup, and input. 11 12 * fast/events/ios/dom-update-on-keydown-quirk-expected.txt: Added. 13 * fast/events/ios/dom-update-on-keydown-quirk.html: Added. 14 1 15 2019-07-17 Myles C. Maxfield <mmaxfield@apple.com> 2 16 -
trunk/Source/WebCore/ChangeLog
r247529 r247530 1 2019-07-17 Daniel Bates <dabates@apple.com> 2 3 Typing into a cell in a Google Sheet lags behind by one character 4 https://bugs.webkit.org/show_bug.cgi?id=199587 5 <rdar://problem/51616845> 6 7 Reviewed by Brent Fulgham. 8 9 Add a Google Sheets quirk. Put all DOM timers scheduled from keydown and keypress event listeners 10 into a holding tank. The timers continue to tick, but are barred from executing their action until 11 the next text insertion or deletion or 32 ms (on device) have elapsed, whichever is sooner. We only 12 allocate a holding tank once per document, only if the quirk is active, and this allocation is done 13 when the document schedules a timer on keydown or keypress. The holding tank lives for the lifetime 14 of the document. 15 16 The story behind the quirk: 17 18 On keypress Google Sheets schedules timers and expects that a DOM update will occur (i.e. text 19 will be inserted or deleted) within the same event loop iteration as the dispatched keypress. The 20 UI Events spec. [1] makes no such guarantee of when a DOM update must occur in relation to the keypress 21 event. It could happen in the same event loop iteration as the key press (as Google expects), the 22 next iteration, 500ms later, 2 minutes later, etc. What the spec does guarantee is that by the time 23 a DOM input event is dispatched that the DOM will be updated. And this is the solution to the problem 24 Google Sheets is trying to solve, but is doing so using pre-IE 9 technology (though similar 25 functionality was available via onpropertychange in IE < 9). 26 27 See also <https://github.com/w3c/uievents/issues/238>, which is tracking a spec. text update for 28 this quirk. 29 30 Test: fast/events/ios/dom-update-on-keydown-quirk.html 31 32 [1] <https://w3c.github.io/uievents/> (Editor's Draft, 14 October 2018) 33 34 * SourcesCocoa.txt: 35 * WebCore.xcodeproj/project.pbxproj: 36 Add some files to the project. 37 38 * dom/Document.cpp: 39 (WebCore::Document::domTimerHoldingTank): Added. 40 * dom/Document.h: 41 (WebCore::Document::domTimerHoldingTankIfExists): Added. 42 43 * page/DOMTimer.cpp: 44 (WebCore::DOMTimer::install): Put the newly instantiated timer into the holding tank. 45 (WebCore::DOMTimer::removeById): Remove the timer from the holding tank. 46 (WebCore::DOMTimer::fired): Check if the timer is in the holding tank. If it is and it is a one- 47 shot timer then schedule it for the next event loop iteration. If it's a repeating timer just 48 let it continue ticking. Otherwise, do what we no now and execute the timer's action. The reason 49 we do not suspend timers in the holding tank is because: 50 1. Far out timers (Google Sheets registers timers as far out as 5 minutes!) are not penalized. 51 Though smart supension logic could avoid this. See (3). 52 53 2. Empirical observations indicate that the keyboard will perform the insertion or deletion 54 reasonably quickly (not the same event loop iteration as the keydown, but within two iterations out). 55 So, the timers in the holding tank are short-lived. 56 57 3. Simplifies the code. There is no need to keep additional bookkeeping to track multiple timer 58 suspension reasons (timers currently can only have one suspension reason) or alternatively defer 59 scheduling a timer until a later time and computing a new "fair" firing time when scheduled. 60 * page/EventHandler.cpp: 61 (WebCore::EventHandler::internalKeyEvent): Place a token on the stack to put all DOM timers 62 scheduled on keydown and keypress into the holding tank if the quirk is enabled. 63 * page/Quirks.cpp: 64 (WebCore::Quirks::needsDeferKeyDownAndKeyPressTimersUntilNextEditingCommand const): Added. 65 * page/Quirks.h: 66 * page/Settings.yaml: Added setting so that this quirk can be enabled from a layout test. This setting 67 also lets us enable the quirk for all sites or for certain third-party apps if desired. 68 * page/ios/DOMTimerHoldingTank.cpp: Added. 69 (WebCore::DOMTimerHoldingTank::DOMTimerHoldingTank): 70 (WebCore::DOMTimerHoldingTank::add): 71 (WebCore::DOMTimerHoldingTank::remove): 72 (WebCore::DOMTimerHoldingTank::contains): 73 (WebCore::DOMTimerHoldingTank::removeAll): 74 (WebCore::DOMTimerHoldingTank::stopExceededMaximumHoldTimer): 75 * page/ios/DOMTimerHoldingTank.h: Added. 76 (WebCore::DeferDOMTimersForScope::DeferDOMTimersForScope): 77 (WebCore::DeferDOMTimersForScope::~DeferDOMTimersForScope): 78 (WebCore::DeferDOMTimersForScope::isDeferring): 79 1 80 2019-07-17 Darin Adler <darin@apple.com> 2 81 -
trunk/Source/WebCore/SourcesCocoa.txt
r247472 r247530 132 132 133 133 page/ios/ContentChangeObserver.cpp 134 page/ios/DOMTimerHoldingTank.cpp 134 135 page/ios/EventHandlerIOS.mm 135 136 page/ios/FrameIOS.mm -
trunk/Source/WebCore/WebCore.xcodeproj/project.pbxproj
r247527 r247530 4148 4148 CE08C3D2152B599A0021B8C2 /* AlternativeTextController.h in Headers */ = {isa = PBXBuildFile; fileRef = CE08C3D0152B599A0021B8C2 /* AlternativeTextController.h */; settings = {ATTRIBUTES = (); }; }; 4149 4149 CE1866451F72E5B400A0CAB6 /* MarkedText.h in Headers */ = {isa = PBXBuildFile; fileRef = CE1866431F72E5B400A0CAB6 /* MarkedText.h */; settings = {ATTRIBUTES = (Private, ); }; }; 4150 CE1A501F22D5350900CBC927 /* DOMTimerHoldingTank.h in Headers */ = {isa = PBXBuildFile; fileRef = CE1A501D22D5350900CBC927 /* DOMTimerHoldingTank.h */; settings = {ATTRIBUTES = (Private, ); }; }; 4150 4151 CE2849871CA360DF00B4A57F /* ContentSecurityPolicyDirectiveNames.h in Headers */ = {isa = PBXBuildFile; fileRef = CE2849861CA360DF00B4A57F /* ContentSecurityPolicyDirectiveNames.h */; }; 4151 4152 CE5169E721F1B84700EA4F78 /* ColorIOS.h in Headers */ = {isa = PBXBuildFile; fileRef = CE5169E521F1B84700EA4F78 /* ColorIOS.h */; settings = {ATTRIBUTES = (Private, ); }; }; … … 13813 13814 CE1866421F72E5B400A0CAB6 /* MarkedText.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = MarkedText.cpp; sourceTree = "<group>"; }; 13814 13815 CE1866431F72E5B400A0CAB6 /* MarkedText.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MarkedText.h; sourceTree = "<group>"; }; 13816 CE1A501D22D5350900CBC927 /* DOMTimerHoldingTank.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DOMTimerHoldingTank.h; sourceTree = "<group>"; }; 13817 CE1A501E22D5350900CBC927 /* DOMTimerHoldingTank.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = DOMTimerHoldingTank.cpp; sourceTree = "<group>"; }; 13815 13818 CE2849861CA360DF00B4A57F /* ContentSecurityPolicyDirectiveNames.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ContentSecurityPolicyDirectiveNames.h; path = csp/ContentSecurityPolicyDirectiveNames.h; sourceTree = "<group>"; }; 13816 13819 CE2849881CA3614600B4A57F /* ContentSecurityPolicyDirectiveNames.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = ContentSecurityPolicyDirectiveNames.cpp; path = csp/ContentSecurityPolicyDirectiveNames.cpp; sourceTree = "<group>"; }; … … 16548 16551 6FE9F09222211035004C5082 /* ContentChangeObserver.cpp */, 16549 16552 6FB5E212221F2447003989CF /* ContentChangeObserver.h */, 16553 CE1A501E22D5350900CBC927 /* DOMTimerHoldingTank.cpp */, 16554 CE1A501D22D5350900CBC927 /* DOMTimerHoldingTank.h */, 16550 16555 FE6938B51045D67E008EABB6 /* EventHandlerIOS.mm */, 16551 16556 FED13D3B0CEA936A00D89466 /* FrameIOS.mm */, … … 29012 29017 BC64640A11D7F304006455B0 /* DOMStringMap.h in Headers */, 29013 29018 188604B40F2E654A000B6443 /* DOMTimer.h in Headers */, 29019 CE1A501F22D5350900CBC927 /* DOMTimerHoldingTank.h in Headers */, 29014 29020 05FD69E012845D4300B2BEB3 /* DOMTimeStamp.h in Headers */, 29015 29021 76FC2B0C12370DA0006A991A /* DOMTokenList.h in Headers */, -
trunk/Source/WebCore/dom/Document.cpp
r247529 r247530 263 263 #include "ContentChangeObserver.h" 264 264 #include "CSSFontSelector.h" 265 #include "DOMTimerHoldingTank.h" 265 266 #include "DeviceMotionClientIOS.h" 266 267 #include "DeviceMotionController.h" … … 8183 8184 8184 8185 #if PLATFORM(IOS_FAMILY) 8186 8185 8187 ContentChangeObserver& Document::contentChangeObserver() 8186 8188 { … … 8189 8191 return *m_contentChangeObserver; 8190 8192 } 8193 8194 DOMTimerHoldingTank& Document::domTimerHoldingTank() 8195 { 8196 if (m_domTimerHoldingTank) 8197 return *m_domTimerHoldingTank; 8198 m_domTimerHoldingTank = std::make_unique<DOMTimerHoldingTank>(); 8199 return *m_domTimerHoldingTank; 8200 } 8201 8191 8202 #endif 8192 8203 -
trunk/Source/WebCore/dom/Document.h
r247472 r247530 107 107 class DOMImplementation; 108 108 class DOMSelection; 109 class DOMTimerHoldingTank; 109 110 class DOMWindow; 110 111 class DOMWrapperWorld; … … 884 885 885 886 WEBCORE_EXPORT ContentChangeObserver& contentChangeObserver(); 887 888 DOMTimerHoldingTank* domTimerHoldingTankIfExists() { return m_domTimerHoldingTank.get(); } 889 DOMTimerHoldingTank& domTimerHoldingTank(); 886 890 #endif 887 891 … … 2045 2049 #if PLATFORM(IOS_FAMILY) 2046 2050 std::unique_ptr<ContentChangeObserver> m_contentChangeObserver; 2051 std::unique_ptr<DOMTimerHoldingTank> m_domTimerHoldingTank; 2047 2052 #endif 2048 2053 -
trunk/Source/WebCore/page/DOMTimer.cpp
r247472 r247530 44 44 #if PLATFORM(IOS_FAMILY) 45 45 #include "ContentChangeObserver.h" 46 #include "DOMTimerHoldingTank.h" 46 47 #endif 47 48 … … 197 198 nestedTimers->add(timer->m_timeoutId, *timer); 198 199 #if PLATFORM(IOS_FAMILY) 199 if (is<Document>(context)) 200 downcast<Document>(context).contentChangeObserver().didInstallDOMTimer(*timer, timeout, singleShot); 200 if (is<Document>(context)) { 201 auto& document = downcast<Document>(context); 202 document.contentChangeObserver().didInstallDOMTimer(*timer, timeout, singleShot); 203 if (DeferDOMTimersForScope::isDeferring()) 204 document.domTimerHoldingTank().add(*timer); 205 } 201 206 #endif 202 207 return timer->m_timeoutId; … … 214 219 if (is<Document>(context)) { 215 220 auto& document = downcast<Document>(context); 216 if (auto* timer = document.findTimeout(timeoutId)) 221 if (auto* timer = document.findTimeout(timeoutId)) { 217 222 document.contentChangeObserver().didRemoveDOMTimer(*timer); 223 if (auto* holdingTank = document.domTimerHoldingTankIfExists()) 224 holdingTank->remove(*timer); 225 } 218 226 } 219 227 #endif … … 284 292 ASSERT(scriptExecutionContext()); 285 293 ScriptExecutionContext& context = *scriptExecutionContext(); 294 295 #if PLATFORM(IOS_FAMILY) 296 if (is<Document>(context)) { 297 auto& document = downcast<Document>(context); 298 if (auto* holdingTank = document.domTimerHoldingTankIfExists(); holdingTank && holdingTank->contains(*this)) { 299 if (!repeatInterval()) 300 startOneShot(0_s); 301 return; 302 } 303 } 304 #endif 286 305 287 306 DOMTimerFireState fireState(context, std::min(m_nestingLevel + 1, maxTimerNestingLevel)); -
trunk/Source/WebCore/page/EventHandler.cpp
r247472 r247530 132 132 #endif 133 133 134 #if PLATFORM(IOS_FAMILY) 135 #include "DOMTimerHoldingTank.h" 136 #endif 137 134 138 namespace WebCore { 135 139 … … 3342 3346 keydown->stopPropagation(); 3343 3347 3348 #if PLATFORM(IOS_FAMILY) 3349 DeferDOMTimersForScope deferralScope { m_frame.document()->quirks().needsDeferKeyDownAndKeyPressTimersUntilNextEditingCommand() }; 3350 #endif 3351 3344 3352 element->dispatchEvent(keydown); 3345 3353 if (handledByInputMethod) -
trunk/Source/WebCore/page/Quirks.cpp
r247472 r247530 318 318 } 319 319 320 bool Quirks::needsDeferKeyDownAndKeyPressTimersUntilNextEditingCommand() const 321 { 322 #if PLATFORM(IOS_FAMILY) 323 if (m_document->settings().needsDeferKeyDownAndKeyPressTimersUntilNextEditingCommandQuirk()) 324 return true; 325 326 if (!needsQuirks()) 327 return false; 328 329 auto& url = m_document->topDocument().url(); 330 return equalLettersIgnoringASCIICase(url.host(), "docs.google.com") && url.path().startsWithIgnoringASCIICase("/spreadsheets/"); 331 #else 332 return false; 333 #endif 334 } 335 320 336 // FIXME(<rdar://problem/50394969>): Remove after desmos.com adopts inputmode="none". 321 337 bool Quirks::needsInputModeNoneImplicitly(const HTMLElement& element) const -
trunk/Source/WebCore/page/Quirks.h
r247472 r247530 55 55 bool shouldDisablePointerEventsQuirk() const; 56 56 bool needsInputModeNoneImplicitly(const HTMLElement&) const; 57 bool needsDeferKeyDownAndKeyPressTimersUntilNextEditingCommand() const; 57 58 58 59 WEBCORE_EXPORT bool shouldDispatchSyntheticMouseEventsWhenModifyingSelection() const; -
trunk/Source/WebCore/page/Settings.yaml
r247472 r247530 163 163 initial: false 164 164 165 # This is an iOS-specific quirk. Unlike Mac, keyboard operations are asynchronous and hence a DOM update as 166 # a result of text insertion or deletion does not occur within the same event loop iteration as a dispatched 167 # DOM keydown event. Some sites, notably Google Sheets, schedule timers on keypress and expect on a DOM update 168 # to have occurred on expiration. When enabled, this quirk puts all such scheduled timers in a holding tank 169 # until the keyboard performs the insertion or deletion. This gives Google Sheets the illusion that the DOM 170 # update happened within the same event loop iteration that the keypress event was dispatched in. 171 needsDeferKeyDownAndKeyPressTimersUntilNextEditingCommandQuirk: 172 initial: false 173 165 174 treatsAnyTextCSSLinkAsStylesheet: 166 175 initial: false -
trunk/Source/WebKit/ChangeLog
r247529 r247530 1 2019-07-17 Daniel Bates <dabates@apple.com> 2 3 Typing into a cell in a Google Sheet lags behind by one character 4 https://bugs.webkit.org/show_bug.cgi?id=199587 5 <rdar://problem/51616845> 6 7 Reviewed by Brent Fulgham. 8 9 Remove all timers from the holding tank on text insertion or deletion (represented as an 10 editing command). Timers that were in the holding tank never stopped ticking and will now 11 be able to execute their action. 12 13 * WebProcess/WebPage/WebPage.cpp: 14 (WebKit::WebPage::executeEditingCommand): 15 (WebKit::WebPage::insertTextAsync): 16 (WebKit::WebPage::setCompositionAsync): 17 (WebKit::WebPage::confirmCompositionAsync): 18 Call platformWillPerformEditingCommand(). 19 20 * WebProcess/WebPage/WebPage.h: 21 (WebKit::WebPage::platformWillPerformEditingCommand): Added. 22 * WebProcess/WebPage/ios/WebPageIOS.mm: 23 (WebKit::WebPage::platformWillPerformEditingCommand): Remove all the timers from the holding 24 tank if we have a holding tank. 25 1 26 2019-07-17 Darin Adler <darin@apple.com> 2 27 -
trunk/Source/WebKit/WebProcess/WebPage/WebPage.cpp
r247490 r247530 1261 1261 void WebPage::executeEditingCommand(const String& commandName, const String& argument) 1262 1262 { 1263 Frame& frame = m_page->focusController().focusedOrMainFrame(); 1263 platformWillPerformEditingCommand(); 1264 1265 auto& frame = m_page->focusController().focusedOrMainFrame(); 1264 1266 1265 1267 if (PluginView* pluginView = focusedPluginViewForFrame(frame)) { … … 5176 5178 void WebPage::insertTextAsync(const String& text, const EditingRange& replacementEditingRange, InsertTextOptions&& options) 5177 5179 { 5178 Frame& frame = m_page->focusController().focusedOrMainFrame(); 5180 platformWillPerformEditingCommand(); 5181 5182 auto& frame = m_page->focusController().focusedOrMainFrame(); 5179 5183 5180 5184 Ref<Frame> protector(frame); … … 5250 5254 void WebPage::setCompositionAsync(const String& text, const Vector<CompositionUnderline>& underlines, const EditingRange& selection, const EditingRange& replacementEditingRange) 5251 5255 { 5252 Frame& frame = m_page->focusController().focusedOrMainFrame(); 5256 platformWillPerformEditingCommand(); 5257 5258 auto& frame = m_page->focusController().focusedOrMainFrame(); 5253 5259 5254 5260 if (frame.selection().selection().isContentEditable()) { … … 5266 5272 void WebPage::confirmCompositionAsync() 5267 5273 { 5274 platformWillPerformEditingCommand(); 5275 5268 5276 Frame& frame = m_page->focusController().focusedOrMainFrame(); 5269 5277 frame.editor().confirmComposition(); -
trunk/Source/WebKit/WebProcess/WebPage/WebPage.h
r247490 r247530 1226 1226 void platformDetach(); 1227 1227 void platformEditorState(WebCore::Frame&, EditorState& result, IncludePostLayoutDataHint) const; 1228 void platformWillPerformEditingCommand(); 1228 1229 void sendEditorStateUpdate(); 1229 1230 … … 1958 1959 }; 1959 1960 1961 #if !PLATFORM(IOS_FAMILY) 1962 inline void WebPage::platformWillPerformEditingCommand() { } 1963 #endif 1964 1960 1965 } // namespace WebKit 1961 1966 -
trunk/Source/WebKit/WebProcess/WebPage/ios/WebPageIOS.mm
r247483 r247530 64 64 #import <WebCore/Chrome.h> 65 65 #import <WebCore/ContentChangeObserver.h> 66 #import <WebCore/DOMTimerHoldingTank.h> 66 67 #import <WebCore/DataDetection.h> 67 68 #import <WebCore/DiagnosticLoggingClient.h> … … 276 277 } 277 278 279 void WebPage::platformWillPerformEditingCommand() 280 { 281 auto& frame = m_page->focusController().focusedOrMainFrame(); 282 if (auto* document = frame.document()) { 283 if (auto* holdingTank = document->domTimerHoldingTankIfExists()) 284 holdingTank->removeAll(); 285 } 286 } 287 278 288 FloatSize WebPage::screenSize() const 279 289 {
Note: See TracChangeset
for help on using the changeset viewer.