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