Title: [206944] trunk
Revision
206944
Author
wenson_hs...@apple.com
Date
2016-10-07 16:47:18 -0700 (Fri, 07 Oct 2016)

Log Message

Support onbeforeinput event handling for the new InputEvent spec
https://bugs.webkit.org/show_bug.cgi?id=163021
<rdar://problem/28658073>

Reviewed by Darin Adler.

Source/WebCore:

Adds support for parsing the onbeforeinput attribute, and for sending default-preventable
`beforeinput` InputEvents to the page. To do this, we introduce two new virtual methods:
willApplyCommand and didApplyCommand on the CompositeEditCommand that are called before and
after CompositeEditCommand::doApply, respectively. willApplyCommand indicates whether or not
the composite editor command should proceed with applying the command.

Tweaks existing layout tests and adds new tests.

Tests: fast/events/before-input-events-different-start-end-elements.html
       fast/events/before-input-events-prevent-default-in-textfield.html
       fast/events/before-input-events-prevent-default.html

* dom/Document.idl:
* dom/Element.idl:
* dom/EventNames.h:
* dom/Node.cpp:
(WebCore::Node::dispatchInputEvent):
(WebCore::Node::defaultEventHandler):

Currently, we fire input events in Node in response to dispatching a webkitEditableContentChangedEvent. After
some discussion, Ryosuke and I believe that it will be ok to instead directly dispatch the input event where we
would normally dispatch a webkitEditableContentChangedEvent.

* editing/CompositeEditCommand.cpp:
(WebCore::EditCommandComposition::unapply):
(WebCore::EditCommandComposition::reapply):

Added calls to Editor::willUnapplyEditing and Editor::willReapplyEditing.

(WebCore::CompositeEditCommand::willApplyCommand):
(WebCore::CompositeEditCommand::apply):
(WebCore::CompositeEditCommand::didApplyCommand):

Added new virtual functions, willApplyCommand and didApplyCommand, that surround a call to
CompositeEditCommand::doApply. By default, they call willApplyEditing and appliedEditing on the editor, but may
be overridden in special cases, such as in TypingCommand, where we invoke appliedEditing after adding a new
typing command to the last open command.

If willApplyCommand returns false, CompositeEditCommand::apply will bail and not proceed with the command.

* editing/CompositeEditCommand.h:
* editing/Editor.cpp:
(WebCore::dispatchBeforeInputEvent):
(WebCore::dispatchBeforeInputEvents):
(WebCore::dispatchInputEvents):
(WebCore::Editor::willApplyEditing):
(WebCore::Editor::appliedEditing):
(WebCore::Editor::willUnapplyEditing):
(WebCore::Editor::unappliedEditing):
(WebCore::Editor::willReapplyEditing):
(WebCore::Editor::reappliedEditing):
(WebCore::Editor::computeAndSetTypingStyle):
(WebCore::dispatchEditableContentChangedEvents): Deleted.
* editing/Editor.h:
* editing/TypingCommand.cpp:
(WebCore::TypingCommand::willApplyCommand):
(WebCore::TypingCommand::didApplyCommand):
(WebCore::TypingCommand::willAddTypingToOpenCommand):
(WebCore::TypingCommand::insertTextRunWithoutNewlines):
(WebCore::TypingCommand::insertLineBreak):
(WebCore::TypingCommand::insertParagraphSeparator):
(WebCore::TypingCommand::insertParagraphSeparatorInQuotedContent):
(WebCore::TypingCommand::deleteKeyPressed):
(WebCore::TypingCommand::forwardDeleteKeyPressed):
(WebCore::TypingCommand::deleteSelection):

These now invoke willAddTypingToOpenCommand before proceeding with creating the command and applying it. The
flow is now:
    - willAddTypingToOpenCommand
    - create and apply a new command
    - typingAddedToOpenCommand

* editing/TypingCommand.h:
(WebCore::TypingCommand::preservesTypingStyle): Deleted.
(WebCore::TypingCommand::shouldRetainAutocorrectionIndicator): Deleted.
(WebCore::TypingCommand::setShouldRetainAutocorrectionIndicator): Deleted.
(WebCore::TypingCommand::shouldStopCaretBlinking): Deleted.
* html/HTMLAttributeNames.in:
* html/HTMLElement.cpp:
(WebCore::HTMLElement::createEventHandlerNameMap):

LayoutTests:

Tweak an existing test to hook into the 'input' event instead of 'webkitEditableContentChanged', as well as
tests added in r206843 to verify that `onbeforeinput` handlers are invoked with InputEvents. Also introduces
new unit tests verifying that calling preventDefault on InputEvents fired by `onbeforeinput` correctly prevent
text from being inserted or deleted.

* editing/undo/undo-after-event-edited.html:
* fast/events/before-input-events-different-start-end-elements-expected.txt: Added.
* fast/events/before-input-events-different-start-end-elements.html: Added.
* fast/events/before-input-events-prevent-default-expected.txt: Added.
* fast/events/before-input-events-prevent-default-in-textfield-expected.txt: Added.
* fast/events/before-input-events-prevent-default-in-textfield.html: Added.
* fast/events/before-input-events-prevent-default.html: Added.
* fast/events/input-events-fired-when-typing-expected.txt:
* fast/events/input-events-fired-when-typing.html:
* platform/ios-simulator/TestExpectations:

Modified Paths

Added Paths

Diff

Modified: trunk/LayoutTests/ChangeLog (206943 => 206944)


--- trunk/LayoutTests/ChangeLog	2016-10-07 23:30:16 UTC (rev 206943)
+++ trunk/LayoutTests/ChangeLog	2016-10-07 23:47:18 UTC (rev 206944)
@@ -1,3 +1,27 @@
+2016-10-07  Wenson Hsieh  <wenson_hs...@apple.com>
+
+        Support onbeforeinput event handling for the new InputEvent spec
+        https://bugs.webkit.org/show_bug.cgi?id=163021
+        <rdar://problem/28658073>
+
+        Reviewed by Darin Adler.
+
+        Tweak an existing test to hook into the 'input' event instead of 'webkitEditableContentChanged', as well as
+        tests added in r206843 to verify that `onbeforeinput` handlers are invoked with InputEvents. Also introduces
+        new unit tests verifying that calling preventDefault on InputEvents fired by `onbeforeinput` correctly prevent
+        text from being inserted or deleted.
+
+        * editing/undo/undo-after-event-edited.html:
+        * fast/events/before-input-events-different-start-end-elements-expected.txt: Added.
+        * fast/events/before-input-events-different-start-end-elements.html: Added.
+        * fast/events/before-input-events-prevent-default-expected.txt: Added.
+        * fast/events/before-input-events-prevent-default-in-textfield-expected.txt: Added.
+        * fast/events/before-input-events-prevent-default-in-textfield.html: Added.
+        * fast/events/before-input-events-prevent-default.html: Added.
+        * fast/events/input-events-fired-when-typing-expected.txt:
+        * fast/events/input-events-fired-when-typing.html:
+        * platform/ios-simulator/TestExpectations:
+
 2016-10-07  Nan Wang  <n_w...@apple.com>
 
         AX: <figcaption> should be AXTitleUIElement for other content inside the <figure>

Modified: trunk/LayoutTests/editing/undo/undo-after-event-edited.html (206943 => 206944)


--- trunk/LayoutTests/editing/undo/undo-after-event-edited.html	2016-10-07 23:30:16 UTC (rev 206943)
+++ trunk/LayoutTests/editing/undo/undo-after-event-edited.html	2016-10-07 23:47:18 UTC (rev 206944)
@@ -17,7 +17,7 @@
 scriptElements[0].parentNode.removeChild(scriptElements[0]);
 var eventHandlerActive = false;
 
-document.addEventListener("webkitEditableContentChanged", function () {
+document.addEventListener("input", function () {
     if (eventHandlerActive)
         return;
     eventHandlerActive = true;

Added: trunk/LayoutTests/fast/events/before-input-events-different-start-end-elements-expected.txt (0 => 206944)


--- trunk/LayoutTests/fast/events/before-input-events-different-start-end-elements-expected.txt	                        (rev 0)
+++ trunk/LayoutTests/fast/events/before-input-events-different-start-end-elements-expected.txt	2016-10-07 23:47:18 UTC (rev 206944)
@@ -0,0 +1,27 @@
+PASS successfullyParsed is true
+
+TEST COMPLETE
+Fired `onbeforeinput` handler!
+Fired `oninput` handler!
+Fired `onbeforeinput` handler!
+Fired `oninput` handler!
+Fired `onbeforeinput` handler!
+Fired `oninput` handler!
+Fired `onbeforeinput` handler!
+Fired `oninput` handler!
+Fired `onbeforeinput` handler!
+Fired `oninput` handler!
+Fired `onbeforeinput` handler!
+Fired `oninput` handler!
+Fired `onbeforeinput` handler!
+Fired `oninput` handler!
+Fired `onbeforeinput` handler!
+Fired `oninput` handler!
+Fired `onbeforeinput` handler!
+Fired `oninput` handler!
+Fired `onbeforeinput` handler!
+Fired `oninput` handler!
+Fired `onbeforeinput` handler!
+Fired `oninput` handler!
+
+

Added: trunk/LayoutTests/fast/events/before-input-events-different-start-end-elements.html (0 => 206944)


--- trunk/LayoutTests/fast/events/before-input-events-different-start-end-elements.html	                        (rev 0)
+++ trunk/LayoutTests/fast/events/before-input-events-different-start-end-elements.html	2016-10-07 23:47:18 UTC (rev 206944)
@@ -0,0 +1,43 @@
+<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML//EN">
+<html>
+
+<head>
+    <script src=""
+    <script>
+        function beginTest()
+        {
+            if (!window.eventSender || !window.internals || !window.testRunner)
+                return;
+
+            internals.settings.setInputEventsEnabled(true);
+            testRunner.dumpAsText();
+            document.querySelector("#foo").focus();
+
+            for (var i = 0; i < 11; i++)
+                eventSender.keyDown("delete");
+        }
+
+        function checkInputEvent(event)
+        {
+            debug("Fired `oninput` handler!");
+        }
+
+        function checkBeforeInputEvent(event)
+        {
+            debug("Fired `onbeforeinput` handler!");
+        }
+    </script>
+</head>
+
+<body _onload_=beginTest()>
+    <div id="foo" contenteditable _oninput_=checkInputEvent(event) _onbeforeinput_=checkBeforeInputEvent(event)>
+        <b>
+            abc
+            <i>def</i>
+            <u>ghi</u>
+        </b>
+    </div>
+    <script src=""
+</body>
+
+</html>

Added: trunk/LayoutTests/fast/events/before-input-events-prevent-default-expected.txt (0 => 206944)


--- trunk/LayoutTests/fast/events/before-input-events-prevent-default-expected.txt	                        (rev 0)
+++ trunk/LayoutTests/fast/events/before-input-events-prevent-default-expected.txt	2016-10-07 23:47:18 UTC (rev 206944)
@@ -0,0 +1,5 @@
+PASS successfullyParsed is true
+
+TEST COMPLETE
+Fired `onbeforeinput`: preventing default!
+

Added: trunk/LayoutTests/fast/events/before-input-events-prevent-default-in-textfield-expected.txt (0 => 206944)


--- trunk/LayoutTests/fast/events/before-input-events-prevent-default-in-textfield-expected.txt	                        (rev 0)
+++ trunk/LayoutTests/fast/events/before-input-events-prevent-default-in-textfield-expected.txt	2016-10-07 23:47:18 UTC (rev 206944)
@@ -0,0 +1,6 @@
+PASS successfullyParsed is true
+
+TEST COMPLETE
+Fired `onbeforeinput`: preventing default!
+The final value is: abc
+

Added: trunk/LayoutTests/fast/events/before-input-events-prevent-default-in-textfield.html (0 => 206944)


--- trunk/LayoutTests/fast/events/before-input-events-prevent-default-in-textfield.html	                        (rev 0)
+++ trunk/LayoutTests/fast/events/before-input-events-prevent-default-in-textfield.html	2016-10-07 23:47:18 UTC (rev 206944)
@@ -0,0 +1,50 @@
+<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML//EN">
+<html>
+
+<head>
+    <script src=""
+    <script>
+        var preventDefaultInputEvents = false;
+
+        function beginTest()
+        {
+            if (!window.eventSender || !window.internals || !window.testRunner)
+                return;
+
+            internals.settings.setInputEventsEnabled(true);
+            testRunner.dumpAsText();
+            let input = document.querySelector("#foo");
+            input.focus();
+
+            eventSender.keyDown("a", []);
+            eventSender.keyDown("b", []);
+            eventSender.keyDown("c", []);
+
+            preventDefaultInputEvents = true;
+
+            eventSender.keyDown("delete");
+            debug(`The final value is: ${input.value}`);
+        }
+
+        function checkInputEvent(event)
+        {
+            if (preventDefaultInputEvents)
+                debug("FAIL: Did not expect to the `oninput` handler to fire.");
+        }
+
+        function checkBeforeInputEvent(event)
+        {
+            if (preventDefaultInputEvents) {
+                debug("Fired `onbeforeinput`: preventing default!");
+                event.preventDefault();
+            }
+        }
+    </script>
+</head>
+
+<body _onload_=beginTest()>
+    <input id="foo" contenteditable value="helloworld" _oninput_=checkInputEvent(event) _onbeforeinput_=checkBeforeInputEvent(event)></div>
+    <script src=""
+</body>
+
+</html>

Added: trunk/LayoutTests/fast/events/before-input-events-prevent-default.html (0 => 206944)


--- trunk/LayoutTests/fast/events/before-input-events-prevent-default.html	                        (rev 0)
+++ trunk/LayoutTests/fast/events/before-input-events-prevent-default.html	2016-10-07 23:47:18 UTC (rev 206944)
@@ -0,0 +1,36 @@
+<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML//EN">
+<html>
+
+<head>
+    <script src=""
+    <script>
+        function beginTest()
+        {
+            if (!window.eventSender || !window.internals || !window.testRunner)
+                return;
+
+            internals.settings.setInputEventsEnabled(true);
+            testRunner.dumpAsText();
+            document.querySelector("#foo").focus();
+            eventSender.keyDown("a", []);
+        }
+
+        function checkInputEvent(event)
+        {
+            debug("FAIL: Did not expect to the `oninput` handler to fire.");
+        }
+
+        function checkBeforeInputEvent(event)
+        {
+            debug("Fired `onbeforeinput`: preventing default!");
+            event.preventDefault();
+        }
+    </script>
+</head>
+
+<body _onload_=beginTest()>
+    <div id="foo" contenteditable _oninput_=checkInputEvent(event) _onbeforeinput_=checkBeforeInputEvent(event)></div>
+    <script src=""
+</body>
+
+</html>

Modified: trunk/LayoutTests/fast/events/input-events-fired-when-typing-expected.txt (206943 => 206944)


--- trunk/LayoutTests/fast/events/input-events-fired-when-typing-expected.txt	2016-10-07 23:30:16 UTC (rev 206943)
+++ trunk/LayoutTests/fast/events/input-events-fired-when-typing-expected.txt	2016-10-07 23:47:18 UTC (rev 206944)
@@ -1,16 +1,32 @@
 PASS successfullyParsed is true
 
 TEST COMPLETE
+Fired `onbeforeinput`!
 PASS event.__lookupGetter__('inputType') is defined.
 PASS Object.getPrototypeOf(event) is InputEvent.prototype
 PASS event.target.id is expectedTargetID
 PASS event.bubbles is true
+PASS event.cancelable is true
+PASS event.composed is true
+Fired `oninput`!
+PASS event.__lookupGetter__('inputType') is defined.
+PASS Object.getPrototypeOf(event) is InputEvent.prototype
+PASS event.target.id is expectedTargetID
+PASS event.bubbles is true
 PASS event.cancelable is false
 PASS event.composed is true
+Fired `onbeforeinput`!
 PASS event.__lookupGetter__('inputType') is defined.
 PASS Object.getPrototypeOf(event) is InputEvent.prototype
 PASS event.target.id is expectedTargetID
 PASS event.bubbles is true
+PASS event.cancelable is true
+PASS event.composed is true
+Fired `oninput`!
+PASS event.__lookupGetter__('inputType') is defined.
+PASS Object.getPrototypeOf(event) is InputEvent.prototype
+PASS event.target.id is expectedTargetID
+PASS event.bubbles is true
 PASS event.cancelable is false
 PASS event.composed is true
 a

Modified: trunk/LayoutTests/fast/events/input-events-fired-when-typing.html (206943 => 206944)


--- trunk/LayoutTests/fast/events/input-events-fired-when-typing.html	2016-10-07 23:30:16 UTC (rev 206943)
+++ trunk/LayoutTests/fast/events/input-events-fired-when-typing.html	2016-10-07 23:47:18 UTC (rev 206944)
@@ -33,6 +33,7 @@
 
         function checkInputEvent(event)
         {
+            debug("Fired `oninput`!");
             shouldBeDefined("event.__lookupGetter__('inputType')");
             shouldBe("Object.getPrototypeOf(event)", "InputEvent.prototype");
             shouldBe("event.target.id", "expectedTargetID");
@@ -40,12 +41,23 @@
             shouldBe("event.cancelable", "false");
             shouldBe("event.composed", "true");
         }
+
+        function checkBeforeInputEvent(event)
+        {
+            debug("Fired `onbeforeinput`!");
+            shouldBeDefined("event.__lookupGetter__('inputType')");
+            shouldBe("Object.getPrototypeOf(event)", "InputEvent.prototype");
+            shouldBe("event.target.id", "expectedTargetID");
+            shouldBe("event.bubbles", "true");
+            shouldBe("event.cancelable", "true");
+            shouldBe("event.composed", "true");
+        }
     </script>
 </head>
 
 <body _onload_=beginTest()>
-    <div id="foo" contenteditable _oninput_=checkInputEvent(event)></div>
-    <input id="bar" _oninput_=checkInputEvent(event)></input>
+    <div id="foo" contenteditable _oninput_=checkInputEvent(event) _onbeforeinput_=checkBeforeInputEvent(event)></div>
+    <input id="bar" _oninput_=checkInputEvent(event) _onbeforeinput_=checkBeforeInputEvent(event)></input>
     <script src=""
 </body>
 

Modified: trunk/LayoutTests/platform/ios-simulator/TestExpectations (206943 => 206944)


--- trunk/LayoutTests/platform/ios-simulator/TestExpectations	2016-10-07 23:30:16 UTC (rev 206943)
+++ trunk/LayoutTests/platform/ios-simulator/TestExpectations	2016-10-07 23:47:18 UTC (rev 206944)
@@ -1198,6 +1198,8 @@
 fast/events/ime-composition-events-001.html [ Failure ]
 fast/events/inputText-never-fired-on-keydown-cancel.html [ Failure ]
 fast/events/input-events-fired-when-typing.html [ Failure ]
+fast/events/before-input-events-prevent-default.html [ Failure ]
+fast/events/before-input-events-prevent-default-in-textfield.html [ Failure ]
 fast/events/key-events-in-input-button.html [ Failure ]
 fast/events/keydown-1.html [ Failure ]
 fast/events/keydown-leftright-keys.html [ Failure ]

Modified: trunk/Source/WebCore/ChangeLog (206943 => 206944)


--- trunk/Source/WebCore/ChangeLog	2016-10-07 23:30:16 UTC (rev 206943)
+++ trunk/Source/WebCore/ChangeLog	2016-10-07 23:47:18 UTC (rev 206944)
@@ -1,3 +1,92 @@
+2016-10-07  Wenson Hsieh  <wenson_hs...@apple.com>
+
+        Support onbeforeinput event handling for the new InputEvent spec
+        https://bugs.webkit.org/show_bug.cgi?id=163021
+        <rdar://problem/28658073>
+
+        Reviewed by Darin Adler.
+
+        Adds support for parsing the onbeforeinput attribute, and for sending default-preventable
+        `beforeinput` InputEvents to the page. To do this, we introduce two new virtual methods:
+        willApplyCommand and didApplyCommand on the CompositeEditCommand that are called before and
+        after CompositeEditCommand::doApply, respectively. willApplyCommand indicates whether or not
+        the composite editor command should proceed with applying the command.
+
+        Tweaks existing layout tests and adds new tests.
+
+        Tests: fast/events/before-input-events-different-start-end-elements.html
+               fast/events/before-input-events-prevent-default-in-textfield.html
+               fast/events/before-input-events-prevent-default.html
+
+        * dom/Document.idl:
+        * dom/Element.idl:
+        * dom/EventNames.h:
+        * dom/Node.cpp:
+        (WebCore::Node::dispatchInputEvent):
+        (WebCore::Node::defaultEventHandler):
+
+        Currently, we fire input events in Node in response to dispatching a webkitEditableContentChangedEvent. After
+        some discussion, Ryosuke and I believe that it will be ok to instead directly dispatch the input event where we
+        would normally dispatch a webkitEditableContentChangedEvent.
+
+        * editing/CompositeEditCommand.cpp:
+        (WebCore::EditCommandComposition::unapply):
+        (WebCore::EditCommandComposition::reapply):
+
+        Added calls to Editor::willUnapplyEditing and Editor::willReapplyEditing.
+
+        (WebCore::CompositeEditCommand::willApplyCommand):
+        (WebCore::CompositeEditCommand::apply):
+        (WebCore::CompositeEditCommand::didApplyCommand):
+
+        Added new virtual functions, willApplyCommand and didApplyCommand, that surround a call to
+        CompositeEditCommand::doApply. By default, they call willApplyEditing and appliedEditing on the editor, but may
+        be overridden in special cases, such as in TypingCommand, where we invoke appliedEditing after adding a new
+        typing command to the last open command.
+
+        If willApplyCommand returns false, CompositeEditCommand::apply will bail and not proceed with the command.
+
+        * editing/CompositeEditCommand.h:
+        * editing/Editor.cpp:
+        (WebCore::dispatchBeforeInputEvent):
+        (WebCore::dispatchBeforeInputEvents):
+        (WebCore::dispatchInputEvents):
+        (WebCore::Editor::willApplyEditing):
+        (WebCore::Editor::appliedEditing):
+        (WebCore::Editor::willUnapplyEditing):
+        (WebCore::Editor::unappliedEditing):
+        (WebCore::Editor::willReapplyEditing):
+        (WebCore::Editor::reappliedEditing):
+        (WebCore::Editor::computeAndSetTypingStyle):
+        (WebCore::dispatchEditableContentChangedEvents): Deleted.
+        * editing/Editor.h:
+        * editing/TypingCommand.cpp:
+        (WebCore::TypingCommand::willApplyCommand):
+        (WebCore::TypingCommand::didApplyCommand):
+        (WebCore::TypingCommand::willAddTypingToOpenCommand):
+        (WebCore::TypingCommand::insertTextRunWithoutNewlines):
+        (WebCore::TypingCommand::insertLineBreak):
+        (WebCore::TypingCommand::insertParagraphSeparator):
+        (WebCore::TypingCommand::insertParagraphSeparatorInQuotedContent):
+        (WebCore::TypingCommand::deleteKeyPressed):
+        (WebCore::TypingCommand::forwardDeleteKeyPressed):
+        (WebCore::TypingCommand::deleteSelection):
+
+        These now invoke willAddTypingToOpenCommand before proceeding with creating the command and applying it. The
+        flow is now:
+            - willAddTypingToOpenCommand
+            - create and apply a new command
+            - typingAddedToOpenCommand
+
+        * editing/TypingCommand.h:
+        (WebCore::TypingCommand::preservesTypingStyle): Deleted.
+        (WebCore::TypingCommand::shouldRetainAutocorrectionIndicator): Deleted.
+        (WebCore::TypingCommand::setShouldRetainAutocorrectionIndicator): Deleted.
+        (WebCore::TypingCommand::shouldStopCaretBlinking): Deleted.
+        * html/HTMLAttributeNames.in:
+        * html/HTMLElement.cpp:
+        (WebCore::HTMLElement::createEventHandlerNameMap):
+
 2016-10-07  Nan Wang  <n_w...@apple.com>
 
         AX: <figcaption> should be AXTitleUIElement for other content inside the <figure>

Modified: trunk/Source/WebCore/dom/Document.idl (206943 => 206944)


--- trunk/Source/WebCore/dom/Document.idl	2016-10-07 23:30:16 UTC (rev 206943)
+++ trunk/Source/WebCore/dom/Document.idl	2016-10-07 23:47:18 UTC (rev 206944)
@@ -192,6 +192,7 @@
     // FIXME: Should these be exposed on Window as well (and therefore moved to GlobalEventHandlers.idl)?
     [NotEnumerable] attribute EventHandler onbeforecopy;
     [NotEnumerable] attribute EventHandler onbeforecut;
+    [NotEnumerable] attribute EventHandler onbeforeinput;
     [NotEnumerable] attribute EventHandler onbeforepaste;
     [NotEnumerable] attribute EventHandler oncopy;
     [NotEnumerable] attribute EventHandler oncut;

Modified: trunk/Source/WebCore/dom/Element.idl (206943 => 206944)


--- trunk/Source/WebCore/dom/Element.idl	2016-10-07 23:30:16 UTC (rev 206943)
+++ trunk/Source/WebCore/dom/Element.idl	2016-10-07 23:47:18 UTC (rev 206944)
@@ -145,6 +145,7 @@
     // FIXME: Should these be exposed on Window as well (and therefore moved to GlobalEventHandlers.idl)?
     [NotEnumerable] attribute EventHandler onbeforecopy;
     [NotEnumerable] attribute EventHandler onbeforecut;
+    [NotEnumerable] attribute EventHandler onbeforeinput;
     [NotEnumerable] attribute EventHandler onbeforepaste;
     [NotEnumerable] attribute EventHandler oncopy;
     [NotEnumerable] attribute EventHandler oncut;

Modified: trunk/Source/WebCore/dom/EventNames.h (206943 => 206944)


--- trunk/Source/WebCore/dom/EventNames.h	2016-10-07 23:30:16 UTC (rev 206943)
+++ trunk/Source/WebCore/dom/EventNames.h	2016-10-07 23:47:18 UTC (rev 206944)
@@ -60,6 +60,7 @@
     macro(autocompleteerror) \
     macro(beforecopy) \
     macro(beforecut) \
+    macro(beforeinput) \
     macro(beforeload) \
     macro(beforepaste) \
     macro(beforeunload) \

Modified: trunk/Source/WebCore/dom/Node.cpp (206943 => 206944)


--- trunk/Source/WebCore/dom/Node.cpp	2016-10-07 23:30:16 UTC (rev 206943)
+++ trunk/Source/WebCore/dom/Node.cpp	2016-10-07 23:47:18 UTC (rev 206944)
@@ -2204,7 +2204,8 @@
 
 void Node::dispatchInputEvent(const AtomicString& inputType)
 {
-    if (document().settings()->inputEventsEnabled())
+    auto* settings = document().settings();
+    if (settings && settings->inputEventsEnabled())
         dispatchScopedEvent(InputEvent::create(eventNames().inputEvent, inputType, true, false, document().defaultView(), 0));
     else
         dispatchScopedEvent(Event::create(eventNames().inputEvent, true, false));
@@ -2272,8 +2273,6 @@
                 frame->eventHandler().defaultTouchEventHandler(renderer->node(), &downcast<TouchEvent>(event));
         }
 #endif
-    } else if (event.type() == eventNames().webkitEditableContentChangedEvent) {
-        dispatchInputEvent(emptyString());
     }
 }
 

Modified: trunk/Source/WebCore/editing/CompositeEditCommand.cpp (206943 => 206944)


--- trunk/Source/WebCore/editing/CompositeEditCommand.cpp	2016-10-07 23:30:16 UTC (rev 206943)
+++ trunk/Source/WebCore/editing/CompositeEditCommand.cpp	2016-10-07 23:47:18 UTC (rev 206944)
@@ -231,6 +231,9 @@
     frame->editor().cancelComposition();
 #endif
 
+    if (!frame->editor().willUnapplyEditing(*this))
+        return;
+
     size_t size = m_commands.size();
     for (size_t i = size; i; --i)
         m_commands[i - 1]->doUnapply();
@@ -255,6 +258,9 @@
     // if one is necessary (like for the creation of VisiblePositions).
     m_document->updateLayoutIgnorePendingStylesheets();
 
+    if (!frame->editor().willReapplyEditing(*this))
+        return;
+
     for (auto& command : m_commands)
         command->doReapply();
 
@@ -311,6 +317,11 @@
     ASSERT(isTopLevelCommand() || !m_composition);
 }
 
+bool CompositeEditCommand::willApplyCommand()
+{
+    return frame().editor().willApplyEditing(*this);
+}
+
 void CompositeEditCommand::apply()
 {
     if (!endingSelection().isContentRichlyEditable()) {
@@ -337,18 +348,23 @@
     // if one is necessary (like for the creation of VisiblePositions).
     document().updateLayoutIgnorePendingStylesheets();
 
+    if (!willApplyCommand())
+        return;
+
     {
         EventQueueScope eventQueueScope;
         doApply();
     }
 
-    // Only need to call appliedEditing for top-level commands,
-    // and TypingCommands do it on their own (see TypingCommand::typingAddedToOpenCommand).
-    if (!isTypingCommand())
-        frame().editor().appliedEditing(this);
+    didApplyCommand();
     setShouldRetainAutocorrectionIndicator(false);
 }
 
+void CompositeEditCommand::didApplyCommand()
+{
+    frame().editor().appliedEditing(this);
+}
+
 EditCommandComposition* CompositeEditCommand::ensureComposition()
 {
     CompositeEditCommand* command = this;

Modified: trunk/Source/WebCore/editing/CompositeEditCommand.h (206943 => 206944)


--- trunk/Source/WebCore/editing/CompositeEditCommand.h	2016-10-07 23:30:16 UTC (rev 206943)
+++ trunk/Source/WebCore/editing/CompositeEditCommand.h	2016-10-07 23:47:18 UTC (rev 206944)
@@ -117,6 +117,10 @@
 protected:
     explicit CompositeEditCommand(Document&, EditAction = EditActionUnspecified);
 
+    // If willApplyCommand returns false, we won't proceed with applying the command.
+    virtual bool willApplyCommand();
+    virtual void didApplyCommand();
+
     //
     // sugary-sweet convenience functions to help create and apply edit commands in composite commands
     //

Modified: trunk/Source/WebCore/editing/Editor.cpp (206943 => 206944)


--- trunk/Source/WebCore/editing/Editor.cpp	2016-10-07 23:30:16 UTC (rev 206943)
+++ trunk/Source/WebCore/editing/Editor.cpp	2016-10-07 23:47:18 UTC (rev 206944)
@@ -59,7 +59,9 @@
 #include "HTMLTextAreaElement.h"
 #include "HitTestResult.h"
 #include "IndentOutdentCommand.h"
+#include "InputEvent.h"
 #include "InsertListCommand.h"
+#include "InsertTextCommand.h"
 #include "KeyboardEvent.h"
 #include "KillRing.h"
 #include "Logging.h"
@@ -109,6 +111,18 @@
 
 namespace WebCore {
 
+static bool dispatchBeforeInputEvent(Element& element, const AtomicString& inputType)
+{
+    auto* settings = element.document().settings();
+    if (!settings || !settings->inputEventsEnabled())
+        return true;
+
+    auto event = InputEvent::create(eventNames().beforeinputEvent, inputType, true, true, element.document().defaultView(), 0);
+    element.dispatchScopedEvent(event);
+
+    return !event->defaultPrevented();
+}
+
 class ClearTextCommand : public DeleteSelectionCommand {
 public:
     ClearTextCommand(Document& document);
@@ -1025,16 +1039,33 @@
         endingTextControl->didEditInnerTextValue();
 }
 
-static void dispatchEditableContentChangedEvents(PassRefPtr<Element> prpStartRoot, PassRefPtr<Element> prpEndRoot)
+static bool dispatchBeforeInputEvents(RefPtr<Element> startRoot, RefPtr<Element> endRoot, const AtomicString& inputTypeName)
 {
-    RefPtr<Element> startRoot = prpStartRoot;
-    RefPtr<Element> endRoot = prpEndRoot;
+    bool continueWithDefaultBehavior = true;
     if (startRoot)
-        startRoot->dispatchEvent(Event::create(eventNames().webkitEditableContentChangedEvent, false, false));
+        continueWithDefaultBehavior &= dispatchBeforeInputEvent(*startRoot, inputTypeName);
     if (endRoot && endRoot != startRoot)
-        endRoot->dispatchEvent(Event::create(eventNames().webkitEditableContentChangedEvent, false, false));
+        continueWithDefaultBehavior &= dispatchBeforeInputEvent(*endRoot, inputTypeName);
+    return continueWithDefaultBehavior;
 }
 
+static void dispatchInputEvents(RefPtr<Element> startRoot, RefPtr<Element> endRoot, const AtomicString& inputTypeName)
+{
+    if (startRoot)
+        startRoot->dispatchInputEvent(inputTypeName);
+    if (endRoot && endRoot != startRoot)
+        endRoot->dispatchInputEvent(inputTypeName);
+}
+
+bool Editor::willApplyEditing(CompositeEditCommand& command) const
+{
+    auto* composition = command.composition();
+    if (!composition)
+        return true;
+
+    return dispatchBeforeInputEvents(composition->startingRootEditableElement(), composition->endingRootEditableElement(), emptyString());
+}
+
 void Editor::appliedEditing(PassRefPtr<CompositeEditCommand> cmd)
 {
     LOG(Editing, "Editor %p appliedEditing", this);
@@ -1051,7 +1082,7 @@
     FrameSelection::SetSelectionOptions options = cmd->isDictationCommand() ? FrameSelection::DictationTriggered : 0;
     
     changeSelectionAfterCommand(newSelection, options);
-    dispatchEditableContentChangedEvents(composition->startingRootEditableElement(), composition->endingRootEditableElement());
+    dispatchInputEvents(composition->startingRootEditableElement(), composition->endingRootEditableElement(), emptyString());
 
     updateEditorUINowIfScheduled();
     
@@ -1074,6 +1105,11 @@
     respondToChangedContents(newSelection);
 }
 
+bool Editor::willUnapplyEditing(const EditCommandComposition& composition) const
+{
+    return dispatchBeforeInputEvents(composition.startingRootEditableElement(), composition.endingRootEditableElement(), emptyString());
+}
+
 void Editor::unappliedEditing(PassRefPtr<EditCommandComposition> cmd)
 {
     document().updateLayout();
@@ -1082,7 +1118,7 @@
 
     VisibleSelection newSelection(cmd->startingSelection());
     changeSelectionAfterCommand(newSelection, FrameSelection::defaultSetSelectionOptions());
-    dispatchEditableContentChangedEvents(cmd->startingRootEditableElement(), cmd->endingRootEditableElement());
+    dispatchInputEvents(cmd->startingRootEditableElement(), cmd->endingRootEditableElement(), emptyString());
 
     updateEditorUINowIfScheduled();
 
@@ -1094,6 +1130,11 @@
     respondToChangedContents(newSelection);
 }
 
+bool Editor::willReapplyEditing(const EditCommandComposition& composition) const
+{
+    return dispatchBeforeInputEvents(composition.startingRootEditableElement(), composition.endingRootEditableElement(), emptyString());
+}
+
 void Editor::reappliedEditing(PassRefPtr<EditCommandComposition> cmd)
 {
     document().updateLayout();
@@ -1102,7 +1143,7 @@
 
     VisibleSelection newSelection(cmd->endingSelection());
     changeSelectionAfterCommand(newSelection, FrameSelection::defaultSetSelectionOptions());
-    dispatchEditableContentChangedEvents(cmd->startingRootEditableElement(), cmd->endingRootEditableElement());
+    dispatchInputEvents(cmd->startingRootEditableElement(), cmd->endingRootEditableElement(), emptyString());
     
     updateEditorUINowIfScheduled();
 
@@ -3052,6 +3093,10 @@
         return;
     }
 
+    auto* element = m_frame.selection().selection().rootEditableElement();
+    if (element && !dispatchBeforeInputEvent(*element, emptyString()))
+        return;
+
     // Calculate the current typing style.
     RefPtr<EditingStyle> typingStyle;
     if (auto existingTypingStyle = m_frame.selection().typingStyle())
@@ -3065,6 +3110,9 @@
     if (!blockStyle->isEmpty())
         applyCommand(ApplyStyleCommand::create(document(), blockStyle.get(), editingAction));
 
+    if (element)
+        element->dispatchInputEvent(emptyString());
+
     // Set the remaining style as the typing style.
     m_frame.selection().setTypingStyle(typingStyle);
 }

Modified: trunk/Source/WebCore/editing/Editor.h (206943 => 206944)


--- trunk/Source/WebCore/editing/Editor.h	2016-10-07 23:30:16 UTC (rev 206943)
+++ trunk/Source/WebCore/editing/Editor.h	2016-10-07 23:47:18 UTC (rev 206944)
@@ -198,6 +198,11 @@
     WEBCORE_EXPORT void applyStyleToSelection(Ref<EditingStyle>&&, EditAction);
     void applyParagraphStyleToSelection(StyleProperties*, EditAction);
 
+    // Returns whether or not we should proceed with editing.
+    bool willApplyEditing(CompositeEditCommand&) const;
+    bool willUnapplyEditing(const EditCommandComposition&) const;
+    bool willReapplyEditing(const EditCommandComposition&) const;
+
     void appliedEditing(PassRefPtr<CompositeEditCommand>);
     void unappliedEditing(PassRefPtr<EditCommandComposition>);
     void reappliedEditing(PassRefPtr<EditCommandComposition>);

Modified: trunk/Source/WebCore/editing/TypingCommand.cpp (206943 => 206944)


--- trunk/Source/WebCore/editing/TypingCommand.cpp	2016-10-07 23:30:16 UTC (rev 206943)
+++ trunk/Source/WebCore/editing/TypingCommand.cpp	2016-10-07 23:47:18 UTC (rev 206944)
@@ -262,6 +262,16 @@
     composition()->setRangeDeletedByUnapply(range);
 }
 
+bool TypingCommand::willApplyCommand()
+{
+    if (!m_isHandlingInitialTypingCommand) {
+        // The TypingCommand will handle the willApplyCommand logic separately in TypingCommand::willAddTypingToOpenCommand.
+        return true;
+    }
+
+    return CompositeEditCommand::willApplyCommand();
+}
+
 void TypingCommand::doApply()
 {
     if (endingSelection().isNoneOrOrphaned())
@@ -298,6 +308,12 @@
     ASSERT_NOT_REACHED();
 }
 
+void TypingCommand::didApplyCommand()
+{
+    // TypingCommands handle applied editing separately (see TypingCommand::typingAddedToOpenCommand).
+    m_isHandlingInitialTypingCommand = false;
+}
+
 void TypingCommand::markMisspellingsAfterTyping(ETypingCommand commandType)
 {
     Frame& frame = this->frame();
@@ -352,6 +368,16 @@
     }
 }
 
+bool TypingCommand::willAddTypingToOpenCommand(ETypingCommand, TextGranularity)
+{
+    if (m_isHandlingInitialTypingCommand)
+        return true;
+
+    // FIXME: Use the newly added typing command and granularity to ensure that an InputEvent with the
+    // correct inputType is dispatched.
+    return frame().editor().willApplyEditing(*this);
+}
+
 void TypingCommand::typingAddedToOpenCommand(ETypingCommand commandTypeForAddedTyping)
 {
     Frame& frame = this->frame();
@@ -393,6 +419,9 @@
 
 void TypingCommand::insertTextRunWithoutNewlines(const String &text, bool selectInsertedText)
 {
+    if (!willAddTypingToOpenCommand(InsertText, CharacterGranularity))
+        return;
+
     RefPtr<InsertTextCommand> command = InsertTextCommand::create(document(), text, selectInsertedText,
         m_compositionType == TextCompositionNone ? InsertTextCommand::RebalanceLeadingAndTrailingWhitespaces : InsertTextCommand::RebalanceAllWhitespaces, EditActionTyping);
 
@@ -406,6 +435,9 @@
     if (!canAppendNewLineFeedToSelection(endingSelection()))
         return;
 
+    if (!willAddTypingToOpenCommand(InsertLineBreak, LineGranularity))
+        return;
+
     applyCommandToComposite(InsertLineBreakCommand::create(document()));
     typingAddedToOpenCommand(InsertLineBreak);
 }
@@ -423,6 +455,9 @@
     if (!canAppendNewLineFeedToSelection(endingSelection()))
         return;
 
+    if (!willAddTypingToOpenCommand(InsertParagraphSeparator, ParagraphGranularity))
+        return;
+
     applyCommandToComposite(InsertParagraphSeparatorCommand::create(document(), false, false, EditActionTyping));
     typingAddedToOpenCommand(InsertParagraphSeparator);
 }
@@ -437,6 +472,9 @@
 
 void TypingCommand::insertParagraphSeparatorInQuotedContent()
 {
+    if (!willAddTypingToOpenCommand(InsertParagraphSeparatorInQuotedContent, ParagraphGranularity))
+        return;
+
     // If the selection starts inside a table, just insert the paragraph separator normally
     // Breaking the blockquote would also break apart the table, which is unecessary when inserting a newline
     if (enclosingNodeOfType(endingSelection().start(), &isTableStructureNode)) {
@@ -479,6 +517,9 @@
 
 void TypingCommand::deleteKeyPressed(TextGranularity granularity, bool shouldAddToKillRing)
 {
+    if (!willAddTypingToOpenCommand(DeleteKey, granularity))
+        return;
+
     Frame& frame = this->frame();
 
     frame.editor().updateMarkersForWordsAffectedByEditing(false);
@@ -592,6 +633,9 @@
 
 void TypingCommand::forwardDeleteKeyPressed(TextGranularity granularity, bool shouldAddToKillRing)
 {
+    if (!willAddTypingToOpenCommand(ForwardDeleteKey, granularity))
+        return;
+
     Frame& frame = this->frame();
 
     frame.editor().updateMarkersForWordsAffectedByEditing(false);
@@ -690,6 +734,9 @@
 
 void TypingCommand::deleteSelection(bool smartDelete)
 {
+    if (!willAddTypingToOpenCommand(DeleteSelection, CharacterGranularity))
+        return;
+
     CompositeEditCommand::deleteSelection(smartDelete);
     typingAddedToOpenCommand(DeleteSelection);
 }

Modified: trunk/Source/WebCore/editing/TypingCommand.h (206943 => 206944)


--- trunk/Source/WebCore/editing/TypingCommand.h	2016-10-07 23:30:16 UTC (rev 206943)
+++ trunk/Source/WebCore/editing/TypingCommand.h	2016-10-07 23:47:18 UTC (rev 206944)
@@ -30,7 +30,7 @@
 
 namespace WebCore {
 
-class TypingCommand : public TextInsertionBaseCommand {
+class TypingCommand final : public TextInsertionBaseCommand {
 public:
     enum ETypingCommand { 
         DeleteSelection,
@@ -104,21 +104,22 @@
 
     static RefPtr<TypingCommand> lastTypingCommandIfStillOpenForTyping(Frame&);
 
-    virtual void doApply();
-    virtual bool isTypingCommand() const;
-    virtual bool preservesTypingStyle() const { return m_preservesTypingStyle; }
-    virtual bool shouldRetainAutocorrectionIndicator() const
+    void doApply();
+    bool isTypingCommand() const;
+    bool preservesTypingStyle() const { return m_preservesTypingStyle; }
+    bool shouldRetainAutocorrectionIndicator() const
     {
         ASSERT(isTopLevelCommand());
         return m_shouldRetainAutocorrectionIndicator;
     }
-    virtual void setShouldRetainAutocorrectionIndicator(bool retain) { m_shouldRetainAutocorrectionIndicator = retain; }
-    virtual bool shouldStopCaretBlinking() const { return true; }
+    void setShouldRetainAutocorrectionIndicator(bool retain) { m_shouldRetainAutocorrectionIndicator = retain; }
+    bool shouldStopCaretBlinking() const { return true; }
     void setShouldPreventSpellChecking(bool prevent) { m_shouldPreventSpellChecking = prevent; }
 
     static void updateSelectionIfDifferentFromCurrentSelection(TypingCommand*, Frame*);
 
     void updatePreservesTypingStyle(ETypingCommand);
+    bool willAddTypingToOpenCommand(ETypingCommand, TextGranularity);
     void markMisspellingsAfterTyping(ETypingCommand);
     void typingAddedToOpenCommand(ETypingCommand);
     bool makeEditableRootEmpty();
@@ -129,11 +130,15 @@
     void insertParagraphSeparatorInQuotedContentAndNotifyAccessibility();
     void insertParagraphSeparatorAndNotifyAccessibility();
 
+    bool willApplyCommand();
+    void didApplyCommand();
+
     ETypingCommand m_commandType;
     String m_textToInsert;
     bool m_openForMoreTyping;
     bool m_selectInsertedText;
     bool m_smartDelete;
+    bool m_isHandlingInitialTypingCommand { true };
     TextGranularity m_granularity;
     TextCompositionType m_compositionType;
     bool m_shouldAddToKillRing;

Modified: trunk/Source/WebCore/html/HTMLAttributeNames.in (206943 => 206944)


--- trunk/Source/WebCore/html/HTMLAttributeNames.in	2016-10-07 23:30:16 UTC (rev 206943)
+++ trunk/Source/WebCore/html/HTMLAttributeNames.in	2016-10-07 23:47:18 UTC (rev 206944)
@@ -188,6 +188,7 @@
 onautocompleteerror
 onbeforecopy
 onbeforecut
+onbeforeinput
 onbeforeload
 onbeforepaste
 onbeforeunload

Modified: trunk/Source/WebCore/html/HTMLElement.cpp (206943 => 206944)


--- trunk/Source/WebCore/html/HTMLElement.cpp	2016-10-07 23:30:16 UTC (rev 206943)
+++ trunk/Source/WebCore/html/HTMLElement.cpp	2016-10-07 23:47:18 UTC (rev 206944)
@@ -231,6 +231,7 @@
         &onautocompleteerrorAttr,
         &onbeforecopyAttr,
         &onbeforecutAttr,
+        &onbeforeinputAttr,
         &onbeforeloadAttr,
         &onbeforepasteAttr,
         &onblurAttr,
_______________________________________________
webkit-changes mailing list
webkit-changes@lists.webkit.org
https://lists.webkit.org/mailman/listinfo/webkit-changes

Reply via email to