Make !ENABLE(DATA_DETECTION) iOS build actually succeed
[WebKit-https.git] / Source / WebKit / UIProcess / ios / WKContentViewInteraction.mm
index 8331043..c176a05 100644 (file)
 #import "TextInputSPI.h"
 #import "UIKitSPI.h"
 #import "WKActionSheetAssistant.h"
+#import "WKDatePickerViewController.h"
+#import "WKError.h"
+#import "WKFocusedFormControlViewController.h"
 #import "WKFormInputControl.h"
 #import "WKFormSelectControl.h"
 #import "WKImagePreviewViewController.h"
 #import "WKInspectorNodeSearchGestureRecognizer.h"
 #import "WKNSURLExtras.h"
+#import "WKNumberPadViewController.h"
 #import "WKPreviewActionItemIdentifiers.h"
 #import "WKPreviewActionItemInternal.h"
 #import "WKPreviewElementInfoInternal.h"
+#import "WKSelectMenuViewController.h"
+#import "WKTextInputViewController.h"
+#import "WKTimePickerViewController.h"
 #import "WKUIDelegatePrivate.h"
 #import "WKWebViewConfiguration.h"
 #import "WKWebViewConfigurationPrivate.h"
 #import "WKWebViewInternal.h"
 #import "WKWebViewPrivate.h"
+#import "WeakObjCPtr.h"
 #import "WebEvent.h"
 #import "WebIOSEventFactory.h"
 #import "WebPageMessages.h"
 #import <CoreText/CTFontDescriptor.h>
 #import <MobileCoreServices/UTCoreTypes.h>
 #import <WebCore/Color.h>
-#import <WebCore/CoreGraphicsSPI.h>
-#import <WebCore/DataDetectorsCoreSPI.h>
-#import <WebCore/DataDetectorsUISPI.h>
+#import <WebCore/DataDetection.h>
 #import <WebCore/FloatQuad.h>
+#import <WebCore/LocalizedStrings.h>
 #import <WebCore/NotImplemented.h>
 #import <WebCore/Pasteboard.h>
 #import <WebCore/Path.h>
 #import <WebCore/PathUtilities.h>
+#import <WebCore/PromisedBlobInfo.h>
 #import <WebCore/RuntimeApplicationChecks.h>
 #import <WebCore/Scrollbar.h>
 #import <WebCore/TextIndicator.h>
-#import <WebCore/TextStream.h>
 #import <WebCore/VisibleSelection.h>
 #import <WebCore/WebCoreNSURLExtras.h>
 #import <WebCore/WebEvent.h>
 #import <WebKit/WebSelectionRect.h> // FIXME: WK2 should not include WebKit headers!
+#import <pal/spi/cg/CoreGraphicsSPI.h>
+#import <pal/spi/cocoa/DataDetectorsCoreSPI.h>
+#import <pal/spi/ios/DataDetectorsUISPI.h>
 #import <wtf/Optional.h>
 #import <wtf/RetainPtr.h>
 #import <wtf/SetForScope.h>
 #import <wtf/SoftLinking.h>
+#import <wtf/text/TextStream.h>
 
 #if ENABLE(DRAG_SUPPORT)
 #import <WebCore/DragData.h>
 
 @end
 
+#if ENABLE(EXTRA_ZOOM_MODE)
+
+@interface WKContentView (ExtraZoomMode) <WKTextFormControlViewControllerDelegate, WKFocusedFormControlViewControllerDelegate, WKSelectMenuViewControllerDelegate>
+@end
+
+#endif
+
 using namespace WebCore;
 using namespace WebKit;
 
@@ -166,7 +184,7 @@ inline bool operator!=(const WKSelectionDrawingInfo& a, const WKSelectionDrawing
     return !(a == b);
 }
 
-static WebCore::TextStream& operator<<(WebCore::TextStream& stream, WKSelectionDrawingInfo::SelectionType type)
+static TextStream& operator<<(TextStream& stream, WKSelectionDrawingInfo::SelectionType type)
 {
     switch (type) {
     case WKSelectionDrawingInfo::SelectionType::None: stream << "none"; break;
@@ -177,7 +195,7 @@ static WebCore::TextStream& operator<<(WebCore::TextStream& stream, WKSelectionD
     return stream;
 }
 
-WebCore::TextStream& operator<<(WebCore::TextStream& stream, const WKSelectionDrawingInfo& info)
+TextStream& operator<<(TextStream& stream, const WKSelectionDrawingInfo& info)
 {
     TextStream::GroupScope group(stream);
     stream.dumpProperty("type", info.type);
@@ -251,41 +269,36 @@ const CGFloat minimumTapHighlightRadius = 2.0;
 @end
 
 @protocol UISelectionInteractionAssistant;
-#if HAVE(LINK_PREVIEW)
-@interface UIPreviewItemController (StagingToRemove)
-@property (strong, nonatomic, readonly) UIGestureRecognizer *presentationSecondaryGestureRecognizer;
-@end
-#endif
 
 @interface WKFocusedElementInfo : NSObject <_WKFocusedElementInfo>
-- (instancetype)initWithAssistedNodeInformation:(const AssistedNodeInformation&)information isUserInitiated:(BOOL)isUserInitiated;
+- (instancetype)initWithAssistedNodeInformation:(const AssistedNodeInformation&)information isUserInitiated:(BOOL)isUserInitiated userObject:(NSObject <NSSecureCoding> *)userObject;
 @end
 
 @interface WKFormInputSession : NSObject <_WKFormInputSession>
 
-- (instancetype)initWithContentView:(WKContentView *)view focusedElementInfo:(WKFocusedElementInfo *)elementInfo userObject:(NSObject <NSSecureCoding> *)userObject;
+- (instancetype)initWithContentView:(WKContentView *)view focusedElementInfo:(WKFocusedElementInfo *)elementInfo requiresStrongPasswordAssistance:(BOOL)requiresStrongPasswordAssistance;
 - (void)invalidate;
 
 @end
 
 @implementation WKFormInputSession {
     WKContentView *_contentView;
-    RetainPtr<NSObject <NSSecureCoding>> _userObject;
     RetainPtr<WKFocusedElementInfo> _focusedElementInfo;
     RetainPtr<UIView> _customInputView;
     RetainPtr<NSArray<UITextSuggestion *>> _suggestions;
     BOOL _accessoryViewShouldNotShow;
     BOOL _forceSecureTextEntry;
+    BOOL _requiresStrongPasswordAssistance;
 }
 
-- (instancetype)initWithContentView:(WKContentView *)view focusedElementInfo:(WKFocusedElementInfo *)elementInfo userObject:(NSObject <NSSecureCoding> *)userObject
+- (instancetype)initWithContentView:(WKContentView *)view focusedElementInfo:(WKFocusedElementInfo *)elementInfo requiresStrongPasswordAssistance:(BOOL)requiresStrongPasswordAssistance
 {
     if (!(self = [super init]))
         return nil;
 
     _contentView = view;
     _focusedElementInfo = elementInfo;
-    _userObject = userObject;
+    _requiresStrongPasswordAssistance = requiresStrongPasswordAssistance;
 
     return self;
 }
@@ -297,7 +310,7 @@ const CGFloat minimumTapHighlightRadius = 2.0;
 
 - (NSObject <NSSecureCoding> *)userObject
 {
-    return _userObject.get();
+    return [_focusedElementInfo userObject];
 }
 
 - (BOOL)isValid
@@ -316,7 +329,7 @@ const CGFloat minimumTapHighlightRadius = 2.0;
         [[_contentView formAccessoryView] showAutoFillButtonWithTitle:title];
     else
         [[_contentView formAccessoryView] hideAutoFillButton];
-    if (UICurrentUserInterfaceIdiomIsPad())
+    if (currentUserInterfaceIdiomIsPad())
         [_contentView reloadInputViews];
 }
 
@@ -374,6 +387,11 @@ const CGFloat minimumTapHighlightRadius = 2.0;
     [suggestionDelegate setSuggestions:suggestions];
 }
 
+- (BOOL)requiresStrongPasswordAssistance
+{
+    return _requiresStrongPasswordAssistance;
+}
+
 - (void)invalidate
 {
     id <UITextInputSuggestionDelegate> suggestionDelegate = (id <UITextInputSuggestionDelegate>)_contentView.inputDelegate;
@@ -387,9 +405,10 @@ const CGFloat minimumTapHighlightRadius = 2.0;
     WKInputType _type;
     RetainPtr<NSString> _value;
     BOOL _isUserInitiated;
+    RetainPtr<NSObject <NSSecureCoding>> _userObject;
 }
 
-- (instancetype)initWithAssistedNodeInformation:(const AssistedNodeInformation&)information isUserInitiated:(BOOL)isUserInitiated
+- (instancetype)initWithAssistedNodeInformation:(const AssistedNodeInformation&)information isUserInitiated:(BOOL)isUserInitiated userObject:(NSObject <NSSecureCoding> *)userObject
 {
     if (!(self = [super init]))
         return nil;
@@ -452,6 +471,7 @@ const CGFloat minimumTapHighlightRadius = 2.0;
     }
     _value = information.value;
     _isUserInitiated = isUserInitiated;
+    _userObject = userObject;
     return self;
 }
 
@@ -469,8 +489,64 @@ const CGFloat minimumTapHighlightRadius = 2.0;
 {
     return _isUserInitiated;
 }
+
+- (NSObject <NSSecureCoding> *)userObject
+{
+    return _userObject.get();
+}
+@end
+
+#if ENABLE(DRAG_SUPPORT)
+
+@interface WKDragSessionContext : NSObject
+- (void)addTemporaryDirectory:(NSString *)temporaryDirectory;
+- (void)cleanUpTemporaryDirectories;
+@end
+
+@implementation WKDragSessionContext {
+    RetainPtr<NSMutableArray> _temporaryDirectories;
+}
+
+- (void)addTemporaryDirectory:(NSString *)temporaryDirectory
+{
+    if (!_temporaryDirectories)
+        _temporaryDirectories = adoptNS([NSMutableArray new]);
+    [_temporaryDirectories addObject:temporaryDirectory];
+}
+
+- (void)cleanUpTemporaryDirectories
+{
+    for (NSString *directory in _temporaryDirectories.get()) {
+        NSError *error = nil;
+        [[NSFileManager defaultManager] removeItemAtPath:directory error:&error];
+        RELEASE_LOG(DragAndDrop, "Removed temporary download directory: %@ with error: %@", directory, error);
+    }
+    _temporaryDirectories = nil;
+}
+
 @end
 
+static WKDragSessionContext *existingLocalDragSessionContext(id <UIDragSession> session)
+{
+    return [session.localContext isKindOfClass:[WKDragSessionContext class]] ? (WKDragSessionContext *)session.localContext : nil;
+}
+
+static WKDragSessionContext *ensureLocalDragSessionContext(id <UIDragSession> session)
+{
+    if (WKDragSessionContext *existingContext = existingLocalDragSessionContext(session))
+        return existingContext;
+
+    if (session.localContext) {
+        RELEASE_LOG(DragAndDrop, "Overriding existing local context: %@ on session: %@", session.localContext, session);
+        ASSERT_NOT_REACHED();
+    }
+
+    session.localContext = [[[WKDragSessionContext alloc] init] autorelease];
+    return (WKDragSessionContext *)session.localContext;
+}
+
+#endif // ENABLE(DRAG_SUPPORT)
+
 @interface WKContentView (WKInteractionPrivate)
 - (void)accessibilitySpeakSelectionSetContent:(NSString *)string;
 - (NSArray *)webSelectionRectsForSelectionRects:(const Vector<WebCore::SelectionRect>&)selectionRects;
@@ -571,6 +647,7 @@ const CGFloat minimumTapHighlightRadius = 2.0;
     _isDoubleTapPending = NO;
     _showDebugTapHighlightsForFastClicking = [[NSUserDefaults standardUserDefaults] boolForKey:@"WebKitShowFastClickDebugTapHighlights"];
     _needsDeferredEndScrollingSelectionUpdate = NO;
+    _isChangingFocus = NO;
 }
 
 - (void)cleanupInteraction
@@ -590,6 +667,8 @@ const CGFloat minimumTapHighlightRadius = 2.0;
     [_highlightView removeFromSuperview];
     _outstandingPositionInformationRequest = std::nullopt;
 
+    _focusRequiresStrongPasswordAssistance = NO;
+
     if (_interactionViewsContainerView) {
         [self.layer removeObserver:self forKeyPath:@"transform"];
         [_interactionViewsContainerView removeFromSuperview];
@@ -625,6 +704,7 @@ const CGFloat minimumTapHighlightRadius = 2.0;
     _layerTreeTransactionIdAtLastTouchStart = 0;
 
 #if ENABLE(DATA_INTERACTION)
+    [existingLocalDragSessionContext(_dragDropInteractionState.dragSession()) cleanUpTemporaryDirectories];
     [self teardownDataInteractionDelegates];
 #endif
 
@@ -646,6 +726,7 @@ const CGFloat minimumTapHighlightRadius = 2.0;
     }
     
     _inputViewUpdateDeferrer = nullptr;
+    _assistedNodeInformation = { };
 }
 
 - (void)_removeDefaultGestureRecognizers
@@ -690,6 +771,24 @@ const CGFloat minimumTapHighlightRadius = 2.0;
     return _page->unobscuredContentRect();
 }
 
+
+#pragma mark - UITextAutoscrolling
+- (void)startAutoscroll:(CGPoint)pointInDocument
+{
+    _page->startAutoscrollAtPosition(pointInDocument);
+}
+
+- (void)cancelAutoscroll
+{
+    _page->cancelAutoscroll();
+}
+
+- (void)scrollSelectionToVisible:(BOOL)animated
+{
+    // Used to scroll selection on keyboard up; we already scroll to visible.
+}
+
+
 - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
 {
     ASSERT([keyPath isEqualToString:@"transform"]);
@@ -829,7 +928,6 @@ const CGFloat minimumTapHighlightRadius = 2.0;
     // and do nothing if the return value is NO.
 
     _resigningFirstResponder = YES;
-
     if (!_webView->_activeFocusedStateRetainCount) {
         // We need to complete the editing operation before we blur the element.
         [_inputPeripheral endEditing];
@@ -1109,12 +1207,12 @@ static NSValue *nsSizeForTapHighlightBorderRadius(WebCore::IntSize borderRadius,
     case InputType::None:
         return NO;
     case InputType::Select:
-        return !UICurrentUserInterfaceIdiomIsPad();
+        return !currentUserInterfaceIdiomIsPad();
     case InputType::Date:
     case InputType::Month:
     case InputType::DateTimeLocal:
     case InputType::Time:
-        return !UICurrentUserInterfaceIdiomIsPad();
+        return !currentUserInterfaceIdiomIsPad();
     default:
         return !_assistedNodeInformation.isReadOnly;
     }
@@ -1131,13 +1229,13 @@ static NSValue *nsSizeForTapHighlightBorderRadius(WebCore::IntSize borderRadius,
     // In case user scaling is force enabled, do not use that scaling when zooming in with an input field.
     // Zooming above the page's default scale factor should only happen when the user performs it.
     [self _zoomToFocusRect:_assistedNodeInformation.elementRect
-             selectionRect:_didAccessoryTabInitiateFocus ? IntRect() : _assistedNodeInformation.selectionRect
-               insideFixed:_assistedNodeInformation.insideFixedPosition
-                  fontSize:_assistedNodeInformation.nodeFontSize
-              minimumScale:_assistedNodeInformation.minimumScaleFactor
-              maximumScale:_assistedNodeInformation.maximumScaleFactorIgnoringAlwaysScalable
-              allowScaling:(_assistedNodeInformation.allowsUserScalingIgnoringAlwaysScalable && !UICurrentUserInterfaceIdiomIsPad())
-               forceScroll:[self requiresAccessoryView]];
+        selectionRect:_didAccessoryTabInitiateFocus ? IntRect() : _assistedNodeInformation.selectionRect
+        insideFixed:_assistedNodeInformation.insideFixedPosition
+        fontSize:_assistedNodeInformation.nodeFontSize
+        minimumScale:_assistedNodeInformation.minimumScaleFactor
+        maximumScale:_assistedNodeInformation.maximumScaleFactorIgnoringAlwaysScalable
+        allowScaling:_assistedNodeInformation.allowsUserScalingIgnoringAlwaysScalable && [[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPhone
+        forceScroll:[self requiresAccessoryView]];
 
     _didAccessoryTabInitiateFocus = NO;
     [self _ensureFormAccessoryView];
@@ -1249,6 +1347,9 @@ static inline bool isSamePair(UIGestureRecognizer *a, UIGestureRecognizer *b, UI
 
 - (SEL)_actionForLongPressFromPositionInformation:(const InteractionInformationAtPosition&)positionInformation
 {
+    if (!_webView.configuration._longPressActionsEnabled)
+        return nil;
+
     if (!positionInformation.touchCalloutEnabled)
         return nil;
 
@@ -1257,8 +1358,7 @@ static inline bool isSamePair(UIGestureRecognizer *a, UIGestureRecognizer *b, UI
 
     if (positionInformation.isLink) {
 #if ENABLE(DATA_DETECTION)
-        NSURL *targetURL = [NSURL URLWithString:positionInformation.url];
-        if ([[getDDDetectionControllerClass() tapAndHoldSchemes] containsObject:targetURL.scheme.lowercaseString])
+        if (DataDetection::canBePresentedByDataDetectors(positionInformation.url))
             return @selector(_showDataDetectorsSheet);
 #endif
         return @selector(_showLinkSheet);
@@ -1476,6 +1576,9 @@ static inline bool isSamePair(UIGestureRecognizer *a, UIGestureRecognizer *b, UI
 
 - (BOOL)hasSelectablePositionAtPoint:(CGPoint)point
 {
+    if (!_webView.configuration._textInteractionGesturesEnabled)
+        return NO;
+
     if (_inspectorNodeSearchEnabled)
         return NO;
 
@@ -1496,20 +1599,20 @@ static inline bool isSamePair(UIGestureRecognizer *a, UIGestureRecognizer *b, UI
 
 - (BOOL)pointIsNearMarkedText:(CGPoint)point
 {
+    if (!_webView.configuration._textInteractionGesturesEnabled)
+        return NO;
+
     InteractionInformationRequest request(roundedIntPoint(point));
     if (![self ensurePositionInformationIsUpToDate:request])
         return NO;
     return _positionInformation.isNearMarkedText;
 }
 
-- (BOOL)pointIsInAssistedNode:(CGPoint)point
-{
-    // This method is still implemented for backwards compatibility with older UIKit versions.
-    return [self textInteractionGesture:UIWKGestureLoupe shouldBeginAtPoint:point];
-}
-
 - (BOOL)textInteractionGesture:(UIWKGestureType)gesture shouldBeginAtPoint:(CGPoint)point
 {
+    if (!_webView.configuration._textInteractionGesturesEnabled)
+        return NO;
+
     InteractionInformationRequest request(roundedIntPoint(point));
     if (![self ensurePositionInformationIsUpToDate:request])
         return NO;
@@ -1854,7 +1957,7 @@ static void cancelPotentialTapIfNecessary(WKContentView* contentView)
     case InputType::Month:
     case InputType::Week:
     case InputType::Time:
-        return !UICurrentUserInterfaceIdiomIsPad();
+        return !currentUserInterfaceIdiomIsPad();
     }
 }
 
@@ -1914,15 +2017,22 @@ FOR_EACH_WKCONTENTVIEW_ACTION(FORWARD_ACTION_TO_WKWEBVIEW)
             return;
         if (!selectedText)
             return;
-        
-        CGRect presentationRect = view->_page->editorState().selectionIsRange ? view->_page->editorState().postLayoutData().selectionRects[0].rect() : view->_page->editorState().postLayoutData().caretRectAtStart;
+
+        auto& editorState = view->_page->editorState();
+        auto& postLayoutData = editorState.postLayoutData();
+        CGRect presentationRect;
+        if (editorState.selectionIsRange && !postLayoutData.selectionRects.isEmpty())
+            presentationRect = postLayoutData.selectionRects[0].rect();
+        else
+            presentationRect = postLayoutData.caretRectAtStart;
         
         String selectionContext = textBefore + selectedText + textAfter;
-        if (view->_textSelectionAssistant) {
-            [view->_textSelectionAssistant lookup:selectionContext withRange:NSMakeRange(textBefore.length(), selectedText.length()) fromRect:presentationRect];
-        } else {
-            [view->_webSelectionAssistant lookup:selectionContext withRange:NSMakeRange(textBefore.length(), selectedText.length()) fromRect:presentationRect];
-        }
+        NSRange selectedRangeInContext = NSMakeRange(textBefore.length(), selectedText.length());
+
+        if (auto textSelectionAssistant = view->_textSelectionAssistant)
+            [textSelectionAssistant lookup:selectionContext withRange:selectedRangeInContext fromRect:presentationRect];
+        else
+            [view->_webSelectionAssistant lookup:selectionContext withRange:selectedRangeInContext fromRect:presentationRect];
     });
 }
 
@@ -2029,6 +2139,14 @@ FOR_EACH_WKCONTENTVIEW_ACTION(FORWARD_ACTION_TO_WKWEBVIEW)
 
 - (UIColor *)insertionPointColor
 {
+    if (!_webView.configuration._textInteractionGesturesEnabled)
+        return [UIColor clearColor];
+
+    if (!_page->editorState().isMissingPostLayoutData) {
+        WebCore::Color caretColor = _page->editorState().postLayoutData().caretColor;
+        if (caretColor.isValid())
+            return [UIColor colorWithCGColor:cachedCGColor(caretColor)];
+    }
     return [UIColor insertionPointColor];
 }
 
@@ -2164,6 +2282,16 @@ FOR_EACH_WKCONTENTVIEW_ACTION(FORWARD_ACTION_TO_WKWEBVIEW)
     return [super canPerformAction:action withSender:sender];
 }
 
+- (id)targetForAction:(SEL)action withSender:(id)sender
+{
+    return [_webView targetForAction:action withSender:sender];
+}
+
+- (id)targetForActionForWebView:(SEL)action withSender:(id)sender
+{
+    return [super targetForAction:action withSender:sender];
+}
+
 - (void)_resetShowingTextStyle:(NSNotification *)notification
 {
     _showingTextStyleOptions = NO;
@@ -2279,8 +2407,16 @@ FOR_EACH_WKCONTENTVIEW_ACTION(FORWARD_ACTION_TO_WKWEBVIEW)
 
 - (void)_accessibilityRetrieveRectsAtSelectionOffset:(NSInteger)offset withText:(NSString *)text
 {
+    [self _accessibilityRetrieveRectsAtSelectionOffset:offset withText:text completionHandler:nil];
+}
+
+- (void)_accessibilityRetrieveRectsAtSelectionOffset:(NSInteger)offset withText:(NSString *)text completionHandler:(void (^)(const Vector<SelectionRect>& rects))completionHandler
+{
     RetainPtr<WKContentView> view = self;
-    _page->requestRectsAtSelectionOffsetWithText(offset, text, [view, offset](const Vector<WebCore::SelectionRect>& selectionRects, CallbackBase::Error error) {
+    _page->requestRectsAtSelectionOffsetWithText(offset, text, [view, offset, capturedCompletionHandler = makeBlockPtr(completionHandler)](const Vector<SelectionRect>& selectionRects, CallbackBase::Error error) {
+        if (capturedCompletionHandler)
+            capturedCompletionHandler(selectionRects);
+
         if (error != WebKit::CallbackBase::Error::None)
             return;
         if ([view respondsToSelector:@selector(_accessibilityDidGetSelectionRects:withGranularity:atOffset:)])
@@ -2288,6 +2424,16 @@ FOR_EACH_WKCONTENTVIEW_ACTION(FORWARD_ACTION_TO_WKWEBVIEW)
     });
 }
 
+- (void)_accessibilityStoreSelection
+{
+    _page->storeSelectionForAccessibility(true);
+}
+
+- (void)_accessibilityClearSelection
+{
+    _page->storeSelectionForAccessibility(false);
+}
+
 // UIWKInteractionViewProtocol
 
 static inline GestureType toGestureType(UIWKGestureType gestureType)
@@ -2443,14 +2589,13 @@ static inline UIWKSelectionFlags toUIWKSelectionFlags(SelectionFlags flags)
     NSInteger uiFlags = UIWKNone;
     if (flags & WordIsNearTap)
         uiFlags |= UIWKWordIsNearTap;
-    if (flags & IsBlockSelection)
-        uiFlags |= UIWKIsBlockSelection;
     if (flags & PhraseBoundaryChanged)
         uiFlags |= UIWKPhraseBoundaryChanged;
 
     return static_cast<UIWKSelectionFlags>(uiFlags);
 }
 
+#if __IPHONE_OS_VERSION_MAX_ALLOWED < 120000
 static inline SelectionHandlePosition toSelectionHandlePosition(UIWKHandlePosition position)
 {
     switch (position) {
@@ -2464,6 +2609,7 @@ static inline SelectionHandlePosition toSelectionHandlePosition(UIWKHandlePositi
         return SelectionHandlePosition::Left;
     }
 }
+#endif
 
 static inline WebCore::TextGranularity toWKTextGranularity(UITextGranularity granularity)
 {
@@ -2520,15 +2666,17 @@ static void selectionChangedWithTouch(WKContentView *view, const WebCore::IntPoi
     if ([view webSelectionAssistant])
         [(UIWKSelectionAssistant *)[view webSelectionAssistant] selectionChangedWithTouchAt:(CGPoint)point withSelectionTouch:toUIWKSelectionTouch((SelectionTouch)touch) withFlags:static_cast<UIWKSelectionFlags>(flags)];
     else
-        [(UIWKTextInteractionAssistant *)[view interactionAssistant] selectionChangedWithTouchAt:(CGPoint)point withSelectionTouch:toUIWKSelectionTouch((SelectionTouch)touch)];
+        [(UIWKTextInteractionAssistant *)[view interactionAssistant] selectionChangedWithTouchAt:(CGPoint)point withSelectionTouch:toUIWKSelectionTouch((SelectionTouch)touch) withFlags:static_cast<UIWKSelectionFlags>(flags)];
 }
 
+#if __IPHONE_OS_VERSION_MAX_ALLOWED < 120000
 - (void)_didUpdateBlockSelectionWithTouch:(SelectionTouch)touch withFlags:(SelectionFlags)flags growThreshold:(CGFloat)growThreshold shrinkThreshold:(CGFloat)shrinkThreshold
 {
     [_webSelectionAssistant blockSelectionChangedWithTouch:toUIWKSelectionTouch(touch) withFlags:toUIWKSelectionFlags(flags) growThreshold:growThreshold shrinkThreshold:shrinkThreshold];
     if (touch != SelectionTouch::Started && touch != SelectionTouch::Moved)
         _usingGestureForSelection = NO;
 }
+#endif
 
 - (BOOL)_isInteractingWithAssistedNode
 {
@@ -2537,19 +2685,31 @@ static void selectionChangedWithTouch(WKContentView *view, const WebCore::IntPoi
 
 - (void)changeSelectionWithGestureAt:(CGPoint)point withGesture:(UIWKGestureType)gestureType withState:(UIGestureRecognizerState)state
 {
+    [self changeSelectionWithGestureAt:point withGesture:gestureType withState:state withFlags:UIWKNone];
+}
+
+- (void)changeSelectionWithGestureAt:(CGPoint)point withGesture:(UIWKGestureType)gestureType withState:(UIGestureRecognizerState)state withFlags:(UIWKSelectionFlags)flags
+{
     _usingGestureForSelection = YES;
-    _page->selectWithGesture(WebCore::IntPoint(point), CharacterGranularity, static_cast<uint32_t>(toGestureType(gestureType)), static_cast<uint32_t>(toGestureRecognizerState(state)), [self _isInteractingWithAssistedNode], [self, state](const WebCore::IntPoint& point, uint32_t gestureType, uint32_t gestureState, uint32_t flags, WebKit::CallbackBase::Error error) {
-        selectionChangedWithGesture(self, point, gestureType, gestureState, flags, error);
+    _page->selectWithGesture(WebCore::IntPoint(point), CharacterGranularity, static_cast<uint32_t>(toGestureType(gestureType)), static_cast<uint32_t>(toGestureRecognizerState(state)), [self _isInteractingWithAssistedNode], [self, state, flags](const WebCore::IntPoint& point, uint32_t gestureType, uint32_t gestureState, uint32_t innerFlags, WebKit::CallbackBase::Error error) {
+        selectionChangedWithGesture(self, point, gestureType, gestureState, flags | innerFlags, error);
         if (state == UIGestureRecognizerStateEnded || state == UIGestureRecognizerStateCancelled)
             _usingGestureForSelection = NO;
     });
 }
 
+#if __IPHONE_OS_VERSION_MAX_ALLOWED < 120000
 - (void)changeSelectionWithTouchAt:(CGPoint)point withSelectionTouch:(UIWKSelectionTouch)touch baseIsStart:(BOOL)baseIsStart
 {
+    [self changeSelectionWithTouchAt:point withSelectionTouch:touch baseIsStart:baseIsStart withFlags:UIWKNone];
+}
+#endif
+
+- (void)changeSelectionWithTouchAt:(CGPoint)point withSelectionTouch:(UIWKSelectionTouch)touch baseIsStart:(BOOL)baseIsStart withFlags:(UIWKSelectionFlags)flags
+{
     _usingGestureForSelection = YES;
-    _page->updateSelectionWithTouches(WebCore::IntPoint(point), static_cast<uint32_t>(toSelectionTouch(touch)), baseIsStart, [self, touch](const WebCore::IntPoint& point, uint32_t touch, uint32_t flags, WebKit::CallbackBase::Error error) {
-        selectionChangedWithTouch(self, point, touch, flags, error);
+    _page->updateSelectionWithTouches(WebCore::IntPoint(point), static_cast<uint32_t>(toSelectionTouch(touch)), baseIsStart, [self, flags](const WebCore::IntPoint& point, uint32_t touch, uint32_t innerFlags, WebKit::CallbackBase::Error error) {
+        selectionChangedWithTouch(self, point, touch, flags | innerFlags, error);
         if (touch != UIWKSelectionTouchStarted && touch != UIWKSelectionTouchMoved)
             _usingGestureForSelection = NO;
     });
@@ -2558,18 +2718,25 @@ static void selectionChangedWithTouch(WKContentView *view, const WebCore::IntPoi
 - (void)changeSelectionWithTouchesFrom:(CGPoint)from to:(CGPoint)to withGesture:(UIWKGestureType)gestureType withState:(UIGestureRecognizerState)gestureState
 {
     _usingGestureForSelection = YES;
-    _page->selectWithTwoTouches(WebCore::IntPoint(from), WebCore::IntPoint(to), static_cast<uint32_t>(toGestureType(gestureType)), static_cast<uint32_t>(toGestureRecognizerState(gestureState)), [self, gestureState](const WebCore::IntPoint& point, uint32_t gestureType, uint32_t gestureState, uint32_t flags, WebKit::CallbackBase::Error error) {
+    _page->selectWithTwoTouches(WebCore::IntPoint(from), WebCore::IntPoint(to), static_cast<uint32_t>(toGestureType(gestureType)), static_cast<uint32_t>(toGestureRecognizerState(gestureState)), [self](const WebCore::IntPoint& point, uint32_t gestureType, uint32_t gestureState, uint32_t flags, WebKit::CallbackBase::Error error) {
         selectionChangedWithGesture(self, point, gestureType, gestureState, flags, error);
         if (gestureState == UIGestureRecognizerStateEnded || gestureState == UIGestureRecognizerStateCancelled)
             _usingGestureForSelection = NO;
     });
 }
 
+#if __IPHONE_OS_VERSION_MAX_ALLOWED < 120000
 - (void)changeBlockSelectionWithTouchAt:(CGPoint)point withSelectionTouch:(UIWKSelectionTouch)touch forHandle:(UIWKHandlePosition)handle
 {
+    // This was only readded to avoid a crash due to incompatibilities with certain version of UIKit.
+    // This should be removed ASAP.
+    // The selection will be properly updated with the next update, selection still functions
+    // without this function doing anything.
+    
     _usingGestureForSelection = YES;
     _page->updateBlockSelectionWithTouch(WebCore::IntPoint(point), static_cast<uint32_t>(toSelectionTouch(touch)), static_cast<uint32_t>(toSelectionHandlePosition(handle)));
 }
+#endif
 
 - (void)moveByOffset:(NSInteger)offset
 {
@@ -2916,7 +3083,7 @@ static void selectionChangedWithTouch(WKContentView *view, const WebCore::IntPoi
     [_formAccessoryView setNextEnabled:_assistedNodeInformation.hasNextNode];
     [_formAccessoryView setPreviousEnabled:_assistedNodeInformation.hasPreviousNode];
 
-    if (UICurrentUserInterfaceIdiomIsPad())
+    if (currentUserInterfaceIdiomIsPad())
         [_formAccessoryView setClearVisible:NO];
     else {
         switch (_assistedNodeInformation.elementType) {
@@ -2952,9 +3119,14 @@ static void selectionChangedWithTouch(WKContentView *view, const WebCore::IntPoi
 {
     [self.inputDelegate selectionDidChange:self];
 }
-    
+
 - (void)insertTextSuggestion:(UITextSuggestion *)textSuggestion
 {
+    // FIXME: Replace NSClassFromString with actual class as soon as UIKit submitted the new class into the iOS SDK.
+    if ([textSuggestion isKindOfClass:NSClassFromString(@"UITextAutofillSuggestion")]) {
+        _page->autofillLoginCredentials([(UITextAutofillSuggestion *)textSuggestion username], [(UITextAutofillSuggestion *)textSuggestion password]);
+        return;
+    }
     id <_WKInputDelegate> inputDelegate = [_webView _inputDelegate];
     if ([inputDelegate respondsToSelector:@selector(_webView:insertTextSuggestion:inInputSession:)])
         [inputDelegate _webView:_webView insertTextSuggestion:textSuggestion inInputSession:_formInputSession.get()];
@@ -2971,7 +3143,7 @@ static void selectionChangedWithTouch(WKContentView *view, const WebCore::IntPoi
 
 - (UITextRange *)selectedTextRange
 {
-    if (_page->editorState().selectionIsNone)
+    if (_page->editorState().selectionIsNone || _page->editorState().isMissingPostLayoutData)
         return nil;
     auto& postLayoutEditorStateData = _page->editorState().postLayoutData();
     FloatRect startRect = postLayoutEditorStateData.caretRectAtStart;
@@ -3151,7 +3323,8 @@ static void selectionChangedWithTouch(WKContentView *view, const WebCore::IntPoi
 
 - (BOOL)hasText
 {
-    return YES;
+    auto& editorState = _page->editorState();
+    return !editorState.isMissingPostLayoutData && editorState.postLayoutData().hasPlainText;
 }
 
 // end of UITextInput protocol implementation
@@ -3571,7 +3744,7 @@ static NSString *contentTypeFromFieldName(WebCore::AutofillFieldName fieldName)
 {
     [self beginSelectionChange];
     RetainPtr<WKContentView> view = self;
-    _page->executeEditCommand(commandName, [view](WebKit::CallbackBase::Error) {
+    _page->executeEditCommand(commandName, { }, [view](WebKit::CallbackBase::Error) {
         [view endSelectionChange];
     });
 }
@@ -3769,7 +3942,10 @@ static NSString *contentTypeFromFieldName(WebCore::AutofillFieldName fieldName)
 - (void)_startAssistingKeyboard
 {
     [self useSelectionAssistantWithGranularity:WKSelectionGranularityCharacter];
+
+#if !ENABLE(EXTRA_ZOOM_MODE)
     [self reloadInputViews];
+#endif
 }
 
 - (void)_stopAssistingKeyboard
@@ -3823,21 +3999,22 @@ static bool isAssistableInputType(InputType type)
     return false;
 }
 
-- (void)_startAssistingNode:(const AssistedNodeInformation&)information userIsInteracting:(BOOL)userIsInteracting blurPreviousNode:(BOOL)blurPreviousNode userObject:(NSObject <NSSecureCoding> *)userObject
+- (void)_startAssistingNode:(const AssistedNodeInformation&)information userIsInteracting:(BOOL)userIsInteracting blurPreviousNode:(BOOL)blurPreviousNode changingActivityState:(BOOL)changingActivityState userObject:(NSObject <NSSecureCoding> *)userObject
 {
+    SetForScope<BOOL> isChangingFocusForScope { _isChangingFocus, _assistedNodeInformation.elementType != InputType::None };
     _inputViewUpdateDeferrer = nullptr;
 
     id <_WKInputDelegate> inputDelegate = [_webView _inputDelegate];
-    RetainPtr<WKFocusedElementInfo> focusedElementInfo = adoptNS([[WKFocusedElementInfo alloc] initWithAssistedNodeInformation:information isUserInitiated:userIsInteracting]);
+    RetainPtr<WKFocusedElementInfo> focusedElementInfo = adoptNS([[WKFocusedElementInfo alloc] initWithAssistedNodeInformation:information isUserInitiated:userIsInteracting userObject:userObject]);
     BOOL shouldShowKeyboard;
 
     if ([inputDelegate respondsToSelector:@selector(_webView:focusShouldStartInputSession:)])
         shouldShowKeyboard = [inputDelegate _webView:_webView focusShouldStartInputSession:focusedElementInfo.get()];
     else {
         // The default behavior is to allow node assistance if the user is interacting or the keyboard is already active.
-        shouldShowKeyboard = userIsInteracting || _textSelectionAssistant;
+        shouldShowKeyboard = userIsInteracting || _textSelectionAssistant || changingActivityState;
 #if ENABLE(DATA_INTERACTION)
-        shouldShowKeyboard |= _dataInteractionState.isPerformingOperation;
+        shouldShowKeyboard |= _dragDropInteractionState.isPerformingDrop();
 #endif
     }
     if (!shouldShowKeyboard)
@@ -3854,14 +4031,25 @@ static bool isAssistableInputType(InputType type)
     if (_assistedNodeInformation.elementType == information.elementType && _assistedNodeInformation.elementRect == information.elementRect)
         return;
 
+    _focusRequiresStrongPasswordAssistance = NO;
+    if ([inputDelegate respondsToSelector:@selector(_webView:focusRequiresStrongPasswordAssistance:)])
+        _focusRequiresStrongPasswordAssistance = [inputDelegate _webView:_webView focusRequiresStrongPasswordAssistance:focusedElementInfo.get()];
+
     BOOL editableChanged = [self setIsEditable:YES];
     _assistedNodeInformation = information;
     _inputPeripheral = nil;
     _traits = nil;
+
     if (![self isFirstResponder])
         [self becomeFirstResponder];
 
+#if ENABLE(EXTRA_ZOOM_MODE)
+    [self presentFocusedFormControlViewController:NO];
+    if (!_isChangingFocus)
+        [self presentViewControllerForCurrentAssistedNode];
+#else
     [self reloadInputViews];
+#endif
     
     switch (information.elementType) {
     case InputType::Select:
@@ -3881,11 +4069,16 @@ static bool isAssistableInputType(InputType type)
     
     [self _displayFormNodeInputView];
 
+#if ENABLE(EXTRA_ZOOM_MODE)
+    if (_isChangingFocus)
+        [_focusedFormControlViewController reloadData:YES];
+#endif
+
     // _inputPeripheral has been initialized in inputView called by reloadInputViews.
     [_inputPeripheral beginEditing];
 
     if ([inputDelegate respondsToSelector:@selector(_webView:didStartInputSession:)]) {
-        _formInputSession = adoptNS([[WKFormInputSession alloc] initWithContentView:self focusedElementInfo:focusedElementInfo.get() userObject:userObject]);
+        _formInputSession = adoptNS([[WKFormInputSession alloc] initWithContentView:self focusedElementInfo:focusedElementInfo.get() requiresStrongPasswordAssistance:_focusRequiresStrongPasswordAssistance]);
         [inputDelegate _webView:_webView didStartInputSession:_formInputSession.get()];
     }
     
@@ -3901,6 +4094,7 @@ static bool isAssistableInputType(InputType type)
 
     _assistedNodeInformation.elementType = InputType::None;
     _inputPeripheral = nil;
+    _focusRequiresStrongPasswordAssistance = NO;
 
     [self _stopAssistingKeyboard];
     [_formAccessoryView hideAutoFillButton];
@@ -3909,6 +4103,12 @@ static bool isAssistableInputType(InputType type)
     // The name is misleading, but this actually clears the selection views and removes any selection.
     [_webSelectionAssistant resignedFirstResponder];
 
+#if ENABLE(EXTRA_ZOOM_MODE)
+    [self dismissAllInputViewControllers];
+    if (!_isChangingFocus)
+        [self dismissFocusedFormControlViewController:[_focusedFormControlViewController isVisible]];
+#endif
+
     // The custom fixed position rect behavior is affected by -isAssistingNode, so if that changes we need to recompute rects.
     if (editableChanged)
         [_webView _scheduleVisibleContentRectUpdate];
@@ -3916,152 +4116,555 @@ static bool isAssistableInputType(InputType type)
     [_webView didEndFormControlInteraction];
 }
 
-- (void)_selectionChanged
+- (void)updateCurrentAssistedNodeInformation:(Function<void(bool didUpdate)>&&)callback
 {
-    _selectionNeedsUpdate = YES;
-    // If we are changing the selection with a gesture there is no need
-    // to wait to paint the selection.
-    if (_usingGestureForSelection)
-        [self _updateChangedSelection];
-}
+    WeakObjCPtr<WKContentView> weakSelf { self };
+    auto identifierBeforeUpdate = _assistedNodeInformation.assistedNodeIdentifier;
+    _page->requestAssistedNodeInformation([callback = WTFMove(callback), identifierBeforeUpdate, weakSelf] (auto& info, auto error) {
+        if (!weakSelf || error != CallbackBase::Error::None || info.assistedNodeIdentifier != identifierBeforeUpdate) {
+            // If the assisted node may have changed in the meantime, don't overwrite assisted node information.
+            callback(false);
+            return;
+        }
 
-- (void)selectWordForReplacement
-{
-    _page->extendSelection(WordGranularity);
+        weakSelf.get()->_assistedNodeInformation = info;
+        callback(true);
+    });
 }
 
-- (void)_updateChangedSelection
+#if ENABLE(EXTRA_ZOOM_MODE)
+
+- (void)dismissAllInputViewControllers
 {
-    [self _updateChangedSelection:NO];
+    [self dismissTextInputViewController:YES];
+    [self dismissNumberPadViewController:YES];
+    [self dismissSelectMenuViewController:YES];
+    [self dismissTimePickerViewController:YES];
+    [self dismissDatePickerViewController:YES];
 }
 
-- (void)_updateChangedSelection:(BOOL)force
+- (void)presentDatePickerViewController:(BOOL)animated
 {
-    if (!_selectionNeedsUpdate)
+    if (_datePickerViewController)
         return;
 
-    WKSelectionDrawingInfo selectionDrawingInfo(_page->editorState());
-    if (force || selectionDrawingInfo != _lastSelectionDrawingInfo) {
-        LOG_WITH_STREAM(Selection, stream << "_updateChangedSelection " << selectionDrawingInfo);
-
-        _lastSelectionDrawingInfo = selectionDrawingInfo;
-
-        // FIXME: We need to figure out what to do if the selection is changed by Javascript.
-        if (_textSelectionAssistant) {
-            _markedText = (_page->editorState().hasComposition) ? _page->editorState().markedText : String();
-            if (!_showingTextStyleOptions)
-                [_textSelectionAssistant selectionChanged];
-        } else if (!_page->editorState().isContentEditable)
-            [_webSelectionAssistant selectionChanged];
-
-        _selectionNeedsUpdate = NO;
-        if (_shouldRestoreSelection) {
-            [_webSelectionAssistant didEndScrollingOverflow];
-            [_textSelectionAssistant didEndScrollingOverflow];
-            _shouldRestoreSelection = NO;
-        }
-    }
+    _datePickerViewController = adoptNS([[WKDatePickerViewController alloc] initWithText:_assistedNodeInformation.value textSuggestions:@[ ]]);
+    [_datePickerViewController setDelegate:self];
+    [_focusedFormControlViewController presentViewController:_datePickerViewController.get() animated:animated completion:nil];
+}
 
-    auto& state = _page->editorState();
-    if (!state.isMissingPostLayoutData && state.postLayoutData().isStableStateUpdate && _needsDeferredEndScrollingSelectionUpdate && _page->inStableState()) {
-        [[self selectionInteractionAssistant] showSelectionCommands];
-        [_webSelectionAssistant didEndScrollingOrZoomingPage];
-        [[_webSelectionAssistant selectionView] setHidden:NO];
+- (void)dismissDatePickerViewController:(BOOL)animated
+{
+    if (!_datePickerViewController)
+        return;
 
-        if (!self.suppressAssistantSelectionView)
-            [_textSelectionAssistant activateSelection];
+    auto datePickerViewController = WTFMove(_datePickerViewController);
+    [datePickerViewController dismissViewControllerAnimated:animated completion:nil];
+}
 
-        [_textSelectionAssistant didEndScrollingOverflow];
+- (void)presentTimePickerViewController:(BOOL)animated
+{
+    if (_timePickerViewController)
+        return;
 
-        _needsDeferredEndScrollingSelectionUpdate = NO;
-    }
+    _timePickerViewController = adoptNS([[WKTimePickerViewController alloc] initWithText:_assistedNodeInformation.value textSuggestions:@[ ]]);
+    [_timePickerViewController setDelegate:self];
+    [_focusedFormControlViewController presentViewController:_timePickerViewController.get() animated:animated completion:nil];
 }
 
-- (BOOL)suppressAssistantSelectionView
+- (void)dismissTimePickerViewController:(BOOL)animated
 {
-    return _suppressAssistantSelectionView;
+    if (!_timePickerViewController)
+        return;
+
+    auto timePickerViewController = WTFMove(_timePickerViewController);
+    [timePickerViewController dismissViewControllerAnimated:animated completion:nil];
 }
 
-- (void)setSuppressAssistantSelectionView:(BOOL)suppressAssistantSelectionView
+- (void)presentSelectMenuViewController:(BOOL)animated
 {
-    if (_suppressAssistantSelectionView == suppressAssistantSelectionView)
+    if (_selectMenuViewController)
         return;
 
-    _suppressAssistantSelectionView = suppressAssistantSelectionView;
-    if (!_textSelectionAssistant)
+    _selectMenuViewController = adoptNS([[WKSelectMenuViewController alloc] init]);
+    [_selectMenuViewController setDelegate:self];
+    [_focusedFormControlViewController presentViewController:_selectMenuViewController.get() animated:animated completion:nil];
+}
+
+- (void)dismissSelectMenuViewController:(BOOL)animated
+{
+    if (!_selectMenuViewController)
         return;
 
-    if (suppressAssistantSelectionView)
-        [_textSelectionAssistant deactivateSelection];
-    else
-        [_textSelectionAssistant activateSelection];
+    auto selectMenuViewController = WTFMove(_selectMenuViewController);
+    [selectMenuViewController dismissViewControllerAnimated:animated completion:nil];
 }
 
-- (void)_showPlaybackTargetPicker:(BOOL)hasVideo fromRect:(const IntRect&)elementRect
+- (void)presentFocusedFormControlViewController:(BOOL)animated
 {
-    if (!_airPlayRoutePicker)
-        _airPlayRoutePicker = adoptNS([[WKAirPlayRoutePicker alloc] init]);
-    [_airPlayRoutePicker showFromView:self];
+    if (_focusedFormControlViewController)
+        return;
+
+    _focusedFormControlViewController = adoptNS([[WKFocusedFormControlViewController alloc] init]);
+    [_focusedFormControlViewController setDelegate:self];
+    [[UIViewController _viewControllerForFullScreenPresentationFromView:self] presentViewController:_focusedFormControlViewController.get() animated:animated completion:nil];
+    [self setInputDelegate:_focusedFormControlViewController.get()];
 }
 
-- (void)_showRunOpenPanel:(API::OpenPanelParameters*)parameters resultListener:(WebOpenPanelResultListenerProxy*)listener
+- (void)dismissNumberPadViewController:(BOOL)animated
 {
-    ASSERT(!_fileUploadPanel);
-    if (_fileUploadPanel)
+    if (!_numberPadViewController)
         return;
 
-    _fileUploadPanel = adoptNS([[WKFileUploadPanel alloc] initWithView:self]);
-    [_fileUploadPanel setDelegate:self];
-    [_fileUploadPanel presentWithParameters:parameters resultListener:listener];
+    auto numberPadViewController = WTFMove(_numberPadViewController);
+    [numberPadViewController dismissViewControllerAnimated:animated completion:nil];
 }
 
-- (void)fileUploadPanelDidDismiss:(WKFileUploadPanel *)fileUploadPanel
+- (void)dismissFocusedFormControlViewController:(BOOL)animated
 {
-    ASSERT(_fileUploadPanel.get() == fileUploadPanel);
+    if (!_focusedFormControlViewController)
+        return;
 
-    [_fileUploadPanel setDelegate:nil];
-    _fileUploadPanel = nil;
+    [_focusedFormControlViewController dismissViewControllerAnimated:animated completion:nil];
+    _focusedFormControlViewController = nil;
+    [self setInputDelegate:nil];
 }
 
-#pragma mark - Implementation of UIWebTouchEventsGestureRecognizerDelegate.
-
-// FIXME: Remove once -gestureRecognizer:shouldIgnoreWebTouchWithEvent: is in UIWebTouchEventsGestureRecognizer.h. Refer to <rdar://problem/33217525> for more details.
-- (BOOL)shouldIgnoreWebTouch
+- (void)presentNumberPadViewController:(BOOL)animated
 {
-    return NO;
+    if (_numberPadViewController)
+        return;
+
+    _numberPadViewController = adoptNS([[WKNumberPadViewController alloc] initWithText:_assistedNodeInformation.value textSuggestions:@[]]);
+    [_numberPadViewController setDelegate:self];
+    [_focusedFormControlViewController presentViewController:_numberPadViewController.get() animated:animated completion:nil];
 }
 
-- (BOOL)gestureRecognizer:(UIWebTouchEventsGestureRecognizer *)gestureRecognizer shouldIgnoreWebTouchWithEvent:(UIEvent *)event
+- (void)presentViewControllerForCurrentAssistedNode
 {
-    _canSendTouchEventsAsynchronously = NO;
+    [self dismissAllInputViewControllers];
 
-    NSSet<UITouch *> *touches = [event touchesForGestureRecognizer:gestureRecognizer];
-    for (UITouch *touch in touches) {
-        if ([touch.view isKindOfClass:[UIScrollView class]] && [(UIScrollView *)touch.view _isInterruptingDeceleration])
-            return YES;
+    switch (_assistedNodeInformation.elementType) {
+    case InputType::Number:
+    case InputType::NumberPad:
+    case InputType::Phone:
+        [self presentNumberPadViewController:YES];
+        break;
+    case InputType::Select:
+        [self presentSelectMenuViewController:YES];
+        break;
+    case InputType::Time:
+        [self presentTimePickerViewController:YES];
+        break;
+    case InputType::Date:
+        [self presentDatePickerViewController:YES];
+        break;
+    case InputType::None:
+        break;
+    default:
+        [self presentTextInputViewController:YES];
+        break;
     }
-    return self._scroller._isInterruptingDeceleration;
 }
 
-- (BOOL)isAnyTouchOverActiveArea:(NSSet *)touches
+- (void)presentTextInputViewController:(BOOL)animated
 {
-    return YES;
-}
+    if (_textInputViewController)
+        return;
 
-#pragma mark - Implementation of WKActionSheetAssistantDelegate.
+    _textInputViewController = adoptNS([[WKTextInputViewController alloc] initWithText:_assistedNodeInformation.value textSuggestions:@[ ]]);
+    [_textInputViewController setDelegate:self];
+    [_focusedFormControlViewController presentViewController:_textInputViewController.get() animated:animated completion:nil];
 
-- (std::optional<WebKit::InteractionInformationAtPosition>)positionInformationForActionSheetAssistant:(WKActionSheetAssistant *)assistant
+    [_textInputViewController setSuggestions:[_focusedFormControlViewController suggestions]];
+}
+
+- (void)dismissTextInputViewController:(BOOL)animated
 {
-    InteractionInformationRequest request(_positionInformation.request.point);
-    request.includeSnapshot = true;
-    request.includeLinkIndicator = assistant.needsLinkIndicator;
-    if (![self ensurePositionInformationIsUpToDate:request])
-        return std::nullopt;
+    if (!_textInputViewController)
+        return;
 
-    return _positionInformation;
+    auto textInputViewController = WTFMove(_textInputViewController);
+    [textInputViewController dismissViewControllerAnimated:animated completion:nil];
 }
 
-- (void)updatePositionInformationForActionSheetAssistant:(WKActionSheetAssistant *)assistant
+- (void)textInputController:(WKTextFormControlViewController *)controller didCommitText:(NSString *)text
+{
+    [self textInputController:controller didCommitText:text withSuggestion:nil];
+}
+
+- (void)textInputController:(WKTextFormControlViewController *)controller didCommitText:(NSString *)text withSuggestion:(UITextSuggestion *)suggestion
+{
+    if (suggestion)
+        [self insertTextSuggestion:suggestion];
+    else
+        _page->setTextAsync(text);
+
+    if (![self actionNameForFocusedFormControlController:_focusedFormControlViewController.get()] && !_assistedNodeInformation.hasNextNode && !_assistedNodeInformation.hasPreviousNode) {
+        // In this case, there's no point in collapsing down to the form control focus UI because there's nothing the user could potentially do
+        // besides dismiss the UI, so we just automatically dismiss the focused form control UI.
+        _page->blurAssistedNode();
+        return;
+    }
+
+    [_focusedFormControlViewController show:NO];
+    [self dismissAllInputViewControllers];
+    [self updateCurrentAssistedNodeInformation:[weakSelf = WeakObjCPtr<WKContentView>(self)] (bool didUpdate) {
+        if (didUpdate)
+            [weakSelf.get()->_focusedFormControlViewController reloadData:YES];
+    }];
+}
+
+- (void)textInputControllerDidRequestDismissal:(WKTextFormControlViewController *)controller
+{
+    _page->blurAssistedNode();
+}
+
+- (void)focusedFormControlControllerDidSubmit:(WKFocusedFormControlViewController *)controller
+{
+    [self insertText:@"\n"];
+    _page->blurAssistedNode();
+}
+
+- (void)focusedFormControlControllerDidCancel:(WKFocusedFormControlViewController *)controller
+{
+    _page->blurAssistedNode();
+}
+
+- (void)focusedFormControlControllerDidBeginEditing:(WKFocusedFormControlViewController *)controller
+{
+    [self updateCurrentAssistedNodeInformation:[weakSelf = WeakObjCPtr<WKContentView>(self)] (bool didUpdate) {
+        if (didUpdate)
+            [weakSelf presentViewControllerForCurrentAssistedNode];
+    }];
+}
+
+- (CGRect)highlightedRectForFocusedFormControlController:(WKFocusedFormControlViewController *)controller inCoordinateSpace:(id <UICoordinateSpace>)coordinateSpace
+{
+    return [self convertRect:_assistedNodeInformation.elementRect toCoordinateSpace:coordinateSpace];
+}
+
+- (NSString *)actionNameForFocusedFormControlController:(WKFocusedFormControlViewController *)controller
+{
+    if (_assistedNodeInformation.formAction.isEmpty())
+        return nil;
+
+    switch (_assistedNodeInformation.elementType) {
+    case InputType::Select:
+    case InputType::Time:
+    case InputType::Date:
+        return nil;
+    case InputType::Search:
+        return formControlSearchButtonTitle();
+    default:
+        return formControlGoButtonTitle();
+    }
+}
+
+- (void)focusedFormControlControllerDidRequestNextNode:(WKFocusedFormControlViewController *)controller
+{
+    if (_assistedNodeInformation.hasNextNode)
+        _page->focusNextAssistedNode(true);
+}
+
+- (void)focusedFormControlControllerDidRequestPreviousNode:(WKFocusedFormControlViewController *)controller
+{
+    if (_assistedNodeInformation.hasPreviousNode)
+        _page->focusNextAssistedNode(false);
+}
+
+- (BOOL)hasNextNodeForFocusedFormControlController:(WKFocusedFormControlViewController *)controller
+{
+    return _assistedNodeInformation.hasNextNode;
+}
+
+- (BOOL)hasPreviousNodeForFocusedFormControlController:(WKFocusedFormControlViewController *)controller
+{
+    return _assistedNodeInformation.hasPreviousNode;
+}
+
+- (void)focusedFormControllerDidUpdateSuggestions:(WKFocusedFormControlViewController *)controller
+{
+    [_textInputViewController setSuggestions:controller.suggestions];
+}
+
+#pragma mark - WKSelectMenuViewControllerDelegate
+
+- (void)selectMenu:(WKSelectMenuViewController *)selectMenu didSelectItemAtIndex:(NSUInteger)index
+{
+    if (!_assistedNodeInformation.isMultiSelect)
+        _page->setAssistedNodeSelectedIndex(index, false);
+
+    _page->blurAssistedNode();
+}
+
+- (void)didCancelSelectionInSelectMenu:(WKSelectMenuViewController *)selectMenu
+{
+    _page->blurAssistedNode();
+}
+
+- (NSUInteger)numberOfItemsInSelectMenu:(WKSelectMenuViewController *)selectMenu
+{
+    return self.assistedNodeSelectOptions.size();
+}
+
+- (NSString *)selectMenu:(WKSelectMenuViewController *)selectMenu displayTextForItemAtIndex:(NSUInteger)index
+{
+    auto& options = self.assistedNodeSelectOptions;
+    if (index >= options.size()) {
+        ASSERT_NOT_REACHED();
+        return @"";
+    }
+
+    return options[index].text;
+}
+
+- (void)selectMenu:(WKSelectMenuViewController *)selectMenu didCheckItemAtIndex:(NSUInteger)index checked:(BOOL)checked
+{
+    ASSERT(_assistedNodeInformation.isMultiSelect);
+    if (index >= self.assistedNodeSelectOptions.size()) {
+        ASSERT_NOT_REACHED();
+        return;
+    }
+
+    auto& option = self.assistedNodeSelectOptions[index];
+    if (option.isSelected == checked) {
+        ASSERT_NOT_REACHED();
+        return;
+    }
+
+    _page->setAssistedNodeSelectedIndex(index, true);
+    option.isSelected = checked;
+}
+
+- (BOOL)selectMenuSupportsMultipleSelection:(WKSelectMenuViewController *)selectMenu
+{
+    return _assistedNodeInformation.isMultiSelect;
+}
+
+- (BOOL)selectMenu:(WKSelectMenuViewController *)selectMenu hasCheckedOptionAtIndex:(NSUInteger)index
+{
+    if (index >= self.assistedNodeSelectOptions.size()) {
+        ASSERT_NOT_REACHED();
+        return NO;
+    }
+
+    return self.assistedNodeSelectOptions[index].isSelected;
+}
+
+- (NSUInteger)startingIndexForSelectMenu:(WKSelectMenuViewController *)selectMenu
+{
+    if (_assistedNodeInformation.isMultiSelect)
+        return 0;
+
+    auto firstSelectedIndex = self.assistedNodeSelectOptions.findMatching([&] (auto& option) {
+        return option.isSelected;
+    });
+
+    return firstSelectedIndex == notFound ? 0 : firstSelectedIndex;
+}
+
+#endif // ENABLE(EXTRA_ZOOM_MODE)
+
+- (void)_wheelChangedWithEvent:(UIEvent *)event
+{
+#if ENABLE(EXTRA_ZOOM_MODE)
+    if ([_numberPadViewController handleWheelEvent:event])
+        return;
+
+    if ([_textInputViewController handleWheelEvent:event])
+        return;
+
+    if ([_selectMenuViewController handleWheelEvent:event])
+        return;
+
+    if ([_timePickerViewController handleWheelEvent:event])
+        return;
+
+    if ([_datePickerViewController handleWheelEvent:event])
+        return;
+
+    if ([_focusedFormControlViewController handleWheelEvent:event])
+        return;
+#endif
+    [super _wheelChangedWithEvent:event];
+}
+
+- (void)_selectionChanged
+{
+    _selectionNeedsUpdate = YES;
+    // If we are changing the selection with a gesture there is no need
+    // to wait to paint the selection.
+    if (_usingGestureForSelection)
+        [self _updateChangedSelection];
+
+    [_webView _didChangeEditorState];
+}
+
+- (void)selectWordForReplacement
+{
+    _page->extendSelection(WordGranularity);
+}
+
+- (void)_updateChangedSelection
+{
+    [self _updateChangedSelection:NO];
+}
+
+- (void)_updateChangedSelection:(BOOL)force
+{
+    if (!_selectionNeedsUpdate || _page->editorState().isMissingPostLayoutData)
+        return;
+
+    WKSelectionDrawingInfo selectionDrawingInfo(_page->editorState());
+    if (force || selectionDrawingInfo != _lastSelectionDrawingInfo) {
+        LOG_WITH_STREAM(Selection, stream << "_updateChangedSelection " << selectionDrawingInfo);
+
+        _lastSelectionDrawingInfo = selectionDrawingInfo;
+
+        // FIXME: We need to figure out what to do if the selection is changed by Javascript.
+        if (_textSelectionAssistant) {
+            _markedText = (_page->editorState().hasComposition) ? _page->editorState().markedText : String();
+            if (!_showingTextStyleOptions)
+                [_textSelectionAssistant selectionChanged];
+        } else if (!_page->editorState().isContentEditable)
+            [_webSelectionAssistant selectionChanged];
+
+        _selectionNeedsUpdate = NO;
+        if (_shouldRestoreSelection) {
+            [_webSelectionAssistant didEndScrollingOverflow];
+            [_textSelectionAssistant didEndScrollingOverflow];
+            _shouldRestoreSelection = NO;
+        }
+    }
+
+    auto& state = _page->editorState();
+    if (!state.isMissingPostLayoutData && state.postLayoutData().isStableStateUpdate && _needsDeferredEndScrollingSelectionUpdate && _page->inStableState()) {
+        [[self selectionInteractionAssistant] showSelectionCommands];
+        [_webSelectionAssistant didEndScrollingOrZoomingPage];
+        [[_webSelectionAssistant selectionView] setHidden:NO];
+
+        if (!self.suppressAssistantSelectionView)
+            [_textSelectionAssistant activateSelection];
+
+        [_textSelectionAssistant didEndScrollingOverflow];
+
+        _needsDeferredEndScrollingSelectionUpdate = NO;
+    }
+}
+
+- (BOOL)suppressAssistantSelectionView
+{
+    return _suppressAssistantSelectionView;
+}
+
+- (void)setSuppressAssistantSelectionView:(BOOL)suppressAssistantSelectionView
+{
+    if (_suppressAssistantSelectionView == suppressAssistantSelectionView)
+        return;
+
+    _suppressAssistantSelectionView = suppressAssistantSelectionView;
+    if (!_textSelectionAssistant)
+        return;
+
+    if (suppressAssistantSelectionView)
+        [_textSelectionAssistant deactivateSelection];
+    else
+        [_textSelectionAssistant activateSelection];
+}
+
+- (void)_showPlaybackTargetPicker:(BOOL)hasVideo fromRect:(const IntRect&)elementRect
+{
+#if __IPHONE_OS_VERSION_MIN_REQUIRED >= 110000 && !PLATFORM(WATCHOS) && !PLATFORM(APPLETV)
+    if (!_airPlayRoutePicker)
+        _airPlayRoutePicker = adoptNS([[WKAirPlayRoutePicker alloc] init]);
+    [_airPlayRoutePicker showFromView:self];
+#else
+    if (!_airPlayRoutePicker)
+        _airPlayRoutePicker = adoptNS([[WKAirPlayRoutePicker alloc] initWithView:self]);
+    [_airPlayRoutePicker show:hasVideo fromRect:elementRect];
+#endif
+}
+
+- (void)_showRunOpenPanel:(API::OpenPanelParameters*)parameters resultListener:(WebOpenPanelResultListenerProxy*)listener
+{
+    ASSERT(!_fileUploadPanel);
+    if (_fileUploadPanel)
+        return;
+
+    _fileUploadPanel = adoptNS([[WKFileUploadPanel alloc] initWithView:self]);
+    [_fileUploadPanel setDelegate:self];
+    [_fileUploadPanel presentWithParameters:parameters resultListener:listener];
+}
+
+- (void)fileUploadPanelDidDismiss:(WKFileUploadPanel *)fileUploadPanel
+{
+    ASSERT(_fileUploadPanel.get() == fileUploadPanel);
+
+    [_fileUploadPanel setDelegate:nil];
+    _fileUploadPanel = nil;
+}
+
+#pragma mark - UITextInputMultiDocument
+
+- (void)_restoreFocusWithToken:(id <NSCopying, NSSecureCoding>)token
+{
+    ASSERT(!_focusStateStack.isEmpty());
+    
+    if (_focusStateStack.takeLast()) {
+        ASSERT(_webView->_activeFocusedStateRetainCount);
+        --_webView->_activeFocusedStateRetainCount;
+    }
+}
+
+- (void)_preserveFocusWithToken:(id <NSCopying, NSSecureCoding>)token destructively:(BOOL)destructively
+{
+    if (!_inputPeripheral) {
+        ++_webView->_activeFocusedStateRetainCount;
+        _focusStateStack.append(true);
+    } else
+        _focusStateStack.append(false);
+}
+
+#pragma mark - Implementation of UIWebTouchEventsGestureRecognizerDelegate.
+
+// FIXME: Remove once -gestureRecognizer:shouldIgnoreWebTouchWithEvent: is in UIWebTouchEventsGestureRecognizer.h. Refer to <rdar://problem/33217525> for more details.
+- (BOOL)shouldIgnoreWebTouch
+{
+    return NO;
+}
+
+- (BOOL)gestureRecognizer:(UIWebTouchEventsGestureRecognizer *)gestureRecognizer shouldIgnoreWebTouchWithEvent:(UIEvent *)event
+{
+    _canSendTouchEventsAsynchronously = NO;
+
+    NSSet<UITouch *> *touches = [event touchesForGestureRecognizer:gestureRecognizer];
+    for (UITouch *touch in touches) {
+        if ([touch.view isKindOfClass:[UIScrollView class]] && [(UIScrollView *)touch.view _isInterruptingDeceleration])
+            return YES;
+    }
+    return self._scroller._isInterruptingDeceleration;
+}
+
+- (BOOL)isAnyTouchOverActiveArea:(NSSet *)touches
+{
+    return YES;
+}
+
+#pragma mark - Implementation of WKActionSheetAssistantDelegate.
+
+- (std::optional<WebKit::InteractionInformationAtPosition>)positionInformationForActionSheetAssistant:(WKActionSheetAssistant *)assistant
+{
+    InteractionInformationRequest request(_positionInformation.request.point);
+    request.includeSnapshot = true;
+    request.includeLinkIndicator = assistant.needsLinkIndicator;
+    if (![self ensurePositionInformationIsUpToDate:request])
+        return std::nullopt;
+
+    return _positionInformation;
+}
+
+- (void)updatePositionInformationForActionSheetAssistant:(WKActionSheetAssistant *)assistant
 {
     _hasValidPositionInformation = NO;
     InteractionInformationRequest request(_positionInformation.request.point);
@@ -4103,7 +4706,7 @@ static bool isAssistableInputType(InputType type)
     if ([uiDelegate respondsToSelector:@selector(_webView:showCustomSheetForElement:)]) {
         if ([uiDelegate _webView:_webView showCustomSheetForElement:element]) {
 #if ENABLE(DATA_INTERACTION)
-            BOOL shouldCancelAllTouches = !_dataInteractionState.sourceAction;
+            BOOL shouldCancelAllTouches = !_dragDropInteractionState.dragSession();
 #else
             BOOL shouldCancelAllTouches = YES;
 #endif
@@ -4174,7 +4777,7 @@ static BOOL shouldEnableDragInteractionForPolicy(_WKDragInteractionPolicy policy
 
 - (void)_didChangeDragInteractionPolicy
 {
-    [_dataInteraction setEnabled:shouldEnableDragInteractionForPolicy(_webView._dragInteractionPolicy)];
+    [_dragInteraction setEnabled:shouldEnableDragInteractionForPolicy(_webView._dragInteractionPolicy)];
 }
 
 - (NSTimeInterval)dragLiftDelay
@@ -4196,25 +4799,25 @@ static BOOL shouldEnableDragInteractionForPolicy(_WKDragInteractionPolicy policy
 
 - (void)setupDataInteractionDelegates
 {
-    _dataInteraction = adoptNS([[UIDragInteraction alloc] initWithDelegate:self]);
-    _dataOperation = adoptNS([[UIDropInteraction alloc] initWithDelegate:self]);
-    [_dataInteraction _setLiftDelay:self.dragLiftDelay];
-    [_dataInteraction setEnabled:shouldEnableDragInteractionForPolicy(_webView._dragInteractionPolicy)];
+    _dragInteraction = adoptNS([[UIDragInteraction alloc] initWithDelegate:self]);
+    _dropInteraction = adoptNS([[UIDropInteraction alloc] initWithDelegate:self]);
+    [_dragInteraction _setLiftDelay:self.dragLiftDelay];
+    [_dragInteraction setEnabled:shouldEnableDragInteractionForPolicy(_webView._dragInteractionPolicy)];
 
-    [self addInteraction:_dataInteraction.get()];
-    [self addInteraction:_dataOperation.get()];
+    [self addInteraction:_dragInteraction.get()];
+    [self addInteraction:_dropInteraction.get()];
 }
 
 - (void)teardownDataInteractionDelegates
 {
-    if (_dataInteraction)
-        [self removeInteraction:_dataInteraction.get()];
+    if (_dragInteraction)
+        [self removeInteraction:_dragInteraction.get()];
 
-    if (_dataOperation)
-        [self removeInteraction:_dataOperation.get()];
+    if (_dropInteraction)
+        [self removeInteraction:_dropInteraction.get()];
 
-    _dataInteraction = nil;
-    _dataOperation = nil;
+    _dragInteraction = nil;
+    _dropInteraction = nil;
 
     [self cleanUpDragSourceSessionState];
 }
@@ -4223,124 +4826,69 @@ static BOOL shouldEnableDragInteractionForPolicy(_WKDragInteractionPolicy policy
 {
     ASSERT(item.sourceAction != DragSourceActionNone);
 
-    _dataInteractionState.image = adoptNS([[UIImage alloc] initWithCGImage:image.get() scale:_page->deviceScaleFactor() orientation:UIImageOrientationUp]);
-    _dataInteractionState.indicatorData = item.image.indicatorData();
-    _dataInteractionState.sourceAction = static_cast<DragSourceAction>(item.sourceAction);
-    _dataInteractionState.adjustedOrigin = item.eventPositionInContentCoordinates;
-    _dataInteractionState.elementBounds = item.elementBounds;
-    _dataInteractionState.linkTitle = item.title.isEmpty() ? nil : (NSString *)item.title;
-    _dataInteractionState.linkURL = item.url.isEmpty() ? nil : (NSURL *)item.url;
+    if (item.promisedBlob)
+        [self _prepareToDragPromisedBlob:item.promisedBlob];
+
+    auto dragImage = adoptNS([[UIImage alloc] initWithCGImage:image.get() scale:_page->deviceScaleFactor() orientation:UIImageOrientationUp]);
+    _dragDropInteractionState.stageDragItem(item, dragImage.get());
+}
+
+- (void)_didHandleAdditionalDragItemsRequest:(BOOL)added
+{
+    auto completion = _dragDropInteractionState.takeAddDragItemCompletionBlock();
+    if (!completion)
+        return;
+
+    WebItemProviderRegistrationInfoList *registrationList = [[WebItemProviderPasteboard sharedInstance] takeRegistrationList];
+    if (!added || !registrationList || !_dragDropInteractionState.hasStagedDragSource()) {
+        _dragDropInteractionState.clearStagedDragSource();
+        completion(@[ ]);
+        return;
+    }
+
+    auto stagedDragSource = _dragDropInteractionState.stagedDragSource();
+    NSArray *dragItemsToAdd = [self _itemsForBeginningOrAddingToSessionWithRegistrationList:registrationList stagedDragSource:stagedDragSource];
+
+    RELEASE_LOG(DragAndDrop, "Drag session: %p adding %tu items", _dragDropInteractionState.dragSession(), dragItemsToAdd.count);
+    _dragDropInteractionState.clearStagedDragSource(dragItemsToAdd.count ? DragDropInteractionState::DidBecomeActive::Yes : DragDropInteractionState::DidBecomeActive::No);
+
+    completion(dragItemsToAdd);
+
+    if (dragItemsToAdd.count)
+        _page->didStartDrag();
 }
 
 - (void)_didHandleStartDataInteractionRequest:(BOOL)started
 {
-    BlockPtr<void()> savedCompletionBlock = _dataInteractionState.dragStartCompletionBlock;
-    _dataInteractionState.dragStartCompletionBlock = nil;
+    BlockPtr<void()> savedCompletionBlock = _dragDropInteractionState.takeDragStartCompletionBlock();
     ASSERT(savedCompletionBlock);
 
     RELEASE_LOG(DragAndDrop, "Handling drag start request (started: %d, completion block: %p)", started, savedCompletionBlock.get());
     if (savedCompletionBlock)
         savedCompletionBlock();
 
-    if (![_dataInteractionState.dragSession items].count) {
-        CGPoint adjustedOrigin = _dataInteractionState.adjustedOrigin;
+    if (!_dragDropInteractionState.dragSession().items.count) {
+        auto positionForDragEnd = roundedIntPoint(_dragDropInteractionState.adjustedPositionForDragEnd());
         [self cleanUpDragSourceSessionState];
         if (started) {
             // A client of the Objective C SPI or UIKit might have prevented the drag from beginning entirely in the UI process, in which case
             // we need to balance the `dragstart` event with a `dragend`.
-            _page->dragEnded(roundedIntPoint(adjustedOrigin), roundedIntPoint([self convertPoint:adjustedOrigin toView:self.window]), DragOperationNone);
+            _page->dragEnded(positionForDragEnd, positionForDragEnd, DragOperationNone);
         }
     }
 }
 
-static RetainPtr<UIImage> uiImageForImage(RefPtr<Image> image)
-{
-    if (!image)
-        return nullptr;
-
-    auto cgImage = image->nativeImage();
-    if (!cgImage)
-        return nullptr;
-
-    return adoptNS([[UIImage alloc] initWithCGImage:cgImage.get()]);
-}
-
-static BOOL shouldUseTextIndicatorToCreatePreviewForDragAction(DragSourceAction action)
-{
-    if (action & (DragSourceActionLink | DragSourceActionSelection))
-        return YES;
-
-#if ENABLE(ATTACHMENT_ELEMENT)
-    if (action & DragSourceActionAttachment)
-        return YES;
-#endif
-
-    return NO;
-}
-
-- (RetainPtr<UITargetedDragPreview>)dragPreviewForImage:(UIImage *)image frameInRootViewCoordinates:(const FloatRect&)frame clippingRectsInFrameCoordinates:(const Vector<FloatRect>&)clippingRects backgroundColor:(UIColor *)backgroundColor
-{
-    if (frame.isEmpty() || !image)
-        return nullptr;
-
-    UIView *container = [self unscaledView];
-    FloatRect frameInContainerCoordinates;
-    NSMutableArray *clippingRectValuesInFrameCoordinates = [NSMutableArray arrayWithCapacity:clippingRects.size()];
-
-    frameInContainerCoordinates = [self convertRect:frame toView:container];
-    if (frameInContainerCoordinates.isEmpty())
-        return nullptr;
-
-    float widthScalingRatio = frameInContainerCoordinates.width() / frame.width();
-    float heightScalingRatio = frameInContainerCoordinates.height() / frame.height();
-    for (auto rect : clippingRects) {
-        rect.scale(widthScalingRatio, heightScalingRatio);
-        [clippingRectValuesInFrameCoordinates addObject:[NSValue valueWithCGRect:rect]];
-    }
-
-    auto imageView = adoptNS([[UIImageView alloc] initWithImage:image]);
-    [imageView setFrame:frameInContainerCoordinates];
-
-    RetainPtr<UIDragPreviewParameters> parameters;
-    if (clippingRectValuesInFrameCoordinates.count)
-        parameters = adoptNS([[UIDragPreviewParameters alloc] initWithTextLineRects:clippingRectValuesInFrameCoordinates]);
-    else
-        parameters = adoptNS([[UIDragPreviewParameters alloc] init]);
-
-    if (backgroundColor)
-        [parameters setBackgroundColor:backgroundColor];
-
-    CGPoint centerInContainerCoordinates = { CGRectGetMidX(frameInContainerCoordinates), CGRectGetMidY(frameInContainerCoordinates) };
-    auto target = adoptNS([[UIDragPreviewTarget alloc] initWithContainer:container center:centerInContainerCoordinates]);
-    auto dragPreview = adoptNS([[UITargetedDragPreview alloc] initWithView:imageView.get() parameters:parameters.get() target:target.get()]);
-    return dragPreview;
-}
-
-- (RetainPtr<UITargetedDragPreview>)dragPreviewForCurrentDataInteractionState
-{
-    auto action = _dataInteractionState.sourceAction;
-    if (action & DragSourceActionImage && _dataInteractionState.image) {
-        Vector<FloatRect> emptyClippingRects;
-        return [self dragPreviewForImage:_dataInteractionState.image.get() frameInRootViewCoordinates:_dataInteractionState.elementBounds clippingRectsInFrameCoordinates:emptyClippingRects backgroundColor:nil];
-    }
-
-    if (shouldUseTextIndicatorToCreatePreviewForDragAction(action) && _dataInteractionState.indicatorData) {
-        auto indicator = _dataInteractionState.indicatorData.value();
-        return [self dragPreviewForImage:uiImageForImage(indicator.contentImage).get() frameInRootViewCoordinates:indicator.textBoundingRectInRootViewCoordinates clippingRectsInFrameCoordinates:indicator.textRectsInBoundingRectCoordinates backgroundColor:[UIColor colorWithCGColor:cachedCGColor(indicator.estimatedBackgroundColor)]];
-    }
-
-    return nil;
-}
-
 - (void)computeClientAndGlobalPointsForDropSession:(id <UIDropSession>)session outClientPoint:(CGPoint *)outClientPoint outGlobalPoint:(CGPoint *)outGlobalPoint
 {
+    // FIXME: This makes the behavior of drag events on iOS consistent with other synthetic mouse events on iOS (see WebPage::completeSyntheticClick).
+    // However, we should experiment with making the client position relative to the window and the global position in document coordinates. See
+    // https://bugs.webkit.org/show_bug.cgi?id=173855 for more details.
+    auto locationInContentView = [session locationInView:self];
     if (outClientPoint)
-        *outClientPoint = [session locationInView:self];
+        *outClientPoint = locationInContentView;
 
-    if (outGlobalPoint) {
-        UIWindow *window = self.window;
-        *outGlobalPoint = window ? [session locationInView:window] : _dataInteractionState.lastGlobalPosition;
-    }
+    if (outGlobalPoint)
+        *outGlobalPoint = locationInContentView;
 }
 
 static UIDropOperation dropOperationForWebCoreDragOperation(DragOperation operation)
@@ -4373,27 +4921,23 @@ static UIDropOperation dropOperationForWebCoreDragOperation(DragOperation operat
         [[WebItemProviderPasteboard sharedInstance] setItemProviders:nil];
     }
 
-    if (auto completionBlock = _dataInteractionState.dragCancelSetDownBlock) {
-        _dataInteractionState.dragCancelSetDownBlock = nil;
-        completionBlock();
-    }
-
-    if (auto completionBlock = _dataInteractionState.dragStartCompletionBlock) {
-        // If the previous drag session is still initializing, we need to ensure that its completion block is called to prevent UIKit from getting out of state.
-        _dataInteractionState.dragStartCompletionBlock = nil;
-        completionBlock();
-    }
-
+    [[WebItemProviderPasteboard sharedInstance] stageRegistrationList:nil];
     [self _restoreCalloutBarIfNeeded];
-    [_dataInteractionState.caretView remove];
-    [_dataInteractionState.visibleContentViewSnapshot removeFromSuperview];
 
-    _dataInteractionState = { };
+    [_visibleContentViewSnapshot removeFromSuperview];
+    _visibleContentViewSnapshot = nil;
+    [_editDropCaretView remove];
+    _editDropCaretView = nil;
+    _isAnimatingConcludeEditDrag = NO;
+    _shouldRestoreCalloutBarAfterDrop = NO;
+
+    _dragDropInteractionState.dragAndDropSessionsDidEnd();
+    _dragDropInteractionState = { };
 }
 
 static NSArray<UIItemProvider *> *extractItemProvidersFromDragItems(NSArray<UIDragItem *> *dragItems)
 {
-    __block NSMutableArray<UIItemProvider *> *providers = [NSMutableArray array];
+    NSMutableArray<UIItemProvider *> *providers = [NSMutableArray array];
     for (UIDragItem *item in dragItems) {
         RetainPtr<UIItemProvider> provider = item.itemProvider;
         if (provider)
@@ -4425,9 +4969,9 @@ static NSArray<UIItemProvider *> *extractItemProvidersFromDropSession(id <UIDrop
     [unselectedContentSnapshot setFrame:data->contentImageWithoutSelectionRectInRootViewCoordinates];
 
     RetainPtr<WKContentView> protectedSelf = self;
-    RetainPtr<UIView> visibleContentViewSnapshot = adoptNS(_dataInteractionState.visibleContentViewSnapshot.leakRef());
+    RetainPtr<UIView> visibleContentViewSnapshot = adoptNS(_visibleContentViewSnapshot.leakRef());
 
-    _dataInteractionState.isAnimatingConcludeEditDrag = YES;
+    _isAnimatingConcludeEditDrag = YES;
     [self insertSubview:unselectedContentSnapshot.get() belowSubview:visibleContentViewSnapshot.get()];
     [UIView animateWithDuration:0.25 animations:^() {
         [visibleContentViewSnapshot setAlpha:0];
@@ -4446,37 +4990,20 @@ static NSArray<UIItemProvider *> *extractItemProvidersFromDropSession(id <UIDrop
 {
     RELEASE_LOG(DragAndDrop, "Finished performing drag controller operation (handled: %d)", handled);
     [[WebItemProviderPasteboard sharedInstance] decrementPendingOperationCount];
-    RetainPtr<id <UIDropSession>> dropSession = _dataInteractionState.dropSession;
+    id <UIDropSession> dropSession = _dragDropInteractionState.dropSession();
     if ([self.webViewUIDelegate respondsToSelector:@selector(_webView:dataInteractionOperationWasHandled:forSession:itemProviders:)])
-        [self.webViewUIDelegate _webView:_webView dataInteractionOperationWasHandled:handled forSession:dropSession.get() itemProviders:[WebItemProviderPasteboard sharedInstance].itemProviders];
+        [self.webViewUIDelegate _webView:_webView dataInteractionOperationWasHandled:handled forSession:dropSession itemProviders:[WebItemProviderPasteboard sharedInstance].itemProviders];
 
-    if (!_dataInteractionState.isAnimatingConcludeEditDrag)
+    if (!_isAnimatingConcludeEditDrag)
         self.suppressAssistantSelectionView = NO;
 
     CGPoint global;
     CGPoint client;
-    [self computeClientAndGlobalPointsForDropSession:dropSession.get() outClientPoint:&client outGlobalPoint:&global];
+    [self computeClientAndGlobalPointsForDropSession:dropSession outClientPoint:&client outGlobalPoint:&global];
     [self cleanUpDragSourceSessionState];
     _page->dragEnded(roundedIntPoint(client), roundedIntPoint(global), _page->currentDragOperation());
 }
 
-- (void)_transitionDragPreviewToImageIfNecessary:(id <UIDragSession>)session
-{
-    if (_dataInteractionState.sourceAction & DragSourceActionImage || !(_dataInteractionState.sourceAction & DragSourceActionLink))
-        return;
-
-    auto linkDraggingCenter = _dataInteractionState.adjustedOrigin;
-    RetainPtr<NSString> title = (NSString *)_dataInteractionState.linkTitle;
-    RetainPtr<NSURL> url = (NSURL *)_dataInteractionState.linkURL;
-    session.items.firstObject.previewProvider = [title, url, linkDraggingCenter] () -> UIDragPreview * {
-        UIURLDragPreviewView *previewView = [UIURLDragPreviewView viewWithTitle:title.get() URL:url.get()];
-        previewView.center = linkDraggingCenter;
-
-        UIDragPreviewParameters *parameters = [[[UIDragPreviewParameters alloc] initWithTextLineRects:@[ [NSValue valueWithCGRect:previewView.bounds] ]] autorelease];
-        return [[[UIDragPreview alloc] initWithView:previewView parameters:parameters] autorelease];
-    };
-}
-
 - (void)_didChangeDataInteractionCaretRect:(CGRect)previousRect currentRect:(CGRect)rect
 {
     BOOL previousRectIsEmpty = CGRectIsEmpty(previousRect);
@@ -4485,18 +5012,68 @@ static NSArray<UIItemProvider *> *extractItemProvidersFromDropSession(id <UIDrop
         return;
 
     if (previousRectIsEmpty) {
-        _dataInteractionState.caretView = adoptNS([[_UITextDragCaretView alloc] initWithTextInputView:self]);
-        [_dataInteractionState.caretView insertAtPosition:[WKTextPosition textPositionWithRect:rect]];
+        _editDropCaretView = adoptNS([[_UITextDragCaretView alloc] initWithTextInputView:self]);
+        [_editDropCaretView insertAtPosition:[WKTextPosition textPositionWithRect:rect]];
         return;
     }
 
     if (currentRectIsEmpty) {
-        [_dataInteractionState.caretView remove];
-        _dataInteractionState.caretView = nil;
+        [_editDropCaretView remove];
+        _editDropCaretView = nil;
         return;
     }
 
-    [_dataInteractionState.caretView updateToPosition:[WKTextPosition textPositionWithRect:rect]];
+    [_editDropCaretView updateToPosition:[WKTextPosition textPositionWithRect:rect]];
+}
+
+- (void)_prepareToDragPromisedBlob:(const PromisedBlobInfo&)info
+{
+    auto session = retainPtr(_dragDropInteractionState.dragSession());
+    if (!session) {
+        ASSERT_NOT_REACHED();
+        return;
+    }
+
+    auto numberOfAdditionalTypes = info.additionalTypes.size();
+    ASSERT(numberOfAdditionalTypes == info.additionalData.size());
+
+    RELEASE_LOG(DragAndDrop, "Drag session: %p preparing to drag blob: %s", session.get(), info.blobURL.string().utf8().data());
+
+    auto registrationList = adoptNS([[WebItemProviderRegistrationInfoList alloc] init]);
+    [registrationList setPreferredPresentationStyle:WebPreferredPresentationStyleAttachment];
+    if (!info.filename.isEmpty())
+        [registrationList setSuggestedName:info.filename];
+    if (numberOfAdditionalTypes == info.additionalData.size() && numberOfAdditionalTypes) {
+        for (size_t index = 0; index < numberOfAdditionalTypes; ++index) {
+            auto nsData = info.additionalData[index]->createNSData();
+            [registrationList addData:nsData.get() forType:info.additionalTypes[index]];
+        }
+    }
+
+    [registrationList addPromisedType:info.contentType fileCallback:[session = WTFMove(session), weakSelf = WeakObjCPtr<WKContentView>(self), url = info.blobURL] (WebItemProviderFileCallback callback) {
+        auto strongSelf = weakSelf.get();
+        if (!strongSelf) {
+            callback(nil, [NSError errorWithDomain:WKErrorDomain code:WKErrorWebViewInvalidated userInfo:nil]);
+            return;
+        }
+
+        NSString *temporaryBlobDirectory = FileSystem::createTemporaryDirectory(@"blobs");
+        NSURL *destinationURL = [NSURL fileURLWithPath:[temporaryBlobDirectory stringByAppendingPathComponent:[NSUUID UUID].UUIDString]];
+
+        RELEASE_LOG(DragAndDrop, "Drag session: %p delivering promised blob at path: %@", session.get(), destinationURL.path);
+        strongSelf->_page->writeBlobToFilePath(url, destinationURL.path, [protectedURL = retainPtr(destinationURL), protectedCallback = makeBlockPtr(callback)] (bool success) {
+            if (success)
+                protectedCallback(protectedURL.get(), nil);
+            else
+                protectedCallback(nil, [NSError errorWithDomain:WKErrorDomain code:WKErrorUnknown userInfo:nil]);
+        });
+
+        [ensureLocalDragSessionContext(session.get()) addTemporaryDirectory:temporaryBlobDirectory];
+    }];
+
+    WebItemProviderPasteboard *pasteboard = [WebItemProviderPasteboard sharedInstance];
+    pasteboard.itemProviders = @[ [registrationList itemProvider] ];
+    [pasteboard stageRegistrationList:registrationList.get()];
 }
 
 - (WKDragDestinationAction)_dragDestinationActionForDropSession:(id <UIDropSession>)session
@@ -4510,20 +5087,67 @@ static NSArray<UIItemProvider *> *extractItemProvidersFromDropSession(id <UIDrop
 
 - (id <UIDragDropSession>)currentDragOrDropSession
 {
-    if (_dataInteractionState.dropSession)
-        return _dataInteractionState.dropSession.get();
-    return _dataInteractionState.dragSession.get();
+    if (_dragDropInteractionState.dropSession())
+        return _dragDropInteractionState.dropSession();
+    return _dragDropInteractionState.dragSession();
 }
 
 - (void)_restoreCalloutBarIfNeeded
 {
-    if (!_dataInteractionState.shouldRestoreCalloutBar)
+    if (!_shouldRestoreCalloutBarAfterDrop)
         return;
 
     // FIXME: This SPI should be renamed in UIKit to reflect a more general purpose of revealing hidden interaction assistant controls.
     [_webSelectionAssistant didEndScrollingOverflow];
     [_textSelectionAssistant didEndScrollingOverflow];
-    _dataInteractionState.shouldRestoreCalloutBar = NO;
+    _shouldRestoreCalloutBarAfterDrop = NO;
+}
+
+- (NSArray<UIDragItem *> *)_itemsForBeginningOrAddingToSessionWithRegistrationList:(WebItemProviderRegistrationInfoList *)registrationList stagedDragSource:(const DragSourceState&)stagedDragSource
+{
+    UIItemProvider *defaultItemProvider = registrationList.itemProvider;
+    if (!defaultItemProvider)
+        return @[ ];
+
+    NSArray *adjustedItemProviders;
+    id <WKUIDelegatePrivate> uiDelegate = self.webViewUIDelegate;
+    if ([uiDelegate respondsToSelector:@selector(_webView:adjustedDataInteractionItemProvidersForItemProvider:representingObjects:additionalData:)]) {
+        auto representingObjects = adoptNS([[NSMutableArray alloc] init]);
+        auto additionalData = adoptNS([[NSMutableDictionary alloc] init]);
+        [registrationList enumerateItems:[representingObjects, additionalData] (id <WebItemProviderRegistrar> item, NSUInteger) {
+            if ([item respondsToSelector:@selector(representingObjectForClient)])
+                [representingObjects addObject:item.representingObjectForClient];
+            if ([item respondsToSelector:@selector(typeIdentifierForClient)] && [item respondsToSelector:@selector(dataForClient)])
+                [additionalData setObject:item.dataForClient forKey:item.typeIdentifierForClient];
+        }];
+        adjustedItemProviders = [uiDelegate _webView:_webView adjustedDataInteractionItemProvidersForItemProvider:defaultItemProvider representingObjects:representingObjects.get() additionalData:additionalData.get()];
+    } else
+        adjustedItemProviders = @[ defaultItemProvider ];
+
+    NSMutableArray *dragItems = [NSMutableArray arrayWithCapacity:adjustedItemProviders.count];
+    for (UIItemProvider *itemProvider in adjustedItemProviders) {
+        auto item = adoptNS([[UIDragItem alloc] initWithItemProvider:itemProvider]);
+        [item _setPrivateLocalContext:@(stagedDragSource.itemIdentifier)];
+        [dragItems addObject:item.autorelease()];
+    }
+
+    return dragItems;
+}
+
+- (NSDictionary *)_autofillContext
+{
+    BOOL provideStrongPasswordAssistance = _focusRequiresStrongPasswordAssistance && _assistedNodeInformation.elementType == InputType::Password;
+    if (_assistedNodeInformation.elementType == InputType::None || (!_assistedNodeInformation.acceptsAutofilledLoginCredentials && !provideStrongPasswordAssistance))
+        return nil;
+
+    if (provideStrongPasswordAssistance)
+        return @{ @"_automaticPasswordKeyboard" : @YES };
+
+    NSURL *platformURL = _assistedNodeInformation.representingPageURL;
+    if (platformURL)
+        return @{ @"_WebViewURL" : platformURL };
+
+    return nil;
 }
 
 #pragma mark - UIDragInteractionDelegate
@@ -4549,6 +5173,17 @@ static NSArray<UIItemProvider *> *extractItemProvidersFromDropSession(id <UIDrop
     return dataOwner;
 }
 
+- (void)_dragInteraction:(UIDragInteraction *)interaction itemsForAddingToSession:(id <UIDragSession>)session withTouchAtPoint:(CGPoint)point completion:(void(^)(NSArray<UIDragItem *> *))completion
+{
+    if (!_dragDropInteractionState.shouldRequestAdditionalItemForDragSession(session)) {
+        completion(@[ ]);
+        return;
+    }
+
+    _dragDropInteractionState.dragSessionWillRequestAdditionalItem(completion);
+    _page->requestAdditionalItemsForDragSession(roundedIntPoint(point), roundedIntPoint(point));
+}
+
 - (void)_dragInteraction:(UIDragInteraction *)interaction prepareForSession:(id <UIDragSession>)session completion:(dispatch_block_t)completion
 {
     [self _cancelLongPressGestureRecognizer];
@@ -4563,9 +5198,9 @@ static NSArray<UIItemProvider *> *extractItemProvidersFromDropSession(id <UIDrop
 
     [self cleanUpDragSourceSessionState];
 
+    _dragDropInteractionState.prepareForDragSession(session, completion);
+
     auto dragOrigin = roundedIntPoint([session locationInView:self]);
-    _dataInteractionState.dragStartCompletionBlock = completion;
-    _dataInteractionState.dragSession = session;
     _page->requestStartDataInteraction(dragOrigin, roundedIntPoint([self convertPoint:dragOrigin toView:self.window]));
 
     RELEASE_LOG(DragAndDrop, "Drag session requested: %p at origin: {%d, %d}", session, dragOrigin.x(), dragOrigin.y());
@@ -4573,57 +5208,28 @@ static NSArray<UIItemProvider *> *extractItemProvidersFromDropSession(id <UIDrop
 
 - (NSArray<UIDragItem *> *)dragInteraction:(UIDragInteraction *)interaction itemsForBeginningSession:(id <UIDragSession>)session
 {
+    ASSERT(interaction == _dragInteraction);
     RELEASE_LOG(DragAndDrop, "Drag items requested for session: %p", session);
-    if (_dataInteractionState.dragSession != session) {
-        RELEASE_LOG(DragAndDrop, "Drag session failed: %p (delegate session does not match %p)", session, _dataInteractionState.dragSession.get());
+    if (_dragDropInteractionState.dragSession() != session) {
+        RELEASE_LOG(DragAndDrop, "Drag session failed: %p (delegate session does not match %p)", session, _dragDropInteractionState.dragSession());
         return @[ ];
     }
 
-    if (_dataInteractionState.sourceAction == DragSourceActionNone) {
-        RELEASE_LOG(DragAndDrop, "Drag session failed: %p (no drag source action)", session);
+    if (!_dragDropInteractionState.hasStagedDragSource()) {
+        RELEASE_LOG(DragAndDrop, "Drag session failed: %p (missing staged drag source)", session);
         return @[ ];
     }
 
-    WebItemProviderPasteboard *draggingPasteboard = [WebItemProviderPasteboard sharedInstance];
-    ASSERT(interaction == _dataInteraction);
-    NSUInteger numberOfItems = draggingPasteboard.numberOfItems;
-    if (!numberOfItems) {
-        RELEASE_LOG(DragAndDrop, "Drag session failed: %p (no item providers generated before adjustment)", session);
+    auto stagedDragSource = _dragDropInteractionState.stagedDragSource();
+    WebItemProviderRegistrationInfoList *registrationList = [[WebItemProviderPasteboard sharedInstance] takeRegistrationList];
+    NSArray *dragItems = [self _itemsForBeginningOrAddingToSessionWithRegistrationList:registrationList stagedDragSource:stagedDragSource];
+    if (![dragItems count])
         _page->dragCancelled();
-        return @[ ];
-    }
 
-    // Give internal clients such as Mail one final chance to augment the contents of each UIItemProvider before sending the drag items off to UIKit.
-    id <WKUIDelegatePrivate> uiDelegate = self.webViewUIDelegate;
-    if ([uiDelegate respondsToSelector:@selector(_webView:adjustedDataInteractionItemProvidersForItemProvider:representingObjects:additionalData:)]) {
-        NSMutableArray *adjustedItemProviders = [NSMutableArray array];
-        for (NSUInteger itemIndex = 0; itemIndex < numberOfItems; ++itemIndex) {
-            WebItemProviderRegistrationInfoList *infoList = [draggingPasteboard registrationInfoAtIndex:itemIndex];
-            auto representingObjects = adoptNS([[NSMutableArray alloc] init]);
-            auto additionalData = adoptNS([[NSMutableDictionary alloc] init]);
-            [infoList enumerateItems:[representingObjects, additionalData] (WebItemProviderRegistrationInfo *item, NSUInteger) {
-                if (item.representingObject)
-                    [representingObjects addObject:item.representingObject];
-                if (item.typeIdentifier && item.data)
-                    [additionalData setObject:item.data forKey:item.typeIdentifier];
-            }];
-            if (NSArray *replacementItemProviders = [uiDelegate _webView:_webView adjustedDataInteractionItemProvidersForItemProvider:[draggingPasteboard itemProviderAtIndex:itemIndex] representingObjects:representingObjects.get() additionalData:additionalData.get()])
-                [adjustedItemProviders addObjectsFromArray:replacementItemProviders];
-        }
-        draggingPasteboard.itemProviders = adjustedItemProviders;
-    } else if ([uiDelegate respondsToSelector:@selector(_webView:adjustedDataInteractionItemProviders:)])
-        draggingPasteboard.itemProviders = [uiDelegate _webView:_webView adjustedDataInteractionItemProviders:draggingPasteboard.itemProviders];
+    RELEASE_LOG(DragAndDrop, "Drag session: %p starting with %tu items", session, [dragItems count]);
+    _dragDropInteractionState.clearStagedDragSource([dragItems count] ? DragDropInteractionState::DidBecomeActive::Yes : DragDropInteractionState::DidBecomeActive::No);
 
-    __block RetainPtr<NSMutableArray> itemsForDragInteraction = [NSMutableArray array];
-    [draggingPasteboard enumerateItemProvidersWithBlock:^(UIItemProvider *itemProvider, NSUInteger index, BOOL *stop) {
-        [itemsForDragInteraction addObject:[[[UIDragItem alloc] initWithItemProvider:itemProvider] autorelease]];
-    }];
-
-    if (![itemsForDragInteraction count])
-        _page->dragCancelled();
-
-    RELEASE_LOG(DragAndDrop, "Drag session: %p starting with %tu items", session, [itemsForDragInteraction count]);
-    return itemsForDragInteraction.get();
+    return dragItems;
 }
 
 - (UITargetedDragPreview *)dragInteraction:(UIDragInteraction *)interaction previewForLiftingItem:(UIDragItem *)item session:(id <UIDragSession>)session
@@ -4634,27 +5240,26 @@ static NSArray<UIItemProvider *> *extractItemProvidersFromDropSession(id <UIDrop
         if (overridenPreview)
             return overridenPreview;
     }
-    return self.dragPreviewForCurrentDataInteractionState.autorelease();
+    return _dragDropInteractionState.previewForDragItem(item, self, self.unscaledView);
 }
 
 - (void)dragInteraction:(UIDragInteraction *)interaction willAnimateLiftWithAnimator:(id <UIDragAnimating>)animator session:(id <UIDragSession>)session
 {
-    if (!_dataInteractionState.shouldRestoreCalloutBar && (_dataInteractionState.sourceAction & DragSourceActionSelection)) {
+    if (!_shouldRestoreCalloutBarAfterDrop && _dragDropInteractionState.anyActiveDragSourceIs(DragSourceActionSelection)) {
         // FIXME: This SPI should be renamed in UIKit to reflect a more general purpose of hiding interaction assistant controls.
         [_webSelectionAssistant willStartScrollingOverflow];
         [_textSelectionAssistant willStartScrollingOverflow];
-        _dataInteractionState.shouldRestoreCalloutBar = YES;
+        _shouldRestoreCalloutBarAfterDrop = YES;
     }
 
-    auto adjustedOrigin = _dataInteractionState.adjustedOrigin;
+    auto positionForDragEnd = roundedIntPoint(_dragDropInteractionState.adjustedPositionForDragEnd());
     RetainPtr<WKContentView> protectedSelf(self);
-    [animator addCompletion:[session, adjustedOrigin, protectedSelf, page = _page] (UIViewAnimatingPosition finalPosition) {
+    [animator addCompletion:[session, positionForDragEnd, protectedSelf, page = _page] (UIViewAnimatingPosition finalPosition) {
         if (finalPosition == UIViewAnimatingPositionStart) {
             RELEASE_LOG(DragAndDrop, "Drag session ended at start: %p", session);
             // The lift was canceled, so -dropInteraction:sessionDidEnd: will never be invoked. This is the last chance to clean up.
             [protectedSelf cleanUpDragSourceSessionState];
-            auto originInWindowCoordinates = [protectedSelf convertPoint:adjustedOrigin toView:[protectedSelf window]];
-            page->dragEnded(roundedIntPoint(adjustedOrigin), roundedIntPoint(originInWindowCoordinates), DragOperationNone);
+            page->dragEnded(positionForDragEnd, positionForDragEnd, DragOperationNone);
         }
     }];
 }
@@ -4667,16 +5272,13 @@ static NSArray<UIItemProvider *> *extractItemProvidersFromDropSession(id <UIDrop
         [uiDelegate _webView:_webView dataInteraction:interaction sessionWillBegin:session];
 
     [_actionSheetAssistant cleanupSheet];
-
-    _dataInteractionState.didBeginDragging = YES;
-    [self _transitionDragPreviewToImageIfNecessary:session];
-
+    _dragDropInteractionState.dragSessionWillBegin();
     _page->didStartDrag();
 }
 
 - (void)dragInteraction:(UIDragInteraction *)interaction session:(id <UIDragSession>)session didEndWithOperation:(UIDropOperation)operation
 {
-    RELEASE_LOG(DragAndDrop, "Drag session ended: %p (with operation: %tu, performing operation: %d, began dragging: %d)", session, operation, _dataInteractionState.isPerformingOperation, _dataInteractionState.didBeginDragging);
+    RELEASE_LOG(DragAndDrop, "Drag session ended: %p (with operation: %tu, performing operation: %d, began dragging: %d)", session, operation, _dragDropInteractionState.isPerformingDrop(), _dragDropInteractionState.didBeginDragging());
 
     [self _restoreCalloutBarIfNeeded];
 
@@ -4684,11 +5286,11 @@ static NSArray<UIItemProvider *> *extractItemProvidersFromDropSession(id <UIDrop
     if ([uiDelegate respondsToSelector:@selector(_webView:dataInteraction:session:didEndWithOperation:)])
         [uiDelegate _webView:_webView dataInteraction:interaction session:session didEndWithOperation:operation];
 
-    if (_dataInteractionState.isPerformingOperation)
+    if (_dragDropInteractionState.isPerformingDrop())
         return;
 
     [self cleanUpDragSourceSessionState];
-    _page->dragEnded(roundedIntPoint(_dataInteractionState.adjustedOrigin), roundedIntPoint([self convertPoint:_dataInteractionState.adjustedOrigin toView:self.window]), operation);
+    _page->dragEnded(roundedIntPoint(_dragDropInteractionState.adjustedPositionForDragEnd()), roundedIntPoint(_dragDropInteractionState.adjustedPositionForDragEnd()), operation);
 }
 
 - (UITargetedDragPreview *)dragInteraction:(UIDragInteraction *)interaction previewForCancellingItem:(UIDragItem *)item withDefault:(UITargetedDragPreview *)defaultPreview
@@ -4699,12 +5301,12 @@ static NSArray<UIItemProvider *> *extractItemProvidersFromDropSession(id <UIDrop
         if (overridenPreview)
             return overridenPreview;
     }
-    return self.dragPreviewForCurrentDataInteractionState.autorelease();
+    return _dragDropInteractionState.previewForDragItem(item, self, self.unscaledView);
 }
 
 - (BOOL)_dragInteraction:(UIDragInteraction *)interaction item:(UIDragItem *)item shouldDelaySetDownAnimationWithCompletion:(void(^)(void))completion
 {
-    _dataInteractionState.dragCancelSetDownBlock = completion;
+    _dragDropInteractionState.dragSessionWillDelaySetDownAnimation(completion);
     return YES;
 }
 
@@ -4712,8 +5314,7 @@ static NSArray<UIItemProvider *> *extractItemProvidersFromDropSession(id <UIDrop
 {
     [animator addCompletion:[protectedSelf = retainPtr(self), page = _page] (UIViewAnimatingPosition finalPosition) {
         page->dragCancelled();
-        if (auto completion = protectedSelf->_dataInteractionState.dragCancelSetDownBlock) {
-            protectedSelf->_dataInteractionState.dragCancelSetDownBlock = nil;
+        if (auto completion = protectedSelf->_dragDropInteractionState.takeDragCancelSetDownBlock()) {
             page->callAfterNextPresentationUpdate([completion] (CallbackBase::Error) {
                 completion();
             });
@@ -4721,6 +5322,11 @@ static NSArray<UIItemProvider *> *extractItemProvidersFromDropSession(id <UIDrop
     }];
 }
 
+- (void)dragInteraction:(UIDragInteraction *)interaction sessionDidTransferItems:(id <UIDragSession>)session
+{
+    [existingLocalDragSessionContext(session) cleanUpTemporaryDirectories];
+}
+
 #pragma mark - UIDropInteractionDelegate
 
 - (NSInteger)_dropInteraction:(UIDropInteraction *)interaction dataOwnerForSession:(id <UIDropSession>)session
@@ -4744,14 +5350,12 @@ static NSArray<UIItemProvider *> *extractItemProvidersFromDropSession(id <UIDrop
 - (void)dropInteraction:(UIDropInteraction *)interaction sessionDidEnter:(id <UIDropSession>)session
 {
     RELEASE_LOG(DragAndDrop, "Drop session entered: %p with %tu items", session, session.items.count);
-    _dataInteractionState.dropSession = session;
-
-    [[WebItemProviderPasteboard sharedInstance] setItemProviders:extractItemProvidersFromDropSession(session)];
-
     auto dragData = [self dragDataForDropSession:session dragDestinationAction:[self _dragDestinationActionForDropSession:session]];
 
+    _dragDropInteractionState.dropSessionDidEnterOrUpdate(session, dragData);
+
+    [[WebItemProviderPasteboard sharedInstance] setItemProviders:extractItemProvidersFromDropSession(session)];
     _page->dragEntered(dragData, "data interaction pasteboard");
-    _dataInteractionState.lastGlobalPosition = dragData.globalPosition();
 }
 
 - (UIDropProposal *)dropInteraction:(UIDropInteraction *)interaction sessionDidUpdate:(id <UIDropSession>)session
@@ -4760,7 +5364,7 @@ static NSArray<UIItemProvider *> *extractItemProvidersFromDropSession(id <UIDrop
 
     auto dragData = [self dragDataForDropSession:session dragDestinationAction:[self _dragDestinationActionForDropSession:session]];
     _page->dragUpdated(dragData, "data interaction pasteboard");
-    _dataInteractionState.lastGlobalPosition = dragData.globalPosition();
+    _dragDropInteractionState.dropSessionDidEnterOrUpdate(session, dragData);
 
     NSUInteger operation = dropOperationForWebCoreDragOperation(_page->currentDragOperation());
     if ([self.webViewUIDelegate respondsToSelector:@selector(_webView:willUpdateDataInteractionOperationToOperation:forSession:)])
@@ -4778,7 +5382,7 @@ static NSArray<UIItemProvider *> *extractItemProvidersFromDropSession(id <UIDrop
     _page->dragExited(dragData, "data interaction pasteboard");
     _page->resetCurrentDragInformation();
 
-    _dataInteractionState.dropSession = nil;
+    _dragDropInteractionState.dropSessionDidExit();
 }
 
 - (void)dropInteraction:(UIDropInteraction *)interaction performDrop:(id <UIDropSession>)session
@@ -4796,9 +5400,10 @@ static NSArray<UIItemProvider *> *extractItemProvidersFromDropSession(id <UIDrop
             return;
     }
 
+    _dragDropInteractionState.dropSessionWillPerformDrop();
+
     [[WebItemProviderPasteboard sharedInstance] setItemProviders:itemProviders];
     [[WebItemProviderPasteboard sharedInstance] incrementPendingOperationCount];
-    _dataInteractionState.isPerformingOperation = YES;
     auto dragData = [self dragDataForDropSession:session dragDestinationAction:WKDragDestinationActionAny];
 
     RELEASE_LOG(DragAndDrop, "Loading data from %tu item providers for session: %p", itemProviders.count, session);
@@ -4817,13 +5422,13 @@ static NSArray<UIItemProvider *> *extractItemProvidersFromDropSession(id <UIDrop
         SandboxExtension::Handle sandboxExtensionHandle;
         SandboxExtension::HandleArray sandboxExtensionForUpload;
         retainedSelf->_page->createSandboxExtensionsIfNeeded(filenames, sandboxExtensionHandle, sandboxExtensionForUpload);
-        retainedSelf->_page->performDragOperation(capturedDragData, "data interaction pasteboard", sandboxExtensionHandle, sandboxExtensionForUpload);
+        retainedSelf->_page->performDragOperation(capturedDragData, "data interaction pasteboard", WTFMove(sandboxExtensionHandle), WTFMove(sandboxExtensionForUpload));
 
-        retainedSelf->_dataInteractionState.visibleContentViewSnapshot = [retainedSelf snapshotViewAfterScreenUpdates:NO];
+        retainedSelf->_visibleContentViewSnapshot = [retainedSelf snapshotViewAfterScreenUpdates:NO];
         [retainedSelf setSuppressAssistantSelectionView:YES];
         [UIView performWithoutAnimation:[retainedSelf] {
-            [retainedSelf->_dataInteractionState.visibleContentViewSnapshot setFrame:[retainedSelf bounds]];
-            [retainedSelf addSubview:retainedSelf->_dataInteractionState.visibleContentViewSnapshot.get()];
+            [retainedSelf->_visibleContentViewSnapshot setFrame:[retainedSelf bounds]];
+            [retainedSelf addSubview:retainedSelf->_visibleContentViewSnapshot.get()];
         }];
     }];
 }
@@ -4845,9 +5450,16 @@ static NSArray<UIItemProvider *> *extractItemProvidersFromDropSession(id <UIDrop
 
 - (void)dropInteraction:(UIDropInteraction *)interaction sessionDidEnd:(id <UIDropSession>)session
 {
-    RELEASE_LOG(DragAndDrop, "Drop session ended: %p (performing operation: %d, began dragging: %d)", session, _dataInteractionState.isPerformingOperation, _dataInteractionState.didBeginDragging);
-    if (_dataInteractionState.isPerformingOperation || _dataInteractionState.didBeginDragging)
+    RELEASE_LOG(DragAndDrop, "Drop session ended: %p (performing operation: %d, began dragging: %d)", session, _dragDropInteractionState.isPerformingDrop(), _dragDropInteractionState.didBeginDragging());
+    if (_dragDropInteractionState.isPerformingDrop()) {
+        // In the case where we are performing a drop, wait until after the drop is handled in the web process to reset drag and drop interaction state.
+        return;
+    }
+
+    if (_dragDropInteractionState.didBeginDragging()) {
+        // In the case where the content view is a source of drag items, wait until -dragInteraction:session:didEndWithOperation: to reset drag and drop interaction state.
         return;
+    }
 
     CGPoint global;
     CGPoint client;
@@ -4856,63 +5468,21 @@ static NSArray<UIItemProvider *> *extractItemProvidersFromDropSession(id <UIDrop
     _page->dragEnded(roundedIntPoint(client), roundedIntPoint(global), DragOperationNone);
 }
 
-#pragma mark - Unit testing support
-
-- (void)_simulateDataInteractionEntered:(id)session
-{
-    [self dropInteraction:_dataOperation.get() sessionDidEnter:session];
-}
-
-- (NSUInteger)_simulateDataInteractionUpdated:(id)session
-{
-    return [self dropInteraction:_dataOperation.get() sessionDidUpdate:session].operation;
-}
-
-- (void)_simulateDataInteractionEnded:(id)session
-{
-    [self dropInteraction:_dataOperation.get() sessionDidEnd:session];
-}
-
-- (void)_simulateDataInteractionPerformOperation:(id)session
-{
-    [self dropInteraction:_dataOperation.get() performDrop:session];
-}
-
-- (void)_simulateDataInteractionSessionDidEnd:(id)session
-{
-    [self dragInteraction:_dataInteraction.get() session:session didEndWithOperation:UIDropOperationCopy];
-}
-
-- (void)_simulateWillBeginDataInteractionWithSession:(id)session
-{
-    [self dragInteraction:_dataInteraction.get() sessionWillBegin:session];
-}
-
-- (NSArray *)_simulatedItemsForSession:(id)session
-{
-    return [self dragInteraction:_dataInteraction.get() itemsForBeginningSession:session];
-}
+#endif
 
-- (void)_simulatePrepareForDataInteractionSession:(id)session completion:(dispatch_block_t)completion
-{
-    [self _dragInteraction:_dataInteraction.get() prepareForSession:session completion:completion];
-}
+@end
 
-#endif
+@implementation WKContentView (WKTesting)
 
 - (void)_simulateLongPressActionAtLocation:(CGPoint)location
 {
     RetainPtr<WKContentView> protectedSelf = self;
-    [self doAfterPositionInformationUpdate:[location, protectedSelf] (InteractionInformationAtPosition) {
+    [self doAfterPositionInformationUpdate:[protectedSelf] (InteractionInformationAtPosition) {
         if (SEL action = [protectedSelf _actionForLongPress])
             [protectedSelf performSelector:action];
     } forRequest:InteractionInformationRequest(roundedIntPoint(location))];
 }
 
-@end
-
-@implementation WKContentView (WKTesting)
-
 - (void)selectFormAccessoryPickerRow:(NSInteger)rowIndex
 {
     if ([_inputPeripheral isKindOfClass:[WKFormSelectControl self]])
@@ -4984,8 +5554,10 @@ static NSArray<UIItemProvider *> *extractItemProvidersFromDropSession(id <UIDrop
             return NO;
         if (linkURL.protocolIsInHTTPFamily())
             return YES;
-        if ([[getDDDetectionControllerClass() tapAndHoldSchemes] containsObject:linkURL.protocol().toString().convertToASCIILowercase()])
+#if ENABLE(DATA_DETECTION)
+        if (DataDetection::canBePresentedByDataDetectors(linkURL))
             return YES;
+#endif
         return NO;
     }
     return YES;
@@ -5004,6 +5576,10 @@ static NSArray<UIItemProvider *> *extractItemProvidersFromDropSession(id <UIDrop
     BOOL supportsAttachmentPreview = ([uiDelegate respondsToSelector:@selector(_attachmentListForWebView:)] || respondsToAttachmentListForWebViewSourceIsManaged)
         && [uiDelegate respondsToSelector:@selector(_webView:indexIntoAttachmentListForElement:)];
     BOOL canShowAttachmentPreview = (_positionInformation.isAttachment || _positionInformation.isImage) && supportsAttachmentPreview;
+    BOOL isDataDetectorLink = NO;
+#if ENABLE(DATA_DETECTION)
+    isDataDetectorLink = _positionInformation.isDataDetectorLink;
+#endif
 
     if (canShowImagePreview && _positionInformation.isAnimatedImage) {
         canShowImagePreview = NO;
@@ -5015,7 +5591,7 @@ static NSArray<UIItemProvider *> *extractItemProvidersFromDropSession(id <UIDrop
         return nil;
 
     const URL& linkURL = _positionInformation.url;
-    if (!useImageURLForLink && (linkURL.isEmpty() || (!linkURL.protocolIsInHTTPFamily() && !_positionInformation.isDataDetectorLink))) {
+    if (!useImageURLForLink && (linkURL.isEmpty() || (!linkURL.protocolIsInHTTPFamily() && !isDataDetectorLink))) {
         if (canShowLinkPreview && !canShowImagePreview)
             return nil;
         canShowLinkPreview = NO;
@@ -5028,7 +5604,8 @@ static NSArray<UIItemProvider *> *extractItemProvidersFromDropSession(id <UIDrop
             dataForPreview[UIPreviewDataLink] = (NSURL *)_positionInformation.imageURL;
         else
             dataForPreview[UIPreviewDataLink] = (NSURL *)linkURL;
-        if (_positionInformation.isDataDetectorLink) {
+#if ENABLE(DATA_DETECTION)
+        if (isDataDetectorLink) {
             NSDictionary *context = nil;
             if ([uiDelegate respondsToSelector:@selector(_dataDetectionContextForWebView:)])
                 context = [uiDelegate _dataDetectionContextForWebView:_webView];
@@ -5052,6 +5629,7 @@ static NSArray<UIItemProvider *> *extractItemProvidersFromDropSession(id <UIDrop
             if (newContext)
                 dataForPreview[UIPreviewDataDDContext] = newContext;
         }
+#endif // ENABLE(DATA_DETECTION)
     } else if (canShowImagePreview) {
         *type = UIPreviewItemTypeImage;
         dataForPreview[UIPreviewDataLink] = (NSURL *)_positionInformation.imageURL;
@@ -5067,11 +5645,15 @@ static NSArray<UIItemProvider *> *extractItemProvidersFromDropSession(id <UIDrop
                 dataForPreview[UIPreviewDataAttachmentList] = [uiDelegate _attachmentListForWebView:_webView];
             dataForPreview[UIPreviewDataAttachmentIndex] = [NSNumber numberWithUnsignedInteger:index];
 
+#if __IPHONE_OS_VERSION_MIN_REQUIRED >= 120000
             // FIXME: Replace the following NSString literal with a UIKit NSString constant.
-            dataForPreview[@"UIPreviewDataAttachmentListSourceIsManaged"] = [NSNumber numberWithBool:sourceIsManaged];
+            dataForPreview[@"UIPreviewDataAttachmentListIsContentManaged"] = [NSNumber numberWithBool:sourceIsManaged];
+#else
+            dataForPreview[UIPreviewDataAttachmentListSourceIsManaged] = [NSNumber numberWithBool:sourceIsManaged];
+#endif
         }
     }
-    
+
     return dataForPreview;
 }