Title: [235878] trunk
Revision
235878
Author
wenson_hs...@apple.com
Date
2018-09-10 18:18:05 -0700 (Mon, 10 Sep 2018)

Log Message

[iOS] Unable to change the value of select elements while preserving focus state
https://bugs.webkit.org/show_bug.cgi?id=189475
<rdar://problem/41125764>

Reviewed by Tim Horton.

Source/WebKit:

With UITextInputMultiDocument support in WebKit (r226911), WKContentView may enter a state where the user is
able to interact with web content, but focus state is being preserved on WKWebView. This prevents keyboard and
input view dismissal from blurring the focused element (which, in turn, means that the value of certain form
controls, such as select elements, cannot be changed). This can happen in the following scenario:

1. Suppose we have a web view embedded inside of a view controller (let's call it `A`).
2. Another view controller (let's call this one `B`) is modally presented over `A`.
3. The web view is removed from `A`'s view hierarchy and reparented under `B`'s view.
4. The user taps a form control in the web view, interacts with the keyboard, and taps the Done button.

After step 2, WKContentView gets a call to `-_preserveFocusWithToken:destructively:`, which increments its focus
retain count to 1. Thus, in step 3, resigning first responder using the Done button fails to blur the element.
To fix this, we split the existing `_activeFocusedStateRetainCount` into two values: `_focusPreservationCount`,
which is safe to reset and resets to 0 when changing the focused element, and `_activeFocusedStateRetainCount`,
which always increments and decrements, and only does so when using `-_retainActiveFocusedState`.

This also fixes a bug wherein `-_restoreFocusWithToken:` is implemented as returning `void` in WebKit, even
though its declaration in UIKit returns a `BOOL`. UIKit currently calls this selector on WKContentView and
stores the result within a `BOOL`; this results in the return value of `-_restoreFocusWithToken:` effectively
becoming the last byte of the most recent value stored in `$rax` when exiting `-_restoreFocusWithToken:`. From
debugging a release build of WebKit, this turns out to just be 0x0, becoming NO, which is correct given that
WKContentView does not attempt to become first responder within `_restoreFocusWithToken:`.

Tests:  FocusPreservationTests.PreserveAndRestoreFocus
        FocusPreservationTests.ChangingFocusedNodeResetsFocusPreservationState

* Platform/spi/ios/UIKitSPI.h:
* UIProcess/API/Cocoa/WKWebView.mm:
(-[WKWebView _incrementFocusPreservationCount]):
(-[WKWebView _decrementFocusPreservationCount]):
(-[WKWebView _resetFocusPreservationCount]):
(-[WKWebView _isRetainingActiveFocusedState]):

Active focus state is retained if either the focus preservation count or active focus state count is non-zero.
Splitting this into two variables ensures that SPI clients of `-_retainActiveFocusedState` won't have active
focus state reset from underneath them, and additionally prevents WKContentView from retaining active focus due
to UITextInputMultiDocument protocol methods while the user is still interacting with it.

* UIProcess/API/Cocoa/WKWebViewInternal.h:

Move _activeFocusedStateRetainCount to the implementation of WKContentView; instead of directly manipulating the
variable, add helper methods to increment, decrement, or reset the focus preservation count.

* UIProcess/ios/PageClientImplIOS.mm:
(WebKit::PageClientImpl::isViewWindowActive):
(WebKit::PageClientImpl::isViewFocused):
* UIProcess/ios/WKContentViewInteraction.h:
* UIProcess/ios/WKContentViewInteraction.mm:
(-[WKContentView resignFirstResponderForWebView]):
(-[WKContentView _startAssistingNode:userIsInteracting:blurPreviousNode:changingActivityState:userObject:]):

Reset the focus preservation count.

(-[WKContentView addFocusedFormControlOverlay]):
(-[WKContentView removeFocusedFormControlOverlay]):

Use `-_retainActiveFocusedState` instead of incrementing and decrementing the counter directly.

(-[WKContentView _restoreFocusWithToken:]):
(-[WKContentView _preserveFocusWithToken:destructively:]):

Use `_incrementFocusPreservationCount` and `_decrementFocusPreservationCount` instead of manipulating counter
variables directly.

Tools:

Adds a pair of new API tests to verify that (1) resigning first responder while preserving focus does not blur
the focused element, and (2) if another element is focused and presents an input view while preserving focus,
then we reset preservation state and allow first responder resignation to blur the focused element.

* TestWebKitAPI/TestWebKitAPI.xcodeproj/project.pbxproj:
* TestWebKitAPI/Tests/ios/FocusPreservationTests.mm: Added.
(webViewForTestingFocusPreservation):
(TestWebKitAPI::TEST):
* TestWebKitAPI/cocoa/TestWKWebView.h:
* TestWebKitAPI/cocoa/TestWKWebView.mm:
(-[TestWKWebView textInputContentView]):
* TestWebKitAPI/ios/UIKitSPI.h:

Modified Paths

Added Paths

Diff

Modified: trunk/Source/WebKit/ChangeLog (235877 => 235878)


--- trunk/Source/WebKit/ChangeLog	2018-09-11 00:51:42 UTC (rev 235877)
+++ trunk/Source/WebKit/ChangeLog	2018-09-11 01:18:05 UTC (rev 235878)
@@ -1,3 +1,75 @@
+2018-09-10  Wenson Hsieh  <wenson_hs...@apple.com>
+
+        [iOS] Unable to change the value of select elements while preserving focus state
+        https://bugs.webkit.org/show_bug.cgi?id=189475
+        <rdar://problem/41125764>
+
+        Reviewed by Tim Horton.
+
+        With UITextInputMultiDocument support in WebKit (r226911), WKContentView may enter a state where the user is
+        able to interact with web content, but focus state is being preserved on WKWebView. This prevents keyboard and
+        input view dismissal from blurring the focused element (which, in turn, means that the value of certain form
+        controls, such as select elements, cannot be changed). This can happen in the following scenario:
+
+        1. Suppose we have a web view embedded inside of a view controller (let's call it `A`).
+        2. Another view controller (let's call this one `B`) is modally presented over `A`.
+        3. The web view is removed from `A`'s view hierarchy and reparented under `B`'s view.
+        4. The user taps a form control in the web view, interacts with the keyboard, and taps the Done button.
+
+        After step 2, WKContentView gets a call to `-_preserveFocusWithToken:destructively:`, which increments its focus
+        retain count to 1. Thus, in step 3, resigning first responder using the Done button fails to blur the element.
+        To fix this, we split the existing `_activeFocusedStateRetainCount` into two values: `_focusPreservationCount`,
+        which is safe to reset and resets to 0 when changing the focused element, and `_activeFocusedStateRetainCount`,
+        which always increments and decrements, and only does so when using `-_retainActiveFocusedState`.
+
+        This also fixes a bug wherein `-_restoreFocusWithToken:` is implemented as returning `void` in WebKit, even
+        though its declaration in UIKit returns a `BOOL`. UIKit currently calls this selector on WKContentView and
+        stores the result within a `BOOL`; this results in the return value of `-_restoreFocusWithToken:` effectively
+        becoming the last byte of the most recent value stored in `$rax` when exiting `-_restoreFocusWithToken:`. From
+        debugging a release build of WebKit, this turns out to just be 0x0, becoming NO, which is correct given that
+        WKContentView does not attempt to become first responder within `_restoreFocusWithToken:`.
+
+        Tests:  FocusPreservationTests.PreserveAndRestoreFocus
+                FocusPreservationTests.ChangingFocusedNodeResetsFocusPreservationState
+
+        * Platform/spi/ios/UIKitSPI.h:
+        * UIProcess/API/Cocoa/WKWebView.mm:
+        (-[WKWebView _incrementFocusPreservationCount]):
+        (-[WKWebView _decrementFocusPreservationCount]):
+        (-[WKWebView _resetFocusPreservationCount]):
+        (-[WKWebView _isRetainingActiveFocusedState]):
+
+        Active focus state is retained if either the focus preservation count or active focus state count is non-zero.
+        Splitting this into two variables ensures that SPI clients of `-_retainActiveFocusedState` won't have active
+        focus state reset from underneath them, and additionally prevents WKContentView from retaining active focus due
+        to UITextInputMultiDocument protocol methods while the user is still interacting with it.
+
+        * UIProcess/API/Cocoa/WKWebViewInternal.h:
+
+        Move _activeFocusedStateRetainCount to the implementation of WKContentView; instead of directly manipulating the
+        variable, add helper methods to increment, decrement, or reset the focus preservation count.
+
+        * UIProcess/ios/PageClientImplIOS.mm:
+        (WebKit::PageClientImpl::isViewWindowActive):
+        (WebKit::PageClientImpl::isViewFocused):
+        * UIProcess/ios/WKContentViewInteraction.h:
+        * UIProcess/ios/WKContentViewInteraction.mm:
+        (-[WKContentView resignFirstResponderForWebView]):
+        (-[WKContentView _startAssistingNode:userIsInteracting:blurPreviousNode:changingActivityState:userObject:]):
+
+        Reset the focus preservation count.
+
+        (-[WKContentView addFocusedFormControlOverlay]):
+        (-[WKContentView removeFocusedFormControlOverlay]):
+
+        Use `-_retainActiveFocusedState` instead of incrementing and decrementing the counter directly.
+
+        (-[WKContentView _restoreFocusWithToken:]):
+        (-[WKContentView _preserveFocusWithToken:destructively:]):
+
+        Use `_incrementFocusPreservationCount` and `_decrementFocusPreservationCount` instead of manipulating counter
+        variables directly.
+
 2018-09-10  James Savage  <james.sav...@apple.com>
 
         Long press on picture/link show menu obscured by keyboard.

Modified: trunk/Source/WebKit/Platform/spi/ios/UIKitSPI.h (235877 => 235878)


--- trunk/Source/WebKit/Platform/spi/ios/UIKitSPI.h	2018-09-11 00:51:42 UTC (rev 235877)
+++ trunk/Source/WebKit/Platform/spi/ios/UIKitSPI.h	2018-09-11 01:18:05 UTC (rev 235878)
@@ -1028,7 +1028,7 @@
 #else
 @protocol UITextInputMultiDocument <NSObject>
 @optional
-- (void)_restoreFocusWithToken:(id <NSCopying, NSSecureCoding>)token completion:(void (^)(BOOL didRestore))completion;
+- (BOOL)_restoreFocusWithToken:(id <NSCopying, NSSecureCoding>)token;
 - (void)_preserveFocusWithToken:(id <NSCopying, NSSecureCoding>)token destructively:(BOOL)destructively;
 @end
 #endif

Modified: trunk/Source/WebKit/UIProcess/API/Cocoa/WKWebView.mm (235877 => 235878)


--- trunk/Source/WebKit/UIProcess/API/Cocoa/WKWebView.mm	2018-09-11 00:51:42 UTC (rev 235877)
+++ trunk/Source/WebKit/UIProcess/API/Cocoa/WKWebView.mm	2018-09-11 01:18:05 UTC (rev 235878)
@@ -359,6 +359,9 @@
     // For release-logging for <rdar://problem/39281269>.
     MonotonicTime _timeOfRequestForVisibleContentRectUpdate;
     MonotonicTime _timeOfLastVisibleContentRectUpdate;
+
+    NSUInteger _focusPreservationCount;
+    NSUInteger _activeFocusedStateRetainCount;
 #endif
 #if PLATFORM(MAC)
     std::unique_ptr<WebKit::WebViewImpl> _impl;
@@ -433,6 +436,31 @@
     return shouldAllowSettingAnyXHRHeaderFromFileURLs;
 }
 
+- (void)_incrementFocusPreservationCount
+{
+    ++_focusPreservationCount;
+}
+
+- (void)_decrementFocusPreservationCount
+{
+    if (_focusPreservationCount)
+        --_focusPreservationCount;
+}
+
+- (void)_resetFocusPreservationCount
+{
+    _focusPreservationCount = 0;
+}
+
+- (BOOL)_isRetainingActiveFocusedState
+{
+    // Focus preservation count fulfills the same role as active focus state count.
+    // However, unlike active focus state, it may be reset to 0 without impacting the
+    // behavior of -_retainActiveFocusedState, and it's harmless to invoke
+    // -_decrementFocusPreservationCount after resetting the count to 0.
+    return _focusPreservationCount || _activeFocusedStateRetainCount;
+}
+
 #endif
 
 static bool shouldRequireUserGestureToLoadVideo()

Modified: trunk/Source/WebKit/UIProcess/API/Cocoa/WKWebViewInternal.h (235877 => 235878)


--- trunk/Source/WebKit/UIProcess/API/Cocoa/WKWebViewInternal.h	2018-09-11 00:51:42 UTC (rev 235877)
+++ trunk/Source/WebKit/UIProcess/API/Cocoa/WKWebViewInternal.h	2018-09-11 01:18:05 UTC (rev 235878)
@@ -73,10 +73,6 @@
     RetainPtr<WKWebViewConfiguration> _configuration;
 
     RefPtr<WebKit::WebPageProxy> _page;
-
-#if PLATFORM(IOS)
-    NSUInteger _activeFocusedStateRetainCount;
-#endif
 }
 
 #if PLATFORM(IOS)
@@ -142,6 +138,10 @@
 - (void)_transliterateChinese:(id)sender;
 - (void)replace:(id)sender;
 
+- (void)_incrementFocusPreservationCount;
+- (void)_decrementFocusPreservationCount;
+- (void)_resetFocusPreservationCount;
+
 @property (nonatomic, readonly) WKPasswordView *_passwordView;
 
 @property (nonatomic, readonly) BOOL _isBackground;
@@ -154,6 +154,7 @@
 @property (nonatomic, readonly) BOOL _haveSetObscuredInsets;
 @property (nonatomic, readonly) UIEdgeInsets _computedObscuredInset;
 @property (nonatomic, readonly) UIEdgeInsets _computedUnobscuredSafeAreaInset;
+@property (nonatomic, readonly, getter=_isRetainingActiveFocusedState) BOOL _retainingActiveFocusedState;
 #endif
 
 #if ENABLE(ACCESSIBILITY_EVENTS)

Modified: trunk/Source/WebKit/UIProcess/ios/PageClientImplIOS.mm (235877 => 235878)


--- trunk/Source/WebKit/UIProcess/ios/PageClientImplIOS.mm	2018-09-11 00:51:42 UTC (rev 235877)
+++ trunk/Source/WebKit/UIProcess/ios/PageClientImplIOS.mm	2018-09-11 01:18:05 UTC (rev 235878)
@@ -161,13 +161,13 @@
 bool PageClientImpl::isViewWindowActive()
 {
     // FIXME: https://bugs.webkit.org/show_bug.cgi?id=133098
-    return isViewVisible() || (m_webView && m_webView->_activeFocusedStateRetainCount);
+    return isViewVisible() || (m_webView && [m_webView _isRetainingActiveFocusedState]);
 }
 
 bool PageClientImpl::isViewFocused()
 {
     // FIXME: https://bugs.webkit.org/show_bug.cgi?id=133098
-    return isViewWindowActive() || (m_webView && m_webView->_activeFocusedStateRetainCount);
+    return isViewWindowActive() || (m_webView && [m_webView _isRetainingActiveFocusedState]);
 }
 
 bool PageClientImpl::isViewVisible()

Modified: trunk/Source/WebKit/UIProcess/ios/WKContentViewInteraction.h (235877 => 235878)


--- trunk/Source/WebKit/UIProcess/ios/WKContentViewInteraction.h	2018-09-11 00:51:42 UTC (rev 235877)
+++ trunk/Source/WebKit/UIProcess/ios/WKContentViewInteraction.h	2018-09-11 01:18:05 UTC (rev 235878)
@@ -277,6 +277,7 @@
     RetainPtr<UINavigationController> _inputNavigationViewControllerForFullScreenInputs;
 
     BOOL _shouldRestoreFirstResponderStatusAfterLosingFocus;
+    BlockPtr<void()> _activeFocusedStateRetainBlock;
 #endif
 }
 

Modified: trunk/Source/WebKit/UIProcess/ios/WKContentViewInteraction.mm (235877 => 235878)


--- trunk/Source/WebKit/UIProcess/ios/WKContentViewInteraction.mm	2018-09-11 00:51:42 UTC (rev 235877)
+++ trunk/Source/WebKit/UIProcess/ios/WKContentViewInteraction.mm	2018-09-11 01:18:05 UTC (rev 235878)
@@ -1011,7 +1011,7 @@
     // and do nothing if the return value is NO.
 
     _resigningFirstResponder = YES;
-    if (!_webView->_activeFocusedStateRetainCount) {
+    if (!_webView._retainingActiveFocusedState) {
         // We need to complete the editing operation before we blur the element.
         [_inputPeripheral endEditing];
         [_formInputSession endEditing];
@@ -4210,6 +4210,8 @@
     if (_assistedNodeInformation.elementType == information.elementType && _assistedNodeInformation.elementRect == information.elementRect)
         return;
 
+    [_webView _resetFocusPreservationCount];
+
     _focusRequiresStrongPasswordAssistance = NO;
     if ([inputDelegate respondsToSelector:@selector(_webView:focusRequiresStrongPasswordAssistance:)])
         _focusRequiresStrongPasswordAssistance = [inputDelegate _webView:_webView focusRequiresStrongPasswordAssistance:focusedElementInfo.get()];
@@ -4340,7 +4342,7 @@
     if (_focusedFormControlView)
         return;
 
-    ++_webView->_activeFocusedStateRetainCount;
+    _activeFocusedStateRetainBlock = makeBlockPtr(_webView._retainActiveFocusedState);
 
     _focusedFormControlView = adoptNS([[WKFocusedFormControlView alloc] initWithFrame:_webView.bounds delegate:self]);
     [_focusedFormControlView hide:NO];
@@ -4353,7 +4355,8 @@
     if (!_focusedFormControlView)
         return;
 
-    --_webView->_activeFocusedStateRetainCount;
+    if (auto releaseActiveFocusState = WTFMove(_activeFocusedStateRetainBlock))
+        releaseActiveFocusState();
 
     [_focusedFormControlView removeFromSuperview];
     _focusedFormControlView = nil;
@@ -4760,20 +4763,26 @@
 
 #pragma mark - UITextInputMultiDocument
 
-- (void)_restoreFocusWithToken:(id <NSCopying, NSSecureCoding>)token
+- (BOOL)_restoreFocusWithToken:(id <NSCopying, NSSecureCoding>)token
 {
-    ASSERT(!_focusStateStack.isEmpty());
-    
-    if (_focusStateStack.takeLast()) {
-        ASSERT(_webView->_activeFocusedStateRetainCount);
-        --_webView->_activeFocusedStateRetainCount;
+    if (_focusStateStack.isEmpty()) {
+        ASSERT_NOT_REACHED();
+        return NO;
     }
+
+    if (_focusStateStack.takeLast())
+        [_webView _decrementFocusPreservationCount];
+
+    // FIXME: Our current behavior in -_restoreFocusWithToken: does not force the web view to become first responder
+    // by refocusing the currently focused element. As such, we return NO here so that UIKit will tell WKContentView
+    // to become first responder in the future.
+    return NO;
 }
 
 - (void)_preserveFocusWithToken:(id <NSCopying, NSSecureCoding>)token destructively:(BOOL)destructively
 {
     if (!_inputPeripheral) {
-        ++_webView->_activeFocusedStateRetainCount;
+        [_webView _incrementFocusPreservationCount];
         _focusStateStack.append(true);
     } else
         _focusStateStack.append(false);

Modified: trunk/Tools/ChangeLog (235877 => 235878)


--- trunk/Tools/ChangeLog	2018-09-11 00:51:42 UTC (rev 235877)
+++ trunk/Tools/ChangeLog	2018-09-11 01:18:05 UTC (rev 235878)
@@ -1,3 +1,24 @@
+2018-09-10  Wenson Hsieh  <wenson_hs...@apple.com>
+
+        [iOS] Unable to change the value of select elements while preserving focus state
+        https://bugs.webkit.org/show_bug.cgi?id=189475
+        <rdar://problem/41125764>
+
+        Reviewed by Tim Horton.
+
+        Adds a pair of new API tests to verify that (1) resigning first responder while preserving focus does not blur
+        the focused element, and (2) if another element is focused and presents an input view while preserving focus,
+        then we reset preservation state and allow first responder resignation to blur the focused element.
+
+        * TestWebKitAPI/TestWebKitAPI.xcodeproj/project.pbxproj:
+        * TestWebKitAPI/Tests/ios/FocusPreservationTests.mm: Added.
+        (webViewForTestingFocusPreservation):
+        (TestWebKitAPI::TEST):
+        * TestWebKitAPI/cocoa/TestWKWebView.h:
+        * TestWebKitAPI/cocoa/TestWKWebView.mm:
+        (-[TestWKWebView textInputContentView]):
+        * TestWebKitAPI/ios/UIKitSPI.h:
+
 2018-09-10  Thomas Denney  <tden...@apple.com>
 
         [WHLSL] Add the test shader type

Modified: trunk/Tools/TestWebKitAPI/TestWebKitAPI.xcodeproj/project.pbxproj (235877 => 235878)


--- trunk/Tools/TestWebKitAPI/TestWebKitAPI.xcodeproj/project.pbxproj	2018-09-11 00:51:42 UTC (rev 235877)
+++ trunk/Tools/TestWebKitAPI/TestWebKitAPI.xcodeproj/project.pbxproj	2018-09-11 01:18:05 UTC (rev 235878)
@@ -835,6 +835,7 @@
 		F4AB578A1F65165400DB0DA1 /* custom-draggable-div.html in Copy Resources */ = {isa = PBXBuildFile; fileRef = F4AB57891F65164B00DB0DA1 /* custom-draggable-div.html */; };
 		F4B825D81EF4DBFB006E417F /* compressed-files.zip in Copy Resources */ = {isa = PBXBuildFile; fileRef = F4B825D61EF4DBD4006E417F /* compressed-files.zip */; };
 		F4B86D4F20BCD5B20099A7E6 /* paint-significant-area-milestone.html in Copy Resources */ = {isa = PBXBuildFile; fileRef = F4B86D4E20BCD5970099A7E6 /* paint-significant-area-milestone.html */; };
+		F4BC0B142146C849002A0478 /* FocusPreservationTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = F4BC0B132146C849002A0478 /* FocusPreservationTests.mm */; };
 		F4BFA68E1E4AD08000154298 /* LegacyDragAndDropTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = F4BFA68C1E4AD08000154298 /* LegacyDragAndDropTests.mm */; };
 		F4C2AB221DD6D95E00E06D5B /* enormous-video-with-sound.html in Copy Resources */ = {isa = PBXBuildFile; fileRef = F4C2AB211DD6D94100E06D5B /* enormous-video-with-sound.html */; };
 		F4C8797F2059D8D3009CD00B /* ScrollViewInsetTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = F4C8797E2059D8D3009CD00B /* ScrollViewInsetTests.mm */; };
@@ -2090,6 +2091,7 @@
 		F4AB57891F65164B00DB0DA1 /* custom-draggable-div.html */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.html; path = "custom-draggable-div.html"; sourceTree = "<group>"; };
 		F4B825D61EF4DBD4006E417F /* compressed-files.zip */ = {isa = PBXFileReference; lastKnownFileType = archive.zip; path = "compressed-files.zip"; sourceTree = "<group>"; };
 		F4B86D4E20BCD5970099A7E6 /* paint-significant-area-milestone.html */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.html; path = "paint-significant-area-milestone.html"; sourceTree = "<group>"; };
+		F4BC0B132146C849002A0478 /* FocusPreservationTests.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = FocusPreservationTests.mm; sourceTree = "<group>"; };
 		F4BFA68C1E4AD08000154298 /* LegacyDragAndDropTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = LegacyDragAndDropTests.mm; sourceTree = "<group>"; };
 		F4C2AB211DD6D94100E06D5B /* enormous-video-with-sound.html */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.html; path = "enormous-video-with-sound.html"; sourceTree = "<group>"; };
 		F4C8797E2059D8D3009CD00B /* ScrollViewInsetTests.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = ScrollViewInsetTests.mm; sourceTree = "<group>"; };
@@ -2550,6 +2552,7 @@
 				2E205BA31F527746005952DD /* AccessibilityTestsIOS.mm */,
 				F45B63FC1F19D410009D38B9 /* ActionSheetTests.mm */,
 				F4D4F3B71E4E36E400BB2767 /* DragAndDropTestsIOS.mm */,
+				F4BC0B132146C849002A0478 /* FocusPreservationTests.mm */,
 				F45E15722112CE2900307E82 /* KeyboardInputTestsIOS.mm */,
 				574F55CE204D3763002948C6 /* LocalAuthenticator.mm */,
 				7560917719259C59009EF06E /* MemoryCacheAddImageToCacheIOS.mm */,
@@ -3789,6 +3792,7 @@
 				7A909A7E1D877480007E10F8 /* FloatPoint.cpp in Sources */,
 				7A909A7F1D877480007E10F8 /* FloatRect.cpp in Sources */,
 				7A909A801D877480007E10F8 /* FloatSize.cpp in Sources */,
+				F4BC0B142146C849002A0478 /* FocusPreservationTests.mm in Sources */,
 				1CAD1F861E5CE7DA00AF2C2C /* FontCache.cpp in Sources */,
 				F456AB1C213EDBA300CB2CEF /* FontManagerTests.mm in Sources */,
 				7CCE7EF51A411AE600447C4C /* ForceRepaint.cpp in Sources */,

Added: trunk/Tools/TestWebKitAPI/Tests/ios/FocusPreservationTests.mm (0 => 235878)


--- trunk/Tools/TestWebKitAPI/Tests/ios/FocusPreservationTests.mm	                        (rev 0)
+++ trunk/Tools/TestWebKitAPI/Tests/ios/FocusPreservationTests.mm	2018-09-11 01:18:05 UTC (rev 235878)
@@ -0,0 +1,108 @@
+/*
+ * Copyright (C) 2018 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.
+ */
+
+#import "config.h"
+#import "Test.h"
+
+#if PLATFORM(IOS)
+
+#import "PlatformUtilities.h"
+#import "TestInputDelegate.h"
+#import "TestWKWebView.h"
+#import "UIKitSPI.h"
+#import <WebKit/WKWebViewPrivate.h>
+#import <WebKit/_WKInputDelegate.h>
+#import <wtf/BlockPtr.h>
+
+static std::pair<RetainPtr<TestWKWebView>, RetainPtr<TestInputDelegate>> webViewForTestingFocusPreservation(void(^focusHandler)(id <_WKFocusedElementInfo>))
+{
+    auto inputDelegate = adoptNS([[TestInputDelegate alloc] init]);
+    [inputDelegate setFocusStartsInputSessionPolicyHandler:[&, focusHandler = makeBlockPtr(focusHandler)] (WKWebView *, id<_WKFocusedElementInfo> info) {
+        focusHandler(info);
+        return _WKFocusStartsInputSessionPolicyAllow;
+    }];
+
+    auto webView = adoptNS([[TestWKWebView alloc] initWithFrame:CGRectMake(0, 0, 320, 500)]);
+    [webView _setInputDelegate:inputDelegate.get()];
+    [webView synchronouslyLoadHTMLString:@"<input></input><select><option selected>foo</option><option>bar</option></select>"];
+    return { webView, inputDelegate };
+}
+
+namespace TestWebKitAPI {
+
+TEST(FocusPreservationTests, PreserveAndRestoreFocus)
+{
+    bool inputFocused = false;
+    auto webViewAndDelegate = webViewForTestingFocusPreservation([&inputFocused] (id <_WKFocusedElementInfo> info) {
+        inputFocused = true;
+    });
+
+    TestWKWebView *webView = webViewAndDelegate.first.get();
+    [webView evaluateJavaScript:@"document.querySelector('input').focus()" completionHandler:nil];
+    Util::run(&inputFocused);
+
+    NSUUID *focusToken = NSUUID.UUID;
+    [webView.textInputContentView _preserveFocusWithToken:focusToken destructively:YES];
+
+    EXPECT_TRUE([webView resignFirstResponder]);
+    EXPECT_TRUE([webView stringByEvaluatingJavaScript:@"document.activeElement == document.querySelector('input')"].boolValue);
+
+    [webView.textInputContentView _restoreFocusWithToken:focusToken];
+    EXPECT_TRUE([webView becomeFirstResponder]);
+}
+
+TEST(FocusPreservationTests, ChangingFocusedNodeResetsFocusPreservationState)
+{
+    bool inputFocused = false;
+    bool selectFocused = false;
+    auto webViewAndDelegate = webViewForTestingFocusPreservation([&inputFocused, &selectFocused] (id <_WKFocusedElementInfo> info) {
+        if (info.type == WKInputTypeSelect)
+            selectFocused = true;
+        else
+            inputFocused = true;
+    });
+
+    TestWKWebView *webView = webViewAndDelegate.first.get();
+    [webView evaluateJavaScript:@"document.querySelector('input').focus()" completionHandler:nil];
+    Util::run(&inputFocused);
+
+    NSUUID *focusToken = NSUUID.UUID;
+    [webView.textInputContentView _preserveFocusWithToken:focusToken destructively:YES];
+
+    [webView evaluateJavaScript:@"document.querySelector('select').focus()" completionHandler:nil];
+    Util::run(&selectFocused);
+
+    EXPECT_NOT_NULL(webView.textInputContentView.inputView);
+    [webView selectFormAccessoryPickerRow:1];
+    EXPECT_TRUE([webView resignFirstResponder]);
+    EXPECT_FALSE([webView stringByEvaluatingJavaScript:@"document.activeElement == document.querySelector('select')"].boolValue);
+    EXPECT_EQ(1, [webView stringByEvaluatingJavaScript:@"document.querySelector('select').selectedIndex"].intValue);
+
+    [webView.textInputContentView _restoreFocusWithToken:focusToken];
+}
+
+} // namespace TestWebKitAPI
+
+#endif // PLATFORM(IOS)

Modified: trunk/Tools/TestWebKitAPI/cocoa/TestWKWebView.h (235877 => 235878)


--- trunk/Tools/TestWebKitAPI/cocoa/TestWKWebView.h	2018-09-11 00:51:42 UTC (rev 235877)
+++ trunk/Tools/TestWebKitAPI/cocoa/TestWKWebView.h	2018-09-11 01:18:05 UTC (rev 235878)
@@ -32,6 +32,7 @@
 
 #if PLATFORM(IOS)
 @class _WKActivatedElementInfo;
+@protocol UITextInputMultiDocument;
 #endif
 
 @interface WKWebView (AdditionalDeclarations)
@@ -63,7 +64,7 @@
 
 #if PLATFORM(IOS)
 @interface TestWKWebView (IOSOnly)
-@property (nonatomic, readonly) UIView <UITextInput> *textInputContentView;
+@property (nonatomic, readonly) UIView <UITextInput, UITextInputMultiDocument> *textInputContentView;
 @property (nonatomic, readonly) RetainPtr<NSArray> selectionRectsAfterPresentationUpdate;
 @property (nonatomic, readonly) CGRect caretViewRectInContentCoordinates;
 @property (nonatomic, readonly) NSArray<NSValue *> *selectionViewRectsInContentCoordinates;

Modified: trunk/Tools/TestWebKitAPI/cocoa/TestWKWebView.mm (235877 => 235878)


--- trunk/Tools/TestWebKitAPI/cocoa/TestWKWebView.mm	2018-09-11 00:51:42 UTC (rev 235877)
+++ trunk/Tools/TestWebKitAPI/cocoa/TestWKWebView.mm	2018-09-11 01:18:05 UTC (rev 235878)
@@ -45,7 +45,7 @@
 #endif
 
 #if PLATFORM(IOS)
-#import <UIKit/UIKit.h>
+#import "UIKitSPI.h"
 #import <wtf/SoftLinking.h>
 SOFT_LINK_FRAMEWORK(UIKit)
 SOFT_LINK_CLASS(UIKit, UIWindow)
@@ -321,9 +321,9 @@
 
 @implementation TestWKWebView (IOSOnly)
 
-- (UIView <UITextInput> *)textInputContentView
+- (UIView <UITextInput, UITextInputMultiDocument> *)textInputContentView
 {
-    return (UIView <UITextInput> *)[self valueForKey:@"_currentContentView"];
+    return (UIView <UITextInput, UITextInputMultiDocument> *)[self valueForKey:@"_currentContentView"];
 }
 
 - (RetainPtr<NSArray>)selectionRectsAfterPresentationUpdate

Modified: trunk/Tools/TestWebKitAPI/ios/UIKitSPI.h (235877 => 235878)


--- trunk/Tools/TestWebKitAPI/ios/UIKitSPI.h	2018-09-11 00:51:42 UTC (rev 235877)
+++ trunk/Tools/TestWebKitAPI/ios/UIKitSPI.h	2018-09-11 01:18:05 UTC (rev 235878)
@@ -31,6 +31,7 @@
 
 #import <UIKit/UIApplication_Private.h>
 #import <UIKit/UIResponder_Private.h>
+#import <UIKit/UITextInputMultiDocument.h>
 #import <UIKit/UITextInputTraits_Private.h>
 #import <UIKit/UITextInput_Private.h>
 #import <UIKit/UIViewController_Private.h>
@@ -79,6 +80,12 @@
 - (void)handleKeyWebEvent:(WebEvent *)theEvent withCompletionHandler:(void (^)(WebEvent *, BOOL))completionHandler;
 @end
 
+@protocol UITextInputMultiDocument <NSObject>
+@optional
+- (void)_preserveFocusWithToken:(id <NSCopying, NSSecureCoding>)token destructively:(BOOL)destructively;
+- (BOOL)_restoreFocusWithToken:(id <NSCopying, NSSecureCoding>)token;
+@end
+
 #if ENABLE(DRAG_SUPPORT)
 
 @interface NSURL ()
_______________________________________________
webkit-changes mailing list
webkit-changes@lists.webkit.org
https://lists.webkit.org/mailman/listinfo/webkit-changes

Reply via email to