Title: [295751] trunk
Revision
295751
Author
wenson_hs...@apple.com
Date
2022-06-22 15:42:57 -0700 (Wed, 22 Jun 2022)

Log Message

REGRESSION (iOS 16): System controls overlap website controls (affects SquareSpace, medium.com, and others)
https://bugs.webkit.org/show_bug.cgi?id=241837
rdar://95658478

Reviewed by Aditya Keerthi.

On iOS 16, the callout bar (which is now built on top of `UIEditMenuInteraction`) no longer respects
evasion rects, passed in through the `UIWKInteractionViewProtocol` delegate method
`-requestRectsToEvadeForSelectionCommandsWithCompletionHandler:`. This was previously used to avoid
overlapping interactable controls on the page when presenting the callout bar, which the layout
test `editing/selection/ios/avoid-showing-callout-menu-over-controls.html` exercises.

To fix this, we're adding a replacement SPI in UIKit, allowing WebKit to vend a preferred
`UIEditMenuArrowDirection` which will be consulted when presenting the edit menu interaction in
order to show the callout bar.

For more details, see: rdar://95652872.

* Source/WebKit/UIProcess/ios/WKContentViewInteraction.mm:
(-[WKContentView requestPreferredArrowDirectionForEditMenuWithCompletionHandler:]):

In the case where there are clickable controls above the selection, use `UIEditMenuArrowDirectionUp`
to make the callout bar present _below_ the selection instead of above; otherwise, simply go with
the default behavior, which puts the callout bar above the selection.

(-[WKContentView requestRectsToEvadeForSelectionCommandsWithCompletionHandler:]):

Pull out common logic for requesting a list of rects to evade into a separate internal helper
method, and use this common helper method to implement both the legacy delegate method (which no
longer works on iOS 16), as well as the new delegate method in iOS 16. For now, I opted to still
implement both methods, such that this test will pass on both iOS 15 and iOS 16 (but only with the
changes in rdar://95652872).

(-[WKContentView _requestEvasionRectsAboveSelectionIfNeeded:]):
* Tools/TestWebKitAPI/Tests/WebKitCocoa/ImageAnalysisTests.mm:

Also, adopt the new SPI in an API test that simulates callout bar appearance on iOS 16.

(TestWebKitAPI::simulateCalloutBarAppearance):
* Tools/TestWebKitAPI/cocoa/TestWKWebView.h:
* Tools/TestWebKitAPI/ios/UIKitSPI.h:
* Tools/WebKitTestRunner/ios/UIScriptControllerIOS.mm:
(WTR::internalClassNamed):
(WTR::UIScriptControllerIOS::menuRect const):
(WTR::UIScriptControllerIOS::contextMenuRect const):

Additionally tweak a couple of script controller hooks, to work with the new `UIEditMenuInteraction`
-based callout bars on iOS 16.

Canonical link: https://commits.webkit.org/251756@main

Modified Paths

Diff

Modified: trunk/Source/WebKit/UIProcess/ios/WKContentViewInteraction.mm (295750 => 295751)


--- trunk/Source/WebKit/UIProcess/ios/WKContentViewInteraction.mm	2022-06-22 22:07:32 UTC (rev 295750)
+++ trunk/Source/WebKit/UIProcess/ios/WKContentViewInteraction.mm	2022-06-22 22:42:57 UTC (rev 295751)
@@ -4624,53 +4624,64 @@
     });
 }
 
-- (void)requestRectsToEvadeForSelectionCommandsWithCompletionHandler:(void(^)(NSArray<NSValue *> *rects))completionHandler
+#if HAVE(UI_EDIT_MENU_INTERACTION)
+
+- (void)requestPreferredArrowDirectionForEditMenuWithCompletionHandler:(void(^)(UIEditMenuArrowDirection))completion
 {
-    if (!completionHandler) {
-        [NSException raise:NSInvalidArgumentException format:@"Expected a nonnull completion handler in %s.", __PRETTY_FUNCTION__];
-        return;
-    }
+    [self _requestEvasionRectsAboveSelectionIfNeeded:[completion = makeBlockPtr(completion)](const Vector<WebCore::FloatRect>& rects) {
+        completion(rects.isEmpty() ? UIEditMenuArrowDirectionAutomatic : UIEditMenuArrowDirectionUp);
+    }];
+}
 
+#endif
+
+- (void)requestRectsToEvadeForSelectionCommandsWithCompletionHandler:(void(^)(NSArray<NSValue *> *rects))completion
+{
+    [self _requestEvasionRectsAboveSelectionIfNeeded:[completion = makeBlockPtr(completion)](const Vector<WebCore::FloatRect>& rects) {
+        completion(createNSArray(rects).get());
+    }];
+}
+
+- (void)_requestEvasionRectsAboveSelectionIfNeeded:(CompletionHandler<void(const Vector<WebCore::FloatRect>&)>&&)completion
+{
     if ([self _shouldSuppressSelectionCommands]) {
-        completionHandler(@[ ]);
+        completion({ });
         return;
     }
 
-    auto requestRectsToEvadeIfNeeded = [startTime = ApproximateTime::now(), weakSelf = WeakObjCPtr<WKContentView>(self), completion = makeBlockPtr(completionHandler)] {
+    auto requestRectsToEvadeIfNeeded = [startTime = ApproximateTime::now(), weakSelf = WeakObjCPtr<WKContentView>(self), completion = WTFMove(completion)]() mutable {
         auto strongSelf = weakSelf.get();
         if (!strongSelf) {
-            completion(@[ ]);
+            completion({ });
             return;
         }
 
         if ([strongSelf webView]._editable) {
-            completion(@[ ]);
+            completion({ });
             return;
         }
 
         auto focusedElementType = strongSelf->_focusedElementInformation.elementType;
         if (focusedElementType != WebKit::InputType::ContentEditable && focusedElementType != WebKit::InputType::TextArea) {
-            completion(@[ ]);
+            completion({ });
             return;
         }
 
         // Give the page some time to present custom editing UI before attempting to detect and evade it.
         auto delayBeforeShowingCalloutBar = std::max(0_s, 0.25_s - (ApproximateTime::now() - startTime));
-        WorkQueue::main().dispatchAfter(delayBeforeShowingCalloutBar, [completion, weakSelf] () mutable {
+        WorkQueue::main().dispatchAfter(delayBeforeShowingCalloutBar, [completion = WTFMove(completion), weakSelf]() mutable {
             auto strongSelf = weakSelf.get();
             if (!strongSelf) {
-                completion(@[ ]);
+                completion({ });
                 return;
             }
 
             if (!strongSelf->_page) {
-                completion(@[ ]);
+                completion({ });
                 return;
             }
 
-            strongSelf->_page->requestEvasionRectsAboveSelection([completion = WTFMove(completion)] (auto& rects) {
-                completion(createNSArray(rects).get());
-            });
+            strongSelf->_page->requestEvasionRectsAboveSelection(WTFMove(completion));
         });
     };
 

Modified: trunk/Tools/TestWebKitAPI/Tests/WebKitCocoa/ImageAnalysisTests.mm (295750 => 295751)


--- trunk/Tools/TestWebKitAPI/Tests/WebKitCocoa/ImageAnalysisTests.mm	2022-06-22 22:07:32 UTC (rev 295750)
+++ trunk/Tools/TestWebKitAPI/Tests/WebKitCocoa/ImageAnalysisTests.mm	2022-06-22 22:42:57 UTC (rev 295751)
@@ -376,7 +376,7 @@
 static void simulateCalloutBarAppearance(TestWKWebView *webView)
 {
     __block bool done = false;
-    [webView.textInputContentView requestRectsToEvadeForSelectionCommandsWithCompletionHandler:^(NSArray<NSValue *> *) {
+    [webView.textInputContentView requestPreferredArrowDirectionForEditMenuWithCompletionHandler:^(UIEditMenuArrowDirection) {
         done = true;
     }];
     Util::run(&done);

Modified: trunk/Tools/TestWebKitAPI/cocoa/TestWKWebView.h (295750 => 295751)


--- trunk/Tools/TestWebKitAPI/cocoa/TestWKWebView.h	2022-06-22 22:07:32 UTC (rev 295750)
+++ trunk/Tools/TestWebKitAPI/cocoa/TestWKWebView.h	2022-06-22 22:42:57 UTC (rev 295751)
@@ -34,7 +34,7 @@
 @protocol UITextInputInternal;
 @protocol UITextInputMultiDocument;
 @protocol UITextInputPrivate;
-@protocol UIWKInteractionViewProtocol_Staging_91919121;
+@protocol UIWKInteractionViewProtocol_Staging_95652872;
 #endif
 
 @interface WKWebView (AdditionalDeclarations)
@@ -50,7 +50,7 @@
 
 @interface WKWebView (TestWebKitAPI)
 #if PLATFORM(IOS_FAMILY)
-@property (nonatomic, readonly) UIView <UITextInputPrivate, UITextInputInternal, UITextInputMultiDocument, UIWKInteractionViewProtocol_Staging_91919121, UITextInputTokenizer> *textInputContentView;
+@property (nonatomic, readonly) UIView <UITextInputPrivate, UITextInputInternal, UITextInputMultiDocument, UIWKInteractionViewProtocol_Staging_95652872, UITextInputTokenizer> *textInputContentView;
 - (NSArray<_WKTextInputContext *> *)synchronouslyRequestTextInputContextsInRect:(CGRect)rect;
 #endif
 @property (nonatomic, readonly) NSUInteger gpuToWebProcessConnectionCount;

Modified: trunk/Tools/TestWebKitAPI/ios/UIKitSPI.h (295750 => 295751)


--- trunk/Tools/TestWebKitAPI/ios/UIKitSPI.h	2022-06-22 22:07:32 UTC (rev 295750)
+++ trunk/Tools/TestWebKitAPI/ios/UIKitSPI.h	2022-06-22 22:42:57 UTC (rev 295751)
@@ -214,7 +214,6 @@
 
 @protocol UIWKInteractionViewProtocol
 - (void)pasteWithCompletionHandler:(void (^)(void))completionHandler;
-- (void)requestRectsToEvadeForSelectionCommandsWithCompletionHandler:(void(^)(NSArray<NSValue *> *rects))completionHandler;
 - (void)requestAutocorrectionRectsForString:(NSString *)input withCompletionHandler:(void (^)(UIWKAutocorrectionRects *rectsForInput))completionHandler;
 - (void)requestAutocorrectionContextWithCompletionHandler:(void (^)(UIWKAutocorrectionContext *autocorrectionContext))completionHandler;
 - (void)selectWordBackward;
@@ -358,6 +357,12 @@
 - (void)didInsertFinalDictationResult;
 @end
 
+@protocol UIWKInteractionViewProtocol_Staging_95652872 <UIWKInteractionViewProtocol_Staging_91919121>
+#if HAVE(UI_EDIT_MENU_INTERACTION)
+- (void)requestPreferredArrowDirectionForEditMenuWithCompletionHandler:(void (^)(UIEditMenuArrowDirection))completionHandler;
+#endif
+@end
+
 #if HAVE(UIFINDINTERACTION)
 @interface UITextSearchOptions ()
 @property (nonatomic, readwrite) UITextSearchMatchMethod wordMatchMethod;

Modified: trunk/Tools/WebKitTestRunner/ios/UIScriptControllerIOS.mm (295750 => 295751)


--- trunk/Tools/WebKitTestRunner/ios/UIScriptControllerIOS.mm	2022-06-22 22:07:32 UTC (rev 295750)
+++ trunk/Tools/WebKitTestRunner/ios/UIScriptControllerIOS.mm	2022-06-22 22:42:57 UTC (rev 295751)
@@ -100,6 +100,14 @@
     return modifiers;
 }
 
+static Class internalClassNamed(NSString *className)
+{
+    auto result = NSClassFromString(className);
+    if (!result)
+        NSLog(@"Warning: an internal class named '%@' does not exist.", className);
+    return result;
+}
+
 Ref<UIScriptController> UIScriptController::create(UIScriptContext& context)
 {
     return adoptRef(*new UIScriptControllerIOS(context));
@@ -1101,17 +1109,18 @@
 
 JSObjectRef UIScriptControllerIOS::menuRect() const
 {
-    UIView *calloutBar = UICalloutBar.activeCalloutBar;
-    if (!calloutBar.window)
-        return nullptr;
-
-    return toObject([calloutBar convertRect:calloutBar.bounds toView:platformContentView()]);
+    UIView *containerView = nil;
+    if (auto *calloutBar = UICalloutBar.activeCalloutBar; calloutBar.window)
+        containerView = calloutBar;
+    else
+        containerView = findAllViewsInHierarchyOfType(webView().textEffectsWindow, internalClassNamed(@"_UIEditMenuListView")).firstObject;
+    return containerView ? toObject([containerView convertRect:containerView.bounds toView:platformContentView()]) : nullptr;
 }
 
 JSObjectRef UIScriptControllerIOS::contextMenuRect() const
 {
     auto *window = webView().window;
-    auto *contextMenuView = [findAllViewsInHierarchyOfType(window, NSClassFromString(@"_UIContextMenuView")) firstObject];
+    auto *contextMenuView = findAllViewsInHierarchyOfType(window, internalClassNamed(@"_UIContextMenuView")).firstObject;
     if (!contextMenuView)
         return nullptr;
 
_______________________________________________
webkit-changes mailing list
webkit-changes@lists.webkit.org
https://lists.webkit.org/mailman/listinfo/webkit-changes

Reply via email to