[iOS] WKWebView should match UITextView behavior when editing text with an RTL keyboard
authorwenson_hsieh@apple.com <wenson_hsieh@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Thu, 6 Dec 2018 21:22:19 +0000 (21:22 +0000)
committerwenson_hsieh@apple.com <wenson_hsieh@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Thu, 6 Dec 2018 21:22:19 +0000 (21:22 +0000)
https://bugs.webkit.org/show_bug.cgi?id=187554
<rdar://problem/42075638>

Reviewed by Tim Horton.

Source/WebKit:

Add support for automatically switching the base writing direction to the default writing direction with respect
to the current keyboard in an editable WKWebView by implementing `-setBaseWritingDirection:forRange:`. On iOS 12
and earlier, UIKit invokes this protocol method whenever the keyboard is changed to one with a different writing
direction, although in some other versions of iOS, this only happens when first focusing an editable area.

Test: editing/input/ios/rtl-keyboard-input-on-focus.html

* Platform/spi/ios/UIKitSPI.h:

Declare UIKeyboardImpl IPI methods mostly for use in WebKitTestRunner (with the exception of
`-setInitialDirection`, which we may invoke when we receive the first post-layout EditorState update after
focusing an editable element).

* UIProcess/PageClient.h:
* UIProcess/WebPageProxy.cpp:
(WebKit::WebPageProxy::increaseListLevel):
(WebKit::WebPageProxy::decreaseListLevel):
(WebKit::WebPageProxy::changeListType):
(WebKit::WebPageProxy::setBaseWritingDirection):

Drive-by style fixes: make these bail and return early if `!isValid()`.

(WebKit::WebPageProxy::resetStateAfterProcessExited):

Reset assisted node state in the UI process upon web process termination.

* UIProcess/WebPageProxy.h:

Add plumbing for `setBaseWritingDirection`, from `WebPageProxy` to `WebPage` to `Editor`.

* UIProcess/ios/PageClientImplIOS.h:
* UIProcess/ios/PageClientImplIOS.mm:
(WebKit::PageClientImpl::didReceiveEditorStateUpdateAfterFocus):
* UIProcess/ios/WKContentViewInteraction.h:
* UIProcess/ios/WKContentViewInteraction.mm:
(-[WKContentView baseWritingDirectionForPosition:inDirection:]):
(coreWritingDirection):
(-[WKContentView setBaseWritingDirection:forRange:]):

Support `-setBaseWritingDirectionForPosition:forRange:`, but only in the case where the given range is the
selected range. This is all that's currently needed to fulfill the requirements in <rdar://problem/42075638>,
though we could potentially add full support for this in the future by mapping the given text range to a DOM
range and moving the selection prior to setting the base writing direction.

(-[WKContentView _didReceiveEditorStateUpdateAfterFocus]):

Add a hook to notify WKContentView when the first post-layout EditorState has been received in the UI process.
When this is invoked, if the web view is editable and the selection is not a range, we call into `UIKeyboardImpl`
to change the initial writing direction if necessary.

* UIProcess/ios/WebPageProxyIOS.mm:
(WebKit::WebPageProxy::startAssistingNode):
(WebKit::WebPageProxy::stopAssistingNode):
(WebKit::WebPageProxy::editorStateChanged):
* WebProcess/WebPage/WebPage.cpp:
(WebKit::WebPage::setBaseWritingDirection):
* WebProcess/WebPage/WebPage.h:
* WebProcess/WebPage/WebPage.messages.in:

Tools:

Add support for simulating the keyboard input mode in layout tests using UIScriptController, as well as a new
`TestOption` to make the web view editable.

* DumpRenderTree/ios/UIScriptControllerIOS.mm:
(WTR::UIScriptController::setKeyboardInputModeIdentifier):
* TestRunnerShared/UIScriptContext/Bindings/UIScriptController.idl:
* TestRunnerShared/UIScriptContext/UIScriptController.cpp:
(WTR::UIScriptController::setKeyboardInputModeIdentifier):
* TestRunnerShared/UIScriptContext/UIScriptController.h:
* WebKitTestRunner/PlatformWebView.h:
* WebKitTestRunner/TestController.cpp:
(WTR::updateTestOptionsFromTestHeader):
* WebKitTestRunner/TestController.h:
(WTR::TestController::overriddenKeyboardInputMode const):
* WebKitTestRunner/TestOptions.h:
(WTR::TestOptions::hasSameInitializationOptions const):
* WebKitTestRunner/cocoa/TestControllerCocoa.mm:
(WTR::TestController::platformCreateWebView):
* WebKitTestRunner/gtk/PlatformWebViewGtk.cpp:
(WTR::PlatformWebView::setEditable):
* WebKitTestRunner/ios/PlatformWebViewIOS.mm:
(WTR::PlatformWebView::setEditable):
* WebKitTestRunner/ios/TestControllerIOS.mm:
(WTR::TestController::platformResetStateToConsistentValues):
(WTR::swizzleCurrentInputMode):
(WTR::TestController::setKeyboardInputModeIdentifier):

Swizzle out several `UIKeyboardInputModeController` methods in order to convince UIKit that the user has
selected a `UIKeyboardInputMode` corresponding to the given identifier. The call to
`-prepareKeyboardInputModeFromPreferences:` is also necessary on iOS 12 in order to update cached writing
direction state in UIKit.

* WebKitTestRunner/ios/UIScriptControllerIOS.mm:
(WTR::UIScriptController::setKeyboardInputModeIdentifier):
* WebKitTestRunner/mac/PlatformWebViewMac.mm:
(WTR::PlatformWebView::setEditable):
* WebKitTestRunner/win/PlatformWebViewWin.cpp:
(WTR::PlatformWebView::setEditable):
* WebKitTestRunner/wpe/PlatformWebViewWPE.cpp:
(WTR::PlatformWebView::setEditable):

LayoutTests:

Add a new layout test to verify that when focusing an editable WKWebView using a right-to-left keyboard input
mode, we will set the base writing direction to be right-to-left, and vice versa.

* TestExpectations:
* editing/input/ios/rtl-keyboard-input-on-focus-expected.txt: Added.
* editing/input/ios/rtl-keyboard-input-on-focus.html: Added.
* platform/ios-wk2/TestExpectations:
* resources/ui-helper.js:

Add a UIHelper method to set the keyboard input mode to the given identifier. Example identifiers are "en_US"
(the default U.S. English keyboard) and "he_IL" (the Hebrew keyboard, which is right-to-left).

(window.UIHelper.setKeyboardInputModeIdentifier):
(window.UIHelper):

git-svn-id: https://svn.webkit.org/repository/webkit/trunk@238939 268f45cc-cd09-0410-ab3c-d52691b4dbfc

36 files changed:
LayoutTests/ChangeLog
LayoutTests/TestExpectations
LayoutTests/editing/input/ios/rtl-keyboard-input-on-focus-expected.txt [new file with mode: 0644]
LayoutTests/editing/input/ios/rtl-keyboard-input-on-focus.html [new file with mode: 0644]
LayoutTests/platform/ios-wk2/TestExpectations
LayoutTests/resources/ui-helper.js
Source/WebKit/ChangeLog
Source/WebKit/Platform/spi/ios/UIKitSPI.h
Source/WebKit/UIProcess/PageClient.h
Source/WebKit/UIProcess/WebPageProxy.cpp
Source/WebKit/UIProcess/WebPageProxy.h
Source/WebKit/UIProcess/ios/PageClientImplIOS.h
Source/WebKit/UIProcess/ios/PageClientImplIOS.mm
Source/WebKit/UIProcess/ios/WKContentViewInteraction.h
Source/WebKit/UIProcess/ios/WKContentViewInteraction.mm
Source/WebKit/UIProcess/ios/WebPageProxyIOS.mm
Source/WebKit/WebProcess/WebPage/WebPage.cpp
Source/WebKit/WebProcess/WebPage/WebPage.h
Source/WebKit/WebProcess/WebPage/WebPage.messages.in
Tools/ChangeLog
Tools/DumpRenderTree/ios/UIScriptControllerIOS.mm
Tools/TestRunnerShared/UIScriptContext/Bindings/UIScriptController.idl
Tools/TestRunnerShared/UIScriptContext/UIScriptController.cpp
Tools/TestRunnerShared/UIScriptContext/UIScriptController.h
Tools/WebKitTestRunner/PlatformWebView.h
Tools/WebKitTestRunner/TestController.cpp
Tools/WebKitTestRunner/TestController.h
Tools/WebKitTestRunner/TestOptions.h
Tools/WebKitTestRunner/cocoa/TestControllerCocoa.mm
Tools/WebKitTestRunner/gtk/PlatformWebViewGtk.cpp
Tools/WebKitTestRunner/ios/PlatformWebViewIOS.mm
Tools/WebKitTestRunner/ios/TestControllerIOS.mm
Tools/WebKitTestRunner/ios/UIScriptControllerIOS.mm
Tools/WebKitTestRunner/mac/PlatformWebViewMac.mm
Tools/WebKitTestRunner/win/PlatformWebViewWin.cpp
Tools/WebKitTestRunner/wpe/PlatformWebViewWPE.cpp

index 3dfd5d9..565ab81 100644 (file)
@@ -1,3 +1,26 @@
+2018-12-06  Wenson Hsieh  <wenson_hsieh@apple.com>
+
+        [iOS] WKWebView should match UITextView behavior when editing text with an RTL keyboard
+        https://bugs.webkit.org/show_bug.cgi?id=187554
+        <rdar://problem/42075638>
+
+        Reviewed by Tim Horton.
+
+        Add a new layout test to verify that when focusing an editable WKWebView using a right-to-left keyboard input
+        mode, we will set the base writing direction to be right-to-left, and vice versa.
+
+        * TestExpectations:
+        * editing/input/ios/rtl-keyboard-input-on-focus-expected.txt: Added.
+        * editing/input/ios/rtl-keyboard-input-on-focus.html: Added.
+        * platform/ios-wk2/TestExpectations:
+        * resources/ui-helper.js:
+
+        Add a UIHelper method to set the keyboard input mode to the given identifier. Example identifiers are "en_US"
+        (the default U.S. English keyboard) and "he_IL" (the Hebrew keyboard, which is right-to-left).
+
+        (window.UIHelper.setKeyboardInputModeIdentifier):
+        (window.UIHelper):
+
 2018-12-06  Jiewen Tan  <jiewen_tan@apple.com>
 
         Layout Test http/tests/misc/resource-timing-resolution.html is a flaky failure
index fdb99e8..a9f6d97 100644 (file)
@@ -15,6 +15,7 @@ accessibility/win [ Skip ]
 displaylists [ Skip ]
 editing/mac [ Skip ]
 editing/caret/ios [ Skip ]
+editing/input/ios [ Skip ]
 editing/find [ Skip ]
 editing/pasteboard/gtk [ Skip ]
 editing/selection/ios [ Skip ]
diff --git a/LayoutTests/editing/input/ios/rtl-keyboard-input-on-focus-expected.txt b/LayoutTests/editing/input/ios/rtl-keyboard-input-on-focus-expected.txt
new file mode 100644 (file)
index 0000000..30b1950
--- /dev/null
@@ -0,0 +1,38 @@
+
+
+Using Hebrew keyboard: 
+    <div><br></div>
+    <div style="direction: rtl;"><br></div>
+
+
+
+Using English keyboard: 
+    <div><br></div>
+    <div style="direction: ltr;"><br></div>
+
+
+
+Observed 'beforeinput' events: [
+    {
+        "inputType": "formatSetInlineTextDirection",
+        "data": "rtl",
+        "order": 1
+    },
+    {
+        "inputType": "formatSetInlineTextDirection",
+        "data": "ltr",
+        "order": 3
+    }
+]
+Observed 'input' events: [
+    {
+        "inputType": "formatSetInlineTextDirection",
+        "data": "rtl",
+        "order": 2
+    },
+    {
+        "inputType": "formatSetInlineTextDirection",
+        "data": "ltr",
+        "order": 4
+    }
+]
diff --git a/LayoutTests/editing/input/ios/rtl-keyboard-input-on-focus.html b/LayoutTests/editing/input/ios/rtl-keyboard-input-on-focus.html
new file mode 100644 (file)
index 0000000..aa723bd
--- /dev/null
@@ -0,0 +1,79 @@
+<!DOCTYPE html> <!-- webkit-test-runner [ useFlexibleViewport=true, editable=true ] -->
+<html>
+<meta name="viewport" content="width=device-width, initial-scale=1">
+<head>
+    <script src="../../../resources/basic-gestures.js"></script>
+    <script src="../../../resources/ui-helper.js"></script>
+    <style>
+        body {
+            margin: 0;
+        }
+    </style>
+    <script>
+    function appendOutput(string) {
+        let pre = document.createElement("pre");
+        pre.textContent = string;
+        document.body.appendChild(pre);
+    }
+
+    beforeInputEvents = [];
+    inputEvents = [];
+    eventNumber = 0;
+
+    async function run()
+    {
+        script.remove();
+
+        document.body.addEventListener("beforeinput", event => {
+            beforeInputEvents.push({
+                "inputType": event.inputType,
+                "data": event.data,
+                "order": ++eventNumber
+            });
+        });
+
+        document.body.addEventListener("input", event => {
+            inputEvents.push({
+                "inputType": event.inputType,
+                "data": event.data,
+                "order": ++eventNumber
+            });
+        });
+
+        await UIHelper.setKeyboardInputModeIdentifier("he_IL");
+        await UIHelper.activateAndWaitForInputSessionAt(100, 250);
+        await UIHelper.ensurePresentationUpdate();
+        const markupUsingHebrewKeyboard = document.body.innerHTML;
+
+        document.body.blur();
+        await UIHelper.waitForKeyboardToHide();
+        await UIHelper.setKeyboardInputModeIdentifier("en_US");
+        await UIHelper.activateAndWaitForInputSessionAt(100, 250);
+        await UIHelper.ensurePresentationUpdate();
+        const markupUsingEnglishKeyboard = document.body.innerHTML;
+
+        appendOutput(`Using Hebrew keyboard: ${markupUsingHebrewKeyboard}`);
+        appendOutput(`Using English keyboard: ${markupUsingEnglishKeyboard}`);
+        appendOutput(`Observed 'beforeinput' events: ${JSON.stringify(beforeInputEvents, null, 4)}`);
+        appendOutput(`Observed 'input' events: ${JSON.stringify(inputEvents, null, 4)}`);
+        testRunner.notifyDone();
+    }
+    </script>
+</head>
+<body onload="run()">
+    <div><br></div>
+    <div><br></div>
+</body>
+<script id="script">
+if (window.testRunner) {
+    testRunner.waitUntilDone();
+    testRunner.dumpAsText();
+} else {
+    const description = document.createElement("p");
+    description.textContent = "Verifies that focusing an editable area with an RTL keyboard switches text direction to RTL. "
+        + "To test manually, switch to a Hebrew keyboard and focus the editable area. "
+        + "The editable area should be made RTL.";
+    document.body.prepend(description);
+}
+</script>
+</html>
index 05135f5..41872a2 100644 (file)
@@ -15,6 +15,7 @@ scrollingcoordinator/ios [ Pass ]
 tiled-drawing/ios [ Pass ]
 fast/web-share [ Pass ]
 editing/find [ Pass ]
+editing/input/ios [ Pass ]
 
 editing/selection/character-granularity-rect.html [ Failure ]
 
index e4df524..67c52c0 100644 (file)
@@ -476,4 +476,13 @@ window.UIHelper = class UIHelper {
 
         return new Promise(resolve => testRunner.runUIScript(`uiController.setMinimumEffectiveWidth(${effectiveWidth})`, resolve));
     }
+
+    static setKeyboardInputModeIdentifier(identifier)
+    {
+        if (!this.isWebKit2())
+            return Promise.resolve();
+
+        const escapedIdentifier = identifier.replace(/`/g, "\\`");
+        return new Promise(resolve => testRunner.runUIScript(`uiController.setKeyboardInputModeIdentifier(\`${escapedIdentifier}\`)`, resolve));
+    }
 }
index 380337c..df33b97 100644 (file)
@@ -1,3 +1,70 @@
+2018-12-06  Wenson Hsieh  <wenson_hsieh@apple.com>
+
+        [iOS] WKWebView should match UITextView behavior when editing text with an RTL keyboard
+        https://bugs.webkit.org/show_bug.cgi?id=187554
+        <rdar://problem/42075638>
+
+        Reviewed by Tim Horton.
+
+        Add support for automatically switching the base writing direction to the default writing direction with respect
+        to the current keyboard in an editable WKWebView by implementing `-setBaseWritingDirection:forRange:`. On iOS 12
+        and earlier, UIKit invokes this protocol method whenever the keyboard is changed to one with a different writing
+        direction, although in some other versions of iOS, this only happens when first focusing an editable area.
+
+        Test: editing/input/ios/rtl-keyboard-input-on-focus.html
+
+        * Platform/spi/ios/UIKitSPI.h:
+
+        Declare UIKeyboardImpl IPI methods mostly for use in WebKitTestRunner (with the exception of
+        `-setInitialDirection`, which we may invoke when we receive the first post-layout EditorState update after
+        focusing an editable element).
+
+        * UIProcess/PageClient.h:
+        * UIProcess/WebPageProxy.cpp:
+        (WebKit::WebPageProxy::increaseListLevel):
+        (WebKit::WebPageProxy::decreaseListLevel):
+        (WebKit::WebPageProxy::changeListType):
+        (WebKit::WebPageProxy::setBaseWritingDirection):
+
+        Drive-by style fixes: make these bail and return early if `!isValid()`.
+
+        (WebKit::WebPageProxy::resetStateAfterProcessExited):
+
+        Reset assisted node state in the UI process upon web process termination.
+
+        * UIProcess/WebPageProxy.h:
+
+        Add plumbing for `setBaseWritingDirection`, from `WebPageProxy` to `WebPage` to `Editor`.
+
+        * UIProcess/ios/PageClientImplIOS.h:
+        * UIProcess/ios/PageClientImplIOS.mm:
+        (WebKit::PageClientImpl::didReceiveEditorStateUpdateAfterFocus):
+        * UIProcess/ios/WKContentViewInteraction.h:
+        * UIProcess/ios/WKContentViewInteraction.mm:
+        (-[WKContentView baseWritingDirectionForPosition:inDirection:]):
+        (coreWritingDirection):
+        (-[WKContentView setBaseWritingDirection:forRange:]):
+
+        Support `-setBaseWritingDirectionForPosition:forRange:`, but only in the case where the given range is the
+        selected range. This is all that's currently needed to fulfill the requirements in <rdar://problem/42075638>,
+        though we could potentially add full support for this in the future by mapping the given text range to a DOM
+        range and moving the selection prior to setting the base writing direction.
+
+        (-[WKContentView _didReceiveEditorStateUpdateAfterFocus]):
+
+        Add a hook to notify WKContentView when the first post-layout EditorState has been received in the UI process.
+        When this is invoked, if the web view is editable and the selection is not a range, we call into `UIKeyboardImpl`
+        to change the initial writing direction if necessary.
+
+        * UIProcess/ios/WebPageProxyIOS.mm:
+        (WebKit::WebPageProxy::startAssistingNode):
+        (WebKit::WebPageProxy::stopAssistingNode):
+        (WebKit::WebPageProxy::editorStateChanged):
+        * WebProcess/WebPage/WebPage.cpp:
+        (WebKit::WebPage::setBaseWritingDirection):
+        * WebProcess/WebPage/WebPage.h:
+        * WebProcess/WebPage/WebPage.messages.in:
+
 2018-12-06  David Quesada  <david_quesada@apple.com>
 
         -[WKProcessPool _resumeDownloadFromData:path:] should allow specifying the originating web view
index 2323796..56988c6 100644 (file)
@@ -913,6 +913,7 @@ typedef enum {
 @end
 
 @interface UIKeyboardInputMode : UITextInputMode <NSCopying>
++ (UIKeyboardInputMode *)keyboardInputModeWithIdentifier:(NSString *)identifier;
 @property (nonatomic, readonly, retain) NSArray <NSString *> *multilingualLanguages;
 @property (nonatomic, readonly, retain) NSString *languageWithRegion;
 @end
@@ -1062,6 +1063,12 @@ typedef NSInteger UICompositingMode;
 - (CGFloat)getVerticalOverlapForView:(UIView *)view usingKeyboardInfo:(NSDictionary *)info;
 @end
 
+@interface UIKeyboardImpl (IPI)
+- (void)setInitialDirection;
+- (void)prepareKeyboardInputModeFromPreferences:(UIKeyboardInputMode *)lastUsedMode;
+@property (nonatomic, readonly) UIKeyboardInputMode *currentInputModeInPreference;
+@end
+
 @interface _UILayerHostView : UIView
 @end
 
index df6ec00..c5179af 100644 (file)
@@ -363,6 +363,7 @@ public:
 
     virtual void startAssistingNode(const AssistedNodeInformation&, bool userIsInteracting, bool blurPreviousNode, bool changingActivityState, API::Object* userData) = 0;
     virtual void stopAssistingNode() = 0;
+    virtual void didReceiveEditorStateUpdateAfterFocus() = 0;
     virtual bool isAssistingNode() = 0;
     virtual bool interpretKeyEvent(const NativeWebKeyboardEvent&, bool isCharEvent) = 0;
     virtual void positionInformationDidChange(const InteractionInformationAtPosition&) = 0;
index 8e5946f..76a9653 100644 (file)
 #include <WebCore/TextIndicator.h>
 #include <WebCore/ValidationBubble.h>
 #include <WebCore/WindowFeatures.h>
+#include <WebCore/WritingDirection.h>
 #include <stdio.h>
 #include <wtf/NeverDestroyed.h>
 #include <wtf/SystemTracing.h>
@@ -1854,20 +1855,34 @@ void WebPageProxy::validateCommand(const String& commandName, WTF::Function<void
 
 void WebPageProxy::increaseListLevel()
 {
-    if (isValid())
-        m_process->send(Messages::WebPage::IncreaseListLevel(), m_pageID);
+    if (!isValid())
+        return;
+
+    m_process->send(Messages::WebPage::IncreaseListLevel(), m_pageID);
 }
 
 void WebPageProxy::decreaseListLevel()
 {
-    if (isValid())
-        m_process->send(Messages::WebPage::DecreaseListLevel(), m_pageID);
+    if (!isValid())
+        return;
+
+    m_process->send(Messages::WebPage::DecreaseListLevel(), m_pageID);
 }
 
 void WebPageProxy::changeListType()
 {
-    if (isValid())
-        m_process->send(Messages::WebPage::ChangeListType(), m_pageID);
+    if (!isValid())
+        return;
+
+    m_process->send(Messages::WebPage::ChangeListType(), m_pageID);
+}
+
+void WebPageProxy::setBaseWritingDirection(WritingDirection direction)
+{
+    if (!isValid())
+        return;
+
+    m_process->send(Messages::WebPage::SetBaseWritingDirection(direction), m_pageID);
 }
 
 void WebPageProxy::updateFontAttributesAfterEditorStateChange()
@@ -6423,6 +6438,8 @@ void WebPageProxy::resetStateAfterProcessExited(ProcessTerminationReason termina
 #endif
 
 #if PLATFORM(IOS_FAMILY)
+    m_waitingForPostLayoutEditorStateUpdateAfterFocusingElement = false;
+    m_deferredNodeAssistanceArguments = nullptr;
     m_activityToken = nullptr;
 #endif
 
index 734d211..53d8c3d 100644 (file)
@@ -178,6 +178,7 @@ enum class HasInsecureContent : bool;
 enum class NotificationDirection : uint8_t;
 enum class ShouldSample : bool;
 enum class ShouldTreatAsContinuingLoad : bool;
+enum class WritingDirection : uint8_t;
 
 struct ApplicationManifest;
 struct BackForwardItemIdentifier;
@@ -563,6 +564,8 @@ public:
     void decreaseListLevel();
     void changeListType();
 
+    void setBaseWritingDirection(WebCore::WritingDirection);
+
     std::optional<WebCore::FontAttributes> cachedFontAttributesAtSelectionStart() const { return m_cachedFontAttributesAtSelectionStart; }
 
 #if PLATFORM(COCOA)
@@ -1765,6 +1768,7 @@ private:
 
     void startAssistingNode(const AssistedNodeInformation&, bool userIsInteracting, bool blurPreviousNode, bool changingActivityState, const UserData&);
     void stopAssistingNode();
+    void didReceiveEditorStateUpdateAfterFocus();
 
     void showInspectorHighlight(const WebCore::Highlight&);
     void hideInspectorHighlight();
@@ -2252,6 +2256,7 @@ private:
 
 #if PLATFORM(IOS_FAMILY)
     std::unique_ptr<NodeAssistanceArguments> m_deferredNodeAssistanceArguments;
+    bool m_waitingForPostLayoutEditorStateUpdateAfterFocusingElement { false };
     bool m_forceAlwaysUserScalable { false };
     WebCore::FloatSize m_viewportConfigurationViewLayoutSize;
     double m_viewportConfigurationLayoutSizeScaleFactor { 1 };
index 8ecee14..6f14b40 100644 (file)
@@ -140,6 +140,7 @@ private:
 
     void startAssistingNode(const AssistedNodeInformation&, bool userIsInteracting, bool blurPreviousNode, bool changingActivityState, API::Object* userData) override;
     void stopAssistingNode() override;
+    void didReceiveEditorStateUpdateAfterFocus() override;
     bool isAssistingNode() override;
     void selectionDidChange() override;
     bool interpretKeyEvent(const NativeWebKeyboardEvent&, bool isCharEvent) override;
index 57b5ace..e9b1f1c 100644 (file)
@@ -578,6 +578,11 @@ void PageClientImpl::stopAssistingNode()
     [m_contentView _stopAssistingNode];
 }
 
+void PageClientImpl::didReceiveEditorStateUpdateAfterFocus()
+{
+    [m_contentView _didReceiveEditorStateUpdateAfterFocus];
+}
+
 void PageClientImpl::showPlaybackTargetPicker(bool hasVideo, const IntRect& elementRect, WebCore::RouteSharingPolicy policy, const String& contextUID)
 {
     [m_contentView _showPlaybackTargetPicker:hasVideo fromRect:elementRect routeSharingPolicy:policy routingContextUID:contextUID];
index da26332..e794e3c 100644 (file)
@@ -388,6 +388,7 @@ FOR_EACH_PRIVATE_WKCONTENTVIEW_ACTION(DECLARE_WKCONTENTVIEW_ACTION_FOR_WEB_VIEW)
 - (void)_disableDoubleTapGesturesDuringTapIfNecessary:(uint64_t)requestID;
 - (void)_startAssistingNode:(const WebKit::AssistedNodeInformation&)information userIsInteracting:(BOOL)userIsInteracting blurPreviousNode:(BOOL)blurPreviousNode changingActivityState:(BOOL)changingActivityState userObject:(NSObject <NSSecureCoding> *)userObject;
 - (void)_stopAssistingNode;
+- (void)_didReceiveEditorStateUpdateAfterFocus;
 - (void)_selectionChanged;
 - (void)_updateChangedSelection;
 - (BOOL)_interpretKeyEvent:(::WebEvent *)theEvent isCharEvent:(BOOL)isCharEvent;
index 9a843e0..2941dbd 100644 (file)
@@ -92,6 +92,7 @@
 #import <WebCore/TextIndicator.h>
 #import <WebCore/VisibleSelection.h>
 #import <WebCore/WebEvent.h>
+#import <WebCore/WritingDirection.h>
 #import <WebKit/WebSelectionRect.h> // FIXME: WK2 should not include WebKit headers!
 #import <pal/spi/cg/CoreGraphicsSPI.h>
 #import <pal/spi/cocoa/DataDetectorsCoreSPI.h>
@@ -3582,16 +3583,34 @@ static void selectionChangedWithTouch(WKContentView *view, const WebCore::IntPoi
     return nil;
 }
 
-ALLOW_DEPRECATED_DECLARATIONS_BEGIN
-- (UITextWritingDirection)baseWritingDirectionForPosition:(UITextPosition *)position inDirection:(UITextStorageDirection)direction
+- (NSWritingDirection)baseWritingDirectionForPosition:(UITextPosition *)position inDirection:(UITextStorageDirection)direction
 {
-    return UITextWritingDirectionLeftToRight;
+    return NSWritingDirectionLeftToRight;
 }
 
-- (void)setBaseWritingDirection:(UITextWritingDirection)writingDirection forRange:(UITextRange *)range
+static WritingDirection coreWritingDirection(NSWritingDirection direction)
 {
+    switch (direction) {
+    case NSWritingDirectionNatural:
+        return WritingDirection::Natural;
+    case NSWritingDirectionLeftToRight:
+        return WritingDirection::LeftToRight;
+    case NSWritingDirectionRightToLeft:
+        return WritingDirection::RightToLeft;
+    default:
+        ASSERT_NOT_REACHED();
+        return WritingDirection::Natural;
+    }
+}
+
+- (void)setBaseWritingDirection:(NSWritingDirection)direction forRange:(UITextRange *)range
+{
+    if (range && ![range isEqual:self.selectedTextRange]) {
+        // We currently only support changing the base writing direction at the selection.
+        return;
+    }
+    _page->setBaseWritingDirection(coreWritingDirection(direction));
 }
-ALLOW_DEPRECATED_DECLARATIONS_END
 
 - (CGRect)firstRectForRange:(UITextRange *)range
 {
@@ -4513,6 +4532,23 @@ static bool isAssistableInputType(InputType type)
     [self _stopSuppressingSelectionAssistantForReason:FocusedElementIsTransparent];
 }
 
+- (void)_didReceiveEditorStateUpdateAfterFocus
+{
+    if (!_page->isEditable())
+        return;
+
+    auto& editorState = _page->editorState();
+    if (editorState.selectionIsNone || editorState.selectionIsRange)
+        return;
+
+    UIKeyboardImpl *keyboard = UIKeyboardImpl.activeInstance;
+    if (keyboard.delegate != self)
+        return;
+
+    // Synchronize the keyboard's writing direction with the newly received EditorState.
+    [keyboard setInitialDirection];
+}
+
 - (void)updateCurrentAssistedNodeInformation:(Function<void(bool didUpdate)>&&)callback
 {
     WeakObjCPtr<WKContentView> weakSelf { self };
index 2795f73..7c890ad 100644 (file)
@@ -895,6 +895,8 @@ void WebPageProxy::didGetTapHighlightGeometries(uint64_t requestID, const WebCor
 
 void WebPageProxy::startAssistingNode(const AssistedNodeInformation& information, bool userIsInteracting, bool blurPreviousNode, bool changingActivityState, const UserData& userData)
 {
+    m_waitingForPostLayoutEditorStateUpdateAfterFocusingElement = true;
+
     API::Object* userDataObject = process().transformHandlesToObjects(userData.object()).get();
     if (m_editorState.isMissingPostLayoutData) {
         m_deferredNodeAssistanceArguments = std::make_unique<NodeAssistanceArguments>(NodeAssistanceArguments { information, userIsInteracting, blurPreviousNode, changingActivityState, userDataObject });
@@ -906,6 +908,7 @@ void WebPageProxy::startAssistingNode(const AssistedNodeInformation& information
 
 void WebPageProxy::stopAssistingNode()
 {
+    m_waitingForPostLayoutEditorStateUpdateAfterFocusingElement = false;
     m_deferredNodeAssistanceArguments = nullptr;
     pageClient().stopAssistingNode();
 }
@@ -1054,6 +1057,11 @@ void WebPageProxy::editorStateChanged(const EditorState& editorState)
     bool couldChangeSecureInputState = m_editorState.isInPasswordField != editorState.isInPasswordField || m_editorState.selectionIsNone;
     
     m_editorState = editorState;
+
+    if (m_waitingForPostLayoutEditorStateUpdateAfterFocusingElement && !m_editorState.isMissingPostLayoutData) {
+        pageClient().didReceiveEditorStateUpdateAfterFocus();
+        m_waitingForPostLayoutEditorStateUpdateAfterFocusingElement = false;
+    }
     
     // Selection being none is a temporary state when editing. Flipping secure input state too quickly was causing trouble (not fully understood).
     if (couldChangeSecureInputState && !editorState.selectionIsNone)
index ac47515..44941ff 100644 (file)
 #include <WebCore/VisiblePosition.h>
 #include <WebCore/VisibleUnits.h>
 #include <WebCore/WebGLStateTracker.h>
+#include <WebCore/WritingDirection.h>
 #include <WebCore/markup.h>
 #include <pal/SessionID.h>
 #include <wtf/ProcessID.h>
@@ -1164,6 +1165,11 @@ void WebPage::changeListType()
     m_page->focusController().focusedOrMainFrame().editor().changeSelectionListType();
 }
 
+void WebPage::setBaseWritingDirection(WritingDirection direction)
+{
+    m_page->focusController().focusedOrMainFrame().editor().setBaseWritingDirection(direction);
+}
+
 bool WebPage::isEditingCommandEnabled(const String& commandName)
 {
     Frame& frame = m_page->focusController().focusedOrMainFrame();
index 074a041..c880029 100644 (file)
@@ -169,6 +169,7 @@ class VisiblePosition;
 enum SyntheticClickType : int8_t;
 enum class ShouldTreatAsContinuingLoad : bool;
 enum class TextIndicatorPresentationTransition : uint8_t;
+enum class WritingDirection : uint8_t;
 
 struct BackForwardItemIdentifier;
 struct CompositionUnderline;
@@ -1215,6 +1216,8 @@ private:
     void decreaseListLevel();
     void changeListType();
 
+    void setBaseWritingDirection(WebCore::WritingDirection);
+
     void setNeedsFontAttributes(bool);
 
     void mouseEvent(const WebMouseEvent&);
index 4323655..520abe7 100644 (file)
@@ -222,6 +222,8 @@ messages -> WebPage LegacyReceiver {
     DecreaseListLevel()
     ChangeListType()
 
+    SetBaseWritingDirection(enum:uint8_t WebCore::WritingDirection direction)
+
     SetNeedsFontAttributes(bool needsFontAttributes)
 
     RequestFontAttributesAtSelectionStart(WebKit::CallbackID callbackID)
index d4a32b2..3dfa182 100644 (file)
@@ -1,3 +1,52 @@
+2018-12-06  Wenson Hsieh  <wenson_hsieh@apple.com>
+
+        [iOS] WKWebView should match UITextView behavior when editing text with an RTL keyboard
+        https://bugs.webkit.org/show_bug.cgi?id=187554
+        <rdar://problem/42075638>
+
+        Reviewed by Tim Horton.
+
+        Add support for simulating the keyboard input mode in layout tests using UIScriptController, as well as a new
+        `TestOption` to make the web view editable.
+
+        * DumpRenderTree/ios/UIScriptControllerIOS.mm:
+        (WTR::UIScriptController::setKeyboardInputModeIdentifier):
+        * TestRunnerShared/UIScriptContext/Bindings/UIScriptController.idl:
+        * TestRunnerShared/UIScriptContext/UIScriptController.cpp:
+        (WTR::UIScriptController::setKeyboardInputModeIdentifier):
+        * TestRunnerShared/UIScriptContext/UIScriptController.h:
+        * WebKitTestRunner/PlatformWebView.h:
+        * WebKitTestRunner/TestController.cpp:
+        (WTR::updateTestOptionsFromTestHeader):
+        * WebKitTestRunner/TestController.h:
+        (WTR::TestController::overriddenKeyboardInputMode const):
+        * WebKitTestRunner/TestOptions.h:
+        (WTR::TestOptions::hasSameInitializationOptions const):
+        * WebKitTestRunner/cocoa/TestControllerCocoa.mm:
+        (WTR::TestController::platformCreateWebView):
+        * WebKitTestRunner/gtk/PlatformWebViewGtk.cpp:
+        (WTR::PlatformWebView::setEditable):
+        * WebKitTestRunner/ios/PlatformWebViewIOS.mm:
+        (WTR::PlatformWebView::setEditable):
+        * WebKitTestRunner/ios/TestControllerIOS.mm:
+        (WTR::TestController::platformResetStateToConsistentValues):
+        (WTR::swizzleCurrentInputMode):
+        (WTR::TestController::setKeyboardInputModeIdentifier):
+
+        Swizzle out several `UIKeyboardInputModeController` methods in order to convince UIKit that the user has
+        selected a `UIKeyboardInputMode` corresponding to the given identifier. The call to
+        `-prepareKeyboardInputModeFromPreferences:` is also necessary on iOS 12 in order to update cached writing
+        direction state in UIKit.
+
+        * WebKitTestRunner/ios/UIScriptControllerIOS.mm:
+        (WTR::UIScriptController::setKeyboardInputModeIdentifier):
+        * WebKitTestRunner/mac/PlatformWebViewMac.mm:
+        (WTR::PlatformWebView::setEditable):
+        * WebKitTestRunner/win/PlatformWebViewWin.cpp:
+        (WTR::PlatformWebView::setEditable):
+        * WebKitTestRunner/wpe/PlatformWebViewWPE.cpp:
+        (WTR::PlatformWebView::setEditable):
+
 2018-12-05  Wenson Hsieh  <wenson_hsieh@apple.com>
 
         [Cocoa] Share ClassMethodSwizzler and InstanceMethodSwizzler between TestWebKitAPI and WebKitTestRunner
index 25662e2..90b42eb 100644 (file)
@@ -436,6 +436,10 @@ JSObjectRef UIScriptController::attachmentInfo(JSStringRef)
     return nullptr;
 }
 
+void UIScriptController::setKeyboardInputModeIdentifier(JSStringRef)
+{
+}
+
 }
 
 #endif // PLATFORM(IOS_FAMILY)
index 915f6db..7d204e8 100644 (file)
@@ -258,6 +258,8 @@ interface UIScriptController {
     void setDefaultCalendarType(DOMString calendarIdentifier);
     readonly attribute object inputViewBounds;
 
+    void setKeyboardInputModeIdentifier(DOMString identifier);
+
     void replaceTextAtRange(DOMString text, long location, long length);
     void removeAllDynamicDictionaries();
 
index e2d0535..f877dce 100644 (file)
@@ -510,6 +510,10 @@ JSObjectRef UIScriptController::attachmentInfo(JSStringRef)
     return nullptr;
 }
 
+void UIScriptController::setKeyboardInputModeIdentifier(JSStringRef)
+{
+}
+
 #endif
 
 #if !PLATFORM(COCOA)
index 2176f9d..7621470 100644 (file)
@@ -171,6 +171,8 @@ public:
     void setDefaultCalendarType(JSStringRef calendarIdentifier);
     JSObjectRef inputViewBounds() const;
 
+    void setKeyboardInputModeIdentifier(JSStringRef);
+
     void replaceTextAtRange(JSStringRef, int location, int length);
     void removeAllDynamicDictionaries();
     
index db419c7..6cdfd73 100644 (file)
@@ -103,6 +103,8 @@ public:
     bool drawsBackground() const;
     void setDrawsBackground(bool);
 
+    void setEditable(bool);
+
     void removeFromWindow();
     void addToWindow();
 
index 56374f7..0850558 100644 (file)
@@ -1257,6 +1257,8 @@ static void updateTestOptionsFromTestHeader(TestOptions& testOptions, const std:
             testOptions.shouldShowSpellCheckingDots = parseBooleanTestHeaderValue(value);
         else if (key == "enableEditableImages")
             testOptions.enableEditableImages = parseBooleanTestHeaderValue(value);
+        else if (key == "editable")
+            testOptions.editable = parseBooleanTestHeaderValue(value);
         pairStart = pairEnd + 1;
     }
 }
index e31b2eb..13a39c6 100644 (file)
 
 #if PLATFORM(COCOA)
 #include "ClassMethodSwizzler.h"
+#include "InstanceMethodSwizzler.h"
 #endif
 
 OBJC_CLASS NSString;
+OBJC_CLASS UIKeyboardInputMode;
 OBJC_CLASS WKWebViewConfiguration;
 
 namespace WTR {
@@ -270,6 +272,12 @@ public:
     RetainPtr<NSString> getOverriddenCalendarIdentifier() const;
     void setDefaultCalendarType(NSString *identifier);
 #endif // PLATFORM(COCOA)
+
+#if PLATFORM(IOS_FAMILY)
+    void setKeyboardInputModeIdentifier(const String&);
+    UIKeyboardInputMode *overriddenKeyboardInputMode() const { return m_overriddenKeyboardInputMode.get(); }
+#endif
+
 private:
     WKRetainPtr<WKPageConfigurationRef> generatePageConfiguration(WKContextConfigurationRef);
     WKRetainPtr<WKContextConfigurationRef> generateContextConfiguration() const;
@@ -441,6 +449,11 @@ private:
     WKRetainPtr<WKContextRef> m_context;
     WKRetainPtr<WKPageGroupRef> m_pageGroup;
 
+#if PLATFORM(IOS_FAMILY)
+    Vector<std::unique_ptr<InstanceMethodSwizzler>> m_inputModeSwizzlers;
+    RetainPtr<UIKeyboardInputMode> m_overriddenKeyboardInputMode;
+#endif
+
     enum State {
         Initial,
         Resetting,
index 36588dc..10786e0 100644 (file)
@@ -65,6 +65,7 @@ struct TestOptions {
     bool shouldIgnoreMetaViewport { false };
     bool shouldShowSpellCheckingDots { false };
     bool enableEditableImages { false };
+    bool editable { false };
 
     float deviceScaleFactor { 1 };
     Vector<String> overrideLanguages;
@@ -107,7 +108,8 @@ struct TestOptions {
             || checkForWorldLeaks != options.checkForWorldLeaks
             || shouldShowSpellCheckingDots != options.shouldShowSpellCheckingDots
             || shouldIgnoreMetaViewport != options.shouldIgnoreMetaViewport
-            || enableEditableImages != options.enableEditableImages)
+            || enableEditableImages != options.enableEditableImages
+            || editable != options.editable)
             return false;
 
         if (experimentalFeatures != options.experimentalFeatures)
index 69a4ac6..11b0e3a 100644 (file)
@@ -171,6 +171,9 @@ void TestController::platformCreateWebView(WKPageConfigurationRef, const TestOpt
 
     if (options.punchOutWhiteBackgroundsInDarkMode)
         m_mainWebView->setDrawsBackground(false);
+
+    if (options.editable)
+        m_mainWebView->setEditable(true);
 #else
     m_mainWebView = std::make_unique<PlatformWebView>(globalWebViewConfiguration, options);
 #endif
index de614b4..b366844 100644 (file)
@@ -198,5 +198,9 @@ void PlatformWebView::setDrawsBackground(bool)
 {
 }
 
+void PlatformWebView::setEditable(bool)
+{
+}
+
 } // namespace WTR
 
index a91cc6d..fda732d 100644 (file)
@@ -294,6 +294,15 @@ void PlatformWebView::changeWindowScaleIfNeeded(float)
     // Retina only surface.
 }
 
+void PlatformWebView::setEditable(bool editable)
+{
+#if WK_API_ENABLED
+    m_view._editable = editable;
+#else
+    UNUSED_PARAM(editable);
+#endif
+}
+
 bool PlatformWebView::drawsBackground() const
 {
     return false;
index 0b437ae..4a1080c 100644 (file)
@@ -116,6 +116,9 @@ void TestController::platformResetStateToConsistentValues(const TestOptions& opt
     cocoaResetStateToConsistentValues(options);
 
     [[UIDevice currentDevice] setOrientation:UIDeviceOrientationPortrait animated:NO];
+
+    m_inputModeSwizzlers.clear();
+    m_overriddenKeyboardInputMode = nil;
     
     BOOL shouldRestoreFirstResponder = NO;
     if (PlatformWebView* platformWebView = mainWebView()) {
@@ -198,4 +201,31 @@ void TestController::setHidden(bool)
     // FIXME: implement for iOS
 }
 
+static UIKeyboardInputMode *swizzleCurrentInputMode()
+{
+    return TestController::singleton().overriddenKeyboardInputMode();
+}
+
+static NSArray<UIKeyboardInputMode *> *swizzleActiveInputModes()
+{
+    return @[ TestController::singleton().overriddenKeyboardInputMode() ];
+}
+
+void TestController::setKeyboardInputModeIdentifier(const String& identifier)
+{
+    m_inputModeSwizzlers.clear();
+    m_overriddenKeyboardInputMode = [UIKeyboardInputMode keyboardInputModeWithIdentifier:identifier];
+    if (!m_overriddenKeyboardInputMode) {
+        ASSERT_NOT_REACHED();
+        return;
+    }
+
+    auto controllerClass = UIKeyboardInputModeController.class;
+    m_inputModeSwizzlers.reserveCapacity(3);
+    m_inputModeSwizzlers.uncheckedAppend(std::make_unique<InstanceMethodSwizzler>(controllerClass, @selector(currentInputMode), reinterpret_cast<IMP>(swizzleCurrentInputMode)));
+    m_inputModeSwizzlers.uncheckedAppend(std::make_unique<InstanceMethodSwizzler>(controllerClass, @selector(currentInputModeInPreference), reinterpret_cast<IMP>(swizzleCurrentInputMode)));
+    m_inputModeSwizzlers.uncheckedAppend(std::make_unique<InstanceMethodSwizzler>(controllerClass, @selector(activeInputModes), reinterpret_cast<IMP>(swizzleActiveInputModes)));
+    [UIKeyboardImpl.sharedInstance prepareKeyboardInputModeFromPreferences:nil];
+}
+
 } // namespace WTR
index abd0bc6..a9d54ad 100644 (file)
@@ -925,6 +925,11 @@ long UIScriptController::numberOfStrokesInEditableImage()
 #endif
 }
 
+void UIScriptController::setKeyboardInputModeIdentifier(JSStringRef identifier)
+{
+    TestController::singleton().setKeyboardInputModeIdentifier(toWTFString(toWK(identifier)));
+}
+
 void UIScriptController::toggleCapsLock(JSValueRef callback)
 {
     // FIXME: Implement for iOS. See <https://bugs.webkit.org/show_bug.cgi?id=191815>.
index f6cb2a0..45d1f9b 100644 (file)
@@ -212,6 +212,15 @@ void PlatformWebView::setDrawsBackground(bool drawsBackground)
 #endif
 }
 
+void PlatformWebView::setEditable(bool editable)
+{
+#if WK_API_ENABLED
+    m_view._editable = editable;
+#else
+    UNUSED_PARAM(editable);
+#endif
+}
+
 RetainPtr<CGImageRef> PlatformWebView::windowSnapshotImage()
 {
     [platformView() display];
index fdc5049..dc9d3a1 100644 (file)
@@ -259,4 +259,8 @@ void PlatformWebView::setDrawsBackground(bool)
 {
 }
 
+void PlatformWebView::setEditable(bool)
+{
+}
+
 } // namespace WTR
index 214933e..282c474 100644 (file)
@@ -141,4 +141,8 @@ void PlatformWebView::setDrawsBackground(bool)
 {
 }
 
+void PlatformWebView::setEditable(bool)
+{
+}
+
 } // namespace WTR