[iOS] -_didFinishTextInteractionInTextInputContext should only zoom to reveal focused...
authordbates@webkit.org <dbates@webkit.org@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Tue, 21 Apr 2020 20:22:11 +0000 (20:22 +0000)
committerdbates@webkit.org <dbates@webkit.org@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Tue, 21 Apr 2020 20:22:11 +0000 (20:22 +0000)
https://bugs.webkit.org/show_bug.cgi?id=210697
<rdar://problem/60997530>

Source/WebCore:

Reviewed by Wenson Hsieh.

For now, add a comment about the return value of setFocusedElement: it returns
whether focus was blocked. If focused wasn't blocked then it will return true
even if the element wasn't actually focused. For example, it will return true
for non-focusable elements: <input disabled>.

I was tempted to fix setFocusedElement() to return true when it actually focused
the element or if the element was already focused, but I decided to defer this
until I audit the callers and run some tests.

* dom/Document.h:

Source/WebKit:

Reviewed by Wenson Hsieh.

Add a new state variable to use to track whether -_focusTextInputContext invoked during a
text interaction actually focused an element. Then in -_didFinishTextInteractionInTextInputContext
condition marking the page to reveal the focused element on this state.

* UIProcess/API/ios/WKWebViewPrivateForTestingIOS.h:
* UIProcess/API/ios/WKWebViewTestingIOS.mm:
(-[WKWebView _willBeginTextInteractionInTextInputContext:]): Turn around and call the same
function on the content view.
(-[WKWebView _didFinishTextInteractionInTextInputContext:]): Ditto.
* UIProcess/ios/WKContentViewInteraction.h:
* UIProcess/ios/WKContentViewInteraction.mm:
(-[WKContentView cleanUpInteraction]): Reset state.
(-[WKContentView _didCommitLoadForMainFrame]): Ditto.
(-[WKContentView _focusTextInputContext:placeCaretAt:completionHandler:]): Update state to
indicate whether the focusd element did change or not. I use bitwise ORing instead of strict
assignment to update it because this function can be called multiple times by an internal
Apple client during a text interaction (e.g. <rdar://problem/59430806>). I thought about
making this a FIXME, but this function is otherwise capable of handling multiple invocations
so I didn't.
(-[WKContentView _willBeginTextInteractionInTextInputContext:]): Reset added state.
(-[WKContentView _didFinishTextInteractionInTextInputContext:]): Check if state was set to
indicate that a focus change happened. If so, do what we do now + reset state. Otherwise,
do everything we do now except for marking the page as needing to reveal the focused element
on the next editor state update.
* WebProcess/WebPage/ios/WebPageIOS.mm:
(WebKit::WebPage::focusTextInputContextAndPlaceCaret): Check that the target is focusable
before calling FocusController::setFocusedElement() because it ultimately calls Document::setFocusedElement()
that can return true for a non-focusable element. I added a comment in Document.h to explain
this subtlety. I also decided not to check the return value of FocusController::setFocusedElement().
Instead I am checking the more important condition that the focused element is the target
element. Passing true to visiblePositionInFocusedNodeForPoint() will ultimately assert this
to be true. Note that setFocusedElement() may not have done anything as m_focusedElement
could have already been equal to the target element. This is OK and I still update the
caret position.

Tools:

Reviewed by Wenson Hsieh.

I use the term "assisted" below and in the tests to describe an element that is both DOM
focused and the UI process is showing an input view (like the software keyboard) for it.
For many of the tests I check for a zoom scale change as a way to detect if the page
zoomed to reveal the focused element.

Add some tests for the following cases:
    1. Place the caret in a focused field that isn't being assisted.
    2. Focusing an assisted element should not scroll the page.
    3. Focusing an offscreen read-only element should not change zoom scale.
    4. Focusing an offscreen element should change the zoom scale.
    5. Calling -_focusTextInputContext on an offscreen element multiple times
    during a text interaction should change the zoom scale. The purpose of this test is
    to ensure that later calls that do not focus the element (because it is already focused)
    don't prevent zooming for the first call that did focus the element.
    6. Focusing an offscreen element, defocusing it, disabling it, and focusing it
    again during a text interaction should not change zoom scale.
    7. Focusing an offscreen element, defocusing it, and focusing it again during a
    text interaction should change zoom scale.
    8. Focusing an assisted element during a text interaction should not change zoom scale.
    9. Focusing a non-assisted focused element during a text interaction should change zoom scale.

While I am here, I consolidated the RAII helper classes IPhoneUserInterfaceSwizzler and
IPadUserInterfaceSwizzler into a single UserInterfaceSwizzler templated class and defined
the former two as template instances.

* TestWebKitAPI/Tests/WebKitCocoa/RequestTextInputContext.mm:
(TextInteractionForScope::TextInteractionForScope):
(TextInteractionForScope::~TextInteractionForScope):
Added convenience RAII object to call -_willBeginTextInteractionInTextInputContext
and -_didFinishTextInteractionInTextInputContext.
(TEST): Added.
* TestWebKitAPI/Tests/ios/ActionSheetTests.mm:
* TestWebKitAPI/Tests/ios/KeyboardInputTestsIOS.mm:
* TestWebKitAPI/ios/IPadUserInterfaceSwizzler.h: Removed.
* TestWebKitAPI/ios/PreferredContentMode.mm:
(IPhoneUserInterfaceSwizzler::IPhoneUserInterfaceSwizzler): Deleted; movedinto UserInterfaceSwizzler.h.
(IPhoneUserInterfaceSwizzler::phoneUserInterfaceIdiom): Deleted; no longer needed.
* TestWebKitAPI/ios/UserInterfaceSwizzler.h: Renamed from Tools/TestWebKitAPI/ios/IPadUserInterfaceSwizzler.h.
(TestWebKitAPI::UserInterfaceSwizzler::UserInterfaceSwizzler): Formerly named IPadUserInterfaceSwizzler;
repurposed into general purpose template class, consolidating code from PreferredContentMode.mm.
Privately inherits from InstanceMethodSwizzler instead of using composition as that simplifies
the source code a tiny bit.
(TestWebKitAPI::UserInterfaceSwizzler::effectiveUserInterfaceIdiom): Renamed; formerly padUserInterfaceIdiom.

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

15 files changed:
Source/WebCore/ChangeLog
Source/WebCore/dom/Document.h
Source/WebKit/ChangeLog
Source/WebKit/UIProcess/API/ios/WKWebViewPrivateForTestingIOS.h
Source/WebKit/UIProcess/API/ios/WKWebViewTestingIOS.mm
Source/WebKit/UIProcess/ios/WKContentViewInteraction.h
Source/WebKit/UIProcess/ios/WKContentViewInteraction.mm
Source/WebKit/WebProcess/WebPage/ios/WebPageIOS.mm
Tools/ChangeLog
Tools/TestWebKitAPI/TestWebKitAPI.xcodeproj/project.pbxproj
Tools/TestWebKitAPI/Tests/WebKitCocoa/RequestTextInputContext.mm
Tools/TestWebKitAPI/Tests/ios/ActionSheetTests.mm
Tools/TestWebKitAPI/Tests/ios/KeyboardInputTestsIOS.mm
Tools/TestWebKitAPI/ios/PreferredContentMode.mm
Tools/TestWebKitAPI/ios/UserInterfaceSwizzler.h [moved from Tools/TestWebKitAPI/ios/IPadUserInterfaceSwizzler.h with 72% similarity]

index ed217cc..652d50e 100644 (file)
@@ -1,3 +1,22 @@
+2020-04-21  Daniel Bates  <dabates@apple.com>
+
+        [iOS] -_didFinishTextInteractionInTextInputContext should only zoom to reveal focused element if it changed
+        https://bugs.webkit.org/show_bug.cgi?id=210697
+        <rdar://problem/60997530>
+
+        Reviewed by Wenson Hsieh.
+
+        For now, add a comment about the return value of setFocusedElement: it returns
+        whether focus was blocked. If focused wasn't blocked then it will return true
+        even if the element wasn't actually focused. For example, it will return true
+        for non-focusable elements: <input disabled>.
+
+        I was tempted to fix setFocusedElement() to return true when it actually focused
+        the element or if the element was already focused, but I decided to defer this
+        until I audit the callers and run some tests.
+
+        * dom/Document.h:
+
 2020-04-21  Andres Gonzalez  <andresg_22@apple.com>
 
         Fix for remoteParentObject and platformWidget not being stored properly in the AXIsolatedObject attributes variant.
index 291134a..558a4a5 100644 (file)
@@ -761,6 +761,8 @@ public:
     MouseEventWithHitTestResults prepareMouseEvent(const HitTestRequest&, const LayoutPoint&, const PlatformMouseEvent&);
 
     enum class FocusRemovalEventsMode { Dispatch, DoNotDispatch };
+    // Returns whether focus was blocked. A true value does not necessarily mean the element was focused.
+    // The element could have already been focused or may not be focusable (e.g. <input disabled>).
     WEBCORE_EXPORT bool setFocusedElement(Element*, FocusDirection = FocusDirectionNone,
         FocusRemovalEventsMode = FocusRemovalEventsMode::Dispatch);
     Element* focusedElement() const { return m_focusedElement.get(); }
index 292ff88..7b1efbd 100644 (file)
@@ -1,3 +1,46 @@
+2020-04-21  Daniel Bates  <dabates@apple.com>
+
+        [iOS] -_didFinishTextInteractionInTextInputContext should only zoom to reveal focused element if it changed
+        https://bugs.webkit.org/show_bug.cgi?id=210697
+        <rdar://problem/60997530>
+
+        Reviewed by Wenson Hsieh. 
+
+        Add a new state variable to use to track whether -_focusTextInputContext invoked during a
+        text interaction actually focused an element. Then in -_didFinishTextInteractionInTextInputContext
+        condition marking the page to reveal the focused element on this state.
+
+        * UIProcess/API/ios/WKWebViewPrivateForTestingIOS.h:
+        * UIProcess/API/ios/WKWebViewTestingIOS.mm:
+        (-[WKWebView _willBeginTextInteractionInTextInputContext:]): Turn around and call the same
+        function on the content view.
+        (-[WKWebView _didFinishTextInteractionInTextInputContext:]): Ditto.
+        * UIProcess/ios/WKContentViewInteraction.h:
+        * UIProcess/ios/WKContentViewInteraction.mm:
+        (-[WKContentView cleanUpInteraction]): Reset state.
+        (-[WKContentView _didCommitLoadForMainFrame]): Ditto.
+        (-[WKContentView _focusTextInputContext:placeCaretAt:completionHandler:]): Update state to
+        indicate whether the focusd element did change or not. I use bitwise ORing instead of strict
+        assignment to update it because this function can be called multiple times by an internal
+        Apple client during a text interaction (e.g. <rdar://problem/59430806>). I thought about
+        making this a FIXME, but this function is otherwise capable of handling multiple invocations
+        so I didn't.
+        (-[WKContentView _willBeginTextInteractionInTextInputContext:]): Reset added state.
+        (-[WKContentView _didFinishTextInteractionInTextInputContext:]): Check if state was set to
+        indicate that a focus change happened. If so, do what we do now + reset state. Otherwise,
+        do everything we do now except for marking the page as needing to reveal the focused element
+        on the next editor state update.
+        * WebProcess/WebPage/ios/WebPageIOS.mm:
+        (WebKit::WebPage::focusTextInputContextAndPlaceCaret): Check that the target is focusable
+        before calling FocusController::setFocusedElement() because it ultimately calls Document::setFocusedElement()
+        that can return true for a non-focusable element. I added a comment in Document.h to explain
+        this subtlety. I also decided not to check the return value of FocusController::setFocusedElement().
+        Instead I am checking the more important condition that the focused element is the target
+        element. Passing true to visiblePositionInFocusedNodeForPoint() will ultimately assert this
+        to be true. Note that setFocusedElement() may not have done anything as m_focusedElement
+        could have already been equal to the target element. This is OK and I still update the
+        caret position.
+
 2020-04-21  Timothy Hatcher  <timothy@apple.com>
 
         Reset m_userScriptsNotified when web process crashes.
index c61b3fa..7447325 100644 (file)
@@ -51,6 +51,8 @@
 - (BOOL)_mayContainEditableElementsInRect:(CGRect)rect;
 - (void)_requestTextInputContextsInRect:(CGRect)rect completionHandler:(void (^)(NSArray<_WKTextInputContext *> *))completionHandler;
 - (void)_focusTextInputContext:(_WKTextInputContext *)context placeCaretAt:(CGPoint)point completionHandler:(void (^)(UIResponder<UITextInput> *))completionHandler;
+- (void)_willBeginTextInteractionInTextInputContext:(_WKTextInputContext *)context;
+- (void)_didFinishTextInteractionInTextInputContext:(_WKTextInputContext *)context;
 - (void)_requestDocumentContext:(UIWKDocumentRequest *)request completionHandler:(void (^)(UIWKDocumentContext *))completionHandler;
 - (void)_adjustSelectionWithDelta:(NSRange)deltaRange completionHandler:(void (^)(void))completionHandler;
 
index 1933d96..a84ed96 100644 (file)
     [_contentView _focusTextInputContext:context placeCaretAt:adjustedPoint completionHandler:completionHandler];
 }
 
+- (void)_willBeginTextInteractionInTextInputContext:(_WKTextInputContext *)context
+{
+    [_contentView _willBeginTextInteractionInTextInputContext:context];
+}
+
+- (void)_didFinishTextInteractionInTextInputContext:(_WKTextInputContext *)context
+{
+    [_contentView _didFinishTextInteractionInTextInputContext:context];
+}
+
 - (void)_requestDocumentContext:(UIWKDocumentRequest *)request completionHandler:(void (^)(UIWKDocumentContext *))completionHandler
 {
     [_contentView requestDocumentContext:request completionHandler:completionHandler];
index 4989262..4607af7 100644 (file)
@@ -353,6 +353,7 @@ struct WKAutoCorrectionData {
     BOOL _didAccessoryTabInitiateFocus;
     BOOL _isExpectingFastSingleTapCommit;
     BOOL _showDebugTapHighlightsForFastClicking;
+    BOOL _textInteractionDidChangeFocusedElement;
 
     WebCore::PointerID m_commitPotentialTapPointerId;
 
@@ -574,11 +575,8 @@ FOR_EACH_PRIVATE_WKCONTENTVIEW_ACTION(DECLARE_WKCONTENTVIEW_ACTION_FOR_WEB_VIEW)
 
 - (void)_requestTextInputContextsInRect:(CGRect)rect completionHandler:(void (^)(NSArray<_WKTextInputContext *> *))completionHandler;
 - (void)_focusTextInputContext:(_WKTextInputContext *)context placeCaretAt:(CGPoint)point completionHandler:(void (^)(UIResponder<UITextInput> *))completionHandler;
-
-#if USE(TEXT_INTERACTION_ADDITIONS)
 - (void)_willBeginTextInteractionInTextInputContext:(_WKTextInputContext *)context;
 - (void)_didFinishTextInteractionInTextInputContext:(_WKTextInputContext *)context;
-#endif
 
 - (void)_handleAutocorrectionContext:(const WebKit::WebAutocorrectionContext&)context;
 
index cb8f5c1..7ec4f3b 100644 (file)
@@ -924,6 +924,8 @@ static WKDragSessionContext *ensureLocalDragSessionContext(id <UIDragSession> se
     _seenHardwareKeyDownInNonEditableElement = NO;
 #endif
 
+    _textInteractionDidChangeFocusedElement = NO;
+
     if (_interactionViewsContainerView) {
         [self.layer removeObserver:self forKeyPath:@"transform"];
         [_interactionViewsContainerView removeFromSuperview];
@@ -4251,6 +4253,7 @@ static void selectionChangedWithTouch(WKContentView *view, const WebCore::IntPoi
     [self _hideTargetedPreviewContainerViews];
     [_webView _didCommitLoadForMainFrame];
 
+    _textInteractionDidChangeFocusedElement = NO;
     _hasValidPositionInformation = NO;
     _positionInformation = { };
 }
@@ -5100,6 +5103,7 @@ static NSString *contentTypeFromFieldName(WebCore::AutofillFieldName fieldName)
 - (void)_focusTextInputContext:(_WKTextInputContext *)context placeCaretAt:(CGPoint)point completionHandler:(void (^)(UIResponder<UITextInput> *))completionHandler
 {
     ASSERT(context);
+    // This function can be called more than once during a text interaction (e.g. <rdar://problem/59430806>).
     if (![self becomeFirstResponder]) {
         completionHandler(nil);
         return;
@@ -5111,14 +5115,15 @@ static NSString *contentTypeFromFieldName(WebCore::AutofillFieldName fieldName)
     _usingGestureForSelection = YES;
     auto checkFocusedElement = [weakSelf = WeakObjCPtr<WKContentView> { self }, context = [context copy], completionHandler = makeBlockPtr(completionHandler)] (bool success) {
         auto strongSelf = weakSelf.get();
-        if (strongSelf)
-            strongSelf->_usingGestureForSelection = NO;
-        if (!strongSelf || !success) {
+        if (!strongSelf) {
             completionHandler(nil);
             return;
         }
-        bool focusedAndEditable = [strongSelf _isTextInputContextFocused:context] && !strongSelf->_focusedElementInformation.isReadOnly;
-        completionHandler(focusedAndEditable ? strongSelf.autorelease() : nil);
+        bool isFocused = success && [strongSelf _isTextInputContextFocused:context];
+        bool isEditable = success && !strongSelf->_focusedElementInformation.isReadOnly;
+        strongSelf->_textInteractionDidChangeFocusedElement |= isFocused;
+        strongSelf->_usingGestureForSelection = NO;
+        completionHandler(isFocused && isEditable ? strongSelf.get() : nil);
     };
     _page->focusTextInputContextAndPlaceCaret(context._textInputContext, WebCore::IntPoint { point }, WTFMove(checkFocusedElement));
 }
@@ -5143,11 +5148,10 @@ static NSString *contentTypeFromFieldName(WebCore::AutofillFieldName fieldName)
     });
 }
 
-#if USE(TEXT_INTERACTION_ADDITIONS)
-
 - (void)_willBeginTextInteractionInTextInputContext:(_WKTextInputContext *)context
 {
     ASSERT(context);
+    _textInteractionDidChangeFocusedElement = NO;
     _page->setShouldRevealCurrentSelectionAfterInsertion(false);
     _page->setCanShowPlaceholder(context._textInputContext, false);
     [self _startSuppressingSelectionAssistantForReason:WebKit::InteractionIsHappening];
@@ -5158,17 +5162,16 @@ static NSString *contentTypeFromFieldName(WebCore::AutofillFieldName fieldName)
     ASSERT(context);
     [self _stopSuppressingSelectionAssistantForReason:WebKit::InteractionIsHappening];
     _page->setCanShowPlaceholder(context._textInputContext, true);
-    // Mark to zoom to reveal the newly focused element on the next editor state update.
-    // Then tell the web process to reveal the current selection, which will send us (the
-    // UI process) an editor state update.
-    // FIXME: Only do this if focus changed since -willBeginTextInteractionInTextInputContext was called.
-    // See <rdar://problem/60997530> for more details.
-    _page->setWaitingForPostLayoutEditorStateUpdateAfterFocusingElement(true);
+    if (_textInteractionDidChangeFocusedElement) {
+        // Mark to zoom to reveal the newly focused element on the next editor state update.
+        // Then tell the web process to reveal the current selection, which will send us (the
+        // UI process) an editor state update.
+        _page->setWaitingForPostLayoutEditorStateUpdateAfterFocusingElement(true);
+        _textInteractionDidChangeFocusedElement = NO;
+    }
     _page->setShouldRevealCurrentSelectionAfterInsertion(true);
 }
 
-#endif
-
 #if USE(UIKIT_KEYBOARD_ADDITIONS)
 
 - (void)modifierFlagsDidChangeFrom:(UIKeyModifierFlags)oldFlags to:(UIKeyModifierFlags)newFlags
index 5511e78..0d5c8e8 100644 (file)
@@ -4329,21 +4329,20 @@ void WebPage::focusTextInputContextAndPlaceCaret(const ElementContext& elementCo
     // Performing layout could have could torn down the element's renderer. Check that we still
     // have one. Otherwise, bail out as this function only focuses elements that have a visual
     // representation.
-    if (!target->renderer()) {
+    if (!target->renderer() || !target->isFocusable()) {
         completionHandler(false);
         return;
     }
 
     UserGestureIndicator gestureIndicator { ProcessingUserGesture, &target->document() };
     SetForScope<bool> userIsInteractingChange { m_userIsInteracting, true };
-    bool didFocus = m_page->focusController().setFocusedElement(target.get(), targetFrame);
+    m_page->focusController().setFocusedElement(target.get(), targetFrame);
 
     // Setting the focused element could tear down the element's renderer. Check that we still have one.
-    if (!didFocus || !target->renderer()) {
+    if (m_focusedElement != target || !target->renderer()) {
         completionHandler(false);
         return;
     }
-    ASSERT(m_focusedElement == target);
     // The function visiblePositionInFocusedNodeForPoint constrains the point to be inside
     // the bounds of the target element.
     auto position = visiblePositionInFocusedNodeForPoint(targetFrame, point, true /* isInteractingWithFocusedElement */);
index 11a2f14..9531720 100644 (file)
@@ -1,3 +1,55 @@
+2020-04-21  Daniel Bates  <dabates@apple.com>
+
+        [iOS] -_didFinishTextInteractionInTextInputContext should only zoom to reveal focused element if it changed
+        https://bugs.webkit.org/show_bug.cgi?id=210697
+        <rdar://problem/60997530>
+
+        Reviewed by Wenson Hsieh.
+
+        I use the term "assisted" below and in the tests to describe an element that is both DOM
+        focused and the UI process is showing an input view (like the software keyboard) for it.
+        For many of the tests I check for a zoom scale change as a way to detect if the page
+        zoomed to reveal the focused element.
+
+        Add some tests for the following cases:
+            1. Place the caret in a focused field that isn't being assisted.
+            2. Focusing an assisted element should not scroll the page.
+            3. Focusing an offscreen read-only element should not change zoom scale.
+            4. Focusing an offscreen element should change the zoom scale.
+            5. Calling -_focusTextInputContext on an offscreen element multiple times
+            during a text interaction should change the zoom scale. The purpose of this test is
+            to ensure that later calls that do not focus the element (because it is already focused)
+            don't prevent zooming for the first call that did focus the element.
+            6. Focusing an offscreen element, defocusing it, disabling it, and focusing it
+            again during a text interaction should not change zoom scale.
+            7. Focusing an offscreen element, defocusing it, and focusing it again during a
+            text interaction should change zoom scale.
+            8. Focusing an assisted element during a text interaction should not change zoom scale.
+            9. Focusing a non-assisted focused element during a text interaction should change zoom scale.
+
+        While I am here, I consolidated the RAII helper classes IPhoneUserInterfaceSwizzler and
+        IPadUserInterfaceSwizzler into a single UserInterfaceSwizzler templated class and defined
+        the former two as template instances.
+
+        * TestWebKitAPI/Tests/WebKitCocoa/RequestTextInputContext.mm:
+        (TextInteractionForScope::TextInteractionForScope):
+        (TextInteractionForScope::~TextInteractionForScope):
+        Added convenience RAII object to call -_willBeginTextInteractionInTextInputContext
+        and -_didFinishTextInteractionInTextInputContext.
+        (TEST): Added.
+        * TestWebKitAPI/Tests/ios/ActionSheetTests.mm:
+        * TestWebKitAPI/Tests/ios/KeyboardInputTestsIOS.mm:
+        * TestWebKitAPI/ios/IPadUserInterfaceSwizzler.h: Removed.
+        * TestWebKitAPI/ios/PreferredContentMode.mm:
+        (IPhoneUserInterfaceSwizzler::IPhoneUserInterfaceSwizzler): Deleted; movedinto UserInterfaceSwizzler.h.
+        (IPhoneUserInterfaceSwizzler::phoneUserInterfaceIdiom): Deleted; no longer needed.
+        * TestWebKitAPI/ios/UserInterfaceSwizzler.h: Renamed from Tools/TestWebKitAPI/ios/IPadUserInterfaceSwizzler.h.
+        (TestWebKitAPI::UserInterfaceSwizzler::UserInterfaceSwizzler): Formerly named IPadUserInterfaceSwizzler;
+        repurposed into general purpose template class, consolidating code from PreferredContentMode.mm.
+        Privately inherits from InstanceMethodSwizzler instead of using composition as that simplifies
+        the source code a tiny bit.
+        (TestWebKitAPI::UserInterfaceSwizzler::effectiveUserInterfaceIdiom): Renamed; formerly padUserInterfaceIdiom.
+
 2020-04-21  Timothy Hatcher  <timothy@apple.com>
 
         Reset m_userScriptsNotified when web process crashes.
index 3585c7d..ca8ffc4 100644 (file)
                F4CD74C520FDACF500DE3794 /* text-with-async-script.html */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.html; path = "text-with-async-script.html"; sourceTree = "<group>"; };
                F4CD74C720FDB49600DE3794 /* TestURLSchemeHandler.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = TestURLSchemeHandler.h; sourceTree = "<group>"; };
                F4CD74C820FDB49600DE3794 /* TestURLSchemeHandler.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = TestURLSchemeHandler.mm; sourceTree = "<group>"; };
-               F4CDAB3322489FE10057A2D9 /* IPadUserInterfaceSwizzler.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = IPadUserInterfaceSwizzler.h; sourceTree = "<group>"; };
+               F4CDAB3322489FE10057A2D9 /* UserInterfaceSwizzler.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = UserInterfaceSwizzler.h; sourceTree = "<group>"; };
                F4CF327F2366552200D3AD07 /* EnterKeyHintTests.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = EnterKeyHintTests.mm; sourceTree = "<group>"; };
                F4D2986D20FEE7370092D636 /* RunScriptAfterDocumentLoad.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = RunScriptAfterDocumentLoad.mm; sourceTree = "<group>"; };
                F4D4F3B41E4E2BCB00BB2767 /* DragAndDropSimulatorIOS.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = DragAndDropSimulatorIOS.mm; sourceTree = "<group>"; };
                        isa = PBXGroup;
                        children = (
                                F4D4F3B41E4E2BCB00BB2767 /* DragAndDropSimulatorIOS.mm */,
-                               F4CDAB3322489FE10057A2D9 /* IPadUserInterfaceSwizzler.h */,
                                2E7765CC16C4D80A00BA2BB1 /* mainIOS.mm */,
                                2D61EC3021B0B75C00A7D1CB /* PencilKitTestSPI.h */,
                                F48D6C0F224B377000E3E2FB /* PreferredContentMode.mm */,
                                F4517B652054C49500C26721 /* TestWKWebViewController.h */,
                                F4517B662054C49500C26721 /* TestWKWebViewController.mm */,
                                F493247C1F44DF8D006F4336 /* UIKitSPI.h */,
+                               F4CDAB3322489FE10057A2D9 /* UserInterfaceSwizzler.h */,
                        );
                        path = ios;
                        sourceTree = "<group>";
index ca4668f..a19774f 100644 (file)
 #import "TestInputDelegate.h"
 #import "TestNavigationDelegate.h"
 #import "TestWKWebView.h"
+#import "UserInterfaceSwizzler.h"
 #import <WebKit/WKPreferencesRefPrivate.h>
 #import <WebKit/WKWebViewConfigurationPrivate.h>
 #import <WebKit/WKWebViewPrivateForTesting.h>
 #import <WebKit/_WKTextInputContext.h>
 #import <wtf/RetainPtr.h>
 
+class TextInteractionForScope {
+public:
+    TextInteractionForScope(const RetainPtr<TestWKWebView>& webView, const RetainPtr<_WKTextInputContext>& textInputContext)
+        : m_webView { webView }
+        , m_textInputContext { textInputContext }
+    {
+        [m_webView _willBeginTextInteractionInTextInputContext:m_textInputContext.get()];
+    }
+
+    ~TextInteractionForScope()
+    {
+        [m_webView _didFinishTextInteractionInTextInputContext:m_textInputContext.get()];
+        [m_webView waitForNextPresentationUpdate];
+    }
+
+private:
+    RetainPtr<TestWKWebView> m_webView;
+    RetainPtr<_WKTextInputContext> m_textInputContext;
+};
+
 @implementation TestWKWebView (SynchronousTextInputContext)
 
 - (NSArray<_WKTextInputContext *> *)synchronouslyRequestTextInputContextsInRect:(CGRect)rect
@@ -333,6 +354,34 @@ TEST(RequestTextInputContext, CaretShouldNotMoveInAlreadyFocusedField2)
     EXPECT_EQ(1, [[webView objectByEvaluatingJavaScript:@"document.activeElement.selectionEnd"] intValue]);
 }
 
+TEST(RequestTextInputContext, PlaceCaretInNonAssistedFocusedField)
+{
+    auto configuration = adoptNS([[WKWebViewConfiguration alloc] init]);
+    auto webView = adoptNS([[TestWKWebView alloc] initWithFrame:NSMakeRect(0, 0, 800, 600) configuration:configuration.get()]);
+    auto inputDelegate = adoptNS([[TestInputDelegate alloc] init]);
+    [webView _setInputDelegate:inputDelegate.get()];
+
+    [webView synchronouslyLoadHTMLString:applyStyle(@"<input type='text' id='input' value='hello world' style='width: 100px; height: 50px;'>")];
+
+    // Use JavaScript to place the caret after the 'h' in the field.
+    [inputDelegate setFocusStartsInputSessionPolicyHandler:[] (WKWebView *, id <_WKFocusedElementInfo>) { return _WKFocusStartsInputSessionPolicyDisallow; }];
+    [webView stringByEvaluatingJavaScript:@"input.focus()"];
+    EXPECT_WK_STREQ("INPUT", [webView stringByEvaluatingJavaScript:@"document.activeElement.tagName"]);
+    [webView stringByEvaluatingJavaScript:@"input.setSelectionRange(1, 1)"];
+    EXPECT_EQ(1, [[webView objectByEvaluatingJavaScript:@"document.activeElement.selectionStart"] intValue]);
+    EXPECT_EQ(1, [[webView objectByEvaluatingJavaScript:@"document.activeElement.selectionEnd"] intValue]);
+
+    // Use -focusTextInputContext: to place the caret at the beginning of the field; the caret should move.
+    [inputDelegate setFocusStartsInputSessionPolicyHandler:[] (WKWebView *, id <_WKFocusedElementInfo>) { return _WKFocusStartsInputSessionPolicyAllow; }];
+    NSArray<_WKTextInputContext *> *contexts = [webView synchronouslyRequestTextInputContextsInRect:[webView bounds]];
+    EXPECT_EQ(1UL, contexts.count);
+    RetainPtr<_WKTextInputContext> inputElement = contexts[0];
+    EXPECT_EQ((UIResponder<UITextInput> *)[webView textInputContentView], [webView synchronouslyFocusTextInputContext:inputElement.get() placeCaretAt:[inputElement boundingRect].origin]);
+    EXPECT_WK_STREQ("INPUT", [webView stringByEvaluatingJavaScript:@"document.activeElement.tagName"]);
+    EXPECT_EQ(0, [[webView objectByEvaluatingJavaScript:@"document.activeElement.selectionStart"] intValue]);
+    EXPECT_EQ(0, [[webView objectByEvaluatingJavaScript:@"document.activeElement.selectionEnd"] intValue]);
+}
+
 TEST(RequestTextInputContext, FocusTextFieldThenProgrammaticallyReplaceWithTextAreaAndFocusTextArea)
 {
     auto configuration = adoptNS([[WKWebViewConfiguration alloc] init]);
@@ -471,4 +520,229 @@ TEST(RequestTextInputContext, SwitchFocusBetweenFrames)
     EXPECT_WK_STREQ("iframeField", [webView stringByEvaluatingJavaScript:@"document.querySelector('iframe').contentDocument.activeElement.value"]);
 }
 
+TEST(RequestTextInputContext, FocusingAssistedElementShouldNotScrollPage)
+{
+    auto configuration = adoptNS([[WKWebViewConfiguration alloc] init]);
+    auto webView = adoptNS([[TestWKWebView alloc] initWithFrame:NSMakeRect(0, 0, 800, 600) configuration:configuration.get()]);
+    auto inputDelegate = adoptNS([[TestInputDelegate alloc] init]);
+    [inputDelegate setFocusStartsInputSessionPolicyHandler:[] (WKWebView *, id <_WKFocusedElementInfo>) { return _WKFocusStartsInputSessionPolicyAllow; }];
+    [webView _setInputDelegate:inputDelegate.get()];
+    [webView synchronouslyLoadHTMLString:applyStyle(@"<body style='height: 4096px'><div id='editable' style='position: fixed; width: 100%; height: 50px; background-color: blue' contenteditable='true'></div></body>")];
+
+    NSArray<_WKTextInputContext *> *contexts = [webView synchronouslyRequestTextInputContextsInRect:[webView bounds]];
+    EXPECT_EQ(1UL, contexts.count);
+
+    // Focus the field using JavaScript and scroll the page.
+    [webView evaluateJavaScriptAndWaitForInputSessionToChange:@"editable.focus()"];
+    EXPECT_WK_STREQ("DIV", [webView stringByEvaluatingJavaScript:@"document.activeElement.tagName"]);
+    [webView stringByEvaluatingJavaScript:@"window.scrollTo(0, 2000)"];
+    EXPECT_EQ(2000, [[webView objectByEvaluatingJavaScript:@"window.scrollY"] intValue]);
+
+    // Focus the field using -focusTextInputContext; page should not scroll.
+    RetainPtr<_WKTextInputContext> editableElement = contexts[0];
+    EXPECT_EQ((UIResponder<UITextInput> *)[webView textInputContentView], [webView synchronouslyFocusTextInputContext:editableElement.get() placeCaretAt:[editableElement boundingRect].origin]);
+    EXPECT_WK_STREQ("DIV", [webView stringByEvaluatingJavaScript:@"document.activeElement.tagName"]);
+    EXPECT_EQ(2000, [[webView objectByEvaluatingJavaScript:@"window.scrollY"] intValue]);
+}
+
+TEST(RequestTextInputContext, TextInteraction_FocusingReadOnlyElementChangesZoomScale)
+{
+    IPhoneUserInterfaceSwizzler userInterfaceSwizzler;
+
+    auto configuration = adoptNS([[WKWebViewConfiguration alloc] init]);
+    auto webView = adoptNS([[TestWKWebView alloc] initWithFrame:NSMakeRect(0, 0, 800, 600) configuration:configuration.get()]);
+    [webView synchronouslyLoadHTMLString:applyStyle(@"<body style='height: 4096px'><input id='input'></body>")];
+
+    NSArray<_WKTextInputContext *> *contexts = [webView synchronouslyRequestTextInputContextsInRect:[webView bounds]];
+    EXPECT_EQ(1UL, contexts.count);
+    RetainPtr<_WKTextInputContext> inputElement = contexts[0];
+
+    EXPECT_WK_STREQ("BODY", [webView stringByEvaluatingJavaScript:@"document.activeElement.tagName"]);
+    [webView stringByEvaluatingJavaScript:@"input.readOnly = true"];
+    [webView scrollView].zoomScale = 2;
+
+    // Focus the field using -focusTextInputContext; zoom scale of scroll view should change to reveal the focused element.
+    {
+        TextInteractionForScope scope { webView, inputElement };
+        EXPECT_NULL([webView synchronouslyFocusTextInputContext:inputElement.get() placeCaretAt:[inputElement boundingRect].origin]);
+    }
+    EXPECT_WK_STREQ("INPUT", [webView stringByEvaluatingJavaScript:@"document.activeElement.tagName"]);
+    EXPECT_LT([webView scrollView].zoomScale, 2);
+}
+
+TEST(RequestTextInputContext, TextInteraction_FocusingElementChangesZoomScale)
+{
+    IPhoneUserInterfaceSwizzler userInterfaceSwizzler;
+
+    auto configuration = adoptNS([[WKWebViewConfiguration alloc] init]);
+    auto webView = adoptNS([[TestWKWebView alloc] initWithFrame:NSMakeRect(0, 0, 800, 600) configuration:configuration.get()]);
+    [webView synchronouslyLoadHTMLString:applyStyle(@"<body style='height: 4096px'><input id='input'></body>")];
+
+    NSArray<_WKTextInputContext *> *contexts = [webView synchronouslyRequestTextInputContextsInRect:[webView bounds]];
+    EXPECT_EQ(1UL, contexts.count);
+    RetainPtr<_WKTextInputContext> inputElement = contexts[0];
+
+    EXPECT_WK_STREQ("BODY", [webView stringByEvaluatingJavaScript:@"document.activeElement.tagName"]);
+    [webView scrollView].zoomScale = 2;
+
+    // Zoom scale of scroll view should change to reveal the focused element.
+    {
+        TextInteractionForScope scope { webView, inputElement };
+        EXPECT_EQ((UIResponder<UITextInput> *)[webView textInputContentView], [webView synchronouslyFocusTextInputContext:inputElement.get() placeCaretAt:[inputElement boundingRect].origin]);
+    }
+    EXPECT_WK_STREQ("INPUT", [webView stringByEvaluatingJavaScript:@"document.activeElement.tagName"]);
+    EXPECT_LT([webView scrollView].zoomScale, 2);
+}
+
+TEST(RequestTextInputContext, TextInteraction_FocusingElementMultipleTimesChangesZoomScale)
+{
+    IPhoneUserInterfaceSwizzler userInterfaceSwizzler;
+
+    auto configuration = adoptNS([[WKWebViewConfiguration alloc] init]);
+    auto webView = adoptNS([[TestWKWebView alloc] initWithFrame:NSMakeRect(0, 0, 800, 600) configuration:configuration.get()]);
+    [webView synchronouslyLoadHTMLString:applyStyle(@"<body style='height: 4096px'><input id='input'></body>")];
+
+    NSArray<_WKTextInputContext *> *contexts = [webView synchronouslyRequestTextInputContextsInRect:[webView bounds]];
+    EXPECT_EQ(1UL, contexts.count);
+    RetainPtr<_WKTextInputContext> inputElement = contexts[0];
+
+    EXPECT_WK_STREQ("BODY", [webView stringByEvaluatingJavaScript:@"document.activeElement.tagName"]);
+    [webView scrollView].zoomScale = 2;
+
+    // Zoom scale of scroll view should change to reveal the focused element.
+    {
+        TextInteractionForScope scope { webView, inputElement };
+        EXPECT_EQ((UIResponder<UITextInput> *)[webView textInputContentView], [webView synchronouslyFocusTextInputContext:inputElement.get() placeCaretAt:[inputElement boundingRect].origin]);
+        EXPECT_WK_STREQ("INPUT", [webView stringByEvaluatingJavaScript:@"document.activeElement.tagName"]);
+        EXPECT_EQ((UIResponder<UITextInput> *)[webView textInputContentView], [webView synchronouslyFocusTextInputContext:inputElement.get() placeCaretAt:[inputElement boundingRect].origin]);
+    }
+    EXPECT_WK_STREQ("INPUT", [webView stringByEvaluatingJavaScript:@"document.activeElement.tagName"]);
+    EXPECT_LT([webView scrollView].zoomScale, 2);
+}
+
+TEST(RequestTextInputContext, TextInteraction_FocusDefocusDisableFocusAgainShouldNotChangeZoomScale)
+{
+    IPhoneUserInterfaceSwizzler userInterfaceSwizzler;
+
+    auto configuration = adoptNS([[WKWebViewConfiguration alloc] init]);
+    auto webView = adoptNS([[TestWKWebView alloc] initWithFrame:NSMakeRect(0, 0, 800, 600) configuration:configuration.get()]);
+    [webView synchronouslyLoadHTMLString:applyStyle(@"<body style='height: 4096px'><input id='input'></body>")];
+
+    NSArray<_WKTextInputContext *> *contexts = [webView synchronouslyRequestTextInputContextsInRect:[webView bounds]];
+    EXPECT_EQ(1UL, contexts.count);
+    RetainPtr<_WKTextInputContext> inputElement = contexts[0];
+
+    EXPECT_WK_STREQ("BODY", [webView stringByEvaluatingJavaScript:@"document.activeElement.tagName"]);
+    [webView scrollView].zoomScale = 2;
+
+    {
+        TextInteractionForScope scope { webView, inputElement };
+        // 1. Focus
+        EXPECT_EQ((UIResponder<UITextInput> *)[webView textInputContentView], [webView synchronouslyFocusTextInputContext:inputElement.get() placeCaretAt:[inputElement boundingRect].origin]);
+        EXPECT_WK_STREQ("INPUT", [webView stringByEvaluatingJavaScript:@"document.activeElement.tagName"]);
+
+        // 2. Defocus
+        [webView stringByEvaluatingJavaScript:@"input.blur()"];
+        EXPECT_WK_STREQ("BODY", [webView stringByEvaluatingJavaScript:@"document.activeElement.tagName"]);
+
+        // 3. Disable
+        [webView stringByEvaluatingJavaScript:@"input.disabled = true"];
+
+        // 4. Focus again; focused element and zoom scale of scroll view should be unchanged.
+        EXPECT_NULL([webView synchronouslyFocusTextInputContext:inputElement.get() placeCaretAt:[inputElement boundingRect].origin]);
+        EXPECT_WK_STREQ("BODY", [webView stringByEvaluatingJavaScript:@"document.activeElement.tagName"]);
+    }
+    EXPECT_EQ(2, [webView scrollView].zoomScale);
+}
+
+TEST(RequestTextInputContext, TextInteraction_FocusDefocusFocusAgainShouldChangeZoomScale)
+{
+    IPhoneUserInterfaceSwizzler userInterfaceSwizzler;
+
+    auto configuration = adoptNS([[WKWebViewConfiguration alloc] init]);
+    auto webView = adoptNS([[TestWKWebView alloc] initWithFrame:NSMakeRect(0, 0, 800, 600) configuration:configuration.get()]);
+    [webView synchronouslyLoadHTMLString:applyStyle(@"<body style='height: 4096px'><input id='input'></body>")];
+
+    NSArray<_WKTextInputContext *> *contexts = [webView synchronouslyRequestTextInputContextsInRect:[webView bounds]];
+    EXPECT_EQ(1UL, contexts.count);
+    RetainPtr<_WKTextInputContext> inputElement = contexts[0];
+
+    EXPECT_WK_STREQ("BODY", [webView stringByEvaluatingJavaScript:@"document.activeElement.tagName"]);
+    [webView scrollView].zoomScale = 2;
+
+    {
+        TextInteractionForScope scope { webView, inputElement };
+        // 1. Focus
+        EXPECT_EQ((UIResponder<UITextInput> *)[webView textInputContentView], [webView synchronouslyFocusTextInputContext:inputElement.get() placeCaretAt:[inputElement boundingRect].origin]);
+        EXPECT_WK_STREQ("INPUT", [webView stringByEvaluatingJavaScript:@"document.activeElement.tagName"]);
+
+        // 2. Defocus
+        [webView stringByEvaluatingJavaScript:@"input.blur()"];
+        EXPECT_WK_STREQ("BODY", [webView stringByEvaluatingJavaScript:@"document.activeElement.tagName"]);
+
+        // 3. Focus again; focused element and zoom scale of scroll view should change.
+        EXPECT_EQ((UIResponder<UITextInput> *)[webView textInputContentView], [webView synchronouslyFocusTextInputContext:inputElement.get() placeCaretAt:[inputElement boundingRect].origin]);
+        EXPECT_WK_STREQ("INPUT", [webView stringByEvaluatingJavaScript:@"document.activeElement.tagName"]);
+    }
+    EXPECT_LT([webView scrollView].zoomScale, 2);
+}
+
+TEST(RequestTextInputContext, TextInteraction_FocusingAssistedElementShouldNotChangeZoomScale)
+{
+    IPhoneUserInterfaceSwizzler userInterfaceSwizzler;
+
+    auto configuration = adoptNS([[WKWebViewConfiguration alloc] init]);
+    auto webView = adoptNS([[TestWKWebView alloc] initWithFrame:NSMakeRect(0, 0, 800, 600) configuration:configuration.get()]);
+    auto inputDelegate = adoptNS([[TestInputDelegate alloc] init]);
+    [inputDelegate setFocusStartsInputSessionPolicyHandler:[] (WKWebView *, id <_WKFocusedElementInfo>) { return _WKFocusStartsInputSessionPolicyAllow; }];
+    [webView _setInputDelegate:inputDelegate.get()];
+    [webView synchronouslyLoadHTMLString:applyStyle(@"<body style='height: 4096px'><input id='input'></body>")];
+
+    NSArray<_WKTextInputContext *> *contexts = [webView synchronouslyRequestTextInputContextsInRect:[webView bounds]];
+    EXPECT_EQ(1UL, contexts.count);
+    RetainPtr<_WKTextInputContext> inputElement = contexts[0];
+
+    [webView evaluateJavaScriptAndWaitForInputSessionToChange:@"input.focus()"];
+    EXPECT_WK_STREQ("INPUT", [webView stringByEvaluatingJavaScript:@"document.activeElement.tagName"]);
+    [webView scrollView].zoomScale = 2;
+
+    // Focus the field using -focusTextInputContext; zoom scale of scroll view should be unchanged.
+    {
+        TextInteractionForScope scope { webView, inputElement };
+        EXPECT_EQ((UIResponder<UITextInput> *)[webView textInputContentView], [webView synchronouslyFocusTextInputContext:inputElement.get() placeCaretAt:[inputElement boundingRect].origin]);
+    }
+    EXPECT_WK_STREQ("INPUT", [webView stringByEvaluatingJavaScript:@"document.activeElement.tagName"]);
+    EXPECT_EQ(2, [webView scrollView].zoomScale);
+}
+
+TEST(RequestTextInputContext, TextInteraction_FocusingNonAssistedFocusedElementChangesZoomScale)
+{
+    IPhoneUserInterfaceSwizzler userInterfaceSwizzler;
+
+    auto configuration = adoptNS([[WKWebViewConfiguration alloc] init]);
+    auto webView = adoptNS([[TestWKWebView alloc] initWithFrame:NSMakeRect(0, 0, 800, 600) configuration:configuration.get()]);
+    auto inputDelegate = adoptNS([[TestInputDelegate alloc] init]);
+    [webView _setInputDelegate:inputDelegate.get()];
+    [webView synchronouslyLoadHTMLString:applyStyle(@"<body style='height: 4096px'><input id='input'></body>")];
+
+    NSArray<_WKTextInputContext *> *contexts = [webView synchronouslyRequestTextInputContextsInRect:[webView bounds]];
+    EXPECT_EQ(1UL, contexts.count);
+    RetainPtr<_WKTextInputContext> inputElement = contexts[0];
+
+    [inputDelegate setFocusStartsInputSessionPolicyHandler:[] (WKWebView *, id <_WKFocusedElementInfo>) { return _WKFocusStartsInputSessionPolicyDisallow; }];
+    [webView stringByEvaluatingJavaScript:@"input.focus()"]; // Will not start input assistance
+    EXPECT_WK_STREQ("INPUT", [webView stringByEvaluatingJavaScript:@"document.activeElement.tagName"]);
+    [webView scrollView].zoomScale = 2;
+
+    // Focus the field using -focusTextInputContext; zoom scale of scroll view should change to reveal the focused element.
+    [inputDelegate setFocusStartsInputSessionPolicyHandler:[] (WKWebView *, id <_WKFocusedElementInfo>) { return _WKFocusStartsInputSessionPolicyAllow; }];
+    {
+        TextInteractionForScope scope { webView, inputElement };
+        // Will start input assistance
+        EXPECT_EQ((UIResponder<UITextInput> *)[webView textInputContentView], [webView synchronouslyFocusTextInputContext:inputElement.get() placeCaretAt:[inputElement boundingRect].origin]);
+    }
+    EXPECT_WK_STREQ("INPUT", [webView stringByEvaluatingJavaScript:@"document.activeElement.tagName"]);
+    EXPECT_LT([webView scrollView].zoomScale, 2);
+}
+
 #endif // PLATFORM(IOS_FAMILY)
index 0b2188d..ecceec6 100644 (file)
 #if PLATFORM(IOS_FAMILY) && !PLATFORM(MACCATALYST)
 
 #import "ClassMethodSwizzler.h"
-#import "IPadUserInterfaceSwizzler.h"
 #import "PlatformUtilities.h"
 #import "TestNavigationDelegate.h"
 #import "TestWKWebView.h"
 #import "TestWKWebViewController.h"
 #import "UIKitSPI.h"
+#import "UserInterfaceSwizzler.h"
 #import <MobileCoreServices/MobileCoreServices.h>
 #import <WebKit/WKUIDelegatePrivate.h>
 #import <WebKit/WKWebViewPrivateForTesting.h>
index 0d407f3..bc74c8c 100644 (file)
 
 #if PLATFORM(IOS_FAMILY)
 
-#import "IPadUserInterfaceSwizzler.h"
-#import "InstanceMethodSwizzler.h"
 #import "PlatformUtilities.h"
 #import "TestCocoa.h"
 #import "TestInputDelegate.h"
 #import "TestWKWebView.h"
 #import "UIKitSPI.h"
+#import "UserInterfaceSwizzler.h"
 #import <WebKit/WKWebViewPrivate.h>
 #import <WebKitLegacy/WebEvent.h>
 #import <cmath>
index 70a3f38..fb60a6a 100644 (file)
 
 #if PLATFORM(IOS_FAMILY)
 
-#import "IPadUserInterfaceSwizzler.h"
 #import "PlatformUtilities.h"
 #import "TestNavigationDelegate.h"
 #import "TestWKWebView.h"
+#import "UserInterfaceSwizzler.h"
 #import <WebKit/WKWebpagePreferences.h>
 #import <WebKit/WKWebpagePreferencesPrivate.h>
 #import <wtf/BlockPtr.h>
 
 @end
 
-class IPhoneUserInterfaceSwizzler {
-public:
-    IPhoneUserInterfaceSwizzler()
-        : m_swizzler(UIDevice.class, @selector(userInterfaceIdiom), reinterpret_cast<IMP>(phoneUserInterfaceIdiom))
-    {
-    }
-
-private:
-    static UIUserInterfaceIdiom phoneUserInterfaceIdiom(id, SEL)
-    {
-        return UIUserInterfaceIdiomPhone;
-    }
-
-    InstanceMethodSwizzler m_swizzler;
-};
-
 namespace TestWebKitAPI {
 
 template <typename ViewClass>
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2019 Apple Inc. All rights reserved.
+ * Copyright (C) 2019-2020 Apple Inc. All rights reserved.
  *
  * Redistribution and use in source and binary forms, with or without
  * modification, are permitted provided that the following conditions
 
 namespace TestWebKitAPI {
 
-class IPadUserInterfaceSwizzler {
+template<UIUserInterfaceIdiom idiom>
+class UserInterfaceSwizzler : private InstanceMethodSwizzler {
 public:
-    IPadUserInterfaceSwizzler()
-        : m_swizzler([UIDevice class], @selector(userInterfaceIdiom), reinterpret_cast<IMP>(padUserInterfaceIdiom))
+    UserInterfaceSwizzler()
+        : InstanceMethodSwizzler { UIDevice.class, @selector(userInterfaceIdiom), reinterpret_cast<IMP>(effectiveUserInterfaceIdiom) }
     {
     }
+
 private:
-    static UIUserInterfaceIdiom padUserInterfaceIdiom(id, SEL)
+    static UIUserInterfaceIdiom effectiveUserInterfaceIdiom(id, SEL)
     {
-        return UIUserInterfaceIdiomPad;
+        return idiom;
     }
-    InstanceMethodSwizzler m_swizzler;
 };
 
+using IPadUserInterfaceSwizzler = UserInterfaceSwizzler<UIUserInterfaceIdiomPad>;
+using IPhoneUserInterfaceSwizzler = UserInterfaceSwizzler<UIUserInterfaceIdiomPhone>;
+
 } // namespace TestWebKitAPI
 
 #endif // PLATFORM(IOS_FAMILY)