[iOS] Occasional crash under -[UITargetedPreview initWithView:parameters:target:] when focusing form controls
https://bugs.webkit.org/show_bug.cgi?id=235248
rdar://79220540

Reviewed by Tim Horton and Aditya Keerthi.

Source/WebKit:

It's possible for `-resizableSnapshotViewFromRect:afterScreenUpdates:withCapInsets:` to return a `nil` snapshot
view in the case where a screen update has not been performed yet (among other scenarios). In the case where
UIKit returns `nil` when we're creating the targeted preview for the context menu when focusing a select element
or file input, we'll crash due to an Objective-C exception in the initializer of UITargetedPreview. Mitigate
this by falling back to an empty UIView after requesting the snapshot view to make our code robust against this
scenario.

Test: KeyboardInputTests.DoNotCrashWhenFocusingSelectWithoutViewSnapshot

* UIProcess/ios/WKContentViewInteraction.mm:
(createFallbackTargetedPreview):

Tools:

Add an API test that exercises the crash by forcing `-resizableSnapshotViewFromRect:afterScreenUpdates:withCapInsets:`
to return nil via swizzling.

* TestWebKitAPI/Tests/ios/KeyboardInputTestsIOS.mm:
(TestWebKitAPI::nilResizableSnapshotViewFromRect):
(TestWebKitAPI::TEST):


git-svn-id: http://svn.webkit.org/repository/webkit/trunk@288039 268f45cc-cd09-0410-ab3c-d52691b4dbfc
diff --git a/Source/WebKit/ChangeLog b/Source/WebKit/ChangeLog
index 83054a2..d49de30 100644
--- a/Source/WebKit/ChangeLog
+++ b/Source/WebKit/ChangeLog
@@ -1,3 +1,23 @@
+2022-01-14  Wenson Hsieh  <wenson_hsieh@apple.com>
+
+        [iOS] Occasional crash under -[UITargetedPreview initWithView:parameters:target:] when focusing form controls
+        https://bugs.webkit.org/show_bug.cgi?id=235248
+        rdar://79220540
+
+        Reviewed by Tim Horton and Aditya Keerthi.
+
+        It's possible for `-resizableSnapshotViewFromRect:afterScreenUpdates:withCapInsets:` to return a `nil` snapshot
+        view in the case where a screen update has not been performed yet (among other scenarios). In the case where
+        UIKit returns `nil` when we're creating the targeted preview for the context menu when focusing a select element
+        or file input, we'll crash due to an Objective-C exception in the initializer of UITargetedPreview. Mitigate
+        this by falling back to an empty UIView after requesting the snapshot view to make our code robust against this
+        scenario.
+
+        Test: KeyboardInputTests.DoNotCrashWhenFocusingSelectWithoutViewSnapshot
+
+        * UIProcess/ios/WKContentViewInteraction.mm:
+        (createFallbackTargetedPreview):
+
 2022-01-14  Dean Jackson  <dino@apple.com>
 
         REGRESSION:  ARKit example loads a page full of random symbols instead of a 3D model
diff --git a/Source/WebKit/UIProcess/ios/WKContentViewInteraction.mm b/Source/WebKit/UIProcess/ios/WKContentViewInteraction.mm
index 798755e..e7211bd 100644
--- a/Source/WebKit/UIProcess/ios/WKContentViewInteraction.mm
+++ b/Source/WebKit/UIProcess/ios/WKContentViewInteraction.mm
@@ -8646,19 +8646,21 @@
     if (backgroundColor)
         [parameters setBackgroundColor:backgroundColor];
 
-    UIView *snapshotView = [rootView resizableSnapshotViewFromRect:frameInRootViewCoordinates afterScreenUpdates:NO withCapInsets:UIEdgeInsetsZero];
+    RetainPtr snapshotView = [rootView resizableSnapshotViewFromRect:frameInRootViewCoordinates afterScreenUpdates:NO withCapInsets:UIEdgeInsetsZero];
+    if (!snapshotView)
+        snapshotView = adoptNS([UIView new]);
 
     CGRect frameInContainerViewCoordinates = [rootView convertRect:frameInRootViewCoordinates toView:containerView];
 
     if (CGRectIsEmpty(frameInContainerViewCoordinates))
         return nil;
 
-    snapshotView.frame = frameInContainerViewCoordinates;
+    [snapshotView setFrame:frameInContainerViewCoordinates];
 
     CGPoint centerInContainerViewCoordinates = CGPointMake(CGRectGetMidX(frameInContainerViewCoordinates), CGRectGetMidY(frameInContainerViewCoordinates));
     auto target = adoptNS([[UIPreviewTarget alloc] initWithContainer:containerView center:centerInContainerViewCoordinates]);
 
-    return adoptNS([[UITargetedPreview alloc] initWithView:snapshotView parameters:parameters.get() target:target.get()]);
+    return adoptNS([[UITargetedPreview alloc] initWithView:snapshotView.get() parameters:parameters.get() target:target.get()]);
 }
 
 - (UITargetedPreview *)_createTargetedContextMenuHintPreviewForFocusedElement
diff --git a/Tools/ChangeLog b/Tools/ChangeLog
index d61c34e..68ddf13 100644
--- a/Tools/ChangeLog
+++ b/Tools/ChangeLog
@@ -1,3 +1,18 @@
+2022-01-14  Wenson Hsieh  <wenson_hsieh@apple.com>
+
+        [iOS] Occasional crash under -[UITargetedPreview initWithView:parameters:target:] when focusing form controls
+        https://bugs.webkit.org/show_bug.cgi?id=235248
+        rdar://79220540
+
+        Reviewed by Tim Horton and Aditya Keerthi.
+
+        Add an API test that exercises the crash by forcing `-resizableSnapshotViewFromRect:afterScreenUpdates:withCapInsets:`
+        to return nil via swizzling.
+
+        * TestWebKitAPI/Tests/ios/KeyboardInputTestsIOS.mm:
+        (TestWebKitAPI::nilResizableSnapshotViewFromRect):
+        (TestWebKitAPI::TEST):
+
 2022-01-07  Jonathan Bedard  <jbedard@apple.com>
 
         [webkitbugspy] Support radar as issue tracker type
diff --git a/Tools/TestWebKitAPI/Tests/ios/KeyboardInputTestsIOS.mm b/Tools/TestWebKitAPI/Tests/ios/KeyboardInputTestsIOS.mm
index 1f5366d..84854dd 100644
--- a/Tools/TestWebKitAPI/Tests/ios/KeyboardInputTestsIOS.mm
+++ b/Tools/TestWebKitAPI/Tests/ios/KeyboardInputTestsIOS.mm
@@ -826,6 +826,27 @@
     EXPECT_EQ(contentView.undoManager, undoManager);
 }
 
+static UIView * nilResizableSnapshotViewFromRect(id, SEL, CGRect, BOOL, UIEdgeInsets)
+{
+    return nil;
+}
+
+TEST(KeyboardInputTests, DoNotCrashWhenFocusingSelectWithoutViewSnapshot)
+{
+    auto webView = adoptNS([[TestWKWebView alloc] initWithFrame:CGRectMake(0, 0, 320, 500)]);
+    auto delegate = adoptNS([TestInputDelegate new]);
+    [webView _setInputDelegate:delegate.get()];
+    [delegate setFocusStartsInputSessionPolicyHandler:[](WKWebView *, id <_WKFocusedElementInfo>) {
+        return _WKFocusStartsInputSessionPolicyAllow;
+    }];
+
+    [webView synchronouslyLoadHTMLString:@"<select id='select'><option>foo</option><option>bar</option></select>"];
+
+    InstanceMethodSwizzler swizzler { UIView.class, @selector(resizableSnapshotViewFromRect:afterScreenUpdates:withCapInsets:), reinterpret_cast<IMP>(nilResizableSnapshotViewFromRect) };
+    [webView stringByEvaluatingJavaScript:@"select.focus()"];
+    [webView waitForNextPresentationUpdate];
+}
+
 } // namespace TestWebKitAPI
 
 #endif // PLATFORM(IOS_FAMILY)