Title: [294514] trunk/Source/WebCore/editing
Revision
294514
Author
[email protected]
Date
2022-05-19 17:00:07 -0700 (Thu, 19 May 2022)

Log Message

Avoid injected bundle delegate calls when text fields are focused and blurred without user interaction
https://bugs.webkit.org/show_bug.cgi?id=240614

Reviewed by Chris Dumez.

Add a mechanism to throttle calls to injected bundle form client via `textFieldDidBeginEditing`, in
the case where the focused element is in a subframe that has never handled an editing command or
user interaction. This yields a small win on Speedometer 2, on M1 MacBookPro:

```
----------------------------------------------------------------------------------------------------------
|               subtest                |     ms      |     ms      |  b / a   | pValue                   |
----------------------------------------------------------------------------------------------------------
| Angular2-TypeScript-TodoMVC          |30.651667    |28.456667    |0.928389  | 0.000000 (significant)   |
| AngularJS-TodoMVC                    |109.733333   |111.623333   |1.017224  | 0.000000 (significant)   |
| BackboneJS-TodoMVC                   |31.371667    |33.988333    |1.083409  | 0.000000 (significant)   |
| Elm-TodoMVC                          |96.818333    |96.893333    |1.000775  | 0.760078                 |
| EmberJS-Debug-TodoMVC                |292.241667   |292.628333   |1.001323  | 0.530967                 |
| EmberJS-TodoMVC                      |99.671667    |98.963333    |0.992893  | 0.040683                 |
| Flight-TodoMVC                       |43.815000    |50.915000    |1.162045  | 0.000000 (significant)   |
| Inferno-TodoMVC                      |46.633333    |44.911667    |0.963081  | 0.000000 (significant)   |
| Preact-TodoMVC                       |11.636667    |11.673333    |1.003151  | 0.862258                 |
| React-Redux-TodoMVC                  |121.411667   |120.096667   |0.989169  | 0.000000 (significant)   |
| React-TodoMVC                        |69.908333    |69.885000    |0.999666  | 0.944581                 |
| Vanilla-ES2015-Babel-Webpack-TodoMVC |47.750000    |46.223333    |0.968028  | 0.000000 (significant)   |
| Vanilla-ES2015-TodoMVC               |48.721667    |48.323333    |0.991824  | 0.001202 (significant)   |
| VanillaJS-TodoMVC                    |40.218333    |38.231667    |0.950603  | 0.000000 (significant)   |
| VueJS-TodoMVC                        |18.420000    |16.793333    |0.911690  | 0.000000 (significant)   |
| jQuery-TodoMVC                       |188.831667   |186.908333   |0.989815  | 0.000005 (significant)   |
----------------------------------------------------------------------------------------------------------

a mean = 343.48012
b mean = 344.99902
pValue = 0.0027314347
(Bigger means are better.)
1.004 times better
Results ARE significant
```

See below for more details.

* Source/WebCore/editing/Editor.cpp:
(WebCore::Editor::Editor):
(WebCore::Editor::stopTextFieldDidBeginEditingTimer):

Stop the `textFieldDidBeginEditing` timer if needed, and return true if and only if it was active.

(WebCore::Editor::textFieldDidBeginEditingTimerFired):

Dispatch the deferred EditorClient call using the currently focused element.

(WebCore::Editor::textFieldDidBeginEditing):

If we're inside of a subframe that has never handled user interaction or editing, then don't eagerly
notify the injected bundle about the newly focused text field; instead, schedule a newly added timer
(`m_textFieldDidBeginEditingTimer`) to perform this call after a short delay.

(WebCore::Editor::textFieldDidEndEditing):

If editing ends (i.e. the text field is blurred) while the `textFieldDidBeginEditing` timer is still
scheduled, then simply elide this call to `textFieldDidBeginEditing` and `textFieldDidEndEditing`
altogether. This prevents us from repeatedly calling into the injected bundle if a page frequently
programmatically focuses and blurs text fields.

(WebCore::Editor::textDidChangeInTextField):
(WebCore::Editor::doTextFieldCommandFromEvent):
(WebCore::Editor::textWillBeDeletedInTextField):
(WebCore::Editor::textDidChangeInTextArea):

If any of these other injected bundle form client hooks are invoked while there is a scheduled
`textFieldDidBeginEditing` timer, then stop the timer and immediately inform the injected bundle
client about the focused text field.

(WebCore::Editor::isInSubframeWithoutUserInteraction const):
(WebCore::Editor::respondToChangedSelection):

Use the new helper function above.

* Source/WebCore/editing/Editor.h:

Canonical link: https://commits.webkit.org/250771@main

Modified Paths

Diff

Modified: trunk/Source/WebCore/editing/Editor.cpp (294513 => 294514)


--- trunk/Source/WebCore/editing/Editor.cpp	2022-05-19 23:46:27 UTC (rev 294513)
+++ trunk/Source/WebCore/editing/Editor.cpp	2022-05-20 00:00:07 UTC (rev 294514)
@@ -138,6 +138,8 @@
 
 namespace WebCore {
 
+constexpr auto textFieldDidBeginEditingClientNotificationDelay = 500_ms;
+
 static bool dispatchBeforeInputEvent(Element& element, const AtomString& inputType, const String& data = { }, RefPtr<DataTransfer>&& dataTransfer = nullptr, const Vector<RefPtr<StaticRange>>& targetRanges = { }, Event::IsCancelable cancelable = Event::IsCancelable::Yes)
 {
     auto event = InputEvent::create(eventNames().beforeinputEvent, inputType, cancelable, element.document().windowProxy(), data, WTFMove(dataTransfer), targetRanges, 0);
@@ -1246,6 +1248,7 @@
 #if ENABLE(TELEPHONE_NUMBER_DETECTION) && !PLATFORM(IOS_FAMILY)
     , m_telephoneNumberDetectionUpdateTimer(*this, &Editor::scanSelectionForTelephoneNumbers, 0_s)
 #endif
+    , m_textFieldDidBeginEditingTimer(*this, &Editor::textFieldDidBeginEditingTimerFired)
 {
 }
 
@@ -3462,43 +3465,95 @@
     return computeAndSetTypingStyle(EditingStyle::create(&properties), editingAction);
 }
 
-void Editor::textFieldDidBeginEditing(Element& e)
+bool Editor::stopTextFieldDidBeginEditingTimer()
 {
-    if (client())
-        client()->textFieldDidBeginEditing(e);
+    if (m_textFieldDidBeginEditingTimer.isActive()) {
+        m_textFieldDidBeginEditingTimer.stop();
+        return true;
+    }
+    return false;
 }
 
-void Editor::textFieldDidEndEditing(Element& e)
+void Editor::textFieldDidBeginEditingTimerFired()
 {
+    auto* client = this->client();
+    if (!client)
+        return;
+
+    if (RefPtr element = m_document.activeElement())
+        client->textFieldDidBeginEditing(*element);
+}
+
+void Editor::textFieldDidBeginEditing(Element& element)
+{
+    auto* client = this->client();
+    if (!client)
+        return;
+
+    if (isInSubframeWithoutUserInteraction()) {
+        m_textFieldDidBeginEditingTimer.startOneShot(textFieldDidBeginEditingClientNotificationDelay);
+        return;
+    }
+
+    client->textFieldDidBeginEditing(element);
+}
+
+void Editor::textFieldDidEndEditing(Element& element)
+{
     dismissCorrectionPanelAsIgnored();
-    if (client())
-        client()->textFieldDidEndEditing(e);
+
+    auto* client = this->client();
+    if (!client)
+        return;
+
+    if (stopTextFieldDidBeginEditingTimer())
+        return;
+
+    client->textFieldDidEndEditing(element);
 }
 
-void Editor::textDidChangeInTextField(Element& e)
+void Editor::textDidChangeInTextField(Element& element)
 {
-    if (client())
-        client()->textDidChangeInTextField(e);
+    auto* client = this->client();
+    if (!client)
+        return;
+
+    if (stopTextFieldDidBeginEditingTimer())
+        client->textFieldDidBeginEditing(element);
+    client->textDidChangeInTextField(element);
 }
 
-bool Editor::doTextFieldCommandFromEvent(Element& e, KeyboardEvent* ke)
+bool Editor::doTextFieldCommandFromEvent(Element& element, KeyboardEvent* event)
 {
-    if (client())
-        return client()->doTextFieldCommandFromEvent(e, ke);
+    auto* client = this->client();
+    if (!client)
+        return false;
 
-    return false;
+    if (stopTextFieldDidBeginEditingTimer())
+        client->textFieldDidBeginEditing(element);
+    return client->doTextFieldCommandFromEvent(element, event);
 }
 
 void Editor::textWillBeDeletedInTextField(Element& input)
 {
-    if (client())
-        client()->textWillBeDeletedInTextField(input);
+    auto* client = this->client();
+    if (!client)
+        return;
+
+    if (stopTextFieldDidBeginEditingTimer())
+        client->textFieldDidBeginEditing(input);
+    client->textWillBeDeletedInTextField(input);
 }
 
-void Editor::textDidChangeInTextArea(Element& e)
+void Editor::textDidChangeInTextArea(Element& element)
 {
-    if (client())
-        client()->textDidChangeInTextArea(e);
+    auto* client = this->client();
+    if (!client)
+        return;
+
+    if (stopTextFieldDidBeginEditingTimer())
+        client->textFieldDidBeginEditing(element);
+    client->textDidChangeInTextArea(element);
 }
 
 void Editor::applyEditingStyleToBodyElement() const
@@ -3686,6 +3741,11 @@
 }
 #endif
 
+bool Editor::isInSubframeWithoutUserInteraction() const
+{
+    return !m_hasHandledAnyEditing && !m_document.hasHadUserInteraction() && !m_document.isTopDocument();
+}
+
 void Editor::respondToChangedSelection(const VisibleSelection&, OptionSet<FrameSelection::SetSelectionOption> options)
 {
 #if PLATFORM(IOS_FAMILY)
@@ -3705,7 +3765,7 @@
     setStartNewKillRingSequence(true);
     m_imageElementsToLoadBeforeRevealingSelection.clear();
 
-    if (!m_hasHandledAnyEditing && !m_document.hasHadUserInteraction() && !m_document.isTopDocument())
+    if (isInSubframeWithoutUserInteraction())
         return;
 
     if (m_editorUIUpdateTimer.isActive())

Modified: trunk/Source/WebCore/editing/Editor.h (294513 => 294514)


--- trunk/Source/WebCore/editing/Editor.h	2022-05-19 23:46:27 UTC (rev 294513)
+++ trunk/Source/WebCore/editing/Editor.h	2022-05-20 00:00:07 UTC (rev 294514)
@@ -632,6 +632,8 @@
 
     std::optional<SimpleRange> adjustedSelectionRange();
 
+    bool isInSubframeWithoutUserInteraction() const;
+
 #if PLATFORM(COCOA)
     RefPtr<SharedBuffer> selectionInWebArchiveFormat();
     String selectionInHTMLFormat();
@@ -647,6 +649,9 @@
     void notifyClientOfAttachmentUpdates();
 #endif
 
+    bool stopTextFieldDidBeginEditingTimer();
+    void textFieldDidBeginEditingTimerFired();
+
     String platformContentTypeForBlobType(const String& type) const;
 
     void postTextStateChangeNotificationForCut(const String&, const VisibleSelection&);
@@ -690,6 +695,8 @@
     Vector<SimpleRange> m_detectedTelephoneNumberRanges;
 #endif
 
+    Timer m_textFieldDidBeginEditingTimer;
+
     mutable std::unique_ptr<ScrollView::ProhibitScrollingWhenChangingContentSizeForScope> m_prohibitScrollingDueToContentSizeChangesWhileTyping;
 
     bool m_isGettingDictionaryPopupInfo { false };
_______________________________________________
webkit-changes mailing list
[email protected]
https://lists.webkit.org/mailman/listinfo/webkit-changes

Reply via email to