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