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