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,