52cdfa52578d2d34f72c6c69209e2f462199a899
[WebKit-https.git] / Source / WebKit / UIProcess / ios / WKContentViewInteraction.mm
1 /*
2  * Copyright (C) 2012-2019 Apple Inc. All rights reserved.
3  *
4  * Redistribution and use in source and binary forms, with or without
5  * modification, are permitted provided that the following conditions
6  * are met:
7  * 1. Redistributions of source code must retain the above copyright
8  *    notice, this list of conditions and the following disclaimer.
9  * 2. Redistributions in binary form must reproduce the above copyright
10  *    notice, this list of conditions and the following disclaimer in the
11  *    documentation and/or other materials provided with the distribution.
12  *
13  * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
14  * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
15  * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16  * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
17  * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
18  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
19  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
20  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
21  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
22  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
23  * THE POSSIBILITY OF SUCH DAMAGE.
24  */
25
26 #import "config.h"
27 #import "WKContentViewInteraction.h"
28
29 #if PLATFORM(IOS_FAMILY)
30
31 #import "APIUIClient.h"
32 #import "CompletionHandlerCallChecker.h"
33 #import "DocumentEditingContext.h"
34 #import "EditableImageController.h"
35 #import "InputViewUpdateDeferrer.h"
36 #import "InsertTextOptions.h"
37 #import "Logging.h"
38 #import "NativeWebKeyboardEvent.h"
39 #import "NativeWebTouchEvent.h"
40 #import "RemoteLayerTreeDrawingAreaProxy.h"
41 #import "RemoteLayerTreeViews.h"
42 #import "SmartMagnificationController.h"
43 #import "TextInputSPI.h"
44 #import "UIKitSPI.h"
45 #import "VersionChecks.h"
46 #import "WKActionSheetAssistant.h"
47 #import "WKContextMenuElementInfoInternal.h"
48 #import "WKContextMenuElementInfoPrivate.h"
49 #import "WKDatePickerViewController.h"
50 #import "WKDrawingCoordinator.h"
51 #import "WKError.h"
52 #import "WKFocusedFormControlView.h"
53 #import "WKFormInputControl.h"
54 #import "WKFormSelectControl.h"
55 #import "WKHighlightLongPressGestureRecognizer.h"
56 #import "WKImagePreviewViewController.h"
57 #import "WKInspectorNodeSearchGestureRecognizer.h"
58 #import "WKNSURLExtras.h"
59 #import "WKPreviewActionItemIdentifiers.h"
60 #import "WKPreviewActionItemInternal.h"
61 #import "WKPreviewElementInfoInternal.h"
62 #import "WKQuickboardListViewController.h"
63 #import "WKSelectMenuListViewController.h"
64 #import "WKSyntheticFlagsChangedWebEvent.h"
65 #import "WKTextInputListViewController.h"
66 #import "WKTimePickerViewController.h"
67 #import "WKUIDelegatePrivate.h"
68 #import "WKWebViewConfiguration.h"
69 #import "WKWebViewConfigurationPrivate.h"
70 #import "WKWebViewInternal.h"
71 #import "WKWebViewPrivate.h"
72 #import "WebAutocorrectionContext.h"
73 #import "WebAutocorrectionData.h"
74 #import "WebDataListSuggestionsDropdownIOS.h"
75 #import "WebEvent.h"
76 #import "WebIOSEventFactory.h"
77 #import "WebPageMessages.h"
78 #import "WebProcessProxy.h"
79 #import "_WKActivatedElementInfoInternal.h"
80 #import "_WKElementAction.h"
81 #import "_WKElementActionInternal.h"
82 #import "_WKFocusedElementInfo.h"
83 #import "_WKInputDelegate.h"
84 #import "_WKTextInputContextInternal.h"
85 #import <CoreText/CTFont.h>
86 #import <CoreText/CTFontDescriptor.h>
87 #import <MobileCoreServices/UTCoreTypes.h>
88 #import <WebCore/Color.h>
89 #import <WebCore/DOMPasteAccess.h>
90 #import <WebCore/DataDetection.h>
91 #import <WebCore/FloatQuad.h>
92 #import <WebCore/FontAttributeChanges.h>
93 #import <WebCore/InputMode.h>
94 #import <WebCore/KeyEventCodesIOS.h>
95 #import <WebCore/LocalizedStrings.h>
96 #import <WebCore/MIMETypeRegistry.h>
97 #import <WebCore/NotImplemented.h>
98 #import <WebCore/Pasteboard.h>
99 #import <WebCore/Path.h>
100 #import <WebCore/PathUtilities.h>
101 #import <WebCore/PromisedAttachmentInfo.h>
102 #import <WebCore/RuntimeApplicationChecks.h>
103 #import <WebCore/Scrollbar.h>
104 #import <WebCore/ShareData.h>
105 #import <WebCore/TextIndicator.h>
106 #import <WebCore/UTIUtilities.h>
107 #import <WebCore/VisibleSelection.h>
108 #import <WebCore/WebEvent.h>
109 #import <WebCore/WritingDirection.h>
110 #import <WebKit/WebSelectionRect.h> // FIXME: WK2 should not include WebKit headers!
111 #import <pal/spi/cg/CoreGraphicsSPI.h>
112 #import <pal/spi/cocoa/DataDetectorsCoreSPI.h>
113 #import <pal/spi/cocoa/LaunchServicesSPI.h>
114 #import <pal/spi/ios/DataDetectorsUISPI.h>
115 #import <pal/spi/ios/GraphicsServicesSPI.h>
116 #import <wtf/BlockObjCExceptions.h>
117 #import <wtf/BlockPtr.h>
118 #import <wtf/Optional.h>
119 #import <wtf/RetainPtr.h>
120 #import <wtf/SetForScope.h>
121 #import <wtf/WeakObjCPtr.h>
122 #import <wtf/cocoa/NSURLExtras.h>
123 #import <wtf/text/TextStream.h>
124
125 #if ENABLE(DRAG_SUPPORT)
126 #import <WebCore/DragData.h>
127 #import <WebCore/DragItem.h>
128 #import <WebCore/PlatformPasteboard.h>
129 #import <WebCore/WebItemProviderPasteboard.h>
130 #endif
131
132 #if PLATFORM(MACCATALYST)
133 #import <UIKit/_UILookupGestureRecognizer.h>
134 #endif
135
136 #if HAVE(HOVER_GESTURE_RECOGNIZER)
137 #import "NativeWebMouseEvent.h"
138 #import <UIKit/UIHoverGestureRecognizer.h>
139 #endif
140
141 #if ENABLE(INPUT_TYPE_COLOR)
142 #import "WKFormColorControl.h"
143 #endif
144
145 #if !USE(UIKIT_KEYBOARD_ADDITIONS)
146 #import "WKWebEvent.h"
147 #endif
148
149 #if USE(APPLE_INTERNAL_SDK) && __has_include(<WebKitAdditions/WKPlatformFileUploadPanel.mm>)
150 #import <WebKitAdditions/WKPlatformFileUploadPanel.mm>
151 #endif
152
153 #if ENABLE(POINTER_EVENTS)
154 #import "RemoteScrollingCoordinatorProxy.h"
155 #import <WebCore/TouchAction.h>
156 #endif
157
158 #if !PLATFORM(MACCATALYST)
159 #import "ManagedConfigurationSPI.h"
160 #import <wtf/SoftLinking.h>
161
162 SOFT_LINK_PRIVATE_FRAMEWORK(ManagedConfiguration);
163 SOFT_LINK_CLASS(ManagedConfiguration, MCProfileConnection);
164 SOFT_LINK_CONSTANT(ManagedConfiguration, MCFeatureDefinitionLookupAllowed, NSString *)
165 #endif
166
167 #if HAVE(LINK_PREVIEW) && USE(UICONTEXTMENU)
168 static NSString * const webkitShowLinkPreviewsPreferenceKey = @"WebKitShowLinkPreviews";
169 #endif
170
171 #if PLATFORM(WATCHOS)
172
173 @interface WKContentView (WatchSupport) <WKFocusedFormControlViewDelegate, WKSelectMenuListViewControllerDelegate, WKTextInputListViewControllerDelegate>
174 @end
175
176 #endif
177
178 namespace WebKit {
179 using namespace WebCore;
180 using namespace WebKit;
181
182 WKSelectionDrawingInfo::WKSelectionDrawingInfo()
183     : type(SelectionType::None)
184 {
185 }
186
187 WKSelectionDrawingInfo::WKSelectionDrawingInfo(const EditorState& editorState)
188 {
189     if (editorState.selectionIsNone) {
190         type = SelectionType::None;
191         return;
192     }
193
194     if (editorState.isInPlugin) {
195         type = SelectionType::Plugin;
196         return;
197     }
198
199     type = SelectionType::Range;
200     auto& postLayoutData = editorState.postLayoutData();
201     caretRect = postLayoutData.caretRectAtEnd;
202     selectionRects = postLayoutData.selectionRects;
203     selectionClipRect = postLayoutData.focusedElementRect;
204 }
205
206 inline bool operator==(const WKSelectionDrawingInfo& a, const WKSelectionDrawingInfo& b)
207 {
208     if (a.type != b.type)
209         return false;
210
211     if (a.type == WKSelectionDrawingInfo::SelectionType::Range) {
212         if (a.caretRect != b.caretRect)
213             return false;
214
215         if (a.selectionRects.size() != b.selectionRects.size())
216             return false;
217
218         for (unsigned i = 0; i < a.selectionRects.size(); ++i) {
219             if (a.selectionRects[i].rect() != b.selectionRects[i].rect())
220                 return false;
221         }
222     }
223
224     if (a.type != WKSelectionDrawingInfo::SelectionType::None && a.selectionClipRect != b.selectionClipRect)
225         return false;
226
227     return true;
228 }
229
230 inline bool operator!=(const WKSelectionDrawingInfo& a, const WKSelectionDrawingInfo& b)
231 {
232     return !(a == b);
233 }
234
235 static TextStream& operator<<(TextStream& stream, WKSelectionDrawingInfo::SelectionType type)
236 {
237     switch (type) {
238     case WKSelectionDrawingInfo::SelectionType::None: stream << "none"; break;
239     case WKSelectionDrawingInfo::SelectionType::Plugin: stream << "plugin"; break;
240     case WKSelectionDrawingInfo::SelectionType::Range: stream << "range"; break;
241     }
242     
243     return stream;
244 }
245
246 TextStream& operator<<(TextStream& stream, const WKSelectionDrawingInfo& info)
247 {
248     TextStream::GroupScope group(stream);
249     stream.dumpProperty("type", info.type);
250     stream.dumpProperty("caret rect", info.caretRect);
251     stream.dumpProperty("selection rects", info.selectionRects);
252     stream.dumpProperty("selection clip rect", info.selectionClipRect);
253     return stream;
254 }
255
256 } // namespace WebKit
257
258 constexpr float highlightDelay = 0.12;
259 constexpr float tapAndHoldDelay = 0.75;
260 constexpr CGFloat minimumTapHighlightRadius = 2.0;
261 constexpr double fasterTapSignificantZoomThreshold = 0.8;
262
263 @interface WKTextRange : UITextRange {
264     CGRect _startRect;
265     CGRect _endRect;
266     BOOL _isNone;
267     BOOL _isRange;
268     BOOL _isEditable;
269     NSArray *_selectionRects;
270     NSUInteger _selectedTextLength;
271 }
272 @property (nonatomic) CGRect startRect;
273 @property (nonatomic) CGRect endRect;
274 @property (nonatomic) BOOL isNone;
275 @property (nonatomic) BOOL isRange;
276 @property (nonatomic) BOOL isEditable;
277 @property (nonatomic) NSUInteger selectedTextLength;
278 @property (copy, nonatomic) NSArray *selectionRects;
279
280 + (WKTextRange *)textRangeWithState:(BOOL)isNone isRange:(BOOL)isRange isEditable:(BOOL)isEditable startRect:(CGRect)startRect endRect:(CGRect)endRect selectionRects:(NSArray *)selectionRects selectedTextLength:(NSUInteger)selectedTextLength;
281
282 @end
283
284 @interface WKTextPosition : UITextPosition {
285     CGRect _positionRect;
286 }
287
288 @property (nonatomic) CGRect positionRect;
289
290 + (WKTextPosition *)textPositionWithRect:(CGRect)positionRect;
291
292 @end
293
294 @interface WKTextSelectionRect : UITextSelectionRect
295
296 @property (nonatomic, retain) WebSelectionRect *webRect;
297
298 + (NSArray *)textSelectionRectsWithWebRects:(NSArray *)webRects;
299
300 @end
301
302 @interface WKAutocorrectionRects : UIWKAutocorrectionRects
303 + (WKAutocorrectionRects *)autocorrectionRectsWithFirstCGRect:(CGRect)firstRect lastCGRect:(CGRect)lastRect;
304 @end
305
306 @interface WKAutocorrectionContext : UIWKAutocorrectionContext
307 + (WKAutocorrectionContext *)emptyAutocorrectionContext;
308 + (WKAutocorrectionContext *)autocorrectionContextWithWebContext:(const WebKit::WebAutocorrectionContext&)context;
309 @end
310
311 @interface UITextInteractionAssistant (UITextInteractionAssistant_Internal)
312 // FIXME: this needs to be moved from the internal header to the private.
313 - (id)initWithView:(UIResponder <UITextInput> *)view;
314 - (void)selectWord;
315 @end
316
317 @interface UIView (UIViewInternalHack)
318 + (BOOL)_addCompletion:(void(^)(BOOL))completion;
319 @end
320
321 @protocol UISelectionInteractionAssistant;
322
323 @interface WKFocusedElementInfo : NSObject <_WKFocusedElementInfo>
324 - (instancetype)initWithFocusedElementInformation:(const WebKit::FocusedElementInformation&)information isUserInitiated:(BOOL)isUserInitiated userObject:(NSObject <NSSecureCoding> *)userObject;
325 @end
326
327 @implementation WKFormInputSession {
328     WeakObjCPtr<WKContentView> _contentView;
329     RetainPtr<WKFocusedElementInfo> _focusedElementInfo;
330     RetainPtr<UIView> _customInputView;
331     RetainPtr<UIView> _customInputAccessoryView;
332     RetainPtr<NSArray<UITextSuggestion *>> _suggestions;
333     BOOL _accessoryViewShouldNotShow;
334     BOOL _forceSecureTextEntry;
335     BOOL _requiresStrongPasswordAssistance;
336 }
337
338 - (instancetype)initWithContentView:(WKContentView *)view focusedElementInfo:(WKFocusedElementInfo *)elementInfo requiresStrongPasswordAssistance:(BOOL)requiresStrongPasswordAssistance
339 {
340     if (!(self = [super init]))
341         return nil;
342
343     _contentView = view;
344     _focusedElementInfo = elementInfo;
345     _requiresStrongPasswordAssistance = requiresStrongPasswordAssistance;
346
347     return self;
348 }
349
350 - (id <_WKFocusedElementInfo>)focusedElementInfo
351 {
352     return _focusedElementInfo.get();
353 }
354
355 - (NSObject <NSSecureCoding> *)userObject
356 {
357     return [_focusedElementInfo userObject];
358 }
359
360 - (BOOL)isValid
361 {
362     return !!_contentView;
363 }
364
365 - (NSString *)accessoryViewCustomButtonTitle
366 {
367     return [[[_contentView formAccessoryView] _autofill] title];
368 }
369
370 - (void)setAccessoryViewCustomButtonTitle:(NSString *)title
371 {
372     if (title.length)
373         [[_contentView formAccessoryView] showAutoFillButtonWithTitle:title];
374     else
375         [[_contentView formAccessoryView] hideAutoFillButton];
376     if (currentUserInterfaceIdiomIsPad())
377         [_contentView reloadInputViews];
378 }
379
380 - (BOOL)accessoryViewShouldNotShow
381 {
382     return _accessoryViewShouldNotShow;
383 }
384
385 - (void)setAccessoryViewShouldNotShow:(BOOL)accessoryViewShouldNotShow
386 {
387     if (_accessoryViewShouldNotShow == accessoryViewShouldNotShow)
388         return;
389
390     _accessoryViewShouldNotShow = accessoryViewShouldNotShow;
391     [_contentView reloadInputViews];
392 }
393
394 - (BOOL)forceSecureTextEntry
395 {
396     return _forceSecureTextEntry;
397 }
398
399 - (void)setForceSecureTextEntry:(BOOL)forceSecureTextEntry
400 {
401     if (_forceSecureTextEntry == forceSecureTextEntry)
402         return;
403
404     _forceSecureTextEntry = forceSecureTextEntry;
405     [_contentView reloadInputViews];
406 }
407
408 - (UIView *)customInputView
409 {
410     return _customInputView.get();
411 }
412
413 - (void)setCustomInputView:(UIView *)customInputView
414 {
415     if (customInputView == _customInputView)
416         return;
417
418     _customInputView = customInputView;
419     [_contentView reloadInputViews];
420 }
421
422 - (UIView *)customInputAccessoryView
423 {
424     return _customInputAccessoryView.get();
425 }
426
427 - (void)setCustomInputAccessoryView:(UIView *)customInputAccessoryView
428 {
429     if (_customInputAccessoryView == customInputAccessoryView)
430         return;
431
432     _customInputAccessoryView = customInputAccessoryView;
433     [_contentView reloadInputViews];
434 }
435
436 - (void)endEditing
437 {
438     if ([_customInputView conformsToProtocol:@protocol(WKFormControl)])
439         [(id<WKFormControl>)_customInputView.get() controlEndEditing];
440 }
441
442 - (NSArray<UITextSuggestion *> *)suggestions
443 {
444     return _suggestions.get();
445 }
446
447 - (void)setSuggestions:(NSArray<UITextSuggestion *> *)suggestions
448 {
449     if (suggestions == _suggestions || [suggestions isEqualToArray:_suggestions.get()])
450         return;
451
452     _suggestions = adoptNS([suggestions copy]);
453     [_contentView updateTextSuggestionsForInputDelegate];
454 }
455
456 - (BOOL)requiresStrongPasswordAssistance
457 {
458     return _requiresStrongPasswordAssistance;
459 }
460
461 - (void)invalidate
462 {
463     id <UITextInputSuggestionDelegate> suggestionDelegate = (id <UITextInputSuggestionDelegate>)[_contentView inputDelegate];
464     [suggestionDelegate setSuggestions:nil];
465     _contentView = nil;
466 }
467
468 - (void)reloadFocusedElementContextView
469 {
470     [_contentView reloadContextViewForPresentedListViewController];
471 }
472
473 @end
474
475 @implementation WKFocusedElementInfo {
476     WKInputType _type;
477     RetainPtr<NSString> _value;
478     BOOL _isUserInitiated;
479     RetainPtr<NSObject <NSSecureCoding>> _userObject;
480     RetainPtr<NSString> _placeholder;
481     RetainPtr<NSString> _label;
482 }
483
484 - (instancetype)initWithFocusedElementInformation:(const WebKit::FocusedElementInformation&)information isUserInitiated:(BOOL)isUserInitiated userObject:(NSObject <NSSecureCoding> *)userObject
485 {
486     if (!(self = [super init]))
487         return nil;
488
489     switch (information.elementType) {
490     case WebKit::InputType::ContentEditable:
491         _type = WKInputTypeContentEditable;
492         break;
493     case WebKit::InputType::Text:
494         _type = WKInputTypeText;
495         break;
496     case WebKit::InputType::Password:
497         _type = WKInputTypePassword;
498         break;
499     case WebKit::InputType::TextArea:
500         _type = WKInputTypeTextArea;
501         break;
502     case WebKit::InputType::Search:
503         _type = WKInputTypeSearch;
504         break;
505     case WebKit::InputType::Email:
506         _type = WKInputTypeEmail;
507         break;
508     case WebKit::InputType::URL:
509         _type = WKInputTypeURL;
510         break;
511     case WebKit::InputType::Phone:
512         _type = WKInputTypePhone;
513         break;
514     case WebKit::InputType::Number:
515         _type = WKInputTypeNumber;
516         break;
517     case WebKit::InputType::NumberPad:
518         _type = WKInputTypeNumberPad;
519         break;
520     case WebKit::InputType::Date:
521         _type = WKInputTypeDate;
522         break;
523     case WebKit::InputType::DateTime:
524         _type = WKInputTypeDateTime;
525         break;
526     case WebKit::InputType::DateTimeLocal:
527         _type = WKInputTypeDateTimeLocal;
528         break;
529     case WebKit::InputType::Month:
530         _type = WKInputTypeMonth;
531         break;
532     case WebKit::InputType::Week:
533         _type = WKInputTypeWeek;
534         break;
535     case WebKit::InputType::Time:
536         _type = WKInputTypeTime;
537         break;
538     case WebKit::InputType::Select:
539         _type = WKInputTypeSelect;
540         break;
541     case WebKit::InputType::Drawing:
542         _type = WKInputTypeDrawing;
543         break;
544 #if ENABLE(INPUT_TYPE_COLOR)
545     case WebKit::InputType::Color:
546         _type = WKInputTypeColor;
547         break;
548 #endif
549     case WebKit::InputType::None:
550         _type = WKInputTypeNone;
551         break;
552     }
553     _value = information.value;
554     _isUserInitiated = isUserInitiated;
555     _userObject = userObject;
556     _placeholder = information.placeholder;
557     _label = information.label;
558     return self;
559 }
560
561 - (WKInputType)type
562 {
563     return _type;
564 }
565
566 - (NSString *)value
567 {
568     return _value.get();
569 }
570
571 - (BOOL)isUserInitiated
572 {
573     return _isUserInitiated;
574 }
575
576 - (NSObject <NSSecureCoding> *)userObject
577 {
578     return _userObject.get();
579 }
580
581 - (NSString *)label
582 {
583     return _label.get();
584 }
585
586 - (NSString *)placeholder
587 {
588     return _placeholder.get();
589 }
590
591 @end
592
593 #if ENABLE(DRAG_SUPPORT)
594
595 @interface WKDragSessionContext : NSObject
596 - (void)addTemporaryDirectory:(NSString *)temporaryDirectory;
597 - (void)cleanUpTemporaryDirectories;
598 @end
599
600 @implementation WKDragSessionContext {
601     RetainPtr<NSMutableArray> _temporaryDirectories;
602 }
603
604 - (void)addTemporaryDirectory:(NSString *)temporaryDirectory
605 {
606     if (!_temporaryDirectories)
607         _temporaryDirectories = adoptNS([NSMutableArray new]);
608     [_temporaryDirectories addObject:temporaryDirectory];
609 }
610
611 - (void)cleanUpTemporaryDirectories
612 {
613     for (NSString *directory in _temporaryDirectories.get()) {
614         NSError *error = nil;
615         [[NSFileManager defaultManager] removeItemAtPath:directory error:&error];
616         RELEASE_LOG(DragAndDrop, "Removed temporary download directory: %@ with error: %@", directory, error);
617     }
618     _temporaryDirectories = nil;
619 }
620
621 @end
622
623 static WKDragSessionContext *existingLocalDragSessionContext(id <UIDragSession> session)
624 {
625     return [session.localContext isKindOfClass:[WKDragSessionContext class]] ? (WKDragSessionContext *)session.localContext : nil;
626 }
627
628 static WKDragSessionContext *ensureLocalDragSessionContext(id <UIDragSession> session)
629 {
630     if (WKDragSessionContext *existingContext = existingLocalDragSessionContext(session))
631         return existingContext;
632
633     if (session.localContext) {
634         RELEASE_LOG(DragAndDrop, "Overriding existing local context: %@ on session: %@", session.localContext, session);
635         ASSERT_NOT_REACHED();
636     }
637
638     session.localContext = [[[WKDragSessionContext alloc] init] autorelease];
639     return (WKDragSessionContext *)session.localContext;
640 }
641
642 #endif // ENABLE(DRAG_SUPPORT)
643
644 @interface WKContentView (WKInteractionPrivate)
645 - (void)accessibilitySpeakSelectionSetContent:(NSString *)string;
646 - (NSArray *)webSelectionRectsForSelectionRects:(const Vector<WebCore::SelectionRect>&)selectionRects;
647 - (void)_accessibilityDidGetSelectionRects:(NSArray *)selectionRects withGranularity:(UITextGranularity)granularity atOffset:(NSInteger)offset;
648 @end
649
650 @implementation WKContentView (WKInteraction)
651
652 static inline bool hasFocusedElement(WebKit::FocusedElementInformation focusedElementInformation)
653 {
654     return (focusedElementInformation.elementType != WebKit::InputType::None);
655 }
656
657 #if ENABLE(POINTER_EVENTS)
658 - (BOOL)preventsPanningInXAxis
659 {
660     return _preventsPanningInXAxis;
661 }
662
663 - (BOOL)preventsPanningInYAxis
664 {
665     return _preventsPanningInYAxis;
666 }
667 #endif
668
669 - (WKFormInputSession *)_formInputSession
670 {
671     return _formInputSession.get();
672 }
673
674 - (void)_createAndConfigureDoubleTapGestureRecognizer
675 {
676     if (_doubleTapGestureRecognizer) {
677         [self removeGestureRecognizer:_doubleTapGestureRecognizer.get()];
678         [_doubleTapGestureRecognizer setDelegate:nil];
679         [_doubleTapGestureRecognizer setGestureFailedTarget:nil action:nil];
680     }
681
682     _doubleTapGestureRecognizer = adoptNS([[WKSyntheticTapGestureRecognizer alloc] initWithTarget:self action:@selector(_doubleTapRecognized:)]);
683     [_doubleTapGestureRecognizer setGestureFailedTarget:self action:@selector(_doubleTapDidFail:)];
684     [_doubleTapGestureRecognizer setNumberOfTapsRequired:2];
685     [_doubleTapGestureRecognizer setDelegate:self];
686     [self addGestureRecognizer:_doubleTapGestureRecognizer.get()];
687     [_singleTapGestureRecognizer requireGestureRecognizerToFail:_doubleTapGestureRecognizer.get()];
688 }
689
690 - (void)_createAndConfigureLongPressGestureRecognizer
691 {
692     _longPressGestureRecognizer = adoptNS([[UILongPressGestureRecognizer alloc] initWithTarget:self action:@selector(_longPressRecognized:)]);
693     [_longPressGestureRecognizer setDelay:tapAndHoldDelay];
694     [_longPressGestureRecognizer setDelegate:self];
695     [_longPressGestureRecognizer _setRequiresQuietImpulse:YES];
696     [self addGestureRecognizer:_longPressGestureRecognizer.get()];
697 }
698
699 - (void)setupInteraction
700 {
701     // If the page is not valid yet then delay interaction setup until the process is launched/relaunched.
702     if (!_page->hasRunningProcess())
703         return;
704
705     if (_hasSetUpInteractions)
706         return;
707
708     if (!_interactionViewsContainerView) {
709         _interactionViewsContainerView = adoptNS([[UIView alloc] init]);
710         [_interactionViewsContainerView layer].name = @"InteractionViewsContainer";
711         [_interactionViewsContainerView setOpaque:NO];
712         [_interactionViewsContainerView layer].anchorPoint = CGPointZero;
713         [self.superview addSubview:_interactionViewsContainerView.get()];
714     }
715
716     _keyboardScrollingAnimator = adoptNS([[WKKeyboardScrollViewAnimator alloc] initWithScrollView:_webView.scrollView]);
717     [_keyboardScrollingAnimator setDelegate:self];
718
719     [self.layer addObserver:self forKeyPath:@"transform" options:NSKeyValueObservingOptionInitial context:nil];
720
721 #if ENABLE(POINTER_EVENTS)
722     _touchActionLeftSwipeGestureRecognizer = adoptNS([[UISwipeGestureRecognizer alloc] initWithTarget:nil action:nil]);
723     [_touchActionLeftSwipeGestureRecognizer setDirection:UISwipeGestureRecognizerDirectionLeft];
724     [_touchActionLeftSwipeGestureRecognizer setDelegate:self];
725     [self addGestureRecognizer:_touchActionLeftSwipeGestureRecognizer.get()];
726
727     _touchActionRightSwipeGestureRecognizer = adoptNS([[UISwipeGestureRecognizer alloc] initWithTarget:nil action:nil]);
728     [_touchActionRightSwipeGestureRecognizer setDirection:UISwipeGestureRecognizerDirectionRight];
729     [_touchActionRightSwipeGestureRecognizer setDelegate:self];
730     [self addGestureRecognizer:_touchActionRightSwipeGestureRecognizer.get()];
731
732     _touchActionUpSwipeGestureRecognizer = adoptNS([[UISwipeGestureRecognizer alloc] initWithTarget:nil action:nil]);
733     [_touchActionUpSwipeGestureRecognizer setDirection:UISwipeGestureRecognizerDirectionUp];
734     [_touchActionUpSwipeGestureRecognizer setDelegate:self];
735     [self addGestureRecognizer:_touchActionUpSwipeGestureRecognizer.get()];
736
737     _touchActionDownSwipeGestureRecognizer = adoptNS([[UISwipeGestureRecognizer alloc] initWithTarget:nil action:nil]);
738     [_touchActionDownSwipeGestureRecognizer setDirection:UISwipeGestureRecognizerDirectionDown];
739     [_touchActionDownSwipeGestureRecognizer setDelegate:self];
740     [self addGestureRecognizer:_touchActionDownSwipeGestureRecognizer.get()];
741 #endif
742
743     _touchEventGestureRecognizer = adoptNS([[UIWebTouchEventsGestureRecognizer alloc] initWithTarget:self action:@selector(_webTouchEventsRecognized:) touchDelegate:self]);
744     [_touchEventGestureRecognizer setDelegate:self];
745     [self addGestureRecognizer:_touchEventGestureRecognizer.get()];
746
747 #if HAVE(HOVER_GESTURE_RECOGNIZER)
748     _hoverGestureRecognizer = adoptNS([[UIHoverGestureRecognizer alloc] initWithTarget:self action:@selector(_hoverGestureRecognizerChanged:)]);
749     [_hoverGestureRecognizer setDelegate:self];
750     [self addGestureRecognizer:_hoverGestureRecognizer.get()];
751 #endif
752
753 #if PLATFORM(MACCATALYST)    
754     _lookupGestureRecognizer = adoptNS([[_UILookupGestureRecognizer alloc] initWithTarget:self action:@selector(_lookupGestureRecognized:)]);
755     [_lookupGestureRecognizer setDelegate:self];
756     [self addGestureRecognizer:_lookupGestureRecognizer.get()];
757     
758 #endif
759
760     _singleTapGestureRecognizer = adoptNS([[WKSyntheticTapGestureRecognizer alloc] initWithTarget:self action:@selector(_singleTapRecognized:)]);
761     [_singleTapGestureRecognizer setDelegate:self];
762     [_singleTapGestureRecognizer setGestureIdentifiedTarget:self action:@selector(_singleTapIdentified:)];
763     [_singleTapGestureRecognizer setResetTarget:self action:@selector(_singleTapDidReset:)];
764 #if ENABLE(POINTER_EVENTS)
765     [_singleTapGestureRecognizer setSupportingWebTouchEventsGestureRecognizer:_touchEventGestureRecognizer.get()];
766 #endif
767     [self addGestureRecognizer:_singleTapGestureRecognizer.get()];
768
769     _nonBlockingDoubleTapGestureRecognizer = adoptNS([[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(_nonBlockingDoubleTapRecognized:)]);
770     [_nonBlockingDoubleTapGestureRecognizer setNumberOfTapsRequired:2];
771     [_nonBlockingDoubleTapGestureRecognizer setDelegate:self];
772     [_nonBlockingDoubleTapGestureRecognizer setEnabled:NO];
773     [self addGestureRecognizer:_nonBlockingDoubleTapGestureRecognizer.get()];
774
775     _doubleTapGestureRecognizerForDoubleClick = adoptNS([[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(_doubleTapRecognizedForDoubleClick:)]);
776     [_doubleTapGestureRecognizerForDoubleClick setNumberOfTapsRequired:2];
777     [_doubleTapGestureRecognizerForDoubleClick setDelegate:self];
778     [self addGestureRecognizer:_doubleTapGestureRecognizerForDoubleClick.get()];
779
780     [self _createAndConfigureDoubleTapGestureRecognizer];
781
782     _twoFingerDoubleTapGestureRecognizer = adoptNS([[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(_twoFingerDoubleTapRecognized:)]);
783     [_twoFingerDoubleTapGestureRecognizer setNumberOfTapsRequired:2];
784     [_twoFingerDoubleTapGestureRecognizer setNumberOfTouchesRequired:2];
785     [_twoFingerDoubleTapGestureRecognizer setDelegate:self];
786     [self addGestureRecognizer:_twoFingerDoubleTapGestureRecognizer.get()];
787
788     _highlightLongPressGestureRecognizer = adoptNS([[WKHighlightLongPressGestureRecognizer alloc] initWithTarget:self action:@selector(_highlightLongPressRecognized:)]);
789     [_highlightLongPressGestureRecognizer setDelay:highlightDelay];
790     [_highlightLongPressGestureRecognizer setDelegate:self];
791     [self addGestureRecognizer:_highlightLongPressGestureRecognizer.get()];
792
793     [self _createAndConfigureLongPressGestureRecognizer];
794
795 #if HAVE(LINK_PREVIEW)
796     [self _updateLongPressAndHighlightLongPressGestures];
797 #endif
798
799 #if ENABLE(DATA_INTERACTION)
800     [self setupDragAndDropInteractions];
801 #endif
802
803     _twoFingerSingleTapGestureRecognizer = adoptNS([[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(_twoFingerSingleTapGestureRecognized:)]);
804     [_twoFingerSingleTapGestureRecognizer setAllowableMovement:60];
805     [_twoFingerSingleTapGestureRecognizer _setAllowableSeparation:150];
806     [_twoFingerSingleTapGestureRecognizer setNumberOfTapsRequired:1];
807     [_twoFingerSingleTapGestureRecognizer setNumberOfTouchesRequired:2];
808     [_twoFingerSingleTapGestureRecognizer setDelaysTouchesEnded:NO];
809     [_twoFingerSingleTapGestureRecognizer setDelegate:self];
810     [_twoFingerSingleTapGestureRecognizer setEnabled:!_webView._editable];
811     [self addGestureRecognizer:_twoFingerSingleTapGestureRecognizer.get()];
812
813     _stylusSingleTapGestureRecognizer = adoptNS([[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(_stylusSingleTapRecognized:)]);
814     [_stylusSingleTapGestureRecognizer setNumberOfTapsRequired:1];
815     [_stylusSingleTapGestureRecognizer setDelegate:self];
816     [_stylusSingleTapGestureRecognizer setAllowedTouchTypes:@[ @(UITouchTypePencil) ]];
817     [self addGestureRecognizer:_stylusSingleTapGestureRecognizer.get()];
818
819 #if ENABLE(POINTER_EVENTS)
820     _touchActionGestureRecognizer = adoptNS([[WKTouchActionGestureRecognizer alloc] initWithTouchActionDelegate:self]);
821     [self addGestureRecognizer:_touchActionGestureRecognizer.get()];
822 #endif
823
824 #if HAVE(LINK_PREVIEW)
825     [self _registerPreview];
826 #endif
827
828     NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
829     [center addObserver:self selector:@selector(_willHideMenu:) name:UIMenuControllerWillHideMenuNotification object:nil];
830     [center addObserver:self selector:@selector(_didHideMenu:) name:UIMenuControllerDidHideMenuNotification object:nil];
831     [center addObserver:self selector:@selector(_keyboardDidRequestDismissal:) name:UIKeyboardPrivateDidRequestDismissalNotification object:nil];
832
833     _showingTextStyleOptions = NO;
834
835     // FIXME: This should be called when we get notified that loading has completed.
836     [self setUpTextSelectionAssistant];
837     
838     _actionSheetAssistant = adoptNS([[WKActionSheetAssistant alloc] initWithView:self]);
839     [_actionSheetAssistant setDelegate:self];
840     _smartMagnificationController = makeUnique<WebKit::SmartMagnificationController>(self);
841     _isExpectingFastSingleTapCommit = NO;
842     _potentialTapInProgress = NO;
843     _isDoubleTapPending = NO;
844     _showDebugTapHighlightsForFastClicking = [[NSUserDefaults standardUserDefaults] boolForKey:@"WebKitShowFastClickDebugTapHighlights"];
845     _needsDeferredEndScrollingSelectionUpdate = NO;
846     _isChangingFocus = NO;
847     _isBlurringFocusedElement = NO;
848
849 #if ENABLE(DATALIST_ELEMENT)
850     _dataListTextSuggestionsInputView = nil;
851     _dataListTextSuggestions = nil;
852 #endif
853
854 #if ENABLE(PLATFORM_DRIVEN_TEXT_CHECKING)
855     _textCheckingController = makeUnique<WebKit::TextCheckingController>(*_page);
856 #endif
857
858     _hasSetUpInteractions = YES;
859 }
860
861 - (void)cleanupInteraction
862 {
863     if (!_hasSetUpInteractions)
864         return;
865
866     _textSelectionAssistant = nil;
867     
868     [_actionSheetAssistant cleanupSheet];
869     _actionSheetAssistant = nil;
870     
871     _smartMagnificationController = nil;
872     _didAccessoryTabInitiateFocus = NO;
873     _isChangingFocusUsingAccessoryTab = NO;
874     _isExpectingFastSingleTapCommit = NO;
875     _needsDeferredEndScrollingSelectionUpdate = NO;
876     [_formInputSession invalidate];
877     _formInputSession = nil;
878     [_highlightView removeFromSuperview];
879     _outstandingPositionInformationRequest = WTF::nullopt;
880
881     _focusRequiresStrongPasswordAssistance = NO;
882     _additionalContextForStrongPasswordAssistance = nil;
883     _waitingForEditDragSnapshot = NO;
884
885 #if USE(UIKIT_KEYBOARD_ADDITIONS)
886     _candidateViewNeedsUpdate = NO;
887     _seenHardwareKeyDownInNonEditableElement = NO;
888 #endif
889
890     if (_interactionViewsContainerView) {
891         [self.layer removeObserver:self forKeyPath:@"transform"];
892         [_interactionViewsContainerView removeFromSuperview];
893         _interactionViewsContainerView = nil;
894     }
895
896     [_touchEventGestureRecognizer setDelegate:nil];
897     [self removeGestureRecognizer:_touchEventGestureRecognizer.get()];
898
899 #if HAVE(HOVER_GESTURE_RECOGNIZER)
900     [_hoverGestureRecognizer setDelegate:nil];
901     [self removeGestureRecognizer:_hoverGestureRecognizer.get()];
902 #endif
903
904 #if PLATFORM(MACCATALYST)    
905     [_lookupGestureRecognizer setDelegate:nil];
906     [self removeGestureRecognizer:_lookupGestureRecognizer.get()];
907 #endif
908
909     [_singleTapGestureRecognizer setDelegate:nil];
910     [_singleTapGestureRecognizer setGestureIdentifiedTarget:nil action:nil];
911     [_singleTapGestureRecognizer setResetTarget:nil action:nil];
912 #if ENABLE(POINTER_EVENTS)
913     [_singleTapGestureRecognizer setSupportingWebTouchEventsGestureRecognizer:nil];
914 #endif
915     [self removeGestureRecognizer:_singleTapGestureRecognizer.get()];
916
917     [_highlightLongPressGestureRecognizer setDelegate:nil];
918     [self removeGestureRecognizer:_highlightLongPressGestureRecognizer.get()];
919
920     [_longPressGestureRecognizer setDelegate:nil];
921     [self removeGestureRecognizer:_longPressGestureRecognizer.get()];
922
923     [_doubleTapGestureRecognizer setDelegate:nil];
924     [self removeGestureRecognizer:_doubleTapGestureRecognizer.get()];
925
926     [_nonBlockingDoubleTapGestureRecognizer setDelegate:nil];
927     [self removeGestureRecognizer:_nonBlockingDoubleTapGestureRecognizer.get()];
928
929     [_doubleTapGestureRecognizerForDoubleClick setDelegate:nil];
930     [self removeGestureRecognizer:_doubleTapGestureRecognizerForDoubleClick.get()];
931
932     [_twoFingerDoubleTapGestureRecognizer setDelegate:nil];
933     [self removeGestureRecognizer:_twoFingerDoubleTapGestureRecognizer.get()];
934
935     [_twoFingerSingleTapGestureRecognizer setDelegate:nil];
936     [self removeGestureRecognizer:_twoFingerSingleTapGestureRecognizer.get()];
937
938     [_stylusSingleTapGestureRecognizer setDelegate:nil];
939     [self removeGestureRecognizer:_stylusSingleTapGestureRecognizer.get()];
940
941 #if ENABLE(POINTER_EVENTS)
942     [self removeGestureRecognizer:_touchActionGestureRecognizer.get()];
943     [self removeGestureRecognizer:_touchActionLeftSwipeGestureRecognizer.get()];
944     [self removeGestureRecognizer:_touchActionRightSwipeGestureRecognizer.get()];
945     [self removeGestureRecognizer:_touchActionUpSwipeGestureRecognizer.get()];
946     [self removeGestureRecognizer:_touchActionDownSwipeGestureRecognizer.get()];
947 #endif
948
949     _layerTreeTransactionIdAtLastTouchStart = { };
950
951 #if ENABLE(DATA_INTERACTION)
952     [existingLocalDragSessionContext(_dragDropInteractionState.dragSession()) cleanUpTemporaryDirectories];
953     [self teardownDragAndDropInteractions];
954 #endif
955
956     _inspectorNodeSearchEnabled = NO;
957     if (_inspectorNodeSearchGestureRecognizer) {
958         [_inspectorNodeSearchGestureRecognizer setDelegate:nil];
959         [self removeGestureRecognizer:_inspectorNodeSearchGestureRecognizer.get()];
960         _inspectorNodeSearchGestureRecognizer = nil;
961     }
962
963 #if HAVE(LINK_PREVIEW)
964     [self _unregisterPreview];
965 #endif
966
967     if (_fileUploadPanel) {
968         [_fileUploadPanel setDelegate:nil];
969         [_fileUploadPanel dismiss];
970         _fileUploadPanel = nil;
971     }
972
973 #if !PLATFORM(WATCHOS) && !PLATFORM(APPLETV)
974     if (_shareSheet) {
975         [_shareSheet setDelegate:nil];
976         [_shareSheet dismiss];
977         _shareSheet = nil;
978     }
979 #endif
980
981     [self _resetInputViewDeferral];
982     _focusedElementInformation = { };
983     
984     [_keyboardScrollingAnimator invalidate];
985     _keyboardScrollingAnimator = nil;
986
987 #if HAVE(PENCILKIT)
988     _drawingCoordinator = nil;
989 #endif
990
991 #if ENABLE(DATALIST_ELEMENT)
992     _dataListTextSuggestionsInputView = nil;
993     _dataListTextSuggestions = nil;
994 #endif
995
996     _hasSetUpInteractions = NO;
997     _suppressSelectionAssistantReasons = { };
998
999 #if ENABLE(POINTER_EVENTS)
1000     [self _resetPanningPreventionFlags];
1001 #endif
1002     [self _handleDOMPasteRequestWithResult:WebCore::DOMPasteAccessResponse::DeniedForGesture];
1003 }
1004
1005 - (void)_removeDefaultGestureRecognizers
1006 {
1007     [self removeGestureRecognizer:_touchEventGestureRecognizer.get()];
1008     [self removeGestureRecognizer:_singleTapGestureRecognizer.get()];
1009     [self removeGestureRecognizer:_highlightLongPressGestureRecognizer.get()];
1010     [self removeGestureRecognizer:_doubleTapGestureRecognizer.get()];
1011     [self removeGestureRecognizer:_nonBlockingDoubleTapGestureRecognizer.get()];
1012     [self removeGestureRecognizer:_doubleTapGestureRecognizerForDoubleClick.get()];
1013     [self removeGestureRecognizer:_twoFingerDoubleTapGestureRecognizer.get()];
1014     [self removeGestureRecognizer:_twoFingerSingleTapGestureRecognizer.get()];
1015     [self removeGestureRecognizer:_stylusSingleTapGestureRecognizer.get()];
1016 #if HAVE(HOVER_GESTURE_RECOGNIZER)
1017     [self removeGestureRecognizer:_hoverGestureRecognizer.get()];
1018 #endif
1019 #if PLATFORM(MACCATALYST)
1020     [self removeGestureRecognizer:_lookupGestureRecognizer.get()];
1021 #endif
1022 #if ENABLE(POINTER_EVENTS)
1023     [self removeGestureRecognizer:_touchActionGestureRecognizer.get()];
1024     [self removeGestureRecognizer:_touchActionLeftSwipeGestureRecognizer.get()];
1025     [self removeGestureRecognizer:_touchActionRightSwipeGestureRecognizer.get()];
1026     [self removeGestureRecognizer:_touchActionUpSwipeGestureRecognizer.get()];
1027     [self removeGestureRecognizer:_touchActionDownSwipeGestureRecognizer.get()];
1028 #endif
1029 }
1030
1031 - (void)_addDefaultGestureRecognizers
1032 {
1033     [self addGestureRecognizer:_touchEventGestureRecognizer.get()];
1034     [self addGestureRecognizer:_singleTapGestureRecognizer.get()];
1035     [self addGestureRecognizer:_highlightLongPressGestureRecognizer.get()];
1036     [self addGestureRecognizer:_doubleTapGestureRecognizer.get()];
1037     [self addGestureRecognizer:_nonBlockingDoubleTapGestureRecognizer.get()];
1038     [self addGestureRecognizer:_doubleTapGestureRecognizerForDoubleClick.get()];
1039     [self addGestureRecognizer:_twoFingerDoubleTapGestureRecognizer.get()];
1040     [self addGestureRecognizer:_twoFingerSingleTapGestureRecognizer.get()];
1041     [self addGestureRecognizer:_stylusSingleTapGestureRecognizer.get()];
1042 #if HAVE(HOVER_GESTURE_RECOGNIZER)
1043     [self addGestureRecognizer:_hoverGestureRecognizer.get()];
1044 #endif
1045 #if PLATFORM(MACCATALYST)
1046     [self addGestureRecognizer:_lookupGestureRecognizer.get()];
1047 #endif
1048 #if ENABLE(POINTER_EVENTS)
1049     [self addGestureRecognizer:_touchActionGestureRecognizer.get()];
1050     [self addGestureRecognizer:_touchActionLeftSwipeGestureRecognizer.get()];
1051     [self addGestureRecognizer:_touchActionRightSwipeGestureRecognizer.get()];
1052     [self addGestureRecognizer:_touchActionUpSwipeGestureRecognizer.get()];
1053     [self addGestureRecognizer:_touchActionDownSwipeGestureRecognizer.get()];
1054 #endif
1055 }
1056
1057 - (void)_didChangeLinkPreviewAvailability
1058 {
1059     [self _updateLongPressAndHighlightLongPressGestures];
1060 }
1061
1062 - (void)_updateLongPressAndHighlightLongPressGestures
1063 {
1064     // We only disable the highlight long press gesture in the case where UIContextMenu is available and we
1065     // also allow link previews, since the context menu interaction's gestures need to take precedence over
1066     // highlight long press gestures.
1067     [_highlightLongPressGestureRecognizer setEnabled:!self._shouldUseContextMenus || !_webView.allowsLinkPreview];
1068
1069     // We only enable the long press gesture in the case where the app is linked on iOS 12 or earlier (and
1070     // therefore prefers the legacy action sheet over context menus), and link previews are also enabled.
1071     [_longPressGestureRecognizer setEnabled:!self._shouldUseContextMenus && _webView.allowsLinkPreview];
1072 }
1073
1074 - (UIView *)unscaledView
1075 {
1076     return _interactionViewsContainerView.get();
1077 }
1078
1079 - (CGFloat)inverseScale
1080 {
1081     return 1 / [[self layer] transform].m11;
1082 }
1083
1084 - (UIScrollView *)_scroller
1085 {
1086     return [_webView scrollView];
1087 }
1088
1089 - (CGRect)unobscuredContentRect
1090 {
1091     return _page->unobscuredContentRect();
1092 }
1093
1094
1095 #pragma mark - UITextAutoscrolling
1096 - (void)startAutoscroll:(CGPoint)pointInDocument
1097 {
1098     _page->startAutoscrollAtPosition(pointInDocument);
1099 }
1100
1101 - (void)cancelAutoscroll
1102 {
1103     _page->cancelAutoscroll();
1104 }
1105
1106 - (void)scrollSelectionToVisible:(BOOL)animated
1107 {
1108     // Used to scroll selection on keyboard up; we already scroll to visible.
1109 }
1110
1111
1112 - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
1113 {
1114     ASSERT([keyPath isEqualToString:@"transform"]);
1115     ASSERT(object == self.layer);
1116
1117     if ([UIView _isInAnimationBlock] && _page->editorState().selectionIsNone) {
1118         // If the utility views are not already visible, we don't want them to become visible during the animation since
1119         // they could not start from a reasonable state.
1120         // This is not perfect since views could also get updated during the animation, in practice this is rare and the end state
1121         // remains correct.
1122         [self _cancelInteraction];
1123         [_interactionViewsContainerView setHidden:YES];
1124         [UIView _addCompletion:^(BOOL){ [_interactionViewsContainerView setHidden:NO]; }];
1125     }
1126
1127     [self _updateTapHighlight];
1128
1129     _selectionNeedsUpdate = YES;
1130     [self _updateChangedSelection:YES];
1131 }
1132
1133 - (void)_enableInspectorNodeSearch
1134 {
1135     _inspectorNodeSearchEnabled = YES;
1136
1137     [self _cancelInteraction];
1138
1139     [self _removeDefaultGestureRecognizers];
1140     _inspectorNodeSearchGestureRecognizer = adoptNS([[WKInspectorNodeSearchGestureRecognizer alloc] initWithTarget:self action:@selector(_inspectorNodeSearchRecognized:)]);
1141     [self addGestureRecognizer:_inspectorNodeSearchGestureRecognizer.get()];
1142 }
1143
1144 - (void)_disableInspectorNodeSearch
1145 {
1146     _inspectorNodeSearchEnabled = NO;
1147
1148     [self _addDefaultGestureRecognizers];
1149     [self removeGestureRecognizer:_inspectorNodeSearchGestureRecognizer.get()];
1150     _inspectorNodeSearchGestureRecognizer = nil;
1151 }
1152
1153 - (UIView *)hitTest:(CGPoint)point withEvent:(::UIEvent *)event
1154 {
1155     for (UIView *subView in [_interactionViewsContainerView.get() subviews]) {
1156         UIView *hitView = [subView hitTest:[subView convertPoint:point fromView:self] withEvent:event];
1157         if (hitView) {
1158             LOG_WITH_STREAM(UIHitTesting, stream << self << "hitTest at " << WebCore::FloatPoint(point) << " found interaction view " << hitView);
1159             return hitView;
1160         }
1161     }
1162
1163     LOG_WITH_STREAM(UIHitTesting, stream << "hit-testing WKContentView subviews " << [[self recursiveDescription] UTF8String]);
1164     UIView* hitView = [super hitTest:point withEvent:event];
1165     LOG_WITH_STREAM(UIHitTesting, stream << " found view " << [hitView class] << " " << (void*)hitView);
1166     return hitView;
1167 }
1168
1169 - (const WebKit::InteractionInformationAtPosition&)positionInformation
1170 {
1171     return _positionInformation;
1172 }
1173
1174 - (void)setInputDelegate:(id <UITextInputDelegate>)inputDelegate
1175 {
1176     _inputDelegate = inputDelegate;
1177 }
1178
1179 - (id <UITextInputDelegate>)inputDelegate
1180 {
1181     return _inputDelegate.getAutoreleased();
1182 }
1183
1184 - (CGPoint)lastInteractionLocation
1185 {
1186     return _lastInteractionLocation;
1187 }
1188
1189 - (BOOL)shouldHideSelectionWhenScrolling
1190 {
1191     if (_isEditable)
1192         return _focusedElementInformation.insideFixedPosition;
1193
1194     auto& editorState = _page->editorState();
1195     return !editorState.isMissingPostLayoutData && editorState.postLayoutData().insideFixedPosition;
1196 }
1197
1198 - (BOOL)isEditable
1199 {
1200     return _isEditable;
1201 }
1202
1203 - (BOOL)setIsEditable:(BOOL)isEditable
1204 {
1205     if (isEditable == _isEditable)
1206         return NO;
1207
1208     _isEditable = isEditable;
1209     return YES;
1210 }
1211
1212 - (void)_endEditing
1213 {
1214     [_inputPeripheral endEditing];
1215     [_formInputSession endEditing];
1216 #if ENABLE(DATALIST_ELEMENT)
1217     [_dataListTextSuggestionsInputView controlEndEditing];
1218 #endif
1219 }
1220
1221 - (void)_cancelPreviousResetInputViewDeferralRequest
1222 {
1223     [NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(_resetInputViewDeferral) object:nil];
1224 }
1225
1226 - (void)_scheduleResetInputViewDeferralAfterBecomingFirstResponder
1227 {
1228     [self _cancelPreviousResetInputViewDeferralRequest];
1229
1230     const NSTimeInterval inputViewDeferralWatchdogTimerDuration = 0.5;
1231     [self performSelector:@selector(_resetInputViewDeferral) withObject:self afterDelay:inputViewDeferralWatchdogTimerDuration];
1232 }
1233
1234 - (void)_resetInputViewDeferral
1235 {
1236     [self _cancelPreviousResetInputViewDeferralRequest];
1237     _inputViewUpdateDeferrer = nullptr;
1238 }
1239
1240 - (BOOL)canBecomeFirstResponder
1241 {
1242     return _becomingFirstResponder;
1243 }
1244
1245 - (BOOL)canBecomeFirstResponderForWebView
1246 {
1247     if (_resigningFirstResponder)
1248         return NO;
1249     // We might want to return something else
1250     // if we decide to enable/disable interaction programmatically.
1251     return YES;
1252 }
1253
1254 - (BOOL)becomeFirstResponder
1255 {
1256     return [_webView becomeFirstResponder];
1257 }
1258
1259 - (BOOL)becomeFirstResponderForWebView
1260 {
1261     if (_resigningFirstResponder)
1262         return NO;
1263
1264     if (!_inputViewUpdateDeferrer)
1265         _inputViewUpdateDeferrer = makeUnique<WebKit::InputViewUpdateDeferrer>(self);
1266
1267     BOOL didBecomeFirstResponder;
1268     {
1269         SetForScope<BOOL> becomingFirstResponder { _becomingFirstResponder, YES };
1270         didBecomeFirstResponder = [super becomeFirstResponder];
1271     }
1272
1273     if (didBecomeFirstResponder) {
1274         _page->installActivityStateChangeCompletionHandler([weakSelf = WeakObjCPtr<WKContentView>(self)] {
1275             if (!weakSelf)
1276                 return;
1277
1278             auto strongSelf = weakSelf.get();
1279             [strongSelf _resetInputViewDeferral];
1280         });
1281
1282         _page->activityStateDidChange(WebCore::ActivityState::IsFocused);
1283
1284         if ([self canShowNonEmptySelectionView])
1285             [_textSelectionAssistant activateSelection];
1286
1287         [self _scheduleResetInputViewDeferralAfterBecomingFirstResponder];
1288     } else
1289         [self _resetInputViewDeferral];
1290
1291     return didBecomeFirstResponder;
1292 }
1293
1294 - (BOOL)resignFirstResponder
1295 {
1296     return [_webView resignFirstResponder];
1297 }
1298
1299 typedef NS_ENUM(NSInteger, EndEditingReason) {
1300     EndEditingReasonAccessoryDone,
1301     EndEditingReasonResigningFirstResponder,
1302 };
1303
1304 - (void)endEditingAndUpdateFocusAppearanceWithReason:(EndEditingReason)reason
1305 {
1306     if (!_webView._retainingActiveFocusedState) {
1307         // We need to complete the editing operation before we blur the element.
1308         [self _endEditing];
1309         if ((reason == EndEditingReasonAccessoryDone && !currentUserInterfaceIdiomIsPad()) || _keyboardDidRequestDismissal || self._shouldUseLegacySelectPopoverDismissalBehavior) {
1310             _page->blurFocusedElement();
1311             // Don't wait for WebPageProxy::blurFocusedElement() to round-trip back to us to hide the keyboard
1312             // because we know that the user explicitly requested us to do so.
1313             [self _elementDidBlur];
1314         }
1315     }
1316
1317     [self _cancelInteraction];
1318     [_textSelectionAssistant deactivateSelection];
1319
1320     [self _resetInputViewDeferral];
1321 }
1322
1323 - (BOOL)resignFirstResponderForWebView
1324 {
1325     // FIXME: Maybe we should call resignFirstResponder on the superclass
1326     // and do nothing if the return value is NO.
1327
1328     SetForScope<BOOL> resigningFirstResponderScope { _resigningFirstResponder, YES };
1329
1330     [self endEditingAndUpdateFocusAppearanceWithReason:EndEditingReasonResigningFirstResponder];
1331
1332     // If the user explicitly dismissed the keyboard then we will lose first responder
1333     // status only to gain it back again. Just don't resign in that case.
1334     if (_keyboardDidRequestDismissal) {
1335         _keyboardDidRequestDismissal = NO;
1336         return NO;
1337     }
1338
1339     bool superDidResign = [super resignFirstResponder];
1340
1341     if (superDidResign) {
1342         [self _handleDOMPasteRequestWithResult:WebCore::DOMPasteAccessResponse::DeniedForGesture];
1343         _page->activityStateDidChange(WebCore::ActivityState::IsFocused);
1344     }
1345
1346     return superDidResign;
1347 }
1348
1349 #if ENABLE(POINTER_EVENTS)
1350 - (void)cancelPointersForGestureRecognizer:(UIGestureRecognizer *)gestureRecognizer
1351 {
1352     NSMapTable<NSNumber *, UITouch *> *activeTouches = [_touchEventGestureRecognizer activeTouchesByIdentifier];
1353     for (NSNumber *touchIdentifier in activeTouches) {
1354         UITouch *touch = [activeTouches objectForKey:touchIdentifier];
1355         if ([touch.gestureRecognizers containsObject:gestureRecognizer])
1356             _page->cancelPointer([touchIdentifier unsignedIntValue], WebCore::roundedIntPoint([touch locationInView:self]));
1357     }
1358 }
1359
1360 - (WTF::Optional<unsigned>)activeTouchIdentifierForGestureRecognizer:(UIGestureRecognizer *)gestureRecognizer
1361 {
1362     NSMapTable<NSNumber *, UITouch *> *activeTouches = [_touchEventGestureRecognizer activeTouchesByIdentifier];
1363     for (NSNumber *touchIdentifier in activeTouches) {
1364         UITouch *touch = [activeTouches objectForKey:touchIdentifier];
1365         if ([touch.gestureRecognizers containsObject:gestureRecognizer])
1366             return [touchIdentifier unsignedIntValue];
1367     }
1368     return WTF::nullopt;
1369 }
1370 #endif
1371
1372 inline static UIKeyModifierFlags gestureRecognizerModifierFlags(UIGestureRecognizer *recognizer)
1373 {
1374     return [recognizer respondsToSelector:@selector(_modifierFlags)] ? recognizer.modifierFlags : 0;
1375 }
1376
1377 - (void)_webTouchEventsRecognized:(UIWebTouchEventsGestureRecognizer *)gestureRecognizer
1378 {
1379     if (!_page->hasRunningProcess())
1380         return;
1381
1382     const _UIWebTouchEvent* lastTouchEvent = gestureRecognizer.lastTouchEvent;
1383
1384     _lastInteractionLocation = lastTouchEvent->locationInDocumentCoordinates;
1385     if (lastTouchEvent->type == UIWebTouchEventTouchBegin) {
1386         [self _handleDOMPasteRequestWithResult:WebCore::DOMPasteAccessResponse::DeniedForGesture];
1387         _layerTreeTransactionIdAtLastTouchStart = downcast<WebKit::RemoteLayerTreeDrawingAreaProxy>(*_page->drawingArea()).lastCommittedLayerTreeTransactionID();
1388
1389 #if ENABLE(TOUCH_EVENTS)
1390         _page->resetPotentialTapSecurityOrigin();
1391 #endif
1392
1393         WebKit::InteractionInformationRequest positionInformationRequest { WebCore::IntPoint(_lastInteractionLocation) };
1394         [self doAfterPositionInformationUpdate:[assistant = WeakObjCPtr<WKActionSheetAssistant>(_actionSheetAssistant.get())] (WebKit::InteractionInformationAtPosition information) {
1395             [assistant interactionDidStartWithPositionInformation:information];
1396         } forRequest:positionInformationRequest];
1397     }
1398
1399 #if ENABLE(TOUCH_EVENTS)
1400     WebKit::NativeWebTouchEvent nativeWebTouchEvent { lastTouchEvent, gestureRecognizerModifierFlags(gestureRecognizer) };
1401     nativeWebTouchEvent.setCanPreventNativeGestures(!_canSendTouchEventsAsynchronously || [gestureRecognizer isDefaultPrevented]);
1402
1403 #if ENABLE(POINTER_EVENTS)
1404     [self _handleTouchActionsForTouchEvent:nativeWebTouchEvent];
1405 #endif
1406
1407     if (_canSendTouchEventsAsynchronously)
1408         _page->handleTouchEventAsynchronously(nativeWebTouchEvent);
1409     else
1410         _page->handleTouchEventSynchronously(nativeWebTouchEvent);
1411
1412     if (nativeWebTouchEvent.allTouchPointsAreReleased()) {
1413         _canSendTouchEventsAsynchronously = NO;
1414
1415 #if ENABLE(POINTER_EVENTS)
1416         if (!_page->isScrollingOrZooming())
1417             [self _resetPanningPreventionFlags];
1418 #endif
1419     }
1420 #endif
1421 }
1422
1423 #if ENABLE(POINTER_EVENTS)
1424 #if ENABLE(TOUCH_EVENTS)
1425 - (void)_handleTouchActionsForTouchEvent:(const WebKit::NativeWebTouchEvent&)touchEvent
1426 {
1427     auto* scrollingCoordinator = _page->scrollingCoordinatorProxy();
1428     if (!scrollingCoordinator)
1429         return;
1430
1431     for (const auto& touchPoint : touchEvent.touchPoints()) {
1432         auto phase = touchPoint.phase();
1433         if (phase == WebKit::WebPlatformTouchPoint::TouchPressed) {
1434             auto touchActions = WebKit::touchActionsForPoint(self, touchPoint.location());
1435             LOG_WITH_STREAM(UIHitTesting, stream << "touchActionsForPoint " << touchPoint.location() << " found " << touchActions);
1436             if (!touchActions || touchActions.contains(WebCore::TouchAction::Auto))
1437                 continue;
1438             [_touchActionGestureRecognizer setTouchActions:touchActions forTouchIdentifier:touchPoint.identifier()];
1439             scrollingCoordinator->setTouchActionsForTouchIdentifier(touchActions, touchPoint.identifier());
1440             _preventsPanningInXAxis = !touchActions.containsAny({ WebCore::TouchAction::PanX, WebCore::TouchAction::Manipulation });
1441             _preventsPanningInYAxis = !touchActions.containsAny({ WebCore::TouchAction::PanY, WebCore::TouchAction::Manipulation });
1442         } else if (phase == WebKit::WebPlatformTouchPoint::TouchReleased || phase == WebKit::WebPlatformTouchPoint::TouchCancelled) {
1443             [_touchActionGestureRecognizer clearTouchActionsForTouchIdentifier:touchPoint.identifier()];
1444             scrollingCoordinator->clearTouchActionsForTouchIdentifier(touchPoint.identifier());
1445         }
1446     }
1447 }
1448 #endif // ENABLE(TOUCH_EVENTS)
1449
1450 - (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch
1451 {
1452     if (gestureRecognizer != _touchActionLeftSwipeGestureRecognizer && gestureRecognizer != _touchActionRightSwipeGestureRecognizer && gestureRecognizer != _touchActionUpSwipeGestureRecognizer && gestureRecognizer != _touchActionDownSwipeGestureRecognizer)
1453         return true;
1454
1455     // We update the enabled state of the various swipe gesture recognizers such that if we have a unidirectional touch-action
1456     // specified (only pan-x or only pan-y) we enable the two recognizers in the opposite axis to prevent scrolling from starting
1457     // if the initial gesture is such a swipe. Since the recognizers are specified to use a single finger for recognition, we don't
1458     // need to worry about the case where there may be more than a single touch for a given UIScrollView.
1459     auto touchActions = WebKit::touchActionsForPoint(self, WebCore::roundedIntPoint([touch locationInView:self]));
1460     if (gestureRecognizer == _touchActionLeftSwipeGestureRecognizer || gestureRecognizer == _touchActionRightSwipeGestureRecognizer)
1461         return touchActions == WebCore::TouchAction::PanY;
1462     return touchActions == WebCore::TouchAction::PanX;
1463 }
1464
1465 #pragma mark - WKTouchActionGestureRecognizerDelegate implementation
1466
1467 - (BOOL)gestureRecognizerMayPanWebView:(UIGestureRecognizer *)gestureRecognizer
1468 {
1469     // The gesture recognizer is the main UIScrollView's UIPanGestureRecognizer.
1470     if (gestureRecognizer == [_webView scrollView].panGestureRecognizer)
1471         return YES;
1472
1473     // The gesture recognizer is a child UIScrollView's UIPanGestureRecognizer created by WebKit.
1474     if (gestureRecognizer.view && [gestureRecognizer.view isKindOfClass:[WKChildScrollView class]])
1475         return YES;
1476
1477     return NO;
1478 }
1479
1480 - (BOOL)gestureRecognizerMayPinchToZoomWebView:(UIGestureRecognizer *)gestureRecognizer
1481 {
1482     // The gesture recognizer is the main UIScrollView's UIPinchGestureRecognizer.
1483     if (gestureRecognizer == [_webView scrollView].pinchGestureRecognizer)
1484         return YES;
1485
1486     // The gesture recognizer is another UIPichGestureRecognizer known to lead to pinch-to-zoom.
1487     if ([gestureRecognizer isKindOfClass:[UIPinchGestureRecognizer class]]) {
1488         if (auto uiDelegate = static_cast<id<WKUIDelegatePrivate>>(_webView.UIDelegate)) {
1489             if ([uiDelegate respondsToSelector:@selector(_webView:gestureRecognizerCouldPinch:)])
1490                 return [uiDelegate _webView:_webView gestureRecognizerCouldPinch:gestureRecognizer];
1491         }
1492     }
1493     return NO;
1494 }
1495
1496 - (BOOL)gestureRecognizerMayDoubleTapToZoomWebView:(UIGestureRecognizer *)gestureRecognizer
1497 {
1498     return gestureRecognizer == _doubleTapGestureRecognizer || gestureRecognizer == _twoFingerDoubleTapGestureRecognizer;
1499 }
1500
1501 - (NSMapTable<NSNumber *, UITouch *> *)touchActionActiveTouches
1502 {
1503     return [_touchEventGestureRecognizer activeTouchesByIdentifier];
1504 }
1505
1506 - (void)_resetPanningPreventionFlags
1507 {
1508     _preventsPanningInXAxis = NO;
1509     _preventsPanningInYAxis = NO;
1510 }
1511 #endif // ENABLE(POINTER_EVENTS)
1512
1513 - (void)_inspectorNodeSearchRecognized:(UIGestureRecognizer *)gestureRecognizer
1514 {
1515     ASSERT(_inspectorNodeSearchEnabled);
1516     [self _resetIsDoubleTapPending];
1517
1518     CGPoint point = [gestureRecognizer locationInView:self];
1519
1520     switch (gestureRecognizer.state) {
1521     case UIGestureRecognizerStateBegan:
1522     case UIGestureRecognizerStateChanged:
1523         _page->inspectorNodeSearchMovedToPosition(point);
1524         break;
1525     case UIGestureRecognizerStateEnded:
1526     case UIGestureRecognizerStateCancelled:
1527     default: // To ensure we turn off node search.
1528         _page->inspectorNodeSearchEndedAtPosition(point);
1529         break;
1530     }
1531 }
1532
1533 static WebCore::FloatQuad inflateQuad(const WebCore::FloatQuad& quad, float inflateSize)
1534 {
1535     // We sort the output points like this (as expected by the highlight view):
1536     //  p2------p3
1537     //  |       |
1538     //  p1------p4
1539
1540     // 1) Sort the points horizontally.
1541     WebCore::FloatPoint points[4] = { quad.p1(), quad.p4(), quad.p2(), quad.p3() };
1542     if (points[0].x() > points[1].x())
1543         std::swap(points[0], points[1]);
1544     if (points[2].x() > points[3].x())
1545         std::swap(points[2], points[3]);
1546
1547     if (points[0].x() > points[2].x())
1548         std::swap(points[0], points[2]);
1549     if (points[1].x() > points[3].x())
1550         std::swap(points[1], points[3]);
1551
1552     if (points[1].x() > points[2].x())
1553         std::swap(points[1], points[2]);
1554
1555     // 2) Swap them vertically to have the output points [p2, p1, p3, p4].
1556     if (points[1].y() < points[0].y())
1557         std::swap(points[0], points[1]);
1558     if (points[3].y() < points[2].y())
1559         std::swap(points[2], points[3]);
1560
1561     // 3) Adjust the positions.
1562     points[0].move(-inflateSize, -inflateSize);
1563     points[1].move(-inflateSize, inflateSize);
1564     points[2].move(inflateSize, -inflateSize);
1565     points[3].move(inflateSize, inflateSize);
1566
1567     return WebCore::FloatQuad(points[1], points[0], points[2], points[3]);
1568 }
1569
1570 #if ENABLE(TOUCH_EVENTS)
1571 - (void)_webTouchEvent:(const WebKit::NativeWebTouchEvent&)touchEvent preventsNativeGestures:(BOOL)preventsNativeGesture
1572 {
1573     if (preventsNativeGesture) {
1574         _longPressCanClick = NO;
1575
1576         _canSendTouchEventsAsynchronously = YES;
1577         [_touchEventGestureRecognizer setDefaultPrevented:YES];
1578     }
1579 }
1580 #endif
1581
1582 static NSValue *nsSizeForTapHighlightBorderRadius(WebCore::IntSize borderRadius, CGFloat borderRadiusScale)
1583 {
1584     return [NSValue valueWithCGSize:CGSizeMake((borderRadius.width() * borderRadiusScale) + minimumTapHighlightRadius, (borderRadius.height() * borderRadiusScale) + minimumTapHighlightRadius)];
1585 }
1586
1587 - (void)_updateTapHighlight
1588 {
1589     if (![_highlightView superview])
1590         return;
1591
1592     {
1593         RetainPtr<UIColor> highlightUIKitColor = adoptNS([[UIColor alloc] initWithCGColor:cachedCGColor(_tapHighlightInformation.color)]);
1594         [_highlightView setColor:highlightUIKitColor.get()];
1595     }
1596
1597     CGFloat selfScale = self.layer.transform.m11;
1598     bool allHighlightRectsAreRectilinear = true;
1599     float deviceScaleFactor = _page->deviceScaleFactor();
1600     const Vector<WebCore::FloatQuad>& highlightedQuads = _tapHighlightInformation.quads;
1601     const size_t quadCount = highlightedQuads.size();
1602     RetainPtr<NSMutableArray> rects = adoptNS([[NSMutableArray alloc] initWithCapacity:static_cast<const NSUInteger>(quadCount)]);
1603     for (size_t i = 0; i < quadCount; ++i) {
1604         const WebCore::FloatQuad& quad = highlightedQuads[i];
1605         if (quad.isRectilinear()) {
1606             WebCore::FloatRect boundingBox = quad.boundingBox();
1607             boundingBox.scale(selfScale);
1608             boundingBox.inflate(minimumTapHighlightRadius);
1609             CGRect pixelAlignedRect = static_cast<CGRect>(encloseRectToDevicePixels(boundingBox, deviceScaleFactor));
1610             [rects addObject:[NSValue valueWithCGRect:pixelAlignedRect]];
1611         } else {
1612             allHighlightRectsAreRectilinear = false;
1613             rects.clear();
1614             break;
1615         }
1616     }
1617
1618     if (allHighlightRectsAreRectilinear)
1619         [_highlightView setFrames:rects.get() boundaryRect:_page->exposedContentRect()];
1620     else {
1621         RetainPtr<NSMutableArray> quads = adoptNS([[NSMutableArray alloc] initWithCapacity:static_cast<const NSUInteger>(quadCount)]);
1622         for (size_t i = 0; i < quadCount; ++i) {
1623             WebCore::FloatQuad quad = highlightedQuads[i];
1624             quad.scale(selfScale);
1625             WebCore::FloatQuad extendedQuad = inflateQuad(quad, minimumTapHighlightRadius);
1626             [quads addObject:[NSValue valueWithCGPoint:extendedQuad.p1()]];
1627             [quads addObject:[NSValue valueWithCGPoint:extendedQuad.p2()]];
1628             [quads addObject:[NSValue valueWithCGPoint:extendedQuad.p3()]];
1629             [quads addObject:[NSValue valueWithCGPoint:extendedQuad.p4()]];
1630         }
1631         [_highlightView setQuads:quads.get() boundaryRect:_page->exposedContentRect()];
1632     }
1633
1634     RetainPtr<NSMutableArray> borderRadii = adoptNS([[NSMutableArray alloc] initWithCapacity:4]);
1635     [borderRadii addObject:nsSizeForTapHighlightBorderRadius(_tapHighlightInformation.topLeftRadius, selfScale)];
1636     [borderRadii addObject:nsSizeForTapHighlightBorderRadius(_tapHighlightInformation.topRightRadius, selfScale)];
1637     [borderRadii addObject:nsSizeForTapHighlightBorderRadius(_tapHighlightInformation.bottomLeftRadius, selfScale)];
1638     [borderRadii addObject:nsSizeForTapHighlightBorderRadius(_tapHighlightInformation.bottomRightRadius, selfScale)];
1639     [_highlightView setCornerRadii:borderRadii.get()];
1640 }
1641
1642 - (void)_showTapHighlight
1643 {
1644     auto shouldPaintTapHighlight = [&](const WebCore::FloatRect& rect) {
1645 #if PLATFORM(MACCATALYST)
1646         UNUSED_PARAM(rect);
1647         return NO;
1648 #else
1649         if (_tapHighlightInformation.nodeHasBuiltInClickHandling)
1650             return true;
1651
1652         static const float highlightPaintThreshold = 0.3; // 30%
1653         float highlightArea = 0;
1654         for (auto highlightQuad : _tapHighlightInformation.quads) {
1655             auto boundingBox = highlightQuad.boundingBox();
1656             highlightArea += boundingBox.area(); 
1657             if (boundingBox.width() > (rect.width() * highlightPaintThreshold) || boundingBox.height() > (rect.height() * highlightPaintThreshold))
1658                 return false;
1659         }
1660         return highlightArea < rect.area() * highlightPaintThreshold;
1661 #endif
1662     };
1663
1664     if (!shouldPaintTapHighlight(_page->unobscuredContentRect()) && !_showDebugTapHighlightsForFastClicking)
1665         return;
1666
1667     if (!_highlightView) {
1668         _highlightView = adoptNS([[_UIHighlightView alloc] initWithFrame:CGRectZero]);
1669         [_highlightView setUserInteractionEnabled:NO];
1670         [_highlightView setOpaque:NO];
1671         [_highlightView setCornerRadius:minimumTapHighlightRadius];
1672     }
1673     [_highlightView layer].opacity = 1;
1674     [_interactionViewsContainerView addSubview:_highlightView.get()];
1675     [self _updateTapHighlight];
1676 }
1677
1678 - (void)_didGetTapHighlightForRequest:(uint64_t)requestID color:(const WebCore::Color&)color quads:(const Vector<WebCore::FloatQuad>&)highlightedQuads topLeftRadius:(const WebCore::IntSize&)topLeftRadius topRightRadius:(const WebCore::IntSize&)topRightRadius bottomLeftRadius:(const WebCore::IntSize&)bottomLeftRadius bottomRightRadius:(const WebCore::IntSize&)bottomRightRadius nodeHasBuiltInClickHandling:(BOOL)nodeHasBuiltInClickHandling
1679 {
1680     if (!_isTapHighlightIDValid || _latestTapID != requestID)
1681         return;
1682
1683     if (hasFocusedElement(_focusedElementInformation) && _positionInformation.nodeAtPositionIsFocusedElement)
1684         return;
1685
1686     _isTapHighlightIDValid = NO;
1687
1688     _tapHighlightInformation.quads = highlightedQuads;
1689     _tapHighlightInformation.topLeftRadius = topLeftRadius;
1690     _tapHighlightInformation.topRightRadius = topRightRadius;
1691     _tapHighlightInformation.bottomLeftRadius = bottomLeftRadius;
1692     _tapHighlightInformation.bottomRightRadius = bottomRightRadius;
1693     _tapHighlightInformation.nodeHasBuiltInClickHandling = nodeHasBuiltInClickHandling;
1694     if (_showDebugTapHighlightsForFastClicking)
1695         _tapHighlightInformation.color = [self _tapHighlightColorForFastClick:![_doubleTapGestureRecognizer isEnabled]];
1696     else
1697         _tapHighlightInformation.color = color;
1698
1699     if (_potentialTapInProgress) {
1700         _hasTapHighlightForPotentialTap = YES;
1701         return;
1702     }
1703
1704     [self _showTapHighlight];
1705     if (_isExpectingFastSingleTapCommit) {
1706         [self _finishInteraction];
1707         if (!_potentialTapInProgress)
1708             _isExpectingFastSingleTapCommit = NO;
1709     }
1710 }
1711
1712 - (BOOL)_mayDisableDoubleTapGesturesDuringSingleTap
1713 {
1714     return _potentialTapInProgress;
1715 }
1716
1717 - (void)_disableDoubleTapGesturesDuringTapIfNecessary:(uint64_t)requestID
1718 {
1719     if (_latestTapID != requestID)
1720         return;
1721
1722     [self _setDoubleTapGesturesEnabled:NO];
1723 }
1724
1725 - (void)_handleSmartMagnificationInformationForPotentialTap:(uint64_t)requestID renderRect:(const WebCore::FloatRect&)renderRect fitEntireRect:(BOOL)fitEntireRect viewportMinimumScale:(double)viewportMinimumScale viewportMaximumScale:(double)viewportMaximumScale nodeIsRootLevel:(BOOL)nodeIsRootLevel
1726 {
1727     const auto& preferences = _page->preferences();
1728
1729     ASSERT(preferences.fasterClicksEnabled());
1730     if (!_potentialTapInProgress)
1731         return;
1732
1733     // We check both the system preference and the page preference, because we only want this
1734     // to apply in "desktop" mode.
1735     if (preferences.preferFasterClickOverDoubleTap() && _page->preferFasterClickOverDoubleTap()) {
1736         RELEASE_LOG(ViewGestures, "Potential tap found an element and fast taps are preferred. Trigger click. (%p)", self);
1737         if (preferences.zoomOnDoubleTapWhenRoot() && nodeIsRootLevel) {
1738             RELEASE_LOG(ViewGestures, "The click handler was on a root-level element, so don't disable double-tap. (%p)", self);
1739             return;
1740         }
1741
1742         if (preferences.alwaysZoomOnDoubleTap()) {
1743             RELEASE_LOG(ViewGestures, "DTTZ is forced on, so don't disable double-tap. (%p)", self);
1744             return;
1745         }
1746
1747         RELEASE_LOG(ViewGestures, "Give preference to click by disabling double-tap. (%p)", self);
1748         [self _setDoubleTapGesturesEnabled:NO];
1749         return;
1750     }
1751
1752     auto targetScale = _smartMagnificationController->zoomFactorForTargetRect(renderRect, fitEntireRect, viewportMinimumScale, viewportMaximumScale);
1753
1754     auto initialScale = [self _initialScaleFactor];
1755     if (std::min(targetScale, initialScale) / std::max(targetScale, initialScale) > fasterTapSignificantZoomThreshold) {
1756         RELEASE_LOG(ViewGestures, "Potential tap would not cause a significant zoom. Trigger click. (%p)", self);
1757         [self _setDoubleTapGesturesEnabled:NO];
1758         return;
1759     }
1760     RELEASE_LOG(ViewGestures, "Potential tap may cause significant zoom. Wait. (%p)", self);
1761 }
1762
1763 - (void)_cancelLongPressGestureRecognizer
1764 {
1765     [_highlightLongPressGestureRecognizer cancel];
1766 }
1767
1768 - (void)_cancelTouchEventGestureRecognizer
1769 {
1770 #if HAVE(CANCEL_WEB_TOUCH_EVENTS_GESTURE)
1771     [_touchEventGestureRecognizer cancel];
1772 #endif
1773 }
1774
1775 - (void)_didScroll
1776 {
1777     [self _cancelLongPressGestureRecognizer];
1778     [self _cancelInteraction];
1779 }
1780
1781 - (void)_scrollingNodeScrollingWillBegin
1782 {
1783     [_textSelectionAssistant willStartScrollingOverflow];    
1784 }
1785
1786 - (void)_scrollingNodeScrollingDidEnd
1787 {
1788     // If scrolling ends before we've received a selection update,
1789     // we postpone showing the selection until the update is received.
1790     if (!_selectionNeedsUpdate) {
1791         _shouldRestoreSelection = YES;
1792         return;
1793     }
1794     [self _updateChangedSelection];
1795     [_textSelectionAssistant didEndScrollingOverflow];
1796 }
1797
1798 - (BOOL)shouldShowAutomaticKeyboardUI
1799 {
1800     // FIXME: We should support inputmode="none" when the hardware keyboard is attached.
1801     // We currently refrain from doing so because that would prevent UIKit from showing
1802     // the language picker when pressing the globe key to change the input language.
1803     if (_focusedElementInformation.inputMode == WebCore::InputMode::None && !GSEventIsHardwareKeyboardAttached())
1804         return NO;
1805
1806     return [self _shouldShowAutomaticKeyboardUIIgnoringInputMode];
1807 }
1808
1809 - (BOOL)_shouldShowAutomaticKeyboardUIIgnoringInputMode
1810 {
1811     switch (_focusedElementInformation.elementType) {
1812     case WebKit::InputType::None:
1813     case WebKit::InputType::Drawing:
1814         return NO;
1815     case WebKit::InputType::Select:
1816 #if ENABLE(INPUT_TYPE_COLOR)
1817     case WebKit::InputType::Color:
1818 #endif
1819     case WebKit::InputType::Date:
1820     case WebKit::InputType::Month:
1821     case WebKit::InputType::DateTimeLocal:
1822     case WebKit::InputType::Time:
1823         return !currentUserInterfaceIdiomIsPad();
1824     default:
1825         return !_focusedElementInformation.isReadOnly;
1826     }
1827     return NO;
1828 }
1829
1830 #if USE(UIKIT_KEYBOARD_ADDITIONS)
1831 - (BOOL)_disableAutomaticKeyboardUI
1832 {
1833     // Always enable automatic keyboard UI if we are not the first responder to avoid
1834     // interfering with other focused views (e.g. Find-in-page).
1835     return [self isFirstResponder] && ![self shouldShowAutomaticKeyboardUI];
1836 }
1837 #endif
1838
1839 - (BOOL)_requiresKeyboardWhenFirstResponder
1840 {
1841     // FIXME: We should add the logic to handle keyboard visibility during focus redirects.
1842     return [self _shouldShowAutomaticKeyboardUIIgnoringInputMode]
1843 #if USE(UIKIT_KEYBOARD_ADDITIONS)
1844         || _seenHardwareKeyDownInNonEditableElement
1845 #endif
1846         ;
1847 }
1848
1849 - (BOOL)_requiresKeyboardResetOnReload
1850 {
1851     return YES;
1852 }
1853
1854 - (void)_zoomToRevealFocusedElement
1855 {
1856     if (_suppressSelectionAssistantReasons.contains(WebKit::EditableRootIsTransparentOrFullyClipped) || _suppressSelectionAssistantReasons.contains(WebKit::FocusedElementIsTooSmall))
1857         return;
1858
1859     // In case user scaling is force enabled, do not use that scaling when zooming in with an input field.
1860     // Zooming above the page's default scale factor should only happen when the user performs it.
1861     [self _zoomToFocusRect:_focusedElementInformation.elementRect
1862         selectionRect:_didAccessoryTabInitiateFocus ? WebCore::FloatRect() : rectToRevealWhenZoomingToFocusedElement(_focusedElementInformation, _page->editorState())
1863         insideFixed:_focusedElementInformation.insideFixedPosition
1864         fontSize:_focusedElementInformation.nodeFontSize
1865         minimumScale:_focusedElementInformation.minimumScaleFactor
1866         maximumScale:_focusedElementInformation.maximumScaleFactorIgnoringAlwaysScalable
1867         allowScaling:_focusedElementInformation.allowsUserScalingIgnoringAlwaysScalable && !currentUserInterfaceIdiomIsPad()
1868         forceScroll:[self requiresAccessoryView]];
1869 }
1870
1871 - (UIView *)inputView
1872 {
1873     return [_webView inputView];
1874 }
1875
1876 - (UIView *)inputViewForWebView
1877 {
1878     if (!hasFocusedElement(_focusedElementInformation))
1879         return nil;
1880
1881     if (_inputPeripheral) {
1882         // FIXME: UIKit may invoke -[WKContentView inputView] at any time when WKContentView is the first responder;
1883         // as such, it doesn't make sense to change the enclosing scroll view's zoom scale and content offset to reveal
1884         // the focused element here. It seems this behavior was added to match logic in legacy WebKit (refer to
1885         // UIWebBrowserView). Instead, we should find the places where we currently assume that UIKit (or other clients)
1886         // invoke -inputView to zoom to the focused element, and either surface SPI for clients to zoom to the focused
1887         // element, or explicitly trigger the zoom from WebKit.
1888         // For instance, one use case that currently relies on this detail is adjusting the zoom scale and viewport upon
1889         // rotation, when a select element is focused. See <https://webkit.org/b/192878> for more information.
1890         [self _zoomToRevealFocusedElement];
1891
1892         [self _updateAccessory];
1893     }
1894
1895     if (UIView *customInputView = [_formInputSession customInputView])
1896         return customInputView;
1897
1898 #if ENABLE(DATALIST_ELEMENT)
1899     if (_dataListTextSuggestionsInputView)
1900         return _dataListTextSuggestionsInputView.get();
1901 #endif
1902
1903     return [_inputPeripheral assistantView];
1904 }
1905
1906 - (CGRect)_selectionClipRect
1907 {
1908     if (!hasFocusedElement(_focusedElementInformation))
1909         return CGRectNull;
1910
1911     if (_page->waitingForPostLayoutEditorStateUpdateAfterFocusingElement())
1912         return _focusedElementInformation.elementRect;
1913
1914     return _page->editorState().postLayoutData().focusedElementRect;
1915 }
1916
1917 static BOOL isBuiltInScrollViewGestureRecognizer(UIGestureRecognizer *recognizer)
1918 {
1919     return ([recognizer isKindOfClass:NSClassFromString(@"UIScrollViewPanGestureRecognizer")]
1920         || [recognizer isKindOfClass:NSClassFromString(@"UIScrollViewPinchGestureRecognizer")]
1921         || [recognizer isKindOfClass:NSClassFromString(@"UIScrollViewKnobLongPressGestureRecognizer")]
1922         );
1923 }
1924
1925 - (BOOL)gestureRecognizer:(UIGestureRecognizer *)preventingGestureRecognizer canPreventGestureRecognizer:(UIGestureRecognizer *)preventedGestureRecognizer
1926 {
1927     // A long-press gesture can not be recognized while panning, but a pan can be recognized
1928     // during a long-press gesture.
1929     BOOL shouldNotPreventScrollViewGestures = preventingGestureRecognizer == _highlightLongPressGestureRecognizer || preventingGestureRecognizer == _longPressGestureRecognizer;
1930     
1931     return !(shouldNotPreventScrollViewGestures && isBuiltInScrollViewGestureRecognizer(preventedGestureRecognizer));
1932 }
1933
1934 - (BOOL)gestureRecognizer:(UIGestureRecognizer *)preventedGestureRecognizer canBePreventedByGestureRecognizer:(UIGestureRecognizer *)preventingGestureRecognizer {
1935     // Don't allow the highlight to be prevented by a selection gesture. Press-and-hold on a link should highlight the link, not select it.
1936     bool isForcePressGesture = NO;
1937 #if __IPHONE_OS_VERSION_MIN_REQUIRED >= 120000
1938     isForcePressGesture = (preventingGestureRecognizer == _textSelectionAssistant.get().forcePressGesture);
1939 #endif
1940 #if PLATFORM(MACCATALYST)
1941     if ((preventingGestureRecognizer == _textSelectionAssistant.get().loupeGesture) && (preventedGestureRecognizer == _highlightLongPressGestureRecognizer || preventedGestureRecognizer == _longPressGestureRecognizer || preventedGestureRecognizer == _textSelectionAssistant.get().forcePressGesture))
1942         return YES;
1943 #endif
1944     
1945     if ((preventingGestureRecognizer == _textSelectionAssistant.get().loupeGesture || isForcePressGesture) && (preventedGestureRecognizer == _highlightLongPressGestureRecognizer || preventedGestureRecognizer == _longPressGestureRecognizer))
1946         return NO;
1947
1948     return YES;
1949 }
1950
1951 static inline bool isSamePair(UIGestureRecognizer *a, UIGestureRecognizer *b, UIGestureRecognizer *x, UIGestureRecognizer *y)
1952 {
1953     return (a == x && b == y) || (b == x && a == y);
1954 }
1955
1956 - (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer*)otherGestureRecognizer
1957 {
1958     if (isSamePair(gestureRecognizer, otherGestureRecognizer, _highlightLongPressGestureRecognizer.get(), _longPressGestureRecognizer.get()))
1959         return YES;
1960
1961 #if HAVE(HOVER_GESTURE_RECOGNIZER)
1962     if ([gestureRecognizer isKindOfClass:[UIHoverGestureRecognizer class]] || [otherGestureRecognizer isKindOfClass:[UIHoverGestureRecognizer class]])
1963         return YES;
1964 #endif
1965
1966 #if __IPHONE_OS_VERSION_MIN_REQUIRED >= 120000
1967 #if PLATFORM(MACCATALYST)
1968     if (isSamePair(gestureRecognizer, otherGestureRecognizer, _textSelectionAssistant.get().loupeGesture, _textSelectionAssistant.get().forcePressGesture))
1969         return YES;
1970
1971     if (isSamePair(gestureRecognizer, otherGestureRecognizer, _singleTapGestureRecognizer.get(), _textSelectionAssistant.get().loupeGesture))
1972         return YES;
1973
1974     if (([gestureRecognizer isKindOfClass:[_UILookupGestureRecognizer class]] && [otherGestureRecognizer isKindOfClass:[UILongPressGestureRecognizer class]]) || ([otherGestureRecognizer isKindOfClass:[UILongPressGestureRecognizer class]] && [gestureRecognizer isKindOfClass:[_UILookupGestureRecognizer class]]))
1975         return YES;
1976 #endif
1977     if (isSamePair(gestureRecognizer, otherGestureRecognizer, _highlightLongPressGestureRecognizer.get(), _textSelectionAssistant.get().forcePressGesture))
1978         return YES;
1979 #endif
1980     if (isSamePair(gestureRecognizer, otherGestureRecognizer, _singleTapGestureRecognizer.get(), _textSelectionAssistant.get().singleTapGesture))
1981         return YES;
1982
1983     if (isSamePair(gestureRecognizer, otherGestureRecognizer, _singleTapGestureRecognizer.get(), _nonBlockingDoubleTapGestureRecognizer.get()))
1984         return YES;
1985
1986     if (isSamePair(gestureRecognizer, otherGestureRecognizer, _highlightLongPressGestureRecognizer.get(), _nonBlockingDoubleTapGestureRecognizer.get()))
1987         return YES;
1988
1989     if (isSamePair(gestureRecognizer, otherGestureRecognizer, _highlightLongPressGestureRecognizer.get(), _previewSecondaryGestureRecognizer.get()))
1990         return YES;
1991
1992     if (isSamePair(gestureRecognizer, otherGestureRecognizer, _highlightLongPressGestureRecognizer.get(), _previewGestureRecognizer.get()))
1993         return YES;
1994
1995     if (isSamePair(gestureRecognizer, otherGestureRecognizer, _nonBlockingDoubleTapGestureRecognizer.get(), _doubleTapGestureRecognizerForDoubleClick.get()))
1996         return YES;
1997
1998     if (isSamePair(gestureRecognizer, otherGestureRecognizer, _doubleTapGestureRecognizer.get(), _doubleTapGestureRecognizerForDoubleClick.get()))
1999         return YES;
2000
2001     return NO;
2002 }
2003
2004 - (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRequireFailureOfGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer
2005 {
2006     if (gestureRecognizer == _touchEventGestureRecognizer && [_webView _isNavigationSwipeGestureRecognizer:otherGestureRecognizer])
2007         return YES;
2008
2009     return NO;
2010 }
2011
2012 - (void)_showImageSheet
2013 {
2014     [_actionSheetAssistant showImageSheet];
2015 }
2016
2017 - (void)_showAttachmentSheet
2018 {
2019     id <WKUIDelegatePrivate> uiDelegate = static_cast<id <WKUIDelegatePrivate>>([_webView UIDelegate]);
2020     if (![uiDelegate respondsToSelector:@selector(_webView:showCustomSheetForElement:)])
2021         return;
2022
2023     auto element = adoptNS([[_WKActivatedElementInfo alloc] _initWithType:_WKActivatedElementTypeAttachment URL:(NSURL *)_positionInformation.url imageURL:(NSURL *)_positionInformation.imageURL location:_positionInformation.request.point title:_positionInformation.title ID:_positionInformation.idAttribute rect:_positionInformation.bounds image:nil]);
2024     ALLOW_DEPRECATED_DECLARATIONS_BEGIN
2025     [uiDelegate _webView:_webView showCustomSheetForElement:element.get()];
2026     ALLOW_DEPRECATED_DECLARATIONS_END
2027 }
2028
2029 - (void)_showLinkSheet
2030 {
2031     [_actionSheetAssistant showLinkSheet];
2032 }
2033
2034 - (void)_showDataDetectorsSheet
2035 {
2036     [_actionSheetAssistant showDataDetectorsSheet];
2037 }
2038
2039 - (SEL)_actionForLongPressFromPositionInformation:(const WebKit::InteractionInformationAtPosition&)positionInformation
2040 {
2041     if (!_webView.configuration._longPressActionsEnabled)
2042         return nil;
2043
2044     if (!positionInformation.touchCalloutEnabled)
2045         return nil;
2046
2047     if (positionInformation.isImage)
2048         return @selector(_showImageSheet);
2049
2050     if (positionInformation.isLink) {
2051 #if ENABLE(DATA_DETECTION)
2052         if (WebCore::DataDetection::canBePresentedByDataDetectors(positionInformation.url))
2053             return @selector(_showDataDetectorsSheet);
2054 #endif
2055         return @selector(_showLinkSheet);
2056     }
2057     if (positionInformation.isAttachment)
2058         return @selector(_showAttachmentSheet);
2059
2060     return nil;
2061 }
2062
2063 - (SEL)_actionForLongPress
2064 {
2065     return [self _actionForLongPressFromPositionInformation:_positionInformation];
2066 }
2067
2068 - (WebKit::InteractionInformationAtPosition)currentPositionInformation
2069 {
2070     return _positionInformation;
2071 }
2072
2073 - (void)doAfterPositionInformationUpdate:(void (^)(WebKit::InteractionInformationAtPosition))action forRequest:(WebKit::InteractionInformationRequest)request
2074 {
2075     if ([self _currentPositionInformationIsValidForRequest:request]) {
2076         // If the most recent position information is already valid, invoke the given action block immediately.
2077         action(_positionInformation);
2078         return;
2079     }
2080
2081     _pendingPositionInformationHandlers.append(InteractionInformationRequestAndCallback(request, action));
2082
2083     if (![self _hasValidOutstandingPositionInformationRequest:request])
2084         [self requestAsynchronousPositionInformationUpdate:request];
2085 }
2086
2087 - (BOOL)ensurePositionInformationIsUpToDate:(WebKit::InteractionInformationRequest)request
2088 {
2089     if ([self _currentPositionInformationIsValidForRequest:request])
2090         return YES;
2091
2092     auto* connection = _page->process().connection();
2093     if (!connection)
2094         return NO;
2095
2096     if ([self _hasValidOutstandingPositionInformationRequest:request])
2097         return connection->waitForAndDispatchImmediately<Messages::WebPageProxy::DidReceivePositionInformation>(_page->webPageID(), 1_s, IPC::WaitForOption::InterruptWaitingIfSyncMessageArrives);
2098
2099     bool receivedResponse = _page->process().sendSync(Messages::WebPage::GetPositionInformation(request), Messages::WebPage::GetPositionInformation::Reply(_positionInformation), _page->webPageID(), 1_s, IPC::SendSyncOption::ForceDispatchWhenDestinationIsWaitingForUnboundedSyncReply);
2100     _hasValidPositionInformation = receivedResponse && _positionInformation.canBeValid;
2101     
2102     // FIXME: We need to clean up these handlers in the event that we are not able to collect data, or if the WebProcess crashes.
2103     if (_hasValidPositionInformation)
2104         [self _invokeAndRemovePendingHandlersValidForCurrentPositionInformation];
2105
2106     return _hasValidPositionInformation;
2107 }
2108
2109 - (void)requestAsynchronousPositionInformationUpdate:(WebKit::InteractionInformationRequest)request
2110 {
2111     if ([self _currentPositionInformationIsValidForRequest:request])
2112         return;
2113
2114     _outstandingPositionInformationRequest = request;
2115
2116     _page->requestPositionInformation(request);
2117 }
2118
2119 - (BOOL)_currentPositionInformationIsValidForRequest:(const WebKit::InteractionInformationRequest&)request
2120 {
2121     return _hasValidPositionInformation && _positionInformation.request.isValidForRequest(request);
2122 }
2123
2124 - (BOOL)_hasValidOutstandingPositionInformationRequest:(const WebKit::InteractionInformationRequest&)request
2125 {
2126     return _outstandingPositionInformationRequest && _outstandingPositionInformationRequest->isValidForRequest(request);
2127 }
2128
2129 - (BOOL)_currentPositionInformationIsApproximatelyValidForRequest:(const WebKit::InteractionInformationRequest&)request radiusForApproximation:(int)radius
2130 {
2131     return _hasValidPositionInformation && _positionInformation.request.isApproximatelyValidForRequest(request, radius);
2132 }
2133
2134 - (void)_invokeAndRemovePendingHandlersValidForCurrentPositionInformation
2135 {
2136     ASSERT(_hasValidPositionInformation);
2137
2138     ++_positionInformationCallbackDepth;
2139     auto updatedPositionInformation = _positionInformation;
2140
2141     for (size_t index = 0; index < _pendingPositionInformationHandlers.size(); ++index) {
2142         auto requestAndHandler = _pendingPositionInformationHandlers[index];
2143         if (!requestAndHandler)
2144             continue;
2145
2146         if (![self _currentPositionInformationIsValidForRequest:requestAndHandler->first])
2147             continue;
2148
2149         _pendingPositionInformationHandlers[index] = WTF::nullopt;
2150
2151         if (requestAndHandler->second)
2152             requestAndHandler->second(updatedPositionInformation);
2153     }
2154
2155     if (--_positionInformationCallbackDepth)
2156         return;
2157
2158     for (int index = _pendingPositionInformationHandlers.size() - 1; index >= 0; --index) {
2159         if (!_pendingPositionInformationHandlers[index])
2160             _pendingPositionInformationHandlers.remove(index);
2161     }
2162 }
2163
2164 #if ENABLE(DATA_DETECTION)
2165 - (NSArray *)_dataDetectionResults
2166 {
2167     return _page->dataDetectionResults();
2168 }
2169 #endif
2170
2171 - (NSArray<NSValue *> *)_uiTextSelectionRects
2172 {
2173     NSMutableArray *textSelectionRects = [NSMutableArray array];
2174
2175     if (_textSelectionAssistant) {
2176         for (WKTextSelectionRect *selectionRect in [_textSelectionAssistant valueForKeyPath:@"selectionView.selection.selectionRects"])
2177             [textSelectionRects addObject:[NSValue valueWithCGRect:selectionRect.webRect.rect]];
2178     }
2179
2180     return textSelectionRects;
2181 }
2182
2183 - (BOOL)_shouldToggleSelectionCommandsAfterTapAt:(CGPoint)point
2184 {
2185     if (_lastSelectionDrawingInfo.selectionRects.isEmpty())
2186         return NO;
2187
2188     WebCore::FloatRect selectionBoundingRect;
2189     BOOL pointIsInSelectionRect = NO;
2190     for (auto& rectInfo : _lastSelectionDrawingInfo.selectionRects) {
2191         auto rect = rectInfo.rect();
2192         if (rect.isEmpty())
2193             continue;
2194
2195         pointIsInSelectionRect |= rect.contains(WebCore::roundedIntPoint(point));
2196         selectionBoundingRect.unite(rect);
2197     }
2198
2199     WebCore::FloatRect unobscuredContentRect = self.unobscuredContentRect;
2200     selectionBoundingRect.intersect(unobscuredContentRect);
2201
2202     float unobscuredArea = unobscuredContentRect.area();
2203     float ratioForConsideringSelectionRectToCoverVastMajorityOfContent = 0.75;
2204     if (!unobscuredArea || selectionBoundingRect.area() / unobscuredArea > ratioForConsideringSelectionRectToCoverVastMajorityOfContent)
2205         return NO;
2206
2207     return pointIsInSelectionRect;
2208 }
2209
2210 - (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer
2211 {
2212     CGPoint point = [gestureRecognizer locationInView:self];
2213
2214     if (gestureRecognizer == _stylusSingleTapGestureRecognizer)
2215         return _webView._stylusTapGestureShouldCreateEditableImage;
2216
2217     auto isInterruptingDecelerationForScrollViewOrAncestor = [&] (UIScrollView *scrollView) {
2218         UIScrollView *mainScroller = _webView.scrollView;
2219         UIView *view = scrollView ?: mainScroller;
2220         while (view) {
2221             if ([view isKindOfClass:UIScrollView.class] && [(UIScrollView *)view _isInterruptingDeceleration])
2222                 return YES;
2223
2224             if (mainScroller == view)
2225                 break;
2226
2227             view = view.superview;
2228         }
2229         return NO;
2230     };
2231
2232     if (gestureRecognizer == _singleTapGestureRecognizer) {
2233         if ([self _shouldToggleSelectionCommandsAfterTapAt:point])
2234             return NO;
2235         return !isInterruptingDecelerationForScrollViewOrAncestor([_singleTapGestureRecognizer lastTouchedScrollView]);
2236     }
2237
2238     if (gestureRecognizer == _doubleTapGestureRecognizerForDoubleClick) {
2239         // Do not start the double-tap-for-double-click gesture recognizer unless we've got a dblclick event handler on the node at the tap location.
2240         WebKit::InteractionInformationRequest request(WebCore::roundedIntPoint(point));
2241         if ([self _currentPositionInformationIsApproximatelyValidForRequest:request radiusForApproximation:[_doubleTapGestureRecognizerForDoubleClick allowableMovement]])
2242             return _positionInformation.nodeAtPositionHasDoubleClickHandler;
2243         if (![self ensurePositionInformationIsUpToDate:request])
2244             return NO;
2245         return _positionInformation.nodeAtPositionHasDoubleClickHandler;
2246     }
2247
2248     if (gestureRecognizer == _highlightLongPressGestureRecognizer
2249         || gestureRecognizer == _doubleTapGestureRecognizer
2250         || gestureRecognizer == _nonBlockingDoubleTapGestureRecognizer
2251         || gestureRecognizer == _twoFingerDoubleTapGestureRecognizer) {
2252
2253         if (hasFocusedElement(_focusedElementInformation)) {
2254             // Request information about the position with sync message.
2255             // If the focused element is the same, prevent the gesture.
2256             if (![self ensurePositionInformationIsUpToDate:WebKit::InteractionInformationRequest(WebCore::roundedIntPoint(point))])
2257                 return NO;
2258             if (_positionInformation.nodeAtPositionIsFocusedElement)
2259                 return NO;
2260         }
2261     }
2262
2263     if (gestureRecognizer == _highlightLongPressGestureRecognizer) {
2264         if (isInterruptingDecelerationForScrollViewOrAncestor([_highlightLongPressGestureRecognizer lastTouchedScrollView]))
2265             return NO;
2266
2267         if (hasFocusedElement(_focusedElementInformation)) {
2268             // This is a different element than the focused one.
2269             // Prevent the gesture if there is no node.
2270             // Allow the gesture if it is a node that wants highlight or if there is an action for it.
2271             if (!_positionInformation.isElement)
2272                 return NO;
2273             return [self _actionForLongPress] != nil;
2274         }
2275         // We still have no idea about what is at the location.
2276         // Send an async message to find out.
2277         _hasValidPositionInformation = NO;
2278         WebKit::InteractionInformationRequest request(WebCore::roundedIntPoint(point));
2279
2280         // If 3D Touch is enabled, asynchronously collect snapshots in the hopes that
2281         // they'll arrive before we have to synchronously request them in
2282         // _interactionShouldBeginFromPreviewItemController.
2283         if (self.traitCollection.forceTouchCapability == UIForceTouchCapabilityAvailable) {
2284             request.includeSnapshot = true;
2285             request.includeLinkIndicator = true;
2286             request.linkIndicatorShouldHaveLegacyMargins = !self._shouldUseContextMenus;
2287         }
2288
2289         [self requestAsynchronousPositionInformationUpdate:request];
2290         return YES;
2291
2292     }
2293
2294     if (gestureRecognizer == _longPressGestureRecognizer) {
2295         // Use the information retrieved with one of the previous calls
2296         // to gestureRecognizerShouldBegin.
2297         // Force a sync call if not ready yet.
2298         WebKit::InteractionInformationRequest request(WebCore::roundedIntPoint(point));
2299         if (![self ensurePositionInformationIsUpToDate:request])
2300             return NO;
2301
2302         if (hasFocusedElement(_focusedElementInformation)) {
2303             // Prevent the gesture if it is the same node.
2304             if (_positionInformation.nodeAtPositionIsFocusedElement)
2305                 return NO;
2306         } else {
2307             // Prevent the gesture if there is no action for the node.
2308             return [self _actionForLongPress] != nil;
2309         }
2310     }
2311
2312     return YES;
2313 }
2314
2315 - (void)_cancelInteraction
2316 {
2317     _isTapHighlightIDValid = NO;
2318     [_highlightView removeFromSuperview];
2319 }
2320
2321 - (void)_finishInteraction
2322 {
2323     _isTapHighlightIDValid = NO;
2324     CGFloat tapHighlightFadeDuration = _showDebugTapHighlightsForFastClicking ? 0.25 : 0.1;
2325     [UIView animateWithDuration:tapHighlightFadeDuration
2326                      animations:^{
2327                          [_highlightView layer].opacity = 0;
2328                      }
2329                      completion:^(BOOL finished){
2330                          if (finished)
2331                              [_highlightView removeFromSuperview];
2332                      }];
2333 }
2334
2335 - (BOOL)canShowNonEmptySelectionView
2336 {
2337     if (_suppressSelectionAssistantReasons)
2338         return NO;
2339
2340     auto& state = _page->editorState();
2341     return !state.isMissingPostLayoutData && !state.selectionIsNone;
2342 }
2343
2344 - (BOOL)hasSelectablePositionAtPoint:(CGPoint)point
2345 {
2346     if (!_webView.configuration._textInteractionGesturesEnabled)
2347         return NO;
2348
2349     if (_suppressSelectionAssistantReasons)
2350         return NO;
2351
2352     if (_inspectorNodeSearchEnabled)
2353         return NO;
2354
2355     WebKit::InteractionInformationRequest request(WebCore::roundedIntPoint(point));
2356     if (![self ensurePositionInformationIsUpToDate:request])
2357         return NO;
2358
2359 #if ENABLE(DRAG_SUPPORT)
2360     if (_positionInformation.hasSelectionAtPosition && self._allowedDragSourceActions & WebCore::DragSourceActionSelection) {
2361         // If the position might initiate a drag, we don't want to consider the content at this position to be selectable.
2362         // FIXME: This should be renamed to something more precise, such as textSelectionShouldRecognizeGestureAtPoint:
2363         return NO;
2364     }
2365 #endif
2366
2367     return _positionInformation.isSelectable;
2368 }
2369
2370 - (BOOL)pointIsNearMarkedText:(CGPoint)point
2371 {
2372     if (!_webView.configuration._textInteractionGesturesEnabled)
2373         return NO;
2374
2375     if (_suppressSelectionAssistantReasons)
2376         return NO;
2377
2378     WebKit::InteractionInformationRequest request(WebCore::roundedIntPoint(point));
2379     if (![self ensurePositionInformationIsUpToDate:request])
2380         return NO;
2381     return _positionInformation.isNearMarkedText;
2382 }
2383
2384 - (BOOL)textInteractionGesture:(UIWKGestureType)gesture shouldBeginAtPoint:(CGPoint)point
2385 {
2386     if (!_webView.configuration._textInteractionGesturesEnabled)
2387         return NO;
2388
2389     if (_domPasteRequestHandler)
2390         return NO;
2391
2392     if (_suppressSelectionAssistantReasons)
2393         return NO;
2394     
2395     if (!self.isFocusingElement) {
2396         if (gesture == UIWKGestureDoubleTap) {
2397             // Don't allow double tap text gestures in noneditable content.
2398             return NO;
2399         }
2400
2401         if (gesture == UIWKGestureOneFingerTap) {
2402             ASSERT(_suppressNonEditableSingleTapTextInteractionCount >= 0);
2403             if (_suppressNonEditableSingleTapTextInteractionCount > 0)
2404                 return NO;
2405
2406             switch ([_textSelectionAssistant loupeGesture].state) {
2407             case UIGestureRecognizerStateBegan:
2408             case UIGestureRecognizerStateChanged:
2409             case UIGestureRecognizerStateEnded: {
2410                 // Avoid handling one-finger taps while the web process is processing certain selection changes.
2411                 // This works around a scenario where UIKeyboardImpl blocks the main thread while handling a one-
2412                 // finger tap, which subsequently prevents the UI process from handling any incoming IPC messages.
2413                 return NO;
2414             }
2415             default:
2416                 break;
2417             }
2418         }
2419     }
2420
2421     WebKit::InteractionInformationRequest request(WebCore::roundedIntPoint(point));
2422     if (![self ensurePositionInformationIsUpToDate:request])
2423         return NO;
2424
2425 #if ENABLE(DRAG_SUPPORT)
2426     if (_positionInformation.hasSelectionAtPosition && gesture == UIWKGestureLoupe && self._allowedDragSourceActions & WebCore::DragSourceActionSelection) {
2427         // If the position might initiate a drag, we don't want to change the selection.
2428         return NO;
2429     }
2430 #endif
2431
2432 #if ENABLE(DATALIST_ELEMENT)
2433     if (_positionInformation.preventTextInteraction)
2434         return NO;
2435 #endif
2436
2437     // If we're currently focusing an editable element, only allow the selection to move within that focused element.
2438     if (self.isFocusingElement)
2439         return _positionInformation.nodeAtPositionIsFocusedElement;
2440
2441     // If we're selecting something, don't activate highlight.
2442     if (gesture == UIWKGestureLoupe && [self hasSelectablePositionAtPoint:point])
2443         [self _cancelLongPressGestureRecognizer];
2444     
2445     // Otherwise, if we're using a text interaction assistant outside of editing purposes (e.g. the selection mode
2446     // is character granularity) then allow text selection.
2447     return YES;
2448 }
2449
2450 - (NSArray *)webSelectionRectsForSelectionRects:(const Vector<WebCore::SelectionRect>&)selectionRects
2451 {
2452     unsigned size = selectionRects.size();
2453     if (!size)
2454         return nil;
2455
2456     NSMutableArray *webRects = [NSMutableArray arrayWithCapacity:size];
2457     for (unsigned i = 0; i < size; i++) {
2458         const WebCore::SelectionRect& coreRect = selectionRects[i];
2459         WebSelectionRect *webRect = [WebSelectionRect selectionRect];
2460         webRect.rect = coreRect.rect();
2461         webRect.writingDirection = coreRect.direction() == WebCore::TextDirection::LTR ? WKWritingDirectionLeftToRight : WKWritingDirectionRightToLeft;
2462         webRect.isLineBreak = coreRect.isLineBreak();
2463         webRect.isFirstOnLine = coreRect.isFirstOnLine();
2464         webRect.isLastOnLine = coreRect.isLastOnLine();
2465         webRect.containsStart = coreRect.containsStart();
2466         webRect.containsEnd = coreRect.containsEnd();
2467         webRect.isInFixedPosition = coreRect.isInFixedPosition();
2468         webRect.isHorizontal = coreRect.isHorizontal();
2469         [webRects addObject:webRect];
2470     }
2471
2472     return webRects;
2473 }
2474
2475 - (NSArray *)webSelectionRects
2476 {
2477     if (_page->editorState().isMissingPostLayoutData || _page->editorState().selectionIsNone)
2478         return nil;
2479     const auto& selectionRects = _page->editorState().postLayoutData().selectionRects;
2480     return [self webSelectionRectsForSelectionRects:selectionRects];
2481 }
2482
2483 - (void)_highlightLongPressRecognized:(UILongPressGestureRecognizer *)gestureRecognizer
2484 {
2485     ASSERT(gestureRecognizer == _highlightLongPressGestureRecognizer);
2486     [self _resetIsDoubleTapPending];
2487
2488     _lastInteractionLocation = gestureRecognizer.startPoint;
2489
2490     switch ([gestureRecognizer state]) {
2491     case UIGestureRecognizerStateBegan:
2492         _longPressCanClick = YES;
2493         cancelPotentialTapIfNecessary(self);
2494         _page->tapHighlightAtPosition([gestureRecognizer startPoint], ++_latestTapID);
2495         _isTapHighlightIDValid = YES;
2496         break;
2497     case UIGestureRecognizerStateEnded:
2498         if (_longPressCanClick && _positionInformation.isElement) {
2499             [self _attemptClickAtLocation:gestureRecognizer.startPoint modifierFlags:gestureRecognizerModifierFlags(gestureRecognizer)];
2500             [self _finishInteraction];
2501         } else
2502             [self _cancelInteraction];
2503         _longPressCanClick = NO;
2504         break;
2505     case UIGestureRecognizerStateCancelled:
2506         [self _cancelInteraction];
2507         _longPressCanClick = NO;
2508         break;
2509     default:
2510         break;
2511     }
2512 }
2513
2514 - (void)_doubleTapRecognizedForDoubleClick:(UITapGestureRecognizer *)gestureRecognizer
2515 {
2516     _page->handleDoubleTapForDoubleClickAtPoint(WebCore::IntPoint(gestureRecognizer.location), WebKit::webEventModifierFlags(gestureRecognizerModifierFlags(gestureRecognizer)), _layerTreeTransactionIdAtLastTouchStart);
2517 }
2518
2519 - (void)_twoFingerSingleTapGestureRecognized:(UITapGestureRecognizer *)gestureRecognizer
2520 {
2521     _isTapHighlightIDValid = YES;
2522     _isExpectingFastSingleTapCommit = YES;
2523     _page->handleTwoFingerTapAtPoint(WebCore::roundedIntPoint(gestureRecognizer.centroid), WebKit::webEventModifierFlags(gestureRecognizerModifierFlags(gestureRecognizer) | UIKeyModifierCommand), ++_latestTapID);
2524 }
2525
2526 - (void)_stylusSingleTapRecognized:(UITapGestureRecognizer *)gestureRecognizer
2527 {
2528     ASSERT(_webView._stylusTapGestureShouldCreateEditableImage);
2529     ASSERT(gestureRecognizer == _stylusSingleTapGestureRecognizer);
2530     _page->handleStylusSingleTapAtPoint(WebCore::roundedIntPoint(gestureRecognizer.location), ++_latestTapID);
2531 }
2532
2533 - (void)_longPressRecognized:(UILongPressGestureRecognizer *)gestureRecognizer
2534 {
2535     ASSERT(gestureRecognizer == _longPressGestureRecognizer);
2536     [self _resetIsDoubleTapPending];
2537     [self _cancelTouchEventGestureRecognizer];
2538     _page->didRecognizeLongPress();
2539
2540     _lastInteractionLocation = gestureRecognizer.startPoint;
2541
2542     if ([gestureRecognizer state] == UIGestureRecognizerStateBegan) {
2543         SEL action = [self _actionForLongPress];
2544         if (action) {
2545             [self performSelector:action];
2546             [self _cancelLongPressGestureRecognizer];
2547         }
2548     }
2549 }
2550
2551 - (void)_endPotentialTapAndEnableDoubleTapGesturesIfNecessary
2552 {
2553     if (_webView._allowsDoubleTapGestures) {
2554         RELEASE_LOG(ViewGestures, "ending potential tap - double taps are back. (%p)", self);
2555
2556         [self _setDoubleTapGesturesEnabled:YES];
2557     }
2558
2559     RELEASE_LOG(ViewGestures, "Ending potential tap. (%p)", self);
2560
2561     _potentialTapInProgress = NO;
2562 }
2563
2564 - (void)_singleTapIdentified:(UITapGestureRecognizer *)gestureRecognizer
2565 {
2566     ASSERT(gestureRecognizer == _singleTapGestureRecognizer);
2567     ASSERT(!_potentialTapInProgress);
2568     [self _resetIsDoubleTapPending];
2569
2570     bool shouldRequestMagnificationInformation = _page->preferences().fasterClicksEnabled();
2571     if (shouldRequestMagnificationInformation)
2572         RELEASE_LOG(ViewGestures, "Single tap identified. Request details on potential zoom. (%p)", self);
2573
2574     _page->potentialTapAtPosition(gestureRecognizer.location, shouldRequestMagnificationInformation, ++_latestTapID);
2575     _potentialTapInProgress = YES;
2576     _isTapHighlightIDValid = YES;
2577     _isExpectingFastSingleTapCommit = !_doubleTapGestureRecognizer.get().enabled;
2578 }
2579
2580 static void cancelPotentialTapIfNecessary(WKContentView* contentView)
2581 {
2582     if (contentView->_potentialTapInProgress) {
2583         [contentView _endPotentialTapAndEnableDoubleTapGesturesIfNecessary];
2584         [contentView _cancelInteraction];
2585         contentView->_page->cancelPotentialTap();
2586     }
2587 }
2588
2589 - (void)_singleTapDidReset:(UITapGestureRecognizer *)gestureRecognizer
2590 {
2591     ASSERT(gestureRecognizer == _singleTapGestureRecognizer);
2592     cancelPotentialTapIfNecessary(self);
2593 #if ENABLE(POINTER_EVENTS)
2594     if (auto* singleTapTouchIdentifier = [_singleTapGestureRecognizer lastActiveTouchIdentifier]) {
2595         WebCore::PointerID pointerId = [singleTapTouchIdentifier unsignedIntValue];
2596         if (m_commitPotentialTapPointerId != pointerId)
2597             _page->touchWithIdentifierWasRemoved(pointerId);
2598     }
2599 #endif
2600     auto actionsToPerform = std::exchange(_actionsToPerformAfterResettingSingleTapGestureRecognizer, { });
2601     for (const auto& action : actionsToPerform)
2602         action();
2603 }
2604
2605 - (void)_doubleTapDidFail:(UITapGestureRecognizer *)gestureRecognizer
2606 {
2607     RELEASE_LOG(ViewGestures, "Double tap was not recognized. (%p)", self);
2608     ASSERT(gestureRecognizer == _doubleTapGestureRecognizer);
2609 }
2610
2611 - (void)_commitPotentialTapFailed
2612 {
2613 #if ENABLE(POINTER_EVENTS)
2614     _page->touchWithIdentifierWasRemoved(m_commitPotentialTapPointerId);
2615     m_commitPotentialTapPointerId = 0;
2616 #endif
2617
2618     [self _cancelInteraction];
2619     
2620     [self _resetInputViewDeferral];
2621 }
2622
2623 - (void)_didNotHandleTapAsClick:(const WebCore::IntPoint&)point
2624 {
2625     [self _resetInputViewDeferral];
2626
2627     // FIXME: we should also take into account whether or not the UI delegate
2628     // has handled this notification.
2629 #if ENABLE(DATA_DETECTION)
2630     if (_hasValidPositionInformation && point == _positionInformation.request.point && _positionInformation.isDataDetectorLink) {
2631         [self _showDataDetectorsSheet];
2632         return;
2633     }
2634 #endif
2635
2636     if (!_isDoubleTapPending)
2637         return;
2638
2639     _smartMagnificationController->handleSmartMagnificationGesture(_lastInteractionLocation);
2640     _isDoubleTapPending = NO;
2641 }
2642
2643 - (void)_didCompleteSyntheticClick
2644 {
2645 #if ENABLE(POINTER_EVENTS)
2646     _page->touchWithIdentifierWasRemoved(m_commitPotentialTapPointerId);
2647     m_commitPotentialTapPointerId = 0;
2648 #endif
2649
2650     RELEASE_LOG(ViewGestures, "Synthetic click completed. (%p)", self);
2651     [self _resetInputViewDeferral];
2652 }
2653
2654 - (void)_singleTapRecognized:(UITapGestureRecognizer *)gestureRecognizer
2655 {
2656     ASSERT(gestureRecognizer == _singleTapGestureRecognizer);
2657
2658     if (![self isFirstResponder])
2659         [self becomeFirstResponder];
2660
2661     ASSERT(_potentialTapInProgress);
2662
2663     _lastInteractionLocation = gestureRecognizer.location;
2664
2665     [self _endPotentialTapAndEnableDoubleTapGesturesIfNecessary];
2666
2667     if (_hasTapHighlightForPotentialTap) {
2668         [self _showTapHighlight];
2669         _hasTapHighlightForPotentialTap = NO;
2670     }
2671
2672     [_inputPeripheral endEditing];
2673
2674     RELEASE_LOG(ViewGestures, "Single tap recognized - commit potential tap (%p)", self);
2675
2676     WebCore::PointerID pointerId = WebCore::mousePointerID;
2677 #if ENABLE(POINTER_EVENTS)
2678     if (auto* singleTapTouchIdentifier = [_singleTapGestureRecognizer lastActiveTouchIdentifier]) {
2679         pointerId = [singleTapTouchIdentifier unsignedIntValue];
2680         m_commitPotentialTapPointerId = pointerId;
2681     }
2682 #endif
2683     _page->commitPotentialTap(WebKit::webEventModifierFlags(gestureRecognizerModifierFlags(gestureRecognizer)), _layerTreeTransactionIdAtLastTouchStart, pointerId);
2684
2685     if (!_isExpectingFastSingleTapCommit)
2686         [self _finishInteraction];
2687 }
2688
2689 - (void)_doubleTapRecognized:(UITapGestureRecognizer *)gestureRecognizer
2690 {
2691     RELEASE_LOG(ViewGestures, "Identified a double tap (%p)", self);
2692
2693     [self _resetIsDoubleTapPending];
2694     _lastInteractionLocation = gestureRecognizer.location;
2695
2696     _smartMagnificationController->handleSmartMagnificationGesture(gestureRecognizer.location);
2697 }
2698
2699 - (void)_resetIsDoubleTapPending
2700 {
2701     _isDoubleTapPending = NO;
2702 }
2703
2704 - (void)_nonBlockingDoubleTapRecognized:(UITapGestureRecognizer *)gestureRecognizer
2705 {
2706     _lastInteractionLocation = gestureRecognizer.location;
2707     _isDoubleTapPending = YES;
2708 }
2709
2710 - (void)_twoFingerDoubleTapRecognized:(UITapGestureRecognizer *)gestureRecognizer
2711 {
2712     [self _resetIsDoubleTapPending];
2713     _lastInteractionLocation = gestureRecognizer.location;
2714
2715     _smartMagnificationController->handleResetMagnificationGesture(gestureRecognizer.location);
2716 }
2717
2718 - (void)_attemptClickAtLocation:(CGPoint)location modifierFlags:(UIKeyModifierFlags)modifierFlags
2719 {
2720     if (![self isFirstResponder])
2721         [self becomeFirstResponder];
2722
2723     [_inputPeripheral endEditing];
2724     _page->handleTap(location, WebKit::webEventModifierFlags(modifierFlags), _layerTreeTransactionIdAtLastTouchStart);
2725 }
2726
2727 - (void)setUpTextSelectionAssistant
2728 {
2729     if (!_textSelectionAssistant)
2730         _textSelectionAssistant = adoptNS([[UIWKTextInteractionAssistant alloc] initWithView:self]);
2731     else {
2732         // Reset the gesture recognizers in case editability has changed.
2733         [_textSelectionAssistant setGestureRecognizers];
2734     }
2735 }
2736
2737 - (void)pasteWithCompletionHandler:(void (^)(void))completionHandler
2738 {
2739     _page->executeEditCommand("Paste"_s, { }, [completion = makeBlockPtr(completionHandler)] (auto) {
2740         if (completion)
2741             completion();
2742     });
2743 }
2744
2745 - (void)clearSelection
2746 {
2747     [self _elementDidBlur];
2748     _page->clearSelection();
2749 }
2750
2751 - (void)_positionInformationDidChange:(const WebKit::InteractionInformationAtPosition&)info
2752 {
2753     _outstandingPositionInformationRequest = WTF::nullopt;
2754
2755     WebKit::InteractionInformationAtPosition newInfo = info;
2756     newInfo.mergeCompatibleOptionalInformation(_positionInformation);
2757
2758     _positionInformation = newInfo;
2759     _hasValidPositionInformation = _positionInformation.canBeValid;
2760     if (_actionSheetAssistant)
2761         [_actionSheetAssistant updateSheetPosition];
2762     [self _invokeAndRemovePendingHandlersValidForCurrentPositionInformation];
2763 }
2764
2765 - (void)_willStartScrollingOrZooming
2766 {
2767     if ([_textSelectionAssistant respondsToSelector:@selector(willStartScrollingOrZooming)])
2768         [_textSelectionAssistant willStartScrollingOrZooming];
2769     else
2770         [_textSelectionAssistant willStartScrollingOverflow];
2771     _page->setIsScrollingOrZooming(true);
2772
2773 #if PLATFORM(WATCHOS)
2774     [_focusedFormControlView disengageFocusedFormControlNavigation];
2775 #endif
2776 }
2777
2778 - (void)scrollViewWillStartPanOrPinchGesture
2779 {
2780     _page->hideValidationMessage();
2781
2782     [_keyboardScrollingAnimator willStartInteractiveScroll];
2783
2784     _canSendTouchEventsAsynchronously = YES;
2785 }
2786
2787 - (void)_didEndScrollingOrZooming
2788 {
2789     if (!_needsDeferredEndScrollingSelectionUpdate) {
2790         if ([_textSelectionAssistant respondsToSelector:@selector(didEndScrollingOrZooming)])
2791             [_textSelectionAssistant didEndScrollingOrZooming];
2792         else
2793             [_textSelectionAssistant didEndScrollingOverflow];
2794     }
2795     _page->setIsScrollingOrZooming(false);
2796
2797 #if ENABLE(POINTER_EVENTS)
2798     [self _resetPanningPreventionFlags];
2799 #endif
2800
2801 #if PLATFORM(WATCHOS)
2802     [_focusedFormControlView engageFocusedFormControlNavigation];
2803 #endif
2804 }
2805
2806 - (BOOL)requiresAccessoryView
2807 {
2808     if ([_formInputSession accessoryViewShouldNotShow])
2809         return NO;
2810
2811     if ([_formInputSession customInputAccessoryView])
2812         return YES;
2813
2814     switch (_focusedElementInformation.elementType) {
2815     case WebKit::InputType::None:
2816     case WebKit::InputType::Drawing:
2817         return NO;
2818     case WebKit::InputType::Text:
2819     case WebKit::InputType::Password:
2820     case WebKit::InputType::Search:
2821     case WebKit::InputType::Email:
2822     case WebKit::InputType::URL:
2823     case WebKit::InputType::Phone:
2824     case WebKit::InputType::Number:
2825     case WebKit::InputType::NumberPad:
2826     case WebKit::InputType::ContentEditable:
2827     case WebKit::InputType::TextArea:
2828     case WebKit::InputType::Select:
2829     case WebKit::InputType::Date:
2830     case WebKit::InputType::DateTime:
2831     case WebKit::InputType::DateTimeLocal:
2832     case WebKit::InputType::Month:
2833     case WebKit:: InputType::Week:
2834     case WebKit::InputType::Time:
2835 #if ENABLE(INPUT_TYPE_COLOR)
2836     case WebKit::InputType::Color:
2837 #endif
2838         return !currentUserInterfaceIdiomIsPad();
2839     }
2840 }
2841
2842 - (UITextInputAssistantItem *)inputAssistantItem
2843 {
2844     return [_webView inputAssistantItem];
2845 }
2846
2847 - (UITextInputAssistantItem *)inputAssistantItemForWebView
2848 {
2849     return [super inputAssistantItem];
2850 }
2851
2852 - (UIView *)inputAccessoryView
2853 {
2854     return [_webView inputAccessoryView];
2855 }
2856
2857 - (UIView *)inputAccessoryViewForWebView
2858 {
2859     if (![self requiresAccessoryView])
2860         return nil;
2861
2862     return [_formInputSession customInputAccessoryView] ?: self.formAccessoryView;
2863 }
2864
2865 - (NSArray *)supportedPasteboardTypesForCurrentSelection
2866 {
2867     if (_page->editorState().selectionIsNone)
2868         return nil;
2869     
2870     static NSMutableArray *richTypes = nil;
2871     static NSMutableArray *plainTextTypes = nil;
2872     if (!plainTextTypes) {
2873         plainTextTypes = [[NSMutableArray alloc] init];
2874         [plainTextTypes addObject:(id)kUTTypeURL];
2875         [plainTextTypes addObjectsFromArray:UIPasteboardTypeListString];
2876
2877         richTypes = [[NSMutableArray alloc] init];
2878         [richTypes addObject:WebCore::WebArchivePboardType];
2879         [richTypes addObjectsFromArray:UIPasteboardTypeListImage];
2880         [richTypes addObjectsFromArray:plainTextTypes];
2881     }
2882
2883     return (_page->editorState().isContentRichlyEditable) ? richTypes : plainTextTypes;
2884 }
2885
2886 #define FORWARD_ACTION_TO_WKWEBVIEW(_action) \
2887     - (void)_action:(id)sender \
2888     { \
2889         SEL action = @selector(_action:);\
2890         [self _willPerformAction:action sender:sender];\
2891         [_webView _action:sender]; \
2892         [self _didPerformAction:action sender:sender];\
2893     }
2894
2895 FOR_EACH_WKCONTENTVIEW_ACTION(FORWARD_ACTION_TO_WKWEBVIEW)
2896 FOR_EACH_PRIVATE_WKCONTENTVIEW_ACTION(FORWARD_ACTION_TO_WKWEBVIEW)
2897
2898 #undef FORWARD_ACTION_TO_WKWEBVIEW
2899
2900 - (void)_lookupForWebView:(id)sender
2901 {
2902     RetainPtr<WKContentView> view = self;
2903     _page->getSelectionContext([view](const String& selectedText, const String& textBefore, const String& textAfter, WebKit::CallbackBase::Error error) {
2904         if (error != WebKit::CallbackBase::Error::None)
2905             return;
2906         if (!selectedText)
2907             return;
2908
2909         auto& editorState = view->_page->editorState();
2910         auto& postLayoutData = editorState.postLayoutData();
2911         CGRect presentationRect;
2912         if (editorState.selectionIsRange && !postLayoutData.selectionRects.isEmpty())
2913             presentationRect = postLayoutData.selectionRects[0].rect();
2914         else
2915             presentationRect = postLayoutData.caretRectAtStart;
2916         
2917         String selectionContext = textBefore + selectedText + textAfter;
2918         NSRange selectedRangeInContext = NSMakeRange(textBefore.length(), selectedText.length());
2919
2920         if (auto textSelectionAssistant = view->_textSelectionAssistant)
2921             [textSelectionAssistant lookup:selectionContext withRange:selectedRangeInContext fromRect:presentationRect];
2922     });
2923 }
2924
2925 - (void)_shareForWebView:(id)sender
2926 {
2927     RetainPtr<WKContentView> view = self;
2928     _page->getSelectionOrContentsAsString([view](const String& string, WebKit::CallbackBase::Error error) {
2929         if (error != WebKit::CallbackBase::Error::None)
2930             return;
2931
2932         if (!view->_textSelectionAssistant || !string || view->_page->editorState().isMissingPostLayoutData)
2933             return;
2934
2935         auto& selectionRects = view->_page->editorState().postLayoutData().selectionRects;
2936         if (selectionRects.isEmpty())
2937             return;
2938
2939         [view->_textSelectionAssistant showShareSheetFor:string fromRect:selectionRects.first().rect()];
2940     });
2941 }
2942
2943 - (void)_addShortcutForWebView:(id)sender
2944 {
2945     if (_textSelectionAssistant)
2946         [_textSelectionAssistant showTextServiceFor:[self selectedText] fromRect:_page->editorState().postLayoutData().selectionRects[0].rect()];
2947 }
2948
2949 - (NSString *)selectedText
2950 {
2951     return (NSString *)_page->editorState().postLayoutData().wordAtSelection;
2952 }
2953
2954 - (void)makeTextWritingDirectionNaturalForWebView:(id)sender
2955 {
2956     // Match platform behavior on iOS as well as legacy WebKit behavior by modifying the
2957     // base (paragraph) writing direction rather than the inline direction.
2958     _page->setBaseWritingDirection(WebCore::WritingDirection::Natural);
2959 }
2960
2961 - (void)makeTextWritingDirectionLeftToRightForWebView:(id)sender
2962 {
2963     _page->setBaseWritingDirection(WebCore::WritingDirection::LeftToRight);
2964 }
2965
2966 - (void)makeTextWritingDirectionRightToLeftForWebView:(id)sender
2967 {
2968     _page->setBaseWritingDirection(WebCore::WritingDirection::RightToLeft);
2969 }
2970
2971 - (BOOL)isReplaceAllowed
2972 {
2973     return _page->editorState().postLayoutData().isReplaceAllowed;
2974 }
2975
2976 - (void)replaceText:(NSString *)text withText:(NSString *)word
2977 {
2978     _page->replaceSelectedText(text, word);
2979 }
2980
2981 - (void)selectWordBackward
2982 {
2983     _page->selectWordBackward();
2984 }
2985
2986 - (void)_promptForReplaceForWebView:(id)sender
2987 {
2988     const auto& wordAtSelection = _page->editorState().postLayoutData().wordAtSelection;
2989     if (wordAtSelection.isEmpty())
2990         return;
2991
2992     [_textSelectionAssistant scheduleReplacementsForText:wordAtSelection];
2993 }
2994
2995 - (void)_transliterateChineseForWebView:(id)sender
2996 {
2997     [_textSelectionAssistant scheduleChineseTransliterationForText:_page->editorState().postLayoutData().wordAtSelection];
2998 }
2999
3000 - (void)replaceForWebView:(id)sender
3001 {
3002     [[UIKeyboardImpl sharedInstance] replaceText:sender];
3003 }
3004
3005 #define WEBCORE_COMMAND_FOR_WEBVIEW(command) \
3006     - (void)_ ## command ## ForWebView:(id)sender { _page->executeEditCommand(#command ## _s); } \
3007     - (void)command ## ForWebView:(id)sender { [self _ ## command ## ForWebView:sender]; }
3008 WEBCORE_COMMAND_FOR_WEBVIEW(insertOrderedList);
3009 WEBCORE_COMMAND_FOR_WEBVIEW(insertUnorderedList);
3010 WEBCORE_COMMAND_FOR_WEBVIEW(insertNestedOrderedList);
3011 WEBCORE_COMMAND_FOR_WEBVIEW(insertNestedUnorderedList);
3012 WEBCORE_COMMAND_FOR_WEBVIEW(indent);
3013 WEBCORE_COMMAND_FOR_WEBVIEW(outdent);
3014 WEBCORE_COMMAND_FOR_WEBVIEW(alignLeft);
3015 WEBCORE_COMMAND_FOR_WEBVIEW(alignRight);
3016 WEBCORE_COMMAND_FOR_WEBVIEW(alignCenter);
3017 WEBCORE_COMMAND_FOR_WEBVIEW(alignJustified);
3018 WEBCORE_COMMAND_FOR_WEBVIEW(pasteAndMatchStyle);
3019 #undef WEBCORE_COMMAND_FOR_WEBVIEW
3020
3021 - (void)_increaseListLevelForWebView:(id)sender
3022 {
3023     _page->increaseListLevel();
3024 }
3025
3026 - (void)_decreaseListLevelForWebView:(id)sender
3027 {
3028     _page->decreaseListLevel();
3029 }
3030
3031 - (void)_changeListTypeForWebView:(id)sender
3032 {
3033     _page->changeListType();
3034 }
3035
3036 - (void)_toggleStrikeThroughForWebView:(id)sender
3037 {
3038     _page->executeEditCommand("StrikeThrough"_s);
3039 }
3040
3041 - (void)increaseSizeForWebView:(id)sender
3042 {
3043     _page->executeEditCommand("FontSizeDelta"_s, "1"_s);
3044 }
3045
3046 - (void)decreaseSizeForWebView:(id)sender
3047 {
3048     _page->executeEditCommand("FontSizeDelta"_s, "-1"_s);
3049 }
3050
3051 - (void)_setFontForWebView:(UIFont *)font sender:(id)sender
3052 {
3053     WebCore::FontChanges changes;
3054     changes.setFontFamily(font.familyName);
3055     changes.setFontName(font.fontName);
3056     changes.setFontSize(font.pointSize);
3057     changes.setBold(font.traits & UIFontTraitBold);
3058     changes.setItalic(font.traits & UIFontTraitItalic);
3059     _page->changeFont(WTFMove(changes));
3060 }
3061
3062 - (void)_setFontSizeForWebView:(CGFloat)fontSize sender:(id)sender
3063 {
3064     WebCore::FontChanges changes;
3065     changes.setFontSize(fontSize);
3066     _page->changeFont(WTFMove(changes));
3067 }
3068
3069 - (void)_setTextColorForWebView:(UIColor *)color sender:(id)sender
3070 {
3071     _page->executeEditCommand("ForeColor"_s, WebCore::Color(color.CGColor).serialized());
3072 }
3073
3074 - (void)toggleStrikeThroughForWebView:(id)sender
3075 {
3076     [self _toggleStrikeThroughForWebView:sender];
3077 }
3078
3079 - (NSDictionary *)textStylingAtPosition:(UITextPosition *)position inDirection:(UITextStorageDirection)direction
3080 {
3081     if (!position || !_page->editorState().isContentRichlyEditable)
3082         return nil;
3083
3084     NSMutableDictionary* result = [NSMutableDictionary dictionary];
3085
3086     auto typingAttributes = _page->editorState().postLayoutData().typingAttributes;
3087     CTFontSymbolicTraits symbolicTraits = 0;
3088     if (typingAttributes & WebKit::AttributeBold)
3089         symbolicTraits |= kCTFontBoldTrait;
3090     if (typingAttributes & WebKit::AttributeItalics)
3091         symbolicTraits |= kCTFontTraitItalic;
3092
3093     // We chose a random font family and size.
3094     // What matters are the traits but the caller expects a font object
3095     // in the dictionary for NSFontAttributeName.
3096     RetainPtr<CTFontDescriptorRef> fontDescriptor = adoptCF(CTFontDescriptorCreateWithNameAndSize(CFSTR("Helvetica"), 10));
3097     if (symbolicTraits)
3098         fontDescriptor = adoptCF(CTFontDescriptorCreateCopyWithSymbolicTraits(fontDescriptor.get(), symbolicTraits, symbolicTraits));
3099     
3100     RetainPtr<CTFontRef> font = adoptCF(CTFontCreateWithFontDescriptor(fontDescriptor.get(), 10, nullptr));
3101     if (font)
3102         [result setObject:(id)font.get() forKey:NSFontAttributeName];
3103     
3104     if (typingAttributes & WebKit::AttributeUnderline)
3105         [result setObject:[NSNumber numberWithInt:NSUnderlineStyleSingle] forKey:NSUnderlineStyleAttributeName];
3106
3107     return result;
3108 }
3109
3110 - (UIColor *)insertionPointColor
3111 {
3112     return [self.textInputTraits insertionPointColor];
3113 }
3114
3115 - (UIColor *)selectionBarColor
3116 {
3117     return [self.textInputTraits selectionBarColor];
3118 }
3119
3120 - (UIColor *)selectionHighlightColor
3121 {
3122     return [self.textInputTraits selectionHighlightColor];
3123 }
3124
3125 - (void)_updateInteractionTintColor
3126 {
3127     UIColor *tintColor = ^{
3128         if (!_webView.configuration._textInteractionGesturesEnabled)
3129             return [UIColor clearColor];
3130
3131         if (!_page->editorState().isMissingPostLayoutData) {
3132             WebCore::Color caretColor = _page->editorState().postLayoutData().caretColor;
3133             if (caretColor.isValid())
3134                 return [UIColor colorWithCGColor:cachedCGColor(caretColor)];
3135         }
3136         
3137         return [self _inheritedInteractionTintColor];    
3138     }();
3139
3140     [_traits _setColorsToMatchTintColor:tintColor];
3141 }
3142
3143 - (void)tintColorDidChange
3144 {
3145     [super tintColorDidChange];
3146
3147     BOOL shouldUpdateTextSelection = self.isFirstResponder && [self canShowNonEmptySelectionView];
3148     if (shouldUpdateTextSelection)
3149         [_textSelectionAssistant deactivateSelection];
3150     [self _updateInteractionTintColor];
3151     if (shouldUpdateTextSelection)
3152         [_textSelectionAssistant activateSelection];
3153 }
3154
3155 - (BOOL)canPerformAction:(SEL)action withSender:(id)sender
3156 {
3157     if (_domPasteRequestHandler)
3158         return action == @selector(paste:);
3159
3160     // These are UIKit IPI selectors. We don't want to forward them to the web view.
3161     auto editorState = _page->editorState();
3162     if (action == @selector(_moveDown:withHistory:) || action == @selector(_moveLeft:withHistory:) || action == @selector(_moveRight:withHistory:)
3163         || action == @selector(_moveToEndOfDocument:withHistory:) || action == @selector(_moveToEndOfLine:withHistory:) || action == @selector(_moveToEndOfParagraph:withHistory:)
3164         || action == @selector(_moveToEndOfWord:withHistory:) || action == @selector(_moveToStartOfDocument:withHistory:) || action == @selector(_moveToStartOfLine:withHistory:)
3165         || action == @selector(_moveToStartOfParagraph:withHistory:) || action == @selector(_moveToStartOfWord:withHistory:) || action == @selector(_moveUp:withHistory:))
3166         return !editorState.selectionIsNone;
3167
3168     if (action == @selector(_deleteByWord) || action == @selector(_deleteForwardAndNotify:) || action == @selector(_deleteToEndOfParagraph) || action == @selector(_deleteToStartOfLine)
3169         || action == @selector(_transpose))
3170         return editorState.isContentEditable;
3171
3172     return [_webView canPerformAction:action withSender:sender];
3173 }
3174
3175 - (BOOL)canPerformActionForWebView:(SEL)action withSender:(id)sender
3176 {
3177     if (_domPasteRequestHandler)
3178         return action == @selector(paste:);
3179
3180     if (action == @selector(_nextAccessoryTab:))
3181         return hasFocusedElement(_focusedElementInformation) && _focusedElementInformation.hasNextNode;
3182     if (action == @selector(_previousAccessoryTab:))
3183         return hasFocusedElement(_focusedElementInformation) && _focusedElementInformation.hasPreviousNode;
3184
3185     auto editorState = _page->editorState();
3186     if (action == @selector(_showTextStyleOptions:))
3187         return editorState.isContentRichlyEditable && editorState.selectionIsRange && !_showingTextStyleOptions;
3188     if (_showingTextStyleOptions)
3189         return (action == @selector(toggleBoldface:) || action == @selector(toggleItalics:) || action == @selector(toggleUnderline:));
3190     // FIXME: Some of the following checks should be removed once internal clients move to the underscore-prefixed versions.
3191     if (action == @selector(toggleBoldface:) || action == @selector(toggleItalics:) || action == @selector(toggleUnderline:) || action == @selector(_toggleStrikeThrough:)
3192         || action == @selector(_alignLeft:) || action == @selector(_alignRight:) || action == @selector(_alignCenter:) || action == @selector(_alignJustified:)
3193         || action == @selector(_setTextColor:sender:) || action == @selector(_setFont:sender:) || action == @selector(_setFontSize:sender:)
3194         || action == @selector(_insertOrderedList:) || action == @selector(_insertUnorderedList:) || action == @selector(_insertNestedOrderedList:) || action == @selector(_insertNestedUnorderedList:)
3195         || action == @selector(_increaseListLevel:) || action == @selector(_decreaseListLevel:) || action == @selector(_changeListType:) || action == @selector(_indent:) || action == @selector(_outdent:)
3196         || action == @selector(increaseSize:) || action == @selector(decreaseSize:) || action == @selector(makeTextWritingDirectionNatural:)) {
3197         // FIXME: This should be more nuanced in the future, rather than returning YES for all richly editable areas. For instance, outdent: should be disabled when the selection is already
3198         // at the outermost indentation level.
3199         return editorState.isContentRichlyEditable;
3200     }
3201     if (action == @selector(cut:))
3202         return !editorState.isInPasswordField && editorState.isContentEditable && editorState.selectionIsRange;
3203     
3204     if (action == @selector(paste:) || action == @selector(_pasteAsQuotation:) || action == @selector(_pasteAndMatchStyle:) || action == @selector(pasteAndMatchStyle:)) {
3205         if (editorState.selectionIsNone || !editorState.isContentEditable)
3206             return NO;
3207         UIPasteboard *pasteboard = [UIPasteboard generalPasteboard];
3208         NSArray *types = [self supportedPasteboardTypesForCurrentSelection];
3209         NSIndexSet *indices = [NSIndexSet indexSetWithIndexesInRange:NSMakeRange(0, [pasteboard numberOfItems])];
3210         if ([pasteboard containsPasteboardTypes:types inItemSet:indices])
3211             return YES;
3212
3213 #if PLATFORM(IOS)
3214         if (editorState.isContentRichlyEditable && _webView.configuration._attachmentElementEnabled) {
3215             for (NSItemProvider *itemProvider in pasteboard.itemProviders) {
3216                 auto preferredPresentationStyle = itemProvider.preferredPresentationStyle;
3217                 if (preferredPresentationStyle == UIPreferredPresentationStyleInline)
3218                     continue;
3219
3220                 if (preferredPresentationStyle == UIPreferredPresentationStyleUnspecified && !itemProvider.suggestedName.length)
3221                     continue;
3222
3223                 if (itemProvider.web_fileUploadContentTypes.count)
3224                     return YES;
3225             }
3226         }
3227 #endif // PLATFORM(IOS)
3228
3229         auto focusedDocumentOrigin = editorState.originIdentifierForPasteboard;
3230         if (focusedDocumentOrigin.isEmpty())
3231             return NO;
3232
3233         NSArray *allCustomPasteboardData = [pasteboard dataForPasteboardType:@(WebCore::PasteboardCustomData::cocoaType()) inItemSet:indices];
3234         for (NSData *data in allCustomPasteboardData) {
3235             auto buffer = WebCore::SharedBuffer::create(data);
3236             if (WebCore::PasteboardCustomData::fromSharedBuffer(buffer.get()).origin == focusedDocumentOrigin)
3237                 return YES;
3238         }
3239         return NO;
3240     }
3241
3242     if (action == @selector(copy:)) {
3243         if (editorState.isInPasswordField)
3244             return NO;
3245         return editorState.selectionIsRange;
3246     }
3247
3248     if (action == @selector(_define:)) {
3249         if (editorState.isInPasswordField || !editorState.selectionIsRange)
3250             return NO;
3251
3252         NSUInteger textLength = editorState.postLayoutData().selectedTextLength;
3253         // FIXME: We should be calling UIReferenceLibraryViewController to check if the length is
3254         // acceptable, but the interface takes a string.
3255         // <rdar://problem/15254406>
3256         if (!textLength || textLength > 200)
3257             return NO;
3258
3259 #if !PLATFORM(MACCATALYST)
3260         if ([[getMCProfileConnectionClass() sharedConnection] effectiveBoolValueForSetting:getMCFeatureDefinitionLookupAllowed()] == MCRestrictedBoolExplicitNo)
3261             return NO;
3262 #endif
3263             
3264         return YES;
3265     }
3266
3267     if (action == @selector(_lookup:)) {
3268         if (editorState.isInPasswordField)
3269             return NO;
3270
3271 #if !PLATFORM(MACCATALYST)
3272         if ([[getMCProfileConnectionClass() sharedConnection] effectiveBoolValueForSetting:getMCFeatureDefinitionLookupAllowed()] == MCRestrictedBoolExplicitNo)
3273             return NO;
3274 #endif
3275
3276         return editorState.selectionIsRange;
3277     }
3278
3279     if (action == @selector(_share:)) {
3280         if (editorState.isInPasswordField || !editorState.selectionIsRange)
3281             return NO;
3282
3283         return editorState.postLayoutData().selectedTextLength > 0;
3284     }
3285
3286     if (action == @selector(_addShortcut:)) {
3287         if (editorState.isInPasswordField || !editorState.selectionIsRange)
3288             return NO;
3289
3290         NSString *selectedText = [self selectedText];
3291         if (![selectedText length])
3292             return NO;
3293
3294         if (!UIKeyboardEnabledInputModesAllowOneToManyShortcuts())
3295             return NO;
3296         if (![selectedText _containsCJScripts])
3297             return NO;
3298         return YES;
3299     }
3300
3301     if (action == @selector(_promptForReplace:)) {
3302         if (!editorState.selectionIsRange || !editorState.postLayoutData().isReplaceAllowed || ![[UIKeyboardImpl activeInstance] autocorrectSpellingEnabled])
3303             return NO;
3304         if ([[self selectedText] _containsCJScriptsOnly])
3305             return NO;
3306         return YES;
3307     }
3308
3309     if (action == @selector(_transliterateChinese:)) {
3310         if (!editorState.selectionIsRange || !editorState.postLayoutData().isReplaceAllowed || ![[UIKeyboardImpl activeInstance] autocorrectSpellingEnabled])
3311             return NO;
3312         return UIKeyboardEnabledInputModesAllowChineseTransliterationForText([self selectedText]);
3313     }
3314
3315     if (action == @selector(select:)) {
3316         // Disable select in password fields so that you can't see word boundaries.
3317         return !editorState.isInPasswordField && [self hasContent] && !editorState.selectionIsNone && !editorState.selectionIsRange;
3318     }
3319
3320     if (action == @selector(selectAll:)) {
3321         // By platform convention we don't show Select All in the callout menu for a range selection.
3322         if ([sender isKindOfClass:UIMenuController.class])
3323             return !editorState.selectionIsNone && !editorState.selectionIsRange;
3324 #if USE(UIKIT_KEYBOARD_ADDITIONS)
3325         return YES;
3326 #else
3327         return !editorState.selectionIsNone && self.hasContent;
3328 #endif
3329     }
3330
3331     if (action == @selector(replace:))
3332         return editorState.isContentEditable && !editorState.isInPasswordField;
3333
3334     if (action == @selector(makeTextWritingDirectionLeftToRight:) || action == @selector(makeTextWritingDirectionRightToLeft:)) {
3335         if (!editorState.isContentEditable)
3336             return NO;
3337
3338         auto baseWritingDirection = editorState.postLayoutData().baseWritingDirection;
3339         if (baseWritingDirection == WebCore::WritingDirection::LeftToRight && !UIKeyboardIsRightToLeftInputModeActive()) {
3340             // A keyboard is considered "active" if it is available for the user to switch to. As such, this check prevents
3341             // text direction actions from showing up in the case where a user has only added left-to-right keyboards, and
3342             // is also not editing right-to-left content.
3343             return NO;
3344         }
3345
3346         if (action == @selector(makeTextWritingDirectionLeftToRight:))
3347             return baseWritingDirection != WebCore::WritingDirection::LeftToRight;
3348
3349         return baseWritingDirection != WebCore::WritingDirection::RightToLeft;
3350     }
3351
3352     return [super canPerformAction:action withSender:sender];
3353 }
3354
3355 - (id)targetForAction:(SEL)action withSender:(id)sender
3356 {
3357     return [_webView targetForAction:action withSender:sender];
3358 }
3359
3360 - (id)targetForActionForWebView:(SEL)action withSender:(id)sender
3361 {
3362     return [super targetForAction:action withSender:sender];
3363 }
3364
3365 - (void)_willHideMenu:(NSNotification *)notification
3366 {
3367     [self _handleDOMPasteRequestWithResult:WebCore::DOMPasteAccessResponse::DeniedForGesture];
3368 }
3369
3370 - (void)_didHideMenu:(NSNotification *)notification
3371 {
3372     _showingTextStyleOptions = NO;
3373     [_textSelectionAssistant hideTextStyleOptions];
3374 }
3375
3376 - (void)_keyboardDidRequestDismissal:(NSNotification *)notification
3377 {
3378     if (![self isFirstResponder])
3379         return;
3380     _keyboardDidRequestDismissal = YES;
3381 }
3382
3383 - (void)copyForWebView:(id)sender
3384 {
3385     _page->executeEditCommand("copy"_s);
3386 }
3387
3388 - (void)cutForWebView:(id)sender
3389 {
3390     [self executeEditCommandWithCallback:@"cut"];
3391 }
3392
3393 - (void)pasteForWebView:(id)sender
3394 {
3395     if (sender == UIMenuController.sharedMenuController && [self _handleDOMPasteRequestWithResult:WebCore::DOMPasteAccessResponse::GrantedForGesture])
3396         return;
3397
3398     _page->executeEditCommand("paste"_s);
3399 }
3400
3401 - (void)_pasteAsQuotationForWebView:(id)sender
3402 {
3403     _page->executeEditCommand("PasteAsQuotation"_s);
3404 }
3405
3406 - (void)selectForWebView:(id)sender
3407 {
3408     [_textSelectionAssistant selectWord];
3409     // We cannot use selectWord command, because we want to be able to select the word even when it is the last in the paragraph.
3410     _page->extendSelection(WebCore::WordGranularity);
3411 }
3412
3413 - (void)selectAllForWebView:(id)sender
3414 {
3415     [_textSelectionAssistant selectAll:sender];
3416     _page->selectAll();
3417 }
3418
3419 - (BOOL)shouldSynthesizeKeyEvents
3420 {
3421     if (_focusedElementInformation.shouldSynthesizeKeyEventsForEditing && self.hasHiddenContentEditable)
3422         return true;
3423     return false;
3424 }
3425
3426 - (void)toggleBoldfaceForWebView:(id)sender
3427 {
3428     if (!_page->editorState().isContentRichlyEditable)
3429         return;
3430
3431     [self executeEditCommandWithCallback:@"toggleBold"];
3432
3433     if (self.shouldSynthesizeKeyEvents)
3434         _page->generateSyntheticEditingCommand(WebKit::SyntheticEditingCommandType::ToggleBoldface);
3435 }
3436
3437 - (void)toggleItalicsForWebView:(id)sender
3438 {
3439     if (!_page->editorState().isContentRichlyEditable)
3440         return;
3441
3442     [self executeEditCommandWithCallback:@"toggleItalic"];
3443
3444     if (self.shouldSynthesizeKeyEvents)
3445         _page->generateSyntheticEditingCommand(WebKit::SyntheticEditingCommandType::ToggleItalic);
3446 }
3447
3448 - (void)toggleUnderlineForWebView:(id)sender
3449 {
3450     if (!_page->editorState().isContentRichlyEditable)
3451         return;
3452
3453     [self executeEditCommandWithCallback:@"toggleUnderline"];
3454
3455     if (self.shouldSynthesizeKeyEvents)
3456         _page->generateSyntheticEditingCommand(WebKit::SyntheticEditingCommandType::ToggleUnderline);
3457 }
3458
3459 - (void)_showTextStyleOptionsForWebView:(id)sender
3460 {
3461     _showingTextStyleOptions = YES;
3462     [_textSelectionAssistant showTextStyleOptions];
3463 }
3464
3465 - (void)_showDictionary:(NSString *)text
3466 {
3467     CGRect presentationRect = _page->editorState().postLayoutData().selectionRects[0].rect();
3468     if (_textSelectionAssistant)
3469         [_textSelectionAssistant showDictionaryFor:text fromRect:presentationRect];
3470 }
3471
3472 - (void)_defineForWebView:(id)sender
3473 {
3474 #if !PLATFORM(MACCATALYST)
3475     if ([[getMCProfileConnectionClass() sharedConnection] effectiveBoolValueForSetting:getMCFeatureDefinitionLookupAllowed()] == MCRestrictedBoolExplicitNo)
3476         return;
3477 #endif
3478
3479     RetainPtr<WKContentView> view = self;
3480     _page->getSelectionOrContentsAsString([view](const String& string, WebKit::CallbackBase::Error error) {
3481         if (error != WebKit::CallbackBase::Error::None)
3482             return;
3483         if (!string)
3484             return;
3485
3486         [view _showDictionary:string];
3487     });
3488 }
3489
3490 - (void)accessibilityRetrieveSpeakSelectionContent
3491 {
3492     RetainPtr<WKContentView> view = self;
3493     RetainPtr<WKWebView> webView = _webView;
3494     _page->getSelectionOrContentsAsString([view, webView](const String& string, WebKit::CallbackBase::Error error) {
3495         if (error != WebKit::CallbackBase::Error::None)
3496             return;
3497         [webView _accessibilityDidGetSpeakSelectionContent:string];
3498         if ([view respondsToSelector:@selector(accessibilitySpeakSelectionSetContent:)])
3499             [view accessibilitySpeakSelectionSetContent:string];
3500     });
3501 }
3502
3503 - (void)_accessibilityRetrieveRectsEnclosingSelectionOffset:(NSInteger)offset withGranularity:(UITextGranularity)granularity
3504 {
3505     RetainPtr<WKContentView> view = self;
3506     _page->requestRectsForGranularityWithSelectionOffset(toWKTextGranularity(granularity), offset , [view, offset, granularity](const Vector<WebCore::SelectionRect>& selectionRects, WebKit::CallbackBase::Error error) {
3507         if (error != WebKit::CallbackBase::Error::None)
3508             return;
3509         if ([view respondsToSelector:@selector(_accessibilityDidGetSelectionRects:withGranularity:atOffset:)])
3510             [view _accessibilityDidGetSelectionRects:[view webSelectionRectsForSelectionRects:selectionRects] withGranularity:granularity atOffset:offset];
3511     });
3512 }
3513
3514 - (void)_accessibilityRetrieveRectsAtSelectionOffset:(NSInteger)offset withText:(NSString *)text
3515 {
3516     [self _accessibilityRetrieveRectsAtSelectionOffset:offset withText:text completionHandler:nil];
3517 }
3518
3519 - (void)_accessibilityRetrieveRectsAtSelectionOffset:(NSInteger)offset withText:(NSString *)text completionHandler:(void (^)(const Vector<WebCore::SelectionRect>& rects))completionHandler
3520 {
3521     RetainPtr<WKContentView> view = self;
3522     _page->requestRectsAtSelectionOffsetWithText(offset, text, [view, offset, capturedCompletionHandler = makeBlockPtr(completionHandler)](const Vector<WebCore::SelectionRect>& selectionRects, WebKit::CallbackBase::Error error) {
3523         if (capturedCompletionHandler)
3524             capturedCompletionHandler(selectionRects);
3525
3526         if (error != WebKit::CallbackBase::Error::None)
3527             return;
3528         if ([view respondsToSelector:@selector(_accessibilityDidGetSelectionRects:withGranularity:atOffset:)])
3529             [view _accessibilityDidGetSelectionRects:[view webSelectionRectsForSelectionRects:selectionRects] withGranularity:UITextGranularityWord atOffset:offset];
3530     });
3531 }
3532
3533 - (void)_accessibilityStoreSelection
3534 {
3535     _page->storeSelectionForAccessibility(true);
3536 }
3537
3538 - (void)_accessibilityClearSelection
3539 {
3540     _page->storeSelectionForAccessibility(false);
3541 }
3542
3543 - (BOOL)_handleDOMPasteRequestWithResult:(WebCore::DOMPasteAccessResponse)response
3544 {
3545     if (auto pasteHandler = WTFMove(_domPasteRequestHandler)) {
3546         [self hideGlobalMenuController];
3547         pasteHandler(response);
3548         return YES;
3549     }
3550     return NO;
3551 }
3552
3553 - (void)_willPerformAction:(SEL)action sender:(id)sender
3554 {
3555     if (action != @selector(paste:))
3556         [self _handleDOMPasteRequestWithResult:WebCore::DOMPasteAccessResponse::DeniedForGesture];
3557 }
3558
3559 - (void)_didPerformAction:(SEL)action sender:(id)sender
3560 {
3561     if (action == @selector(paste:))
3562         [self _handleDOMPasteRequestWithResult:WebCore::DOMPasteAccessResponse::DeniedForGesture];
3563 }
3564
3565 // UIWKInteractionViewProtocol
3566
3567 static inline WebKit::GestureType toGestureType(UIWKGestureType gestureType)
3568 {
3569     switch (gestureType) {
3570     case UIWKGestureLoupe:
3571         return WebKit::GestureType::Loupe;
3572     case UIWKGestureOneFingerTap:
3573         return WebKit::GestureType::OneFingerTap;
3574     case UIWKGestureTapAndAHalf:
3575         return WebKit::GestureType::TapAndAHalf;
3576     case UIWKGestureDoubleTap:
3577         return WebKit::GestureType::DoubleTap;
3578     case UIWKGestureTapAndHalf:
3579         return WebKit::GestureType::TapAndHalf;
3580     case UIWKGestureDoubleTapInUneditable:
3581         return WebKit::GestureType::DoubleTapInUneditable;
3582     case UIWKGestureOneFingerTapInUneditable:
3583         return WebKit::GestureType::OneFingerTapInUneditable;
3584     case UIWKGestureOneFingerTapSelectsAll:
3585         return WebKit::GestureType::OneFingerTapSelectsAll;
3586     case UIWKGestureOneFingerDoubleTap:
3587         return WebKit::GestureType::OneFingerDoubleTap;
3588     case UIWKGestureOneFingerTripleTap:
3589         return WebKit::GestureType::OneFingerTripleTap;
3590     case UIWKGestureTwoFingerSingleTap:
3591         return WebKit::GestureType::TwoFingerSingleTap;
3592     case UIWKGestureTwoFingerRangedSelectGesture:
3593         return WebKit::GestureType::TwoFingerRangedSelectGesture;
3594     case UIWKGestureTapOnLinkWithGesture:
3595         return WebKit::GestureType::TapOnLinkWithGesture;
3596     case UIWKGesturePhraseBoundary:
3597         return WebKit::GestureType::PhraseBoundary;
3598     case UIWKGestureMakeWebSelection:
3599         ASSERT_NOT_REACHED();
3600         return WebKit::GestureType::Loupe;
3601     }
3602     ASSERT_NOT_REACHED();
3603     return WebKit::GestureType::Loupe;
3604 }
3605
3606 static inline UIWKGestureType toUIWKGestureType(WebKit::GestureType gestureType)
3607 {
3608     switch (gestureType) {
3609     case WebKit::GestureType::Loupe:
3610         return UIWKGestureLoupe;
3611     case WebKit::GestureType::OneFingerTap:
3612         return UIWKGestureOneFingerTap;
3613     case WebKit::GestureType::TapAndAHalf:
3614         return UIWKGestureTapAndAHalf;
3615     case WebKit::GestureType::DoubleTap:
3616         return UIWKGestureDoubleTap;
3617     case WebKit::GestureType::TapAndHalf:
3618         return UIWKGestureTapAndHalf;
3619     case WebKit::GestureType::DoubleTapInUneditable:
3620         return UIWKGestureDoubleTapInUneditable;
3621     case WebKit::GestureType::OneFingerTapInUneditable:
3622         return UIWKGestureOneFingerTapInUneditable;
3623     case WebKit::GestureType::OneFingerTapSelectsAll:
3624         return UIWKGestureOneFingerTapSelectsAll;
3625     case WebKit::GestureType::OneFingerDoubleTap:
3626         return UIWKGestureOneFingerDoubleTap;
3627     case WebKit::GestureType::OneFingerTripleTap:
3628         return UIWKGestureOneFingerTripleTap;
3629     case WebKit::GestureType::TwoFingerSingleTap:
3630         return UIWKGestureTwoFingerSingleTap;
3631     case WebKit::GestureType::TwoFingerRangedSelectGesture:
3632         return UIWKGestureTwoFingerRangedSelectGesture;
3633     case WebKit::GestureType::TapOnLinkWithGesture:
3634         return UIWKGestureTapOnLinkWithGesture;
3635     case WebKit::GestureType::PhraseBoundary:
3636         return UIWKGesturePhraseBoundary;
3637     }
3638 }
3639
3640 static inline WebKit::SelectionTouch toSelectionTouch(UIWKSelectionTouch touch)
3641 {
3642     switch (touch) {
3643     case UIWKSelectionTouchStarted:
3644         return WebKit::SelectionTouch::Started;
3645     case UIWKSelectionTouchMoved:
3646         return WebKit::SelectionTouch::Moved;
3647     case UIWKSelectionTouchEnded:
3648         return WebKit::SelectionTouch::Ended;
3649     case UIWKSelectionTouchEndedMovingForward:
3650         return WebKit::SelectionTouch::EndedMovingForward;
3651     case UIWKSelectionTouchEndedMovingBackward:
3652         return WebKit::SelectionTouch::EndedMovingBackward;
3653     case UIWKSelectionTouchEndedNotMoving:
3654         return WebKit::SelectionTouch::EndedNotMoving;
3655     }
3656     ASSERT_NOT_REACHED();
3657     return WebKit::SelectionTouch::Ended;
3658 }
3659
3660 static inline UIWKSelectionTouch toUIWKSelectionTouch(WebKit::SelectionTouch touch)
3661 {
3662     switch (touch) {
3663     case WebKit::SelectionTouch::Started:
3664         return UIWKSelectionTouchStarted;
3665     case WebKit::SelectionTouch::Moved:
3666         return UIWKSelectionTouchMoved;
3667     case WebKit::SelectionTouch::Ended:
3668         return UIWKSelectionTouchEnded;
3669     case WebKit::SelectionTouch::EndedMovingForward:
3670         return UIWKSelectionTouchEndedMovingForward;
3671     case WebKit::SelectionTouch::EndedMovingBackward:
3672         return UIWKSelectionTouchEndedMovingBackward;
3673     case WebKit::SelectionTouch::EndedNotMoving:
3674         return UIWKSelectionTouchEndedNotMoving;
3675     }
3676 }
3677
3678 static inline WebKit::GestureRecognizerState toGestureRecognizerState(UIGestureRecognizerState state)
3679 {
3680     switch (state) {
3681     case UIGestureRecognizerStatePossible:
3682         return WebKit::GestureRecognizerState::Possible;
3683     case UIGestureRecognizerStateBegan:
3684         return WebKit::GestureRecognizerState::Began;
3685     case UIGestureRecognizerStateChanged:
3686         return WebKit::GestureRecognizerState::Changed;
3687     case UIGestureRecognizerStateCancelled:
3688         return WebKit::GestureRecognizerState::Cancelled;
3689     case UIGestureRecognizerStateEnded:
3690         return WebKit::GestureRecognizerState::Ended;
3691     case UIGestureRecognizerStateFailed:
3692         return WebKit::GestureRecognizerState::Failed;
3693     }
3694 }
3695
3696 static inline UIGestureRecognizerState toUIGestureRecognizerState(WebKit::GestureRecognizerState state)
3697 {
3698     switch (state) {
3699     case WebKit::GestureRecognizerState::Possible:
3700         return UIGestureRecognizerStatePossible;
3701     case WebKit::GestureRecognizerState::Began:
3702         return UIGestureRecognizerStateBegan;
3703     case WebKit::GestureRecognizerState::Changed:
3704         return UIGestureRecognizerStateChanged;
3705     case WebKit::GestureRecognizerState::Cancelled:
3706         return UIGestureRecognizerStateCancelled;
3707     case WebKit::GestureRecognizerState::Ended:
3708         return UIGestureRecognizerStateEnded;
3709     case WebKit::GestureRecognizerState::Failed:
3710         return UIGestureRecognizerStateFailed;
3711     }
3712 }
3713
3714 static inline UIWKSelectionFlags toUIWKSelectionFlags(WebKit::SelectionFlags flags)
3715 {
3716     NSInteger uiFlags = UIWKNone;
3717     if (flags & WebKit::WordIsNearTap)
3718         uiFlags |= UIWKWordIsNearTap;
3719     if (flags & WebKit::PhraseBoundaryChanged)
3720         uiFlags |= UIWKPhraseBoundaryChanged;
3721
3722     return static_cast<UIWKSelectionFlags>(uiFlags);
3723 }
3724
3725 static inline WebCore::TextGranularity toWKTextGranularity(UITextGranularity granularity)
3726 {
3727     switch (granularity) {
3728     case UITextGranularityCharacter:
3729         return WebCore::CharacterGranularity;
3730     case UITextGranularityWord:
3731         return WebCore::WordGranularity;
3732     case UITextGranularitySentence:
3733         return WebCore::SentenceGranularity;
3734     case UITextGranularityParagraph:
3735         return WebCore::ParagraphGranularity;
3736     case UITextGranularityLine:
3737         return WebCore::LineGranularity;
3738     case UITextGranularityDocument:
3739         return WebCore::DocumentGranularity;
3740     }
3741 }
3742
3743 static inline WebCore::SelectionDirection toWKSelectionDirection(UITextDirection direction)
3744 {
3745     switch (direction) {
3746     case UITextLayoutDirectionDown:
3747     case UITextLayoutDirectionRight:
3748         return WebCore::DirectionRight;
3749     case UITextLayoutDirectionUp:
3750     case UITextLayoutDirectionLeft:
3751         return WebCore::DirectionLeft;
3752     default:
3753         // UITextDirection is not an enum, but we only want to accept values from UITextLayoutDirection.
3754         ASSERT_NOT_REACHED();
3755         return WebCore::DirectionRight;
3756     }
3757 }
3758
3759 static void selectionChangedWithGesture(WKContentView *view, const WebCore::IntPoint& point, uint32_t gestureType, uint32_t gestureState, uint32_t flags, WebKit::CallbackBase::Error error)
3760 {
3761     if (error != WebKit::CallbackBase::Error::None) {
3762         ASSERT_NOT_REACHED();
3763         return;
3764     }
3765     [(UIWKTextInteractionAssistant *)[view interactionAssistant] selectionChangedWithGestureAt:(CGPoint)point withGesture:toUIWKGestureType((WebKit::GestureType)gestureType) withState:toUIGestureRecognizerState(static_cast<WebKit::GestureRecognizerState>(gestureState)) withFlags:(toUIWKSelectionFlags((WebKit::SelectionFlags)flags))];
3766 }
3767
3768 static void selectionChangedWithTouch(WKContentView *view, const WebCore::IntPoint& point, uint32_t touch, uint32_t flags, WebKit::CallbackBase::Error error)
3769 {
3770     if (error != WebKit::CallbackBase::Error::None) {
3771         ASSERT_NOT_REACHED();
3772         return;
3773     }
3774     [(UIWKTextInteractionAssistant *)[view interactionAssistant] selectionChangedWithTouchAt:(CGPoint)point withSelectionTouch:toUIWKSelectionTouch((WebKit::SelectionTouch)touch) withFlags:static_cast<UIWKSelectionFlags>(flags)];
3775 }
3776
3777 - (BOOL)_isInteractingWithFocusedElement
3778 {
3779     return hasFocusedElement(_focusedElementInformation);
3780 }
3781
3782 - (void)changeSelectionWithGestureAt:(CGPoint)point withGesture:(UIWKGestureType)gestureType withState:(UIGestureRecognizerState)state
3783 {
3784     [self changeSelectionWithGestureAt:point withGesture:gestureType withState:state withFlags:UIWKNone];
3785 }
3786
3787 - (void)changeSelectionWithGestureAt:(CGPoint)point withGesture:(UIWKGestureType)gestureType withState:(UIGestureRecognizerState)state withFlags:(UIWKSelectionFlags)flags
3788 {
3789     _usingGestureForSelection = YES;
3790     _page->selectWithGesture(WebCore::IntPoint(point), WebCore::CharacterGranularity, static_cast<uint32_t>(toGestureType(gestureType)), static_cast<uint32_t>(toGestureRecognizerState(state)), [self _isInteractingWithFocusedElement], [self, state, flags](const WebCore::IntPoint& point, uint32_t gestureType, uint32_t gestureState, uint32_t innerFlags, WebKit::CallbackBase::Error error) {
3791         selectionChangedWithGesture(self, point, gestureType, gestureState, flags | innerFlags, error);
3792         if (state == UIGestureRecognizerStateEnded || state == UIGestureRecognizerStateCancelled)
3793             _usingGestureForSelection = NO;
3794     });
3795 }
3796
3797 - (void)changeSelectionWithTouchAt:(CGPoint)point withSelectionTouch:(UIWKSelectionTouch)touch baseIsStart:(BOOL)baseIsStart withFlags:(UIWKSelectionFlags)flags
3798 {
3799     _usingGestureForSelection = YES;
3800     _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) {
3801         selectionChangedWithTouch(self, point, touch, flags | innerFlags, error);
3802         if (touch != UIWKSelectionTouchStarted && touch != UIWKSelectionTouchMoved)
3803             _usingGestureForSelection = NO;
3804     });
3805 }
3806
3807 - (void)changeSelectionWithTouchesFrom:(CGPoint)from to:(CGPoint)to withGesture:(UIWKGestureType)gestureType withState:(UIGestureRecognizerState)gestureState
3808 {
3809     _usingGestureForSelection = YES;
3810     _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) {
3811         selectionChangedWithGesture(self, point, gestureType, gestureState, flags, error);
3812         if (gestureState == UIGestureRecognizerStateEnded || gestureState == UIGestureRecognizerStateCancelled)
3813             _usingGestureForSelection = NO;
3814     });
3815 }
3816
3817 - (void)moveByOffset:(NSInteger)offset
3818 {
3819     if (!offset)
3820         return;
3821     
3822     [self beginSelectionChange];
3823     RetainPtr<WKContentView> view = self;
3824     _page->moveSelectionByOffset(offset, [view](WebKit::CallbackBase::Error) {
3825         [view endSelectionChange];
3826     });
3827 }
3828
3829 - (const WebKit::WKAutoCorrectionData&)autocorrectionData
3830 {
3831     return _autocorrectionData;
3832 }
3833
3834 // The completion handler can pass nil if input does not match the actual text preceding the insertion point.
3835 - (void)requestAutocorrectionRectsForString:(NSString *)input withCompletionHandler:(void (^)(UIWKAutocorrectionRects *rectsForInput))completionHandler
3836 {
3837     if (!completionHandler) {
3838         [NSException raise:NSInvalidArgumentException format:@"Expected a nonnull completion handler in %s.", __PRETTY_FUNCTION__];
3839         return;
3840     }
3841
3842     if (!input || ![input length]) {
3843         completionHandler(nil);
3844         return;
3845     }
3846
3847     _page->requestAutocorrectionData(input, [view = retainPtr(self), completion = makeBlockPtr(completionHandler)](auto data) {
3848         CGRect firstRect;
3849         CGRect lastRect;
3850         auto& rects = data.textRects;
3851         if (rects.isEmpty()) {
3852             firstRect = CGRectZero;
3853             lastRect = CGRectZero;
3854         } else {
3855             firstRect = rects.first();
3856             lastRect = rects.last();
3857         }
3858
3859         view->_autocorrectionData.font = data.font;
3860         view->_autocorrectionData.textFirstRect = firstRect;
3861         view->_autocorrectionData.textLastRect = lastRect;
3862
3863         completion(!rects.isEmpty() ? [WKAutocorrectionRects autocorrectionRectsWithFirstCGRect:firstRect lastCGRect:lastRect] : nil);
3864     });
3865 }
3866
3867 - (void)requestRectsToEvadeForSelectionCommandsWithCompletionHandler:(void(^)(NSArray<NSValue *> *rects))completionHandler
3868 {
3869     if (!completionHandler) {
3870         [NSException raise:NSInvalidArgumentException format:@"Expected a nonnull completion handler in %s.", __PRETTY_FUNCTION__];
3871         return;
3872     }
3873
3874     if ([self _shouldSuppressSelectionCommands] || _webView._editable) {
3875         completionHandler(@[ ]);
3876         return;
3877     }
3878
3879     if (_focusedElementInformation.elementType != WebKit::InputType::ContentEditable && _focusedElementInformation.elementType != WebKit::InputType::TextArea) {
3880         completionHandler(@[ ]);
3881         return;
3882     }
3883
3884     // Give the page some time to present custom editing UI before attempting to detect and evade it.
3885     auto delayBeforeShowingCalloutBar = (0.25_s).nanoseconds();
3886     dispatch_after(dispatch_time(DISPATCH_TIME_NOW, delayBeforeShowingCalloutBar), dispatch_get_main_queue(), [completion = makeBlockPtr(completionHandler), weakSelf = WeakObjCPtr<WKContentView>(self)] () mutable {
3887         if (!weakSelf) {
3888             completion(@[ ]);
3889             return;
3890         }
3891
3892         auto strongSelf = weakSelf.get();
3893         if (!strongSelf->_page) {
3894             completion(@[ ]);
3895             return;
3896         }
3897
3898         strongSelf->_page->requestEvasionRectsAboveSelection([completion = WTFMove(completion)] (auto& rects) {
3899             auto rectsAsValues = adoptNS([[NSMutableArray alloc] initWithCapacity:rects.size()]);
3900             for (auto& floatRect : rects)
3901                 [rectsAsValues addObject:[NSValue valueWithCGRect:floatRect]];
3902             completion(rectsAsValues.get());
3903         });
3904     });
3905 }
3906
3907 - (void)selectPositionAtPoint:(CGPoint)point completionHandler:(void (^)(void))completionHandler
3908 {
3909     _usingGestureForSelection = YES;
3910     UIWKSelectionCompletionHandler selectionHandler = [completionHandler copy];
3911     RetainPtr<WKContentView> view = self;
3912     
3913     _page->selectPositionAtPoint(WebCore::IntPoint(point), [self _isInteractingWithFocusedElement], [view, selectionHandler](WebKit::CallbackBase::Error error) {
3914         selectionHandler();
3915         view->_usingGestureForSelection = NO;
3916         [selectionHandler release];
3917     });
3918 }
3919
3920 - (void)selectPositionAtBoundary:(UITextGranularity)granularity inDirection:(UITextDirection)direction fromPoint:(CGPoint)point completionHandler:(void (^)(void))completionHandler
3921 {
3922     _usingGestureForSelection = YES;
3923     UIWKSelectionCompletionHandler selectionHandler = [completionHandler copy];
3924     RetainPtr<WKContentView> view = self;
3925     
3926     _page->selectPositionAtBoundaryWithDirection(WebCore::IntPoint(point), toWKTextGranularity(granularity), toWKSelectionDirection(direction), [self _isInteractingWithFocusedElement], [view, selectionHandler](WebKit::CallbackBase::Error error) {
3927