Add quirks to emulate undo and redo in hidden editable areas on some websites
https://bugs.webkit.org/show_bug.cgi?id=197452

Reviewed by Alex Christensen.

Source/WebCore:

UI change, not testable.

We need to send synthetic keyboard events to the web process to emulate undo and redo
key combinations for when we are trying to get our undo and redo UI to work
on rich editing websites that only listen to keystrokes, and don't let us use our
undo manager to help manage the input content.

* page/EventHandler.cpp:
(WebCore::EventHandler::keyEvent):
* platform/PlatformKeyboardEvent.h:
(WebCore::PlatformKeyboardEvent::PlatformKeyboardEvent):
(WebCore::PlatformKeyboardEvent::isSyntheticEvent):
(WebCore::PlatformKeyboardEvent::setSyntheticEvent):
* platform/ios/KeyEventIOS.mm:
(WebCore::PlatformKeyboardEvent::currentStateOfModifierKeys):
* platform/ios/PlatformEventFactoryIOS.mm:
(WebCore::PlatformKeyboardEventBuilder::PlatformKeyboardEventBuilder):
* platform/mac/PlatformEventFactoryMac.mm:
(WebCore::PlatformKeyboardEventBuilder::PlatformKeyboardEventBuilder):

Source/WebKit:

We need to make our own undo manager to allow undo even when
the manager is empty. This is to interface with rich editing
websites that don't actually interface with our undo abilities.
Then we need to generate synthetic undo and redo in the web process.

* UIProcess/WebPageProxy.cpp:
(WebKit::WebPageProxy::isCurrentURLHost const):
* UIProcess/WebPageProxy.h:
* UIProcess/ios/WKContentView.mm:
(-[WKNSUndoManager initWithContentView:]):
(-[WKNSUndoManager canUndo]):
(-[WKNSUndoManager canRedo]):
(-[WKNSUndoManager undo]):
(-[WKNSUndoManager redo]):
(-[WKContentView undoManager]):
* UIProcess/ios/WKContentViewInteraction.h:
* UIProcess/ios/WKContentViewInteraction.mm:
(-[WKContentView generateSyntheticUndoRedo:]):
(-[WKContentView hasHiddenContentEditable]):
* UIProcess/ios/WebPageProxyIOS.mm:
(WebKit::WebPageProxy::generateSyntheticUndoRedo):
* WebProcess/WebPage/WebPage.h:
* WebProcess/WebPage/WebPage.messages.in:
* WebProcess/WebPage/ios/WebPageIOS.mm:
(WebKit::WebPage::handleEditingKeyboardEvent):
(WebKit::WebPage::generateSyntheticUndoRedo):


git-svn-id: http://svn.webkit.org/repository/webkit/trunk@245079 268f45cc-cd09-0410-ab3c-d52691b4dbfc
diff --git a/Source/WebCore/ChangeLog b/Source/WebCore/ChangeLog
index 2c6ec8b..9d57a21 100644
--- a/Source/WebCore/ChangeLog
+++ b/Source/WebCore/ChangeLog
@@ -1,3 +1,30 @@
+2019-05-08  Megan Gardner  <megan_gardner@apple.com>
+
+        Add quirks to emulate undo and redo in hidden editable areas on some websites
+        https://bugs.webkit.org/show_bug.cgi?id=197452
+
+        Reviewed by Alex Christensen.
+
+        UI change, not testable.
+
+        We need to send synthetic keyboard events to the web process to emulate undo and redo
+        key combinations for when we are trying to get our undo and redo UI to work
+        on rich editing websites that only listen to keystrokes, and don't let us use our
+        undo manager to help manage the input content.
+
+        * page/EventHandler.cpp:
+        (WebCore::EventHandler::keyEvent):
+        * platform/PlatformKeyboardEvent.h:
+        (WebCore::PlatformKeyboardEvent::PlatformKeyboardEvent):
+        (WebCore::PlatformKeyboardEvent::isSyntheticEvent):
+        (WebCore::PlatformKeyboardEvent::setSyntheticEvent):
+        * platform/ios/KeyEventIOS.mm:
+        (WebCore::PlatformKeyboardEvent::currentStateOfModifierKeys):
+        * platform/ios/PlatformEventFactoryIOS.mm:
+        (WebCore::PlatformKeyboardEventBuilder::PlatformKeyboardEventBuilder):
+        * platform/mac/PlatformEventFactoryMac.mm:
+        (WebCore::PlatformKeyboardEventBuilder::PlatformKeyboardEventBuilder):
+
 2019-05-08  Don Olmstead  <don.olmstead@sony.com>
 
         CSSFontFaceSource fails to compile when !ENABLE(SVG_FONTS)
diff --git a/Source/WebCore/page/Quirks.cpp b/Source/WebCore/page/Quirks.cpp
index 522a33a..2abd896 100644
--- a/Source/WebCore/page/Quirks.cpp
+++ b/Source/WebCore/page/Quirks.cpp
@@ -235,6 +235,11 @@
     return false;
 }
 
+static bool shouldEmulateUndoRedoInHiddenEditableAreasForHost(const StringView&)
+{
+    return false;
+}
+
 #endif
 
 bool Quirks::shouldDispatchSyntheticMouseEventsWhenModifyingSelection() const
@@ -255,6 +260,14 @@
     return false;
 }
 
+bool Quirks::shouldEmulateUndoRedoInHiddenEditableAreas() const
+{
+    if (!needsQuirks())
+        return false;
+
+    return shouldEmulateUndoRedoInHiddenEditableAreasForHost(m_document->topDocument().url().host());
+}
+
 bool Quirks::shouldSuppressAutocorrectionAndAutocaptializationInHiddenEditableAreas() const
 {
     if (!needsQuirks())
diff --git a/Source/WebCore/page/Quirks.h b/Source/WebCore/page/Quirks.h
index 7689f70..d3d5f88 100644
--- a/Source/WebCore/page/Quirks.h
+++ b/Source/WebCore/page/Quirks.h
@@ -54,6 +54,7 @@
 
     WEBCORE_EXPORT bool shouldDispatchSyntheticMouseEventsWhenModifyingSelection() const;
     WEBCORE_EXPORT bool shouldSuppressAutocorrectionAndAutocaptializationInHiddenEditableAreas() const;
+    WEBCORE_EXPORT bool shouldEmulateUndoRedoInHiddenEditableAreas() const;
     WEBCORE_EXPORT bool isTouchBarUpdateSupressedForHiddenContentEditable() const;
     WEBCORE_EXPORT bool isNeverRichlyEditableForTouchBar() const;
 
diff --git a/Source/WebCore/platform/PlatformKeyboardEvent.h b/Source/WebCore/platform/PlatformKeyboardEvent.h
index b7a02a9..1a386cc 100644
--- a/Source/WebCore/platform/PlatformKeyboardEvent.h
+++ b/Source/WebCore/platform/PlatformKeyboardEvent.h
@@ -128,6 +128,9 @@
         bool isAutoRepeat() const { return m_autoRepeat; }
         bool isKeypad() const { return m_isKeypad; }
         bool isSystemKey() const { return m_isSystemKey; }
+        
+        bool isSyntheticEvent() const { return m_isSyntheticEvent; }
+        void setIsSyntheticEvent() { m_isSyntheticEvent = true; }
 
         WEBCORE_EXPORT static bool currentCapsLockState();
         WEBCORE_EXPORT static void getCurrentModifierState(bool& shiftKey, bool& ctrlKey, bool& altKey, bool& metaKey);
@@ -190,6 +193,8 @@
         bool m_autoRepeat;
         bool m_isKeypad;
         bool m_isSystemKey;
+        
+        bool m_isSyntheticEvent { false };
 
 #if PLATFORM(COCOA)
 #if !PLATFORM(IOS_FAMILY)
diff --git a/Source/WebKit/ChangeLog b/Source/WebKit/ChangeLog
index aa93f41..5c62974 100644
--- a/Source/WebKit/ChangeLog
+++ b/Source/WebKit/ChangeLog
@@ -1,3 +1,37 @@
+2019-05-08  Megan Gardner  <megan_gardner@apple.com>
+
+        Add quirks to emulate undo and redo in hidden editable areas on some websites
+        https://bugs.webkit.org/show_bug.cgi?id=197452
+
+        Reviewed by Alex Christensen.
+
+        We need to make our own undo manager to allow undo even when 
+        the manager is empty. This is to interface with rich editing 
+        websites that don't actually interface with our undo abilities.
+        Then we need to generate synthetic undo and redo in the web process.
+
+        * UIProcess/WebPageProxy.cpp:
+        (WebKit::WebPageProxy::isCurrentURLHost const):
+        * UIProcess/WebPageProxy.h:
+        * UIProcess/ios/WKContentView.mm:
+        (-[WKNSUndoManager initWithContentView:]):
+        (-[WKNSUndoManager canUndo]):
+        (-[WKNSUndoManager canRedo]):
+        (-[WKNSUndoManager undo]):
+        (-[WKNSUndoManager redo]):
+        (-[WKContentView undoManager]):
+        * UIProcess/ios/WKContentViewInteraction.h:
+        * UIProcess/ios/WKContentViewInteraction.mm:
+        (-[WKContentView generateSyntheticUndoRedo:]):
+        (-[WKContentView hasHiddenContentEditable]):
+        * UIProcess/ios/WebPageProxyIOS.mm:
+        (WebKit::WebPageProxy::generateSyntheticUndoRedo):
+        * WebProcess/WebPage/WebPage.h:
+        * WebProcess/WebPage/WebPage.messages.in:
+        * WebProcess/WebPage/ios/WebPageIOS.mm:
+        (WebKit::WebPage::handleEditingKeyboardEvent):
+        (WebKit::WebPage::generateSyntheticUndoRedo):
+
 2019-05-08  Alex Christensen  <achristensen@webkit.org>
 
         Add SPI to set HSTS storage directory
diff --git a/Source/WebKit/Shared/FocusedElementInformation.cpp b/Source/WebKit/Shared/FocusedElementInformation.cpp
index bc2bc33..cdef1a2 100644
--- a/Source/WebKit/Shared/FocusedElementInformation.cpp
+++ b/Source/WebKit/Shared/FocusedElementInformation.cpp
@@ -104,6 +104,7 @@
     encoder << suggestedColors;
 #endif
 #endif
+    encoder << shouldSynthesizeKeyEventsForUndoAndRedo;
 }
 
 bool FocusedElementInformation::decode(IPC::Decoder& decoder, FocusedElementInformation& result)
@@ -222,6 +223,8 @@
         return false;
 #endif
 #endif
+    if (!decoder.decode(result.shouldSynthesizeKeyEventsForUndoAndRedo))
+        return false;
 
     return true;
 }
diff --git a/Source/WebKit/Shared/FocusedElementInformation.h b/Source/WebKit/Shared/FocusedElementInformation.h
index 188d5a7..b3900ce 100644
--- a/Source/WebKit/Shared/FocusedElementInformation.h
+++ b/Source/WebKit/Shared/FocusedElementInformation.h
@@ -136,6 +136,7 @@
     Vector<WebCore::Color> suggestedColors;
 #endif
 #endif
+    bool shouldSynthesizeKeyEventsForUndoAndRedo { false };
 
     FocusedElementIdentifier focusedElementIdentifier { 0 };
 
diff --git a/Source/WebKit/Shared/SyntheticEditingCommandType.h b/Source/WebKit/Shared/SyntheticEditingCommandType.h
new file mode 100644
index 0000000..7e381e4
--- /dev/null
+++ b/Source/WebKit/Shared/SyntheticEditingCommandType.h
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2019 Apple Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+namespace WebKit {
+    
+enum class SyntheticEditingCommandType : uint8_t  {
+    Undo,
+    Redo
+};
+
+} // namespace WebKit
+
+namespace WTF {
+
+template<> struct EnumTraits<WebKit::SyntheticEditingCommandType> {
+    using values = EnumValues <
+    WebKit::SyntheticEditingCommandType,
+    WebKit::SyntheticEditingCommandType::Undo,
+    WebKit::SyntheticEditingCommandType::Redo
+    >;
+};
+
+}
diff --git a/Source/WebKit/UIProcess/WebPageProxy.cpp b/Source/WebKit/UIProcess/WebPageProxy.cpp
index f29d5a1..a8ab15e 100644
--- a/Source/WebKit/UIProcess/WebPageProxy.cpp
+++ b/Source/WebKit/UIProcess/WebPageProxy.cpp
@@ -81,6 +81,7 @@
 #include "SafeBrowsingWarning.h"
 #include "ShareSheetCallbackID.h"
 #include "SharedBufferDataReference.h"
+#include "SyntheticEditingCommandType.h"
 #include "TextChecker.h"
 #include "TextCheckerState.h"
 #include "TextInputContext.h"
diff --git a/Source/WebKit/UIProcess/WebPageProxy.h b/Source/WebKit/UIProcess/WebPageProxy.h
index 03786e8..b239a9c 100644
--- a/Source/WebKit/UIProcess/WebPageProxy.h
+++ b/Source/WebKit/UIProcess/WebPageProxy.h
@@ -46,6 +46,7 @@
 #include "ShareSheetCallbackID.h"
 #include "ShareableBitmap.h"
 #include "SuspendedPageProxy.h"
+#include "SyntheticEditingCommandType.h"
 #include "SystemPreviewController.h"
 #include "UserMediaPermissionRequestManagerProxy.h"
 #include "VisibleContentRectUpdateInfo.h"
@@ -722,6 +723,7 @@
     void requestEvasionRectsAboveSelection(CompletionHandler<void(const Vector<WebCore::FloatRect>&)>&&);
     void updateSelectionWithDelta(int64_t locationDelta, int64_t lengthDelta, CompletionHandler<void()>&&);
     void requestDocumentEditingContext(WebKit::DocumentEditingContextRequest, CompletionHandler<void(WebKit::DocumentEditingContext)>&&);
+    void generateSyntheticEditingCommand(SyntheticEditingCommandType);
 #if ENABLE(DATA_INTERACTION)
     void didHandleDragStartRequest(bool started);
     void didHandleAdditionalDragItemsRequest(bool added);
@@ -729,7 +731,7 @@
     void requestAdditionalItemsForDragSession(const WebCore::IntPoint& clientPosition, const WebCore::IntPoint& globalPosition, WebCore::DragSourceAction allowedActions);
     void didConcludeEditDrag(Optional<WebCore::TextIndicatorData>);
 #endif
-#endif
+#endif // PLATFORM(IOS_FAMILY)
 #if ENABLE(DATA_DETECTION)
     void setDataDetectionResult(const DataDetectionResult&);
 #endif
diff --git a/Source/WebKit/UIProcess/ios/WKContentView.mm b/Source/WebKit/UIProcess/ios/WKContentView.mm
index 6b2d9f9..ab68809 100644
--- a/Source/WebKit/UIProcess/ios/WKContentView.mm
+++ b/Source/WebKit/UIProcess/ios/WKContentView.mm
@@ -59,6 +59,7 @@
 #import <WebCore/InspectorOverlay.h>
 #import <WebCore/NotImplemented.h>
 #import <WebCore/PlatformScreen.h>
+#import <WebCore/Quirks.h>
 #import <pal/spi/cocoa/QuartzCoreSPI.h>
 #import <wtf/RetainPtr.h>
 #import <wtf/text/TextStream.h>
@@ -172,6 +173,41 @@
 
 @end
 
+@interface WKQuirkyNSUndoManager : NSUndoManager
+@property (readonly, weak) WKContentView *contentView;
+@end
+
+@implementation WKQuirkyNSUndoManager
+- (instancetype)initWithContentView:(WKContentView *)contentView
+{
+    if (!(self = [super init]))
+        return nil;
+    _contentView = contentView;
+    return self;
+}
+
+- (BOOL)canUndo 
+{
+    return YES;
+}
+
+- (BOOL)canRedo 
+{
+    return YES;
+}
+
+- (void)undo 
+{
+    [self.contentView generateSyntheticEditingCommand:WebKit::SyntheticEditingCommandType::Undo];
+}
+
+- (void)redo 
+{
+    [self.contentView generateSyntheticEditingCommand:WebKit::SyntheticEditingCommandType::Redo];
+}
+
+@end
+
 @implementation WKContentView {
     std::unique_ptr<WebKit::PageClientImpl> _pageClient;
     ALLOW_DEPRECATED_DECLARATIONS_BEGIN
@@ -190,6 +226,7 @@
     WebKit::HistoricalVelocityData _historicalKinematicData;
 
     RetainPtr<NSUndoManager> _undoManager;
+    RetainPtr<WKQuirkyNSUndoManager> _quirkyUndoManager;
 
     BOOL _isPrintingToPDF;
     RetainPtr<CGPDFDocumentRef> _printedDocument;
@@ -498,9 +535,13 @@
 
 - (NSUndoManager *)undoManager
 {
+    if (self.focusedElementInformation.shouldSynthesizeKeyEventsForUndoAndRedo && self.hasHiddenContentEditable) {
+        if (!_quirkyUndoManager)
+            _quirkyUndoManager = adoptNS([[WKQuirkyNSUndoManager alloc] initWithContentView:self]);
+        return _quirkyUndoManager.get();
+    }
     if (!_undoManager)
         _undoManager = adoptNS([[NSUndoManager alloc] init]);
-
     return _undoManager.get();
 }
 
diff --git a/Source/WebKit/UIProcess/ios/WKContentViewInteraction.h b/Source/WebKit/UIProcess/ios/WKContentViewInteraction.h
index 99839a2..a223ba11 100644
--- a/Source/WebKit/UIProcess/ios/WKContentViewInteraction.h
+++ b/Source/WebKit/UIProcess/ios/WKContentViewInteraction.h
@@ -36,6 +36,7 @@
 #import "FocusedElementInformation.h"
 #import "GestureTypes.h"
 #import "InteractionInformationAtPosition.h"
+#import "SyntheticEditingCommandType.h"
 #import "TextCheckingController.h"
 #import "UIKitSPI.h"
 #import "WKActionSheetAssistant.h"
@@ -471,6 +472,9 @@
 
 - (void)willFinishIgnoringCalloutBarFadeAfterPerformingAction;
 
+- (BOOL)hasHiddenContentEditable;
+- (void)generateSyntheticEditingCommand:(WebKit::SyntheticEditingCommandType)command;
+
 // UIWebFormAccessoryDelegate protocol
 - (void)accessoryDone;
 
diff --git a/Source/WebKit/UIProcess/ios/WKContentViewInteraction.mm b/Source/WebKit/UIProcess/ios/WKContentViewInteraction.mm
index 4ecce81..d9c674a 100644
--- a/Source/WebKit/UIProcess/ios/WKContentViewInteraction.mm
+++ b/Source/WebKit/UIProcess/ios/WKContentViewInteraction.mm
@@ -4476,6 +4476,11 @@
     [super _handleKeyUIEvent:event];
 }
 
+- (void)generateSyntheticEditingCommand:(WebKit::SyntheticEditingCommandType)command
+{
+    _page->generateSyntheticEditingCommand(command);
+}
+
 #if !USE(UIKIT_KEYBOARD_ADDITIONS)
 - (void)handleKeyEvent:(::UIEvent *)event
 {
@@ -5166,6 +5171,7 @@
     BOOL editableChanged = [self setIsEditable:NO];
 
     _focusedElementInformation.elementType = WebKit::InputType::None;
+    _focusedElementInformation.shouldSynthesizeKeyEventsForUndoAndRedo = false;
     _inputPeripheral = nil;
     _focusRequiresStrongPasswordAssistance = NO;
 
@@ -5730,6 +5736,11 @@
     return !_ignoreSelectionCommandFadeCount;
 }
 
+- (BOOL)hasHiddenContentEditable
+{
+    return _suppressSelectionAssistantReasons.contains(WebKit::EditableRootIsTransparentOrFullyClipped);
+}
+
 - (BOOL)_shouldSuppressSelectionCommands
 {
     return !!_suppressSelectionAssistantReasons;
diff --git a/Source/WebKit/UIProcess/ios/WebPageProxyIOS.mm b/Source/WebKit/UIProcess/ios/WebPageProxyIOS.mm
index 9030b7b..fabafef 100644
--- a/Source/WebKit/UIProcess/ios/WebPageProxyIOS.mm
+++ b/Source/WebKit/UIProcess/ios/WebPageProxyIOS.mm
@@ -1102,6 +1102,14 @@
     process().send(Messages::WebPage::ContentSizeCategoryDidChange(contentSizeCategory), m_pageID);
 }
 
+void WebPageProxy::generateSyntheticEditingCommand(WebKit::SyntheticEditingCommandType command)
+{
+    if (!hasRunningProcess())
+        return;
+
+    process().send(Messages::WebPage::GenerateSyntheticEditingCommand(command), m_pageID);
+}
+
 void WebPageProxy::updateEditorState(const EditorState& editorState)
 {
     bool couldChangeSecureInputState = m_editorState.isInPasswordField != editorState.isInPasswordField || m_editorState.selectionIsNone;
diff --git a/Source/WebKit/WebKit.xcodeproj/project.pbxproj b/Source/WebKit/WebKit.xcodeproj/project.pbxproj
index 8f7773e..26c05c7 100644
--- a/Source/WebKit/WebKit.xcodeproj/project.pbxproj
+++ b/Source/WebKit/WebKit.xcodeproj/project.pbxproj
@@ -896,6 +896,7 @@
 		41FABD2A1F4DE001006A6C97 /* CacheStorageEngineCache.h in Headers */ = {isa = PBXBuildFile; fileRef = 41FABD281F4DDFDC006A6C97 /* CacheStorageEngineCache.h */; };
 		41FAF5F51E3C0649001AE678 /* WebRTCResolver.h in Headers */ = {isa = PBXBuildFile; fileRef = 41FAF5F41E3C0641001AE678 /* WebRTCResolver.h */; };
 		41FAF5F81E3C1021001AE678 /* LibWebRTCResolver.h in Headers */ = {isa = PBXBuildFile; fileRef = 41FAF5F61E3C0B47001AE678 /* LibWebRTCResolver.h */; };
+		4459984222833E8700E61373 /* SyntheticEditingCommandType.h in Headers */ = {isa = PBXBuildFile; fileRef = 4459984122833E6000E61373 /* SyntheticEditingCommandType.h */; };
 		449D90DA21FDC30B00F677C0 /* LocalAuthenticationSoftLink.mm in Sources */ = {isa = PBXBuildFile; fileRef = 449D90D821FD63FE00F677C0 /* LocalAuthenticationSoftLink.mm */; };
 		460F488F1F996F7100CF4B87 /* WebSWContextManagerConnectionMessageReceiver.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 460F488D1F996F6C00CF4B87 /* WebSWContextManagerConnectionMessageReceiver.cpp */; };
 		460F48901F996F7100CF4B87 /* WebSWContextManagerConnectionMessages.h in Headers */ = {isa = PBXBuildFile; fileRef = 460F488E1F996F6C00CF4B87 /* WebSWContextManagerConnectionMessages.h */; };
@@ -3136,6 +3137,7 @@
 		41FBE823206DA79C000F0741 /* NetworkContentRuleListManager.messages.in */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = NetworkContentRuleListManager.messages.in; sourceTree = "<group>"; };
 		41FBE824206DA79C000F0741 /* NetworkContentRuleListManager.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = NetworkContentRuleListManager.cpp; sourceTree = "<group>"; };
 		4450AEBF1DC3FAE5009943F2 /* SharedMemoryCocoa.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = SharedMemoryCocoa.cpp; sourceTree = "<group>"; };
+		4459984122833E6000E61373 /* SyntheticEditingCommandType.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SyntheticEditingCommandType.h; sourceTree = "<group>"; };
 		449D90D821FD63FE00F677C0 /* LocalAuthenticationSoftLink.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = LocalAuthenticationSoftLink.mm; sourceTree = "<group>"; };
 		44A481C621F2D27B00F2F919 /* ClientCertificateAuthenticationXPCConstants.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = ClientCertificateAuthenticationXPCConstants.cpp; sourceTree = "<group>"; };
 		460F488D1F996F6C00CF4B87 /* WebSWContextManagerConnectionMessageReceiver.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = WebSWContextManagerConnectionMessageReceiver.cpp; path = DerivedSources/WebKit2/WebSWContextManagerConnectionMessageReceiver.cpp; sourceTree = BUILT_PRODUCTS_DIR; };
@@ -5914,6 +5916,7 @@
 				2DA944981884E4F000ED86DB /* NativeWebTouchEventIOS.mm */,
 				A118A9EC1907AD6F00F7C92B /* QuickLookDocumentData.cpp */,
 				A118A9ED1907AD6F00F7C92B /* QuickLookDocumentData.h */,
+				4459984122833E6000E61373 /* SyntheticEditingCommandType.h */,
 				F40D1B68220BDC0F00B49A01 /* WebAutocorrectionContext.h */,
 				F44DFEB01E9E752F0038D196 /* WebIconUtilities.h */,
 				F44DFEB11E9E752F0038D196 /* WebIconUtilities.mm */,
@@ -9528,6 +9531,7 @@
 				1AB31A9716BC688100F6DBC9 /* StorageManagerMessages.h in Headers */,
 				1AE00D6C18327C1200087DD7 /* StringReference.h in Headers */,
 				296BD85D15019BC30071F424 /* StringUtilities.h in Headers */,
+				4459984222833E8700E61373 /* SyntheticEditingCommandType.h in Headers */,
 				3157135F2040A9B20084F9CF /* SystemPreviewController.h in Headers */,
 				CE1A0BD61A48E6C60054EF74 /* TCCSPI.h in Headers */,
 				1AA417CB12C00CCA002BE67B /* TextChecker.h in Headers */,
diff --git a/Source/WebKit/WebProcess/WebPage/WebPage.h b/Source/WebKit/WebProcess/WebPage/WebPage.h
index 32748ce..c29645d 100644
--- a/Source/WebKit/WebProcess/WebPage/WebPage.h
+++ b/Source/WebKit/WebProcess/WebPage/WebPage.h
@@ -170,6 +170,7 @@
 class SelectionRect;
 class SharedBuffer;
 class SubstituteData;
+class SyntheticEditingCommandType;
 class TextCheckingRequest;
 class VisiblePosition;
 
@@ -1224,6 +1225,7 @@
     RefPtr<WebCore::Range> rangeForWebSelectionAtPosition(const WebCore::IntPoint&, const WebCore::VisiblePosition&, SelectionFlags&);
     void getFocusedElementInformation(FocusedElementInformation&);
     void platformInitializeAccessibility();
+    void generateSyntheticEditingCommand(SyntheticEditingCommandType);
     void handleSyntheticClick(WebCore::Node& nodeRespondingToClick, const WebCore::FloatPoint& location, OptionSet<WebKit::WebEvent::Modifier>);
     void completeSyntheticClick(WebCore::Node& nodeRespondingToClick, const WebCore::FloatPoint& location, OptionSet<WebKit::WebEvent::Modifier>, WebCore::SyntheticClickType);
     void sendTapHighlightForNodeIfNecessary(uint64_t requestID, WebCore::Node*);
diff --git a/Source/WebKit/WebProcess/WebPage/WebPage.messages.in b/Source/WebKit/WebProcess/WebPage/WebPage.messages.in
index 1c9a6d1..f2f49d5 100644
--- a/Source/WebKit/WebProcess/WebPage/WebPage.messages.in
+++ b/Source/WebKit/WebProcess/WebPage/WebPage.messages.in
@@ -113,6 +113,7 @@
     SetIsShowingInputViewForFocusedElement(bool showingInputView)
     UpdateSelectionWithDelta(int64_t locationDelta, int64_t lengthDelta) -> () Async
     RequestDocumentEditingContext(struct WebKit::DocumentEditingContextRequest request) -> (struct WebKit::DocumentEditingContext response) Async
+GenerateSyntheticEditingCommand(enum:uint8_t WebKit::SyntheticEditingCommandType command)
 #endif
 
     SetControlledByAutomation(bool controlled)
diff --git a/Source/WebKit/WebProcess/WebPage/ios/WebPageIOS.mm b/Source/WebKit/WebProcess/WebPage/ios/WebPageIOS.mm
index 8f4047e..e3ee326 100644
--- a/Source/WebKit/WebProcess/WebPage/ios/WebPageIOS.mm
+++ b/Source/WebKit/WebProcess/WebPage/ios/WebPageIOS.mm
@@ -42,6 +42,7 @@
 #import "PrintInfo.h"
 #import "RemoteLayerTreeDrawingArea.h"
 #import "SandboxUtilities.h"
+#import "SyntheticEditingCommandType.h"
 #import "TextCheckingControllerProxy.h"
 #import "UIKitSPI.h"
 #import "UserData.h"
@@ -419,6 +420,11 @@
     auto* platformEvent = event.underlyingPlatformEvent();
     if (!platformEvent)
         return false;
+    
+    // Don't send synthetic events to the UIProcess. They are only
+    // used for interacting with JavaScript.
+    if (platformEvent->isSyntheticEvent())
+        return false;
 
     // FIXME: Interpret the event immediately upon receiving it in UI process, without sending to WebProcess first.
     bool eventWasHandled = false;
@@ -580,6 +586,46 @@
     return AccessibilityObject::isARIAControl(ariaRole) || AccessibilityObject::isARIAInput(ariaRole);
 }
 
+void WebPage::generateSyntheticEditingCommand(SyntheticEditingCommandType command)
+{
+    PlatformKeyboardEvent keyEvent;
+    auto& frame = m_page->focusController().focusedOrMainFrame();
+    
+    OptionSet<PlatformEvent::Modifier> modifiers;
+    modifiers.add(PlatformEvent::Modifier::MetaKey);
+    
+    switch (command) {
+    case SyntheticEditingCommandType::Undo:
+        keyEvent = PlatformKeyboardEvent(PlatformEvent::KeyDown, "z", "z",
+#if ENABLE(KEYBOARD_KEY_ATTRIBUTE)
+        "z",
+#endif
+#if ENABLE(KEYBOARD_CODE_ATTRIBUTE)
+        "KeyZ"_s,
+#endif
+        @"U+005A", 90, false, false, false, modifiers, WallTime::now());
+        break;
+    case SyntheticEditingCommandType::Redo:
+        keyEvent = PlatformKeyboardEvent(PlatformEvent::KeyDown, "y", "y",
+#if ENABLE(KEYBOARD_KEY_ATTRIBUTE)
+        "y",
+#endif
+#if ENABLE(KEYBOARD_CODE_ATTRIBUTE)
+        "KeyY"_s,
+#endif
+        @"U+0059", 89, false, false, false, modifiers, WallTime::now());
+        break;
+    default:
+        break;
+    }
+
+    keyEvent.setIsSyntheticEvent();
+    
+    PlatformKeyboardEvent::setCurrentModifierState(modifiers);
+    
+    frame.eventHandler().keyEvent(keyEvent);
+}
+
 void WebPage::handleSyntheticClick(Node& nodeRespondingToClick, const WebCore::FloatPoint& location, OptionSet<WebEvent::Modifier> modifiers)
 {
     if (!nodeRespondingToClick.document().settings().contentChangeObserverEnabled()) {
@@ -2824,6 +2870,7 @@
             information.isAutocorrect = focusedElement.shouldAutocorrect();
             information.autocapitalizeType = focusedElement.autocapitalizeType();
             information.inputMode = focusedElement.canonicalInputMode();
+            information.shouldSynthesizeKeyEventsForUndoAndRedo = focusedElement.document().quirks().shouldEmulateUndoRedoInHiddenEditableAreas();
         } else {
             information.isAutocorrect = true;
             information.autocapitalizeType = AutocapitalizeTypeDefault;