cdca85167ac620dd4c9ecfee524aff6deff491cc
[WebKit-https.git] / Source / WebKit2 / UIProcess / ios / WKContentViewInteraction.mm
1 /*
2  * Copyright (C) 2012-2014 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)
30
31 #import "APIUIClient.h"
32 #import "EditingRange.h"
33 #import "Logging.h"
34 #import "ManagedConfigurationSPI.h"
35 #import "NativeWebKeyboardEvent.h"
36 #import "NativeWebTouchEvent.h"
37 #import "SmartMagnificationController.h"
38 #import "TextInputSPI.h"
39 #import "UIKitSPI.h"
40 #import "WKActionSheetAssistant.h"
41 #import "WKFormInputControl.h"
42 #import "WKFormSelectControl.h"
43 #import "WKImagePreviewViewController.h"
44 #import "WKInspectorNodeSearchGestureRecognizer.h"
45 #import "WKNSURLExtras.h"
46 #import "WKPreviewActionItemIdentifiers.h"
47 #import "WKPreviewActionItemInternal.h"
48 #import "WKPreviewElementInfoInternal.h"
49 #import "WKUIDelegatePrivate.h"
50 #import "WKWebViewConfiguration.h"
51 #import "WKWebViewInternal.h"
52 #import "WKWebViewPrivate.h"
53 #import "WebEvent.h"
54 #import "WebIOSEventFactory.h"
55 #import "WebPageMessages.h"
56 #import "WebProcessProxy.h"
57 #import "_WKActivatedElementInfoInternal.h"
58 #import "_WKElementAction.h"
59 #import "_WKFocusedElementInfo.h"
60 #import "_WKFormInputSession.h"
61 #import "_WKInputDelegate.h"
62 #import <CoreText/CTFont.h>
63 #import <CoreText/CTFontDescriptor.h>
64 #import <MobileCoreServices/UTCoreTypes.h>
65 #import <WebCore/Color.h>
66 #import <WebCore/CoreGraphicsSPI.h>
67 #import <WebCore/DataDetectorsCoreSPI.h>
68 #import <WebCore/DataDetectorsUISPI.h>
69 #import <WebCore/FloatQuad.h>
70 #import <WebCore/Pasteboard.h>
71 #import <WebCore/Path.h>
72 #import <WebCore/PathUtilities.h>
73 #import <WebCore/RuntimeApplicationChecks.h>
74 #import <WebCore/Scrollbar.h>
75 #import <WebCore/SoftLinking.h>
76 #import <WebCore/TextIndicator.h>
77 #import <WebCore/TextStream.h>
78 #import <WebCore/WebCoreNSURLExtras.h>
79 #import <WebCore/WebEvent.h>
80 #import <WebKit/WebSelectionRect.h> // FIXME: WK2 should not include WebKit headers!
81 #import <wtf/RetainPtr.h>
82
83 @interface UIEvent(UIEventInternal)
84 @property (nonatomic, assign) UIKeyboardInputFlags _inputFlags;
85 @end
86
87 @interface WKWebEvent : WebEvent
88 @property (nonatomic, retain) UIEvent *uiEvent;
89 @end
90
91 @implementation WKWebEvent
92
93 - (void)dealloc
94 {
95     [_uiEvent release];
96     [super dealloc];
97 }
98
99 @end
100
101 using namespace WebCore;
102 using namespace WebKit;
103
104 namespace WebKit {
105
106 WKSelectionDrawingInfo::WKSelectionDrawingInfo()
107     : type(SelectionType::None)
108 {
109 }
110
111 WKSelectionDrawingInfo::WKSelectionDrawingInfo(const EditorState& editorState)
112 {
113     if (editorState.selectionIsNone) {
114         type = SelectionType::None;
115         return;
116     }
117
118     if (editorState.isInPlugin) {
119         type = SelectionType::Plugin;
120         return;
121     }
122
123     type = SelectionType::Range;
124     auto& postLayoutData = editorState.postLayoutData();
125     caretRect = postLayoutData.caretRectAtEnd;
126     selectionRects = postLayoutData.selectionRects;
127 }
128
129 inline bool operator==(const WKSelectionDrawingInfo& a, const WKSelectionDrawingInfo& b)
130 {
131     if (a.type != b.type)
132         return false;
133
134     if (a.type == WKSelectionDrawingInfo::SelectionType::Range) {
135         if (a.caretRect != b.caretRect)
136             return false;
137
138         if (a.selectionRects.size() != b.selectionRects.size())
139             return false;
140
141         for (unsigned i = 0; i < a.selectionRects.size(); ++i) {
142             if (a.selectionRects[i].rect() != b.selectionRects[i].rect())
143                 return false;
144         }
145     }
146
147     return true;
148 }
149
150 inline bool operator!=(const WKSelectionDrawingInfo& a, const WKSelectionDrawingInfo& b)
151 {
152     return !(a == b);
153 }
154
155 static WebCore::TextStream& operator<<(WebCore::TextStream& stream, WKSelectionDrawingInfo::SelectionType type)
156 {
157     switch (type) {
158     case WKSelectionDrawingInfo::SelectionType::None: stream << "none"; break;
159     case WKSelectionDrawingInfo::SelectionType::Plugin: stream << "plugin"; break;
160     case WKSelectionDrawingInfo::SelectionType::Range: stream << "range"; break;
161     }
162     
163     return stream;
164 }
165
166 WebCore::TextStream& operator<<(WebCore::TextStream& stream, const WKSelectionDrawingInfo& info)
167 {
168     TextStream::GroupScope group(stream);
169     stream.dumpProperty("type", info.type);
170     stream.dumpProperty("caret rect", info.caretRect);
171     stream.dumpProperty("selection rects", info.selectionRects);
172     return stream;
173 }
174
175 } // namespace WebKit
176
177 static const float highlightDelay = 0.12;
178 static const float tapAndHoldDelay  = 0.75;
179 const CGFloat minimumTapHighlightRadius = 2.0;
180
181 @interface WKTextRange : UITextRange {
182     CGRect _startRect;
183     CGRect _endRect;
184     BOOL _isNone;
185     BOOL _isRange;
186     BOOL _isEditable;
187     NSArray *_selectionRects;
188     NSUInteger _selectedTextLength;
189 }
190 @property (nonatomic) CGRect startRect;
191 @property (nonatomic) CGRect endRect;
192 @property (nonatomic) BOOL isNone;
193 @property (nonatomic) BOOL isRange;
194 @property (nonatomic) BOOL isEditable;
195 @property (nonatomic) NSUInteger selectedTextLength;
196 @property (copy, nonatomic) NSArray *selectionRects;
197
198 + (WKTextRange *)textRangeWithState:(BOOL)isNone isRange:(BOOL)isRange isEditable:(BOOL)isEditable startRect:(CGRect)startRect endRect:(CGRect)endRect selectionRects:(NSArray *)selectionRects selectedTextLength:(NSUInteger)selectedTextLength;
199
200 @end
201
202 @interface WKTextPosition : UITextPosition {
203     CGRect _positionRect;
204 }
205
206 @property (nonatomic) CGRect positionRect;
207
208 + (WKTextPosition *)textPositionWithRect:(CGRect)positionRect;
209
210 @end
211
212 @interface WKTextSelectionRect : UITextSelectionRect
213
214 @property (nonatomic, retain) WebSelectionRect *webRect;
215
216 + (NSArray *)textSelectionRectsWithWebRects:(NSArray *)webRects;
217
218 @end
219
220 @interface WKAutocorrectionRects : UIWKAutocorrectionRects
221 + (WKAutocorrectionRects *)autocorrectionRectsWithRects:(CGRect)firstRect lastRect:(CGRect)lastRect;
222 @end
223
224 @interface WKAutocorrectionContext : UIWKAutocorrectionContext
225 + (WKAutocorrectionContext *)autocorrectionContextWithData:(NSString *)beforeText markedText:(NSString *)markedText selectedText:(NSString *)selectedText afterText:(NSString *)afterText selectedRangeInMarkedText:(NSRange)range;
226 @end
227
228 @interface UITextInteractionAssistant (UITextInteractionAssistant_Internal)
229 // FIXME: this needs to be moved from the internal header to the private.
230 - (id)initWithView:(UIResponder <UITextInput> *)view;
231 - (void)selectWord;
232 - (void)scheduleReanalysis;
233 @end
234
235 @interface UITextInteractionAssistant (StagingToRemove)
236 - (void)scheduleReplacementsForText:(NSString *)text;
237 - (void)showTextServiceFor:(NSString *)selectedTerm fromRect:(CGRect)presentationRect;
238 - (void)scheduleChineseTransliterationForText:(NSString *)text;
239 - (void)showShareSheetFor:(NSString *)selectedTerm fromRect:(CGRect)presentationRect;
240 - (void)lookup:(NSString *)textWithContext fromRect:(CGRect)presentationRect;
241 - (void)lookup:(NSString *)textWithContext withRange:(NSRange)range fromRect:(CGRect)presentationRect;
242 @end
243
244 @interface UIWKSelectionAssistant (StagingToRemove)
245 - (void)showTextServiceFor:(NSString *)selectedTerm fromRect:(CGRect)presentationRect;
246 - (void)lookup:(NSString *)textWithContext fromRect:(CGRect)presentationRect;
247 - (void)lookup:(NSString *)textWithContext withRange:(NSRange)range fromRect:(CGRect)presentationRect;
248 @end
249
250 @interface UIKeyboardImpl (StagingToRemove)
251 - (void)didHandleWebKeyEvent;
252 - (void)didHandleWebKeyEvent:(WebIOSEvent *)event;
253 - (void)deleteFromInputWithFlags:(NSUInteger)flags;
254 - (void)addInputString:(NSString *)string withFlags:(NSUInteger)flags withInputManagerHint:(NSString *)hint;
255 @end
256
257 @interface UIView (UIViewInternalHack)
258 + (BOOL)_addCompletion:(void(^)(BOOL))completion;
259 @end
260
261 @protocol UISelectionInteractionAssistant;
262 #if HAVE(LINK_PREVIEW)
263 @interface UIPreviewItemController (StagingToRemove)
264 @property (strong, nonatomic, readonly) UIGestureRecognizer *presentationSecondaryGestureRecognizer;
265 @end
266 #endif
267
268 @interface DDDetectionController (StagingToRemove)
269 - (DDResultRef)resultForURL:(NSURL *)url identifier:(NSString *)identifier selectedText:(NSString *)selectedText results:(NSArray *)results context:(NSDictionary *)context extendedContext:(NSDictionary **)extendedContext;
270 @end
271
272 @interface WKFocusedElementInfo : NSObject <_WKFocusedElementInfo>
273 - (instancetype)initWithAssistedNodeInformation:(const AssistedNodeInformation&)information isUserInitiated:(BOOL)isUserInitiated;
274 @end
275
276 @interface WKFormInputSession : NSObject <_WKFormInputSession>
277
278 - (instancetype)initWithContentView:(WKContentView *)view focusedElementInfo:(WKFocusedElementInfo *)elementInfo userObject:(NSObject <NSSecureCoding> *)userObject;
279 - (void)invalidate;
280
281 @end
282
283 @implementation WKFormInputSession {
284     WKContentView *_contentView;
285     RetainPtr<NSObject <NSSecureCoding>> _userObject;
286     RetainPtr<WKFocusedElementInfo> _focusedElementInfo;
287     RetainPtr<UIView> _customInputView;
288     RetainPtr<NSArray<UITextSuggestion *>> _suggestions;
289     BOOL _accessoryViewShouldNotShow;
290     BOOL _forceSecureTextEntry;
291 }
292
293 - (instancetype)initWithContentView:(WKContentView *)view focusedElementInfo:(WKFocusedElementInfo *)elementInfo userObject:(NSObject <NSSecureCoding> *)userObject
294 {
295     if (!(self = [super init]))
296         return nil;
297
298     _contentView = view;
299     _focusedElementInfo = elementInfo;
300     _userObject = userObject;
301
302     return self;
303 }
304
305 - (id <_WKFocusedElementInfo>)focusedElementInfo
306 {
307     return _focusedElementInfo.get();
308 }
309
310 - (NSObject <NSSecureCoding> *)userObject
311 {
312     return _userObject.get();
313 }
314
315 - (BOOL)isValid
316 {
317     return _contentView != nil;
318 }
319
320 - (NSString *)accessoryViewCustomButtonTitle
321 {
322     return [[[_contentView formAccessoryView] _autofill] title];
323 }
324
325 - (void)setAccessoryViewCustomButtonTitle:(NSString *)title
326 {
327     if (title.length)
328         [[_contentView formAccessoryView] showAutoFillButtonWithTitle:title];
329     else
330         [[_contentView formAccessoryView] hideAutoFillButton];
331     if (UICurrentUserInterfaceIdiomIsPad())
332         [_contentView reloadInputViews];
333 }
334
335 - (BOOL)accessoryViewShouldNotShow
336 {
337     return _accessoryViewShouldNotShow;
338 }
339
340 - (void)setAccessoryViewShouldNotShow:(BOOL)accessoryViewShouldNotShow
341 {
342     if (_accessoryViewShouldNotShow == accessoryViewShouldNotShow)
343         return;
344
345     _accessoryViewShouldNotShow = accessoryViewShouldNotShow;
346     [_contentView reloadInputViews];
347 }
348
349 - (BOOL)forceSecureTextEntry
350 {
351     return _forceSecureTextEntry;
352 }
353
354 - (void)setForceSecureTextEntry:(BOOL)forceSecureTextEntry
355 {
356     if (_forceSecureTextEntry == forceSecureTextEntry)
357         return;
358
359     _forceSecureTextEntry = forceSecureTextEntry;
360     [_contentView reloadInputViews];
361 }
362
363 - (UIView *)customInputView
364 {
365     return _customInputView.get();
366 }
367
368 - (void)setCustomInputView:(UIView *)customInputView
369 {
370     if (customInputView == _customInputView)
371         return;
372
373     _customInputView = customInputView;
374     [_contentView reloadInputViews];
375 }
376
377 - (NSArray<UITextSuggestion *> *)suggestions
378 {
379     return _suggestions.get();
380 }
381
382 - (void)setSuggestions:(NSArray<UITextSuggestion *> *)suggestions
383 {
384 #if __IPHONE_OS_VERSION_MIN_REQUIRED >= 100000
385     id <UITextInputSuggestionDelegate> suggestionDelegate = (id <UITextInputSuggestionDelegate>)_contentView.inputDelegate;
386     _suggestions = adoptNS([suggestions copy]);
387     [suggestionDelegate setSuggestions:suggestions];
388 #endif
389 }
390
391 - (void)invalidate
392 {
393 #if __IPHONE_OS_VERSION_MIN_REQUIRED >= 100000
394     id <UITextInputSuggestionDelegate> suggestionDelegate = (id <UITextInputSuggestionDelegate>)_contentView.inputDelegate;
395     [suggestionDelegate setSuggestions:nil];
396 #endif
397     _contentView = nil;
398 }
399
400 @end
401
402 @implementation WKFocusedElementInfo {
403     WKInputType _type;
404     RetainPtr<NSString> _value;
405     BOOL _isUserInitiated;
406 }
407
408 - (instancetype)initWithAssistedNodeInformation:(const AssistedNodeInformation&)information isUserInitiated:(BOOL)isUserInitiated
409 {
410     if (!(self = [super init]))
411         return nil;
412
413     switch (information.elementType) {
414     case WebKit::InputType::ContentEditable:
415         _type = WKInputTypeContentEditable;
416         break;
417     case WebKit::InputType::Text:
418         _type = WKInputTypeText;
419         break;
420     case WebKit::InputType::Password:
421         _type = WKInputTypePassword;
422         break;
423     case WebKit::InputType::TextArea:
424         _type = WKInputTypeTextArea;
425         break;
426     case WebKit::InputType::Search:
427         _type = WKInputTypeSearch;
428         break;
429     case WebKit::InputType::Email:
430         _type = WKInputTypeEmail;
431         break;
432     case WebKit::InputType::URL:
433         _type = WKInputTypeURL;
434         break;
435     case WebKit::InputType::Phone:
436         _type = WKInputTypePhone;
437         break;
438     case WebKit::InputType::Number:
439         _type = WKInputTypeNumber;
440         break;
441     case WebKit::InputType::NumberPad:
442         _type = WKInputTypeNumberPad;
443         break;
444     case WebKit::InputType::Date:
445         _type = WKInputTypeDate;
446         break;
447     case WebKit::InputType::DateTime:
448         _type = WKInputTypeDateTime;
449         break;
450     case WebKit::InputType::DateTimeLocal:
451         _type = WKInputTypeDateTimeLocal;
452         break;
453     case WebKit::InputType::Month:
454         _type = WKInputTypeMonth;
455         break;
456     case WebKit::InputType::Week:
457         _type = WKInputTypeWeek;
458         break;
459     case WebKit::InputType::Time:
460         _type = WKInputTypeTime;
461         break;
462     case WebKit::InputType::Select:
463         _type = WKInputTypeSelect;
464         break;
465     case WebKit::InputType::None:
466         _type = WKInputTypeNone;
467         break;
468     }
469     _value = information.value;
470     _isUserInitiated = isUserInitiated;
471     return self;
472 }
473
474 - (WKInputType)type
475 {
476     return _type;
477 }
478
479 - (NSString *)value
480 {
481     return _value.get();
482 }
483
484 - (BOOL)isUserInitiated
485 {
486     return _isUserInitiated;
487 }
488 @end
489
490 @interface WKContentView (WKInteractionPrivate)
491 - (void)accessibilitySpeakSelectionSetContent:(NSString *)string;
492 @end
493
494 @implementation WKContentView (WKInteraction)
495
496 static UIWebSelectionMode toUIWebSelectionMode(WKSelectionGranularity granularity)
497 {
498     switch (granularity) {
499     case WKSelectionGranularityDynamic:
500         return UIWebSelectionModeWeb;
501     case WKSelectionGranularityCharacter:
502         return UIWebSelectionModeTextOnly;
503     }
504
505     ASSERT_NOT_REACHED();
506     return UIWebSelectionModeWeb;
507 }
508
509 - (void)_createAndConfigureDoubleTapGestureRecognizer
510 {
511     _doubleTapGestureRecognizer = adoptNS([[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(_doubleTapRecognized:)]);
512     [_doubleTapGestureRecognizer setNumberOfTapsRequired:2];
513     [_doubleTapGestureRecognizer setDelegate:self];
514     [self addGestureRecognizer:_doubleTapGestureRecognizer.get()];
515     [_singleTapGestureRecognizer requireGestureRecognizerToFail:_doubleTapGestureRecognizer.get()];
516 }
517
518 - (void)setupInteraction
519 {
520     if (!_interactionViewsContainerView) {
521         _interactionViewsContainerView = adoptNS([[UIView alloc] init]);
522         [_interactionViewsContainerView setOpaque:NO];
523         [_interactionViewsContainerView layer].anchorPoint = CGPointZero;
524         [self.superview addSubview:_interactionViewsContainerView.get()];
525     }
526
527     [self.layer addObserver:self forKeyPath:@"transform" options:NSKeyValueObservingOptionInitial context:nil];
528
529 #if ENABLE(TOUCH_EVENTS)
530     _touchEventGestureRecognizer = adoptNS([[UIWebTouchEventsGestureRecognizer alloc] initWithTarget:self action:@selector(_webTouchEventsRecognized:) touchDelegate:self]);
531     [_touchEventGestureRecognizer setDelegate:self];
532     [self addGestureRecognizer:_touchEventGestureRecognizer.get()];
533 #endif
534
535     _singleTapGestureRecognizer = adoptNS([[WKSyntheticClickTapGestureRecognizer alloc] initWithTarget:self action:@selector(_singleTapCommited:)]);
536     [_singleTapGestureRecognizer setDelegate:self];
537     [_singleTapGestureRecognizer setGestureRecognizedTarget:self action:@selector(_singleTapRecognized:)];
538     [_singleTapGestureRecognizer setResetTarget:self action:@selector(_singleTapDidReset:)];
539     [self addGestureRecognizer:_singleTapGestureRecognizer.get()];
540
541     _nonBlockingDoubleTapGestureRecognizer = adoptNS([[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(_nonBlockingDoubleTapRecognized:)]);
542     [_nonBlockingDoubleTapGestureRecognizer setNumberOfTapsRequired:2];
543     [_nonBlockingDoubleTapGestureRecognizer setDelegate:self];
544     [_nonBlockingDoubleTapGestureRecognizer setEnabled:NO];
545     [self addGestureRecognizer:_nonBlockingDoubleTapGestureRecognizer.get()];
546
547     [self _createAndConfigureDoubleTapGestureRecognizer];
548
549     _twoFingerDoubleTapGestureRecognizer = adoptNS([[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(_twoFingerDoubleTapRecognized:)]);
550     [_twoFingerDoubleTapGestureRecognizer setNumberOfTapsRequired:2];
551     [_twoFingerDoubleTapGestureRecognizer setNumberOfTouchesRequired:2];
552     [_twoFingerDoubleTapGestureRecognizer setDelegate:self];
553     [self addGestureRecognizer:_twoFingerDoubleTapGestureRecognizer.get()];
554
555     _highlightLongPressGestureRecognizer = adoptNS([[_UIWebHighlightLongPressGestureRecognizer alloc] initWithTarget:self action:@selector(_highlightLongPressRecognized:)]);
556     [_highlightLongPressGestureRecognizer setDelay:highlightDelay];
557     [_highlightLongPressGestureRecognizer setDelegate:self];
558     [self addGestureRecognizer:_highlightLongPressGestureRecognizer.get()];
559
560     _longPressGestureRecognizer = adoptNS([[UILongPressGestureRecognizer alloc] initWithTarget:self action:@selector(_longPressRecognized:)]);
561     [_longPressGestureRecognizer setDelay:tapAndHoldDelay];
562     [_longPressGestureRecognizer setDelegate:self];
563     [_longPressGestureRecognizer _setRequiresQuietImpulse:YES];
564     [self addGestureRecognizer:_longPressGestureRecognizer.get()];
565
566     _twoFingerSingleTapGestureRecognizer = adoptNS([[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(_twoFingerSingleTapGestureRecognized:)]);
567     [_twoFingerSingleTapGestureRecognizer setAllowableMovement:60];
568     [_twoFingerSingleTapGestureRecognizer _setAllowableSeparation:150];
569     [_twoFingerSingleTapGestureRecognizer setNumberOfTapsRequired:1];
570     [_twoFingerSingleTapGestureRecognizer setNumberOfTouchesRequired:2];
571     [_twoFingerSingleTapGestureRecognizer setDelaysTouchesEnded:NO];
572     [_twoFingerSingleTapGestureRecognizer setDelegate:self];
573     [self addGestureRecognizer:_twoFingerSingleTapGestureRecognizer.get()];
574
575 #if HAVE(LINK_PREVIEW)
576     [self _registerPreview];
577 #endif
578
579     [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(_resetShowingTextStyle:) name:UIMenuControllerDidHideMenuNotification object:nil];
580     _showingTextStyleOptions = NO;
581
582     // FIXME: This should be called when we get notified that loading has completed.
583     [self useSelectionAssistantWithMode:toUIWebSelectionMode([_webView _selectionGranularity])];
584     
585     _actionSheetAssistant = adoptNS([[WKActionSheetAssistant alloc] initWithView:self]);
586     [_actionSheetAssistant setDelegate:self];
587     _smartMagnificationController = std::make_unique<SmartMagnificationController>(self);
588     _isExpectingFastSingleTapCommit = NO;
589     _potentialTapInProgress = NO;
590     _isDoubleTapPending = NO;
591     _showDebugTapHighlightsForFastClicking = [[NSUserDefaults standardUserDefaults] boolForKey:@"WebKitShowFastClickDebugTapHighlights"];
592 }
593
594 - (void)cleanupInteraction
595 {
596     _webSelectionAssistant = nil;
597     _textSelectionAssistant = nil;
598     _actionSheetAssistant = nil;
599     _smartMagnificationController = nil;
600     _didAccessoryTabInitiateFocus = NO;
601     _isExpectingFastSingleTapCommit = NO;
602     [_formInputSession invalidate];
603     _formInputSession = nil;
604     [_highlightView removeFromSuperview];
605
606     if (_interactionViewsContainerView) {
607         [self.layer removeObserver:self forKeyPath:@"transform"];
608         [_interactionViewsContainerView removeFromSuperview];
609         _interactionViewsContainerView = nil;
610     }
611
612     [_touchEventGestureRecognizer setDelegate:nil];
613     [self removeGestureRecognizer:_touchEventGestureRecognizer.get()];
614
615     [_singleTapGestureRecognizer setDelegate:nil];
616     [_singleTapGestureRecognizer setGestureRecognizedTarget:nil action:nil];
617     [_singleTapGestureRecognizer setResetTarget:nil action:nil];
618     [self removeGestureRecognizer:_singleTapGestureRecognizer.get()];
619
620     [_highlightLongPressGestureRecognizer setDelegate:nil];
621     [self removeGestureRecognizer:_highlightLongPressGestureRecognizer.get()];
622
623     [_longPressGestureRecognizer setDelegate:nil];
624     [self removeGestureRecognizer:_longPressGestureRecognizer.get()];
625
626     [_doubleTapGestureRecognizer setDelegate:nil];
627     [self removeGestureRecognizer:_doubleTapGestureRecognizer.get()];
628
629     [_nonBlockingDoubleTapGestureRecognizer setDelegate:nil];
630     [self removeGestureRecognizer:_nonBlockingDoubleTapGestureRecognizer.get()];
631
632     [_twoFingerDoubleTapGestureRecognizer setDelegate:nil];
633     [self removeGestureRecognizer:_twoFingerDoubleTapGestureRecognizer.get()];
634
635     [_twoFingerSingleTapGestureRecognizer setDelegate:nil];
636     [self removeGestureRecognizer:_twoFingerSingleTapGestureRecognizer.get()];
637
638     _inspectorNodeSearchEnabled = NO;
639     if (_inspectorNodeSearchGestureRecognizer) {
640         [_inspectorNodeSearchGestureRecognizer setDelegate:nil];
641         [self removeGestureRecognizer:_inspectorNodeSearchGestureRecognizer.get()];
642         _inspectorNodeSearchGestureRecognizer = nil;
643     }
644
645 #if HAVE(LINK_PREVIEW)
646     [self _unregisterPreview];
647 #endif
648
649     if (_fileUploadPanel) {
650         [_fileUploadPanel setDelegate:nil];
651         [_fileUploadPanel dismiss];
652         _fileUploadPanel = nil;
653     }
654 }
655
656 - (void)_removeDefaultGestureRecognizers
657 {
658     [self removeGestureRecognizer:_touchEventGestureRecognizer.get()];
659     [self removeGestureRecognizer:_singleTapGestureRecognizer.get()];
660     [self removeGestureRecognizer:_highlightLongPressGestureRecognizer.get()];
661     [self removeGestureRecognizer:_doubleTapGestureRecognizer.get()];
662     [self removeGestureRecognizer:_nonBlockingDoubleTapGestureRecognizer.get()];
663     [self removeGestureRecognizer:_twoFingerDoubleTapGestureRecognizer.get()];
664     [self removeGestureRecognizer:_twoFingerSingleTapGestureRecognizer.get()];
665 }
666
667 - (void)_addDefaultGestureRecognizers
668 {
669     [self addGestureRecognizer:_touchEventGestureRecognizer.get()];
670     [self addGestureRecognizer:_singleTapGestureRecognizer.get()];
671     [self addGestureRecognizer:_highlightLongPressGestureRecognizer.get()];
672     [self addGestureRecognizer:_doubleTapGestureRecognizer.get()];
673     [self addGestureRecognizer:_nonBlockingDoubleTapGestureRecognizer.get()];
674     [self addGestureRecognizer:_twoFingerDoubleTapGestureRecognizer.get()];
675     [self addGestureRecognizer:_twoFingerSingleTapGestureRecognizer.get()];
676 }
677
678 - (UIView*)unscaledView
679 {
680     return _interactionViewsContainerView.get();
681 }
682
683 - (CGFloat)inverseScale
684 {
685     return 1 / [[self layer] transform].m11;
686 }
687
688 - (UIScrollView *)_scroller
689 {
690     return [_webView scrollView];
691 }
692
693 - (CGRect)unobscuredContentRect
694 {
695     return _page->unobscuredContentRect();
696 }
697
698 - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
699 {
700     ASSERT([keyPath isEqualToString:@"transform"]);
701     ASSERT(object == self.layer);
702
703     if ([UIView _isInAnimationBlock] && _page->editorState().selectionIsNone) {
704         // If the utility views are not already visible, we don't want them to become visible during the animation since
705         // they could not start from a reasonable state.
706         // This is not perfect since views could also get updated during the animation, in practice this is rare and the end state
707         // remains correct.
708         [self _cancelInteraction];
709         [_interactionViewsContainerView setHidden:YES];
710         [UIView _addCompletion:^(BOOL){ [_interactionViewsContainerView setHidden:NO]; }];
711     }
712
713     _selectionNeedsUpdate = YES;
714     [self _updateChangedSelection:YES];
715     [self _updateTapHighlight];
716 }
717
718 - (void)_enableInspectorNodeSearch
719 {
720     _inspectorNodeSearchEnabled = YES;
721
722     [self _cancelInteraction];
723
724     [self _removeDefaultGestureRecognizers];
725     _inspectorNodeSearchGestureRecognizer = adoptNS([[WKInspectorNodeSearchGestureRecognizer alloc] initWithTarget:self action:@selector(_inspectorNodeSearchRecognized:)]);
726     [self addGestureRecognizer:_inspectorNodeSearchGestureRecognizer.get()];
727 }
728
729 - (void)_disableInspectorNodeSearch
730 {
731     _inspectorNodeSearchEnabled = NO;
732
733     [self _addDefaultGestureRecognizers];
734     [self removeGestureRecognizer:_inspectorNodeSearchGestureRecognizer.get()];
735     _inspectorNodeSearchGestureRecognizer = nil;
736 }
737
738 - (UIView *)hitTest:(CGPoint)point withEvent:(::UIEvent *)event
739 {
740     for (UIView *subView in [_interactionViewsContainerView.get() subviews]) {
741         UIView *hitView = [subView hitTest:[subView convertPoint:point fromView:self] withEvent:event];
742         if (hitView)
743             return hitView;
744     }
745     return [super hitTest:point withEvent:event];
746 }
747
748 - (const InteractionInformationAtPosition&)positionInformation
749 {
750     return _positionInformation;
751 }
752
753 - (void)setInputDelegate:(id <UITextInputDelegate>)inputDelegate
754 {
755     _inputDelegate = inputDelegate;
756 }
757
758 - (id <UITextInputDelegate>)inputDelegate
759 {
760     return _inputDelegate;
761 }
762
763 - (CGPoint)lastInteractionLocation
764 {
765     return _lastInteractionLocation;
766 }
767
768 - (BOOL)isEditable
769 {
770     return _isEditable;
771 }
772
773 - (BOOL)setIsEditable:(BOOL)isEditable
774 {
775     if (isEditable == _isEditable)
776         return NO;
777
778     _isEditable = isEditable;
779     return YES;
780 }
781
782 - (BOOL)canBecomeFirstResponder
783 {
784     if (_resigningFirstResponder)
785         return NO;
786     // We might want to return something else
787     // if we decide to enable/disable interaction programmatically.
788     return YES;
789 }
790
791 - (BOOL)becomeFirstResponder
792 {
793     if (_resigningFirstResponder)
794         return NO;
795     BOOL didBecomeFirstResponder = [super becomeFirstResponder];
796     if (didBecomeFirstResponder)
797         [_textSelectionAssistant activateSelection];
798
799     return didBecomeFirstResponder;
800 }
801
802 - (BOOL)resignFirstResponder
803 {
804     // FIXME: Maybe we should call resignFirstResponder on the superclass
805     // and do nothing if the return value is NO.
806
807     _resigningFirstResponder = YES;
808
809     if (!_webView->_activeFocusedStateRetainCount) {
810         // We need to complete the editing operation before we blur the element.
811         [_inputPeripheral endEditing];
812         _page->blurAssistedNode();
813     }
814
815     [self _cancelInteraction];
816     [_webSelectionAssistant resignedFirstResponder];
817     [_textSelectionAssistant deactivateSelection];
818
819     bool superDidResign = [super resignFirstResponder];
820
821     _resigningFirstResponder = NO;
822
823     return superDidResign;
824 }
825
826 #if ENABLE(TOUCH_EVENTS)
827 - (void)_webTouchEventsRecognized:(UIWebTouchEventsGestureRecognizer *)gestureRecognizer
828 {
829     const _UIWebTouchEvent* lastTouchEvent = gestureRecognizer.lastTouchEvent;
830     NativeWebTouchEvent nativeWebTouchEvent(lastTouchEvent);
831
832     _lastInteractionLocation = lastTouchEvent->locationInDocumentCoordinates;
833     nativeWebTouchEvent.setCanPreventNativeGestures(!_canSendTouchEventsAsynchronously || [gestureRecognizer isDefaultPrevented]);
834
835     if (_canSendTouchEventsAsynchronously)
836         _page->handleTouchEventAsynchronously(nativeWebTouchEvent);
837     else
838         _page->handleTouchEventSynchronously(nativeWebTouchEvent);
839
840     if (nativeWebTouchEvent.allTouchPointsAreReleased())
841         _canSendTouchEventsAsynchronously = NO;
842 }
843 #endif
844
845 - (void)_inspectorNodeSearchRecognized:(UIGestureRecognizer *)gestureRecognizer
846 {
847     ASSERT(_inspectorNodeSearchEnabled);
848     [self _resetIsDoubleTapPending];
849
850     CGPoint point = [gestureRecognizer locationInView:self];
851
852     switch (gestureRecognizer.state) {
853     case UIGestureRecognizerStateBegan:
854     case UIGestureRecognizerStateChanged:
855         _page->inspectorNodeSearchMovedToPosition(point);
856         break;
857     case UIGestureRecognizerStateEnded:
858     case UIGestureRecognizerStateCancelled:
859     default: // To ensure we turn off node search.
860         _page->inspectorNodeSearchEndedAtPosition(point);
861         break;
862     }
863 }
864
865 static FloatQuad inflateQuad(const FloatQuad& quad, float inflateSize)
866 {
867     // We sort the output points like this (as expected by the highlight view):
868     //  p2------p3
869     //  |       |
870     //  p1------p4
871
872     // 1) Sort the points horizontally.
873     FloatPoint points[4] = { quad.p1(), quad.p4(), quad.p2(), quad.p3() };
874     if (points[0].x() > points[1].x())
875         std::swap(points[0], points[1]);
876     if (points[2].x() > points[3].x())
877         std::swap(points[2], points[3]);
878
879     if (points[0].x() > points[2].x())
880         std::swap(points[0], points[2]);
881     if (points[1].x() > points[3].x())
882         std::swap(points[1], points[3]);
883
884     if (points[1].x() > points[2].x())
885         std::swap(points[1], points[2]);
886
887     // 2) Swap them vertically to have the output points [p2, p1, p3, p4].
888     if (points[1].y() < points[0].y())
889         std::swap(points[0], points[1]);
890     if (points[3].y() < points[2].y())
891         std::swap(points[2], points[3]);
892
893     // 3) Adjust the positions.
894     points[0].move(-inflateSize, -inflateSize);
895     points[1].move(-inflateSize, inflateSize);
896     points[2].move(inflateSize, -inflateSize);
897     points[3].move(inflateSize, inflateSize);
898
899     return FloatQuad(points[1], points[0], points[2], points[3]);
900 }
901
902 #if ENABLE(TOUCH_EVENTS)
903 - (void)_webTouchEvent:(const WebKit::NativeWebTouchEvent&)touchEvent preventsNativeGestures:(BOOL)preventsNativeGesture
904 {
905     if (preventsNativeGesture) {
906         _highlightLongPressCanClick = NO;
907
908         _canSendTouchEventsAsynchronously = YES;
909         [_touchEventGestureRecognizer setDefaultPrevented:YES];
910     }
911 }
912 #endif
913
914 static inline bool highlightedQuadsAreSmallerThanRect(const Vector<FloatQuad>& quads, const FloatRect& rect)
915 {
916     for (size_t i = 0; i < quads.size(); ++i) {
917         FloatRect boundingBox = quads[i].boundingBox();
918         if (boundingBox.width() > rect.width() || boundingBox.height() > rect.height())
919             return false;
920     }
921     return true;
922 }
923
924 static NSValue *nsSizeForTapHighlightBorderRadius(WebCore::IntSize borderRadius, CGFloat borderRadiusScale)
925 {
926     return [NSValue valueWithCGSize:CGSizeMake((borderRadius.width() * borderRadiusScale) + minimumTapHighlightRadius, (borderRadius.height() * borderRadiusScale) + minimumTapHighlightRadius)];
927 }
928
929 - (void)_updateTapHighlight
930 {
931     if (![_highlightView superview])
932         return;
933
934     {
935         RetainPtr<UIColor> highlightUIKitColor = adoptNS([[UIColor alloc] initWithCGColor:cachedCGColor(_tapHighlightInformation.color)]);
936         [_highlightView setColor:highlightUIKitColor.get()];
937     }
938
939     CGFloat selfScale = self.layer.transform.m11;
940     bool allHighlightRectsAreRectilinear = true;
941     float deviceScaleFactor = _page->deviceScaleFactor();
942     const Vector<WebCore::FloatQuad>& highlightedQuads = _tapHighlightInformation.quads;
943     const size_t quadCount = highlightedQuads.size();
944     RetainPtr<NSMutableArray> rects = adoptNS([[NSMutableArray alloc] initWithCapacity:static_cast<const NSUInteger>(quadCount)]);
945     for (size_t i = 0; i < quadCount; ++i) {
946         const FloatQuad& quad = highlightedQuads[i];
947         if (quad.isRectilinear()) {
948             FloatRect boundingBox = quad.boundingBox();
949             boundingBox.scale(selfScale);
950             boundingBox.inflate(minimumTapHighlightRadius);
951             CGRect pixelAlignedRect = static_cast<CGRect>(encloseRectToDevicePixels(boundingBox, deviceScaleFactor));
952             [rects addObject:[NSValue valueWithCGRect:pixelAlignedRect]];
953         } else {
954             allHighlightRectsAreRectilinear = false;
955             rects.clear();
956             break;
957         }
958     }
959
960     if (allHighlightRectsAreRectilinear)
961         [_highlightView setFrames:rects.get() boundaryRect:_page->exposedContentRect()];
962     else {
963         RetainPtr<NSMutableArray> quads = adoptNS([[NSMutableArray alloc] initWithCapacity:static_cast<const NSUInteger>(quadCount)]);
964         for (size_t i = 0; i < quadCount; ++i) {
965             FloatQuad quad = highlightedQuads[i];
966             quad.scale(selfScale);
967             FloatQuad extendedQuad = inflateQuad(quad, minimumTapHighlightRadius);
968             [quads addObject:[NSValue valueWithCGPoint:extendedQuad.p1()]];
969             [quads addObject:[NSValue valueWithCGPoint:extendedQuad.p2()]];
970             [quads addObject:[NSValue valueWithCGPoint:extendedQuad.p3()]];
971             [quads addObject:[NSValue valueWithCGPoint:extendedQuad.p4()]];
972         }
973         [_highlightView setQuads:quads.get() boundaryRect:_page->exposedContentRect()];
974     }
975
976     RetainPtr<NSMutableArray> borderRadii = adoptNS([[NSMutableArray alloc] initWithCapacity:4]);
977     [borderRadii addObject:nsSizeForTapHighlightBorderRadius(_tapHighlightInformation.topLeftRadius, selfScale)];
978     [borderRadii addObject:nsSizeForTapHighlightBorderRadius(_tapHighlightInformation.topRightRadius, selfScale)];
979     [borderRadii addObject:nsSizeForTapHighlightBorderRadius(_tapHighlightInformation.bottomLeftRadius, selfScale)];
980     [borderRadii addObject:nsSizeForTapHighlightBorderRadius(_tapHighlightInformation.bottomRightRadius, selfScale)];
981     [_highlightView setCornerRadii:borderRadii.get()];
982 }
983
984 - (void)_showTapHighlight
985 {
986     if (!highlightedQuadsAreSmallerThanRect(_tapHighlightInformation.quads, _page->unobscuredContentRect()) && !_showDebugTapHighlightsForFastClicking)
987         return;
988
989     if (!_highlightView) {
990         _highlightView = adoptNS([[_UIHighlightView alloc] initWithFrame:CGRectZero]);
991         [_highlightView setUserInteractionEnabled:NO];
992         [_highlightView setOpaque:NO];
993         [_highlightView setCornerRadius:minimumTapHighlightRadius];
994     }
995     [_highlightView layer].opacity = 1;
996     [_interactionViewsContainerView addSubview:_highlightView.get()];
997     [self _updateTapHighlight];
998 }
999
1000 - (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
1001 {
1002     if (!_isTapHighlightIDValid || _latestTapID != requestID)
1003         return;
1004
1005     _isTapHighlightIDValid = NO;
1006
1007     _tapHighlightInformation.quads = highlightedQuads;
1008     _tapHighlightInformation.topLeftRadius = topLeftRadius;
1009     _tapHighlightInformation.topRightRadius = topRightRadius;
1010     _tapHighlightInformation.bottomLeftRadius = bottomLeftRadius;
1011     _tapHighlightInformation.bottomRightRadius = bottomRightRadius;
1012     if (_showDebugTapHighlightsForFastClicking)
1013         _tapHighlightInformation.color = [self _tapHighlightColorForFastClick:![_doubleTapGestureRecognizer isEnabled]];
1014     else
1015         _tapHighlightInformation.color = color;
1016
1017     if (_potentialTapInProgress) {
1018         _hasTapHighlightForPotentialTap = YES;
1019         return;
1020     }
1021
1022     [self _showTapHighlight];
1023     if (_isExpectingFastSingleTapCommit) {
1024         [self _finishInteraction];
1025         if (!_potentialTapInProgress)
1026             _isExpectingFastSingleTapCommit = NO;
1027     }
1028 }
1029
1030 - (BOOL)_mayDisableDoubleTapGesturesDuringSingleTap
1031 {
1032     return _potentialTapInProgress;
1033 }
1034
1035 - (void)_disableDoubleTapGesturesDuringTapIfNecessary:(uint64_t)requestID
1036 {
1037     if (_latestTapID != requestID)
1038         return;
1039
1040     [self _setDoubleTapGesturesEnabled:NO];
1041 }
1042
1043 - (void)_cancelLongPressGestureRecognizer
1044 {
1045     [_highlightLongPressGestureRecognizer cancel];
1046 }
1047
1048 - (void)_didScroll
1049 {
1050     [self _cancelLongPressGestureRecognizer];
1051     [self _cancelInteraction];
1052 }
1053
1054 - (void)_overflowScrollingWillBegin
1055 {
1056     [_webSelectionAssistant willStartScrollingOverflow];
1057     [_textSelectionAssistant willStartScrollingOverflow];    
1058 }
1059
1060 - (void)_overflowScrollingDidEnd
1061 {
1062     // If scrolling ends before we've received a selection update,
1063     // we postpone showing the selection until the update is received.
1064     if (!_selectionNeedsUpdate) {
1065         _shouldRestoreSelection = YES;
1066         return;
1067     }
1068     [self _updateChangedSelection];
1069     [_webSelectionAssistant didEndScrollingOverflow];
1070     [_textSelectionAssistant didEndScrollingOverflow];
1071 }
1072
1073 - (BOOL)_requiresKeyboardWhenFirstResponder
1074 {
1075     // FIXME: We should add the logic to handle keyboard visibility during focus redirects.
1076     switch (_assistedNodeInformation.elementType) {
1077     case InputType::None:
1078         return NO;
1079     case InputType::Select:
1080         return !UICurrentUserInterfaceIdiomIsPad();
1081     case InputType::Date:
1082     case InputType::Month:
1083     case InputType::DateTimeLocal:
1084     case InputType::Time:
1085         return !UICurrentUserInterfaceIdiomIsPad();
1086     default:
1087         return !_assistedNodeInformation.isReadOnly;
1088     }
1089     return NO;
1090 }
1091
1092 - (BOOL)_requiresKeyboardResetOnReload
1093 {
1094     return YES;
1095 }
1096
1097 - (void)_displayFormNodeInputView
1098 {
1099     // In case user scaling is force enabled, do not use that scaling when zooming in with an input field.
1100     // Zooming above the page's default scale factor should only happen when the user performs it.
1101     [self _zoomToFocusRect:_assistedNodeInformation.elementRect
1102              selectionRect: _didAccessoryTabInitiateFocus ? IntRect() : _assistedNodeInformation.selectionRect
1103                   fontSize:_assistedNodeInformation.nodeFontSize
1104               minimumScale:_assistedNodeInformation.minimumScaleFactor
1105               maximumScale:_assistedNodeInformation.maximumScaleFactorIgnoringAlwaysScalable
1106               allowScaling:(_assistedNodeInformation.allowsUserScalingIgnoringAlwaysScalable && !UICurrentUserInterfaceIdiomIsPad())
1107                forceScroll:[self requiresAccessoryView]];
1108
1109     _didAccessoryTabInitiateFocus = NO;
1110     [self _ensureFormAccessoryView];
1111     [self _updateAccessory];
1112 }
1113
1114 - (UIView *)inputView
1115 {
1116     if (_assistedNodeInformation.elementType == InputType::None)
1117         return nil;
1118
1119     if (!_inputPeripheral)
1120         _inputPeripheral = adoptNS(_assistedNodeInformation.elementType == InputType::Select ? [[WKFormSelectControl alloc] initWithView:self] : [[WKFormInputControl alloc] initWithView:self]);
1121     else
1122         [self _displayFormNodeInputView];
1123
1124     return [_formInputSession customInputView] ?: [_inputPeripheral assistantView];
1125 }
1126
1127 - (CGRect)_selectionClipRect
1128 {
1129     if (_assistedNodeInformation.elementType == InputType::None)
1130         return CGRectNull;
1131     return _page->editorState().postLayoutData().selectionClipRect;
1132 }
1133
1134 - (BOOL)gestureRecognizer:(UIGestureRecognizer *)preventingGestureRecognizer canPreventGestureRecognizer:(UIGestureRecognizer *)preventedGestureRecognizer
1135 {
1136     // A long-press gesture can not be recognized while panning, but a pan can be recognized
1137     // during a long-press gesture.
1138     BOOL shouldNotPreventScrollViewGestures = preventingGestureRecognizer == _highlightLongPressGestureRecognizer || preventingGestureRecognizer == _longPressGestureRecognizer;
1139     return !(shouldNotPreventScrollViewGestures
1140         && ([preventedGestureRecognizer isKindOfClass:NSClassFromString(@"UIScrollViewPanGestureRecognizer")] || [preventedGestureRecognizer isKindOfClass:NSClassFromString(@"UIScrollViewPinchGestureRecognizer")]));
1141 }
1142
1143 - (BOOL)gestureRecognizer:(UIGestureRecognizer *)preventedGestureRecognizer canBePreventedByGestureRecognizer:(UIGestureRecognizer *)preventingGestureRecognizer {
1144     // 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.
1145     if ((preventingGestureRecognizer == _textSelectionAssistant.get().loupeGesture || [_webSelectionAssistant isSelectionGestureRecognizer:preventingGestureRecognizer])
1146         && (preventedGestureRecognizer == _highlightLongPressGestureRecognizer || preventedGestureRecognizer == _longPressGestureRecognizer)) {
1147         return NO;
1148     }
1149
1150     return YES;
1151 }
1152
1153 static inline bool isSamePair(UIGestureRecognizer *a, UIGestureRecognizer *b, UIGestureRecognizer *x, UIGestureRecognizer *y)
1154 {
1155     return (a == x && b == y) || (b == x && a == y);
1156 }
1157
1158 - (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer*)otherGestureRecognizer
1159 {
1160     if (isSamePair(gestureRecognizer, otherGestureRecognizer, _highlightLongPressGestureRecognizer.get(), _longPressGestureRecognizer.get()))
1161         return YES;
1162
1163     if (isSamePair(gestureRecognizer, otherGestureRecognizer, _highlightLongPressGestureRecognizer.get(), _webSelectionAssistant.get().selectionLongPressRecognizer))
1164         return YES;
1165
1166     if (isSamePair(gestureRecognizer, otherGestureRecognizer, _singleTapGestureRecognizer.get(), _textSelectionAssistant.get().singleTapGesture))
1167         return YES;
1168
1169     if (isSamePair(gestureRecognizer, otherGestureRecognizer, _singleTapGestureRecognizer.get(), _nonBlockingDoubleTapGestureRecognizer.get()))
1170         return YES;
1171
1172     if (isSamePair(gestureRecognizer, otherGestureRecognizer, _highlightLongPressGestureRecognizer.get(), _nonBlockingDoubleTapGestureRecognizer.get()))
1173         return YES;
1174
1175     if (isSamePair(gestureRecognizer, otherGestureRecognizer, _highlightLongPressGestureRecognizer.get(), _previewSecondaryGestureRecognizer.get()))
1176         return YES;
1177
1178     if (isSamePair(gestureRecognizer, otherGestureRecognizer, _highlightLongPressGestureRecognizer.get(), _previewGestureRecognizer.get()))
1179         return YES;
1180
1181     return NO;
1182 }
1183
1184 - (void)_showImageSheet
1185 {
1186     [_actionSheetAssistant showImageSheet];
1187 }
1188
1189 - (void)_showAttachmentSheet
1190 {
1191     id <WKUIDelegatePrivate> uiDelegate = static_cast<id <WKUIDelegatePrivate>>([_webView UIDelegate]);
1192     if (![uiDelegate respondsToSelector:@selector(_webView:showCustomSheetForElement:)])
1193         return;
1194
1195     auto element = adoptNS([[_WKActivatedElementInfo alloc] _initWithType:_WKActivatedElementTypeAttachment URL:[NSURL _web_URLWithWTFString:_positionInformation.url] location:_positionInformation.request.point title:_positionInformation.title ID:_positionInformation.idAttribute rect:_positionInformation.bounds image:nil]);
1196     [uiDelegate _webView:_webView showCustomSheetForElement:element.get()];
1197 }
1198
1199 - (void)_showLinkSheet
1200 {
1201     [_actionSheetAssistant showLinkSheet];
1202 }
1203
1204 - (void)_showDataDetectorsSheet
1205 {
1206     [_actionSheetAssistant showDataDetectorsSheet];
1207 }
1208
1209 - (SEL)_actionForLongPress
1210 {
1211     if (!_positionInformation.touchCalloutEnabled)
1212         return nil;
1213
1214     if (_positionInformation.isImage)
1215         return @selector(_showImageSheet);
1216
1217     if (_positionInformation.isLink) {
1218         NSURL *targetURL = [NSURL URLWithString:_positionInformation.url];
1219         if ([[getDDDetectionControllerClass() tapAndHoldSchemes] containsObject:[targetURL scheme]])
1220             return @selector(_showDataDetectorsSheet);
1221         return @selector(_showLinkSheet);
1222     }
1223     
1224     if (_positionInformation.isAttachment)
1225         return @selector(_showAttachmentSheet);
1226
1227     return nil;
1228 }
1229
1230 - (void)ensurePositionInformationIsUpToDate:(WebKit::InteractionInformationRequest)request
1231 {
1232     if (_hasValidPositionInformation && _positionInformation.request.isValidForRequest(request))
1233         return;
1234
1235     if (_outstandingPositionInformationRequest && _outstandingPositionInformationRequest->isValidForRequest(request)) {
1236         if (auto* connection = _page->process().connection()) {
1237             connection->waitForAndDispatchImmediately<Messages::WebPageProxy::DidReceivePositionInformation>(_page->pageID(), Seconds::infinity());
1238             return;
1239         }
1240     }
1241
1242     _page->getPositionInformation(request, _positionInformation);
1243     _hasValidPositionInformation = YES;
1244 }
1245
1246 - (void)requestAsynchronousPositionInformationUpdate:(WebKit::InteractionInformationRequest)request
1247 {
1248     if (_hasValidPositionInformation && _positionInformation.request.isValidForRequest(request))
1249         return;
1250
1251     _outstandingPositionInformationRequest = request;
1252
1253     _page->requestPositionInformation(request);
1254 }
1255
1256 #if ENABLE(DATA_DETECTION)
1257 - (NSArray *)_dataDetectionResults
1258 {
1259     return _page->dataDetectionResults();
1260 }
1261 #endif
1262
1263 - (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer
1264 {
1265     CGPoint point = [gestureRecognizer locationInView:self];
1266
1267     if (gestureRecognizer == _highlightLongPressGestureRecognizer
1268         || gestureRecognizer == _doubleTapGestureRecognizer
1269         || gestureRecognizer == _nonBlockingDoubleTapGestureRecognizer
1270         || gestureRecognizer == _twoFingerDoubleTapGestureRecognizer
1271         || gestureRecognizer == _singleTapGestureRecognizer) {
1272
1273         if (_textSelectionAssistant) {
1274             // Request information about the position with sync message.
1275             // If the assisted node is the same, prevent the gesture.
1276             InteractionInformationRequest request(roundedIntPoint(point));
1277             _page->getPositionInformation(request, _positionInformation);
1278             _hasValidPositionInformation = YES;
1279             if (_positionInformation.nodeAtPositionIsAssistedNode)
1280                 return NO;
1281         }
1282     }
1283
1284     if (gestureRecognizer == _highlightLongPressGestureRecognizer) {
1285         if (_textSelectionAssistant) {
1286             // This is a different node than the assisted one.
1287             // Prevent the gesture if there is no node.
1288             // Allow the gesture if it is a node that wants highlight or if there is an action for it.
1289             if (!_positionInformation.isElement)
1290                 return NO;
1291             return [self _actionForLongPress] != nil;
1292         } else {
1293             // We still have no idea about what is at the location.
1294             // Send and async message to find out.
1295             _hasValidPositionInformation = NO;
1296             InteractionInformationRequest request(roundedIntPoint(point));
1297
1298             // If 3D Touch is enabled, asynchronously collect snapshots in the hopes that
1299             // they'll arrive before we have to synchronously request them in
1300             // _interactionShouldBeginFromPreviewItemController.
1301             if (self.traitCollection.forceTouchCapability == UIForceTouchCapabilityAvailable) {
1302                 request.includeSnapshot = true;
1303                 request.includeLinkIndicator = true;
1304             }
1305
1306             [self requestAsynchronousPositionInformationUpdate:request];
1307             return YES;
1308         }
1309     }
1310
1311     if (gestureRecognizer == _longPressGestureRecognizer) {
1312         // Use the information retrieved with one of the previous calls
1313         // to gestureRecognizerShouldBegin.
1314         // Force a sync call if not ready yet.
1315         InteractionInformationRequest request(roundedIntPoint(point));
1316         [self ensurePositionInformationIsUpToDate:request];
1317
1318         if (_textSelectionAssistant) {
1319             // Prevent the gesture if it is the same node.
1320             if (_positionInformation.nodeAtPositionIsAssistedNode)
1321                 return NO;
1322         } else {
1323             // Prevent the gesture if there is no action for the node.
1324             return [self _actionForLongPress] != nil;
1325         }
1326     }
1327
1328     return YES;
1329 }
1330
1331 - (void)_cancelInteraction
1332 {
1333     _isTapHighlightIDValid = NO;
1334     [_highlightView removeFromSuperview];
1335 }
1336
1337 - (void)_finishInteraction
1338 {
1339     _isTapHighlightIDValid = NO;
1340     CGFloat tapHighlightFadeDuration = _showDebugTapHighlightsForFastClicking ? 0.25 : 0.1;
1341     [UIView animateWithDuration:tapHighlightFadeDuration
1342                      animations:^{
1343                          [_highlightView layer].opacity = 0;
1344                      }
1345                      completion:^(BOOL finished){
1346                          if (finished)
1347                              [_highlightView removeFromSuperview];
1348                      }];
1349 }
1350
1351 - (BOOL)hasSelectablePositionAtPoint:(CGPoint)point
1352 {
1353     if (_inspectorNodeSearchEnabled)
1354         return NO;
1355
1356     InteractionInformationRequest request(roundedIntPoint(point));
1357     [self ensurePositionInformationIsUpToDate:request];
1358     return _positionInformation.isSelectable;
1359 }
1360
1361 - (BOOL)pointIsNearMarkedText:(CGPoint)point
1362 {
1363     InteractionInformationRequest request(roundedIntPoint(point));
1364     [self ensurePositionInformationIsUpToDate:request];
1365     return _positionInformation.isNearMarkedText;
1366 }
1367
1368 - (BOOL)pointIsInAssistedNode:(CGPoint)point
1369 {
1370     InteractionInformationRequest request(roundedIntPoint(point));
1371     [self ensurePositionInformationIsUpToDate:request];
1372     return _positionInformation.nodeAtPositionIsAssistedNode;
1373 }
1374
1375 - (NSArray *)webSelectionRects
1376 {
1377     if (_page->editorState().selectionIsNone)
1378         return nil;
1379     const auto& selectionRects = _page->editorState().postLayoutData().selectionRects;
1380     unsigned size = selectionRects.size();
1381     if (!size)
1382         return nil;
1383
1384     NSMutableArray *webRects = [NSMutableArray arrayWithCapacity:size];
1385     for (unsigned i = 0; i < size; i++) {
1386         const WebCore::SelectionRect& coreRect = selectionRects[i];
1387         WebSelectionRect *webRect = [WebSelectionRect selectionRect];
1388         webRect.rect = coreRect.rect();
1389         webRect.writingDirection = coreRect.direction() == LTR ? WKWritingDirectionLeftToRight : WKWritingDirectionRightToLeft;
1390         webRect.isLineBreak = coreRect.isLineBreak();
1391         webRect.isFirstOnLine = coreRect.isFirstOnLine();
1392         webRect.isLastOnLine = coreRect.isLastOnLine();
1393         webRect.containsStart = coreRect.containsStart();
1394         webRect.containsEnd = coreRect.containsEnd();
1395         webRect.isInFixedPosition = coreRect.isInFixedPosition();
1396         webRect.isHorizontal = coreRect.isHorizontal();
1397         [webRects addObject:webRect];
1398     }
1399
1400     return webRects;
1401 }
1402
1403 - (void)_highlightLongPressRecognized:(UILongPressGestureRecognizer *)gestureRecognizer
1404 {
1405     ASSERT(gestureRecognizer == _highlightLongPressGestureRecognizer);
1406     [self _resetIsDoubleTapPending];
1407
1408     _lastInteractionLocation = gestureRecognizer.startPoint;
1409
1410     switch ([gestureRecognizer state]) {
1411     case UIGestureRecognizerStateBegan:
1412         _highlightLongPressCanClick = YES;
1413         cancelPotentialTapIfNecessary(self);
1414         _page->tapHighlightAtPosition([gestureRecognizer startPoint], ++_latestTapID);
1415         _isTapHighlightIDValid = YES;
1416         break;
1417     case UIGestureRecognizerStateEnded:
1418         if (_highlightLongPressCanClick && _positionInformation.isElement) {
1419             [self _attemptClickAtLocation:[gestureRecognizer startPoint]];
1420             [self _finishInteraction];
1421         } else
1422             [self _cancelInteraction];
1423         _highlightLongPressCanClick = NO;
1424         break;
1425     case UIGestureRecognizerStateCancelled:
1426         [self _cancelInteraction];
1427         _highlightLongPressCanClick = NO;
1428         break;
1429     default:
1430         break;
1431     }
1432 }
1433
1434 - (void)_twoFingerSingleTapGestureRecognized:(UITapGestureRecognizer *)gestureRecognizer
1435 {
1436     _isTapHighlightIDValid = YES;
1437     _isExpectingFastSingleTapCommit = YES;
1438     _page->handleTwoFingerTapAtPoint(roundedIntPoint(gestureRecognizer.centroid), ++_latestTapID);
1439 }
1440
1441 - (void)_longPressRecognized:(UILongPressGestureRecognizer *)gestureRecognizer
1442 {
1443     ASSERT(gestureRecognizer == _longPressGestureRecognizer);
1444     [self _resetIsDoubleTapPending];
1445
1446     _lastInteractionLocation = gestureRecognizer.startPoint;
1447
1448     if ([gestureRecognizer state] == UIGestureRecognizerStateBegan) {
1449         SEL action = [self _actionForLongPress];
1450         if (action) {
1451             [self performSelector:action];
1452             [self _cancelLongPressGestureRecognizer];
1453         }
1454     }
1455 }
1456
1457 - (void)_endPotentialTapAndEnableDoubleTapGesturesIfNecessary
1458 {
1459     if (_webView._allowsDoubleTapGestures)
1460         [self _setDoubleTapGesturesEnabled:YES];
1461
1462     _potentialTapInProgress = NO;
1463 }
1464
1465 - (void)_singleTapRecognized:(UITapGestureRecognizer *)gestureRecognizer
1466 {
1467     ASSERT(gestureRecognizer == _singleTapGestureRecognizer);
1468     ASSERT(!_potentialTapInProgress);
1469     [self _resetIsDoubleTapPending];
1470
1471     _page->potentialTapAtPosition(gestureRecognizer.location, ++_latestTapID);
1472     _potentialTapInProgress = YES;
1473     _isTapHighlightIDValid = YES;
1474     _isExpectingFastSingleTapCommit = !_doubleTapGestureRecognizer.get().enabled;
1475 }
1476
1477 static void cancelPotentialTapIfNecessary(WKContentView* contentView)
1478 {
1479     if (contentView->_potentialTapInProgress) {
1480         [contentView _endPotentialTapAndEnableDoubleTapGesturesIfNecessary];
1481         [contentView _cancelInteraction];
1482         contentView->_page->cancelPotentialTap();
1483     }
1484 }
1485
1486 - (void)_singleTapDidReset:(UITapGestureRecognizer *)gestureRecognizer
1487 {
1488     ASSERT(gestureRecognizer == _singleTapGestureRecognizer);
1489     cancelPotentialTapIfNecessary(self);
1490 }
1491
1492 - (void)_commitPotentialTapFailed
1493 {
1494     [self _cancelInteraction];
1495 }
1496
1497 - (void)_didNotHandleTapAsClick:(const WebCore::IntPoint&)point
1498 {
1499     // FIXME: we should also take into account whether or not the UI delegate
1500     // has handled this notification.
1501     if (_hasValidPositionInformation && point == _positionInformation.request.point && _positionInformation.isDataDetectorLink) {
1502         [self _showDataDetectorsSheet];
1503         return;
1504     }
1505
1506     if (!_isDoubleTapPending)
1507         return;
1508
1509     _smartMagnificationController->handleSmartMagnificationGesture(_lastInteractionLocation);
1510     _isDoubleTapPending = NO;
1511 }
1512
1513 - (void)_singleTapCommited:(UITapGestureRecognizer *)gestureRecognizer
1514 {
1515     ASSERT(gestureRecognizer == _singleTapGestureRecognizer);
1516
1517     if (![self isFirstResponder])
1518         [self becomeFirstResponder];
1519
1520     if (_webSelectionAssistant && ![_webSelectionAssistant shouldHandleSingleTapAtPoint:gestureRecognizer.location]) {
1521         [self _singleTapDidReset:gestureRecognizer];
1522         return;
1523     }
1524
1525     ASSERT(_potentialTapInProgress);
1526
1527     // We don't want to clear the selection if it is in editable content.
1528     // The selection could have been set by autofocusing on page load and not
1529     // reflected in the UI process since the user was not interacting with the page.
1530     if (!_page->editorState().isContentEditable)
1531         [_webSelectionAssistant clearSelection];
1532
1533     _lastInteractionLocation = gestureRecognizer.location;
1534
1535     [self _endPotentialTapAndEnableDoubleTapGesturesIfNecessary];
1536
1537     if (_hasTapHighlightForPotentialTap) {
1538         [self _showTapHighlight];
1539         _hasTapHighlightForPotentialTap = NO;
1540     }
1541
1542     [_inputPeripheral endEditing];
1543     _page->commitPotentialTap();
1544
1545     if (!_isExpectingFastSingleTapCommit)
1546         [self _finishInteraction];
1547 }
1548
1549 - (void)_doubleTapRecognized:(UITapGestureRecognizer *)gestureRecognizer
1550 {
1551     [self _resetIsDoubleTapPending];
1552     _lastInteractionLocation = gestureRecognizer.location;
1553
1554     _smartMagnificationController->handleSmartMagnificationGesture(gestureRecognizer.location);
1555 }
1556
1557 - (void)_resetIsDoubleTapPending
1558 {
1559     _isDoubleTapPending = NO;
1560 }
1561
1562 - (void)_nonBlockingDoubleTapRecognized:(UITapGestureRecognizer *)gestureRecognizer
1563 {
1564     _lastInteractionLocation = gestureRecognizer.location;
1565     _isDoubleTapPending = YES;
1566 }
1567
1568 - (void)_twoFingerDoubleTapRecognized:(UITapGestureRecognizer *)gestureRecognizer
1569 {
1570     [self _resetIsDoubleTapPending];
1571     _lastInteractionLocation = gestureRecognizer.location;
1572
1573     _smartMagnificationController->handleResetMagnificationGesture(gestureRecognizer.location);
1574 }
1575
1576 - (void)_attemptClickAtLocation:(CGPoint)location
1577 {
1578     if (![self isFirstResponder])
1579         [self becomeFirstResponder];
1580
1581     [_inputPeripheral endEditing];
1582     _page->handleTap(location);
1583 }
1584
1585 - (void)useSelectionAssistantWithMode:(UIWebSelectionMode)selectionMode
1586 {
1587     if (selectionMode == UIWebSelectionModeWeb) {
1588         if (_textSelectionAssistant) {
1589             [_textSelectionAssistant deactivateSelection];
1590             _textSelectionAssistant = nil;
1591         }
1592         if (!_webSelectionAssistant)
1593             _webSelectionAssistant = adoptNS([[UIWKSelectionAssistant alloc] initWithView:self]);
1594     } else if (selectionMode == UIWebSelectionModeTextOnly) {
1595         if (_webSelectionAssistant)
1596             _webSelectionAssistant = nil;
1597
1598         if (!_textSelectionAssistant)
1599             _textSelectionAssistant = adoptNS([[UIWKTextInteractionAssistant alloc] initWithView:self]);
1600         else {
1601             // Reset the gesture recognizers in case editibility has changed.
1602             [_textSelectionAssistant setGestureRecognizers];
1603         }
1604
1605         if (self.isFirstResponder)
1606             [_textSelectionAssistant activateSelection];
1607     }
1608 }
1609
1610 - (void)clearSelection
1611 {
1612     _page->clearSelection();
1613 }
1614
1615 - (void)_positionInformationDidChange:(const InteractionInformationAtPosition&)info
1616 {
1617     _outstandingPositionInformationRequest = std::nullopt;
1618
1619     InteractionInformationAtPosition newInfo = info;
1620     newInfo.mergeCompatibleOptionalInformation(_positionInformation);
1621
1622     _positionInformation = newInfo;
1623     _hasValidPositionInformation = YES;
1624     if (_actionSheetAssistant)
1625         [_actionSheetAssistant updateSheetPosition];
1626 }
1627
1628 - (void)_willStartScrollingOrZooming
1629 {
1630     [_webSelectionAssistant willStartScrollingOrZoomingPage];
1631     [_textSelectionAssistant willStartScrollingOverflow];
1632     _page->setIsScrollingOrZooming(true);
1633 }
1634
1635 - (void)scrollViewWillStartPanOrPinchGesture
1636 {
1637     _page->hideValidationMessage();
1638
1639     _canSendTouchEventsAsynchronously = YES;
1640 }
1641
1642 - (void)_didEndScrollingOrZooming
1643 {
1644     [_webSelectionAssistant didEndScrollingOrZoomingPage];
1645     [_textSelectionAssistant didEndScrollingOverflow];
1646     _page->setIsScrollingOrZooming(false);
1647 }
1648
1649 - (BOOL)requiresAccessoryView
1650 {
1651     if ([_formInputSession accessoryViewShouldNotShow])
1652         return NO;
1653
1654     switch (_assistedNodeInformation.elementType) {
1655     case InputType::None:
1656         return NO;
1657     case InputType::Text:
1658     case InputType::Password:
1659     case InputType::Search:
1660     case InputType::Email:
1661     case InputType::URL:
1662     case InputType::Phone:
1663     case InputType::Number:
1664     case InputType::NumberPad:
1665     case InputType::ContentEditable:
1666     case InputType::TextArea:
1667     case InputType::Select:
1668     case InputType::Date:
1669     case InputType::DateTime:
1670     case InputType::DateTimeLocal:
1671     case InputType::Month:
1672     case InputType::Week:
1673     case InputType::Time:
1674         return !UICurrentUserInterfaceIdiomIsPad();
1675     }
1676 }
1677
1678 - (void)_ensureFormAccessoryView
1679 {
1680     if (_formAccessoryView)
1681         return;
1682
1683     _formAccessoryView = adoptNS([[UIWebFormAccessory alloc] initWithInputAssistantItem:self.inputAssistantItem]);
1684     [_formAccessoryView setDelegate:self];
1685 }
1686
1687 - (UIView *)inputAccessoryView
1688 {
1689     if (![self requiresAccessoryView])
1690         return nil;
1691
1692     return self.formAccessoryView;
1693 }
1694
1695 - (NSArray *)supportedPasteboardTypesForCurrentSelection
1696 {
1697     if (_page->editorState().selectionIsNone)
1698         return nil;
1699     
1700     static NSMutableArray *richTypes = nil;
1701     static NSMutableArray *plainTextTypes = nil;
1702     if (!plainTextTypes) {
1703         plainTextTypes = [[NSMutableArray alloc] init];
1704         [plainTextTypes addObject:(id)kUTTypeURL];
1705         [plainTextTypes addObjectsFromArray:UIPasteboardTypeListString];
1706
1707         richTypes = [[NSMutableArray alloc] init];
1708         [richTypes addObject:WebArchivePboardType];
1709         [richTypes addObjectsFromArray:UIPasteboardTypeListImage];
1710         [richTypes addObjectsFromArray:plainTextTypes];
1711     }
1712
1713     return (_page->editorState().isContentRichlyEditable) ? richTypes : plainTextTypes;
1714 }
1715
1716 - (void)_lookup:(id)sender
1717 {
1718     RetainPtr<WKContentView> view = self;
1719     _page->getSelectionContext([view](const String& selectedText, const String& textBefore, const String& textAfter, CallbackBase::Error error) {
1720         if (error != CallbackBase::Error::None)
1721             return;
1722         if (!selectedText)
1723             return;
1724         
1725         CGRect presentationRect = view->_page->editorState().selectionIsRange ? view->_page->editorState().postLayoutData().selectionRects[0].rect() : view->_page->editorState().postLayoutData().caretRectAtStart;
1726         
1727         String selectionContext = textBefore + selectedText + textAfter;
1728         if (view->_textSelectionAssistant) {
1729             if ([view->_textSelectionAssistant respondsToSelector:@selector(lookup:withRange:fromRect:)])
1730                 [view->_textSelectionAssistant lookup:selectionContext withRange:NSMakeRange(textBefore.length(), selectedText.length()) fromRect:presentationRect];
1731             else
1732                 [view->_textSelectionAssistant lookup:selectedText fromRect:presentationRect];
1733         } else {
1734             if ([view->_webSelectionAssistant respondsToSelector:@selector(lookup:withRange:fromRect:)])
1735                 [view->_webSelectionAssistant lookup:selectionContext withRange:NSMakeRange(textBefore.length(), selectedText.length()) fromRect:presentationRect];
1736             else
1737                 [view->_webSelectionAssistant lookup:selectedText fromRect:presentationRect];
1738         }
1739     });
1740 }
1741
1742 - (void)_share:(id)sender
1743 {
1744     RetainPtr<WKContentView> view = self;
1745     _page->getSelectionOrContentsAsString([view](const String& string, CallbackBase::Error error) {
1746         if (error != CallbackBase::Error::None)
1747             return;
1748         if (!string)
1749             return;
1750
1751         CGRect presentationRect = view->_page->editorState().postLayoutData().selectionRects[0].rect();
1752
1753         if (view->_textSelectionAssistant && [view->_textSelectionAssistant respondsToSelector:@selector(showShareSheetFor:fromRect:)])
1754             [view->_textSelectionAssistant showShareSheetFor:string fromRect:presentationRect];
1755         else if (view->_webSelectionAssistant && [view->_webSelectionAssistant respondsToSelector:@selector(showShareSheetFor:fromRect:)])
1756             [view->_webSelectionAssistant showShareSheetFor:string fromRect:presentationRect];
1757     });
1758 }
1759
1760 - (void)_addShortcut:(id)sender
1761 {
1762     if (_textSelectionAssistant && [_textSelectionAssistant respondsToSelector:@selector(showTextServiceFor:fromRect:)])
1763         [_textSelectionAssistant showTextServiceFor:[self selectedText] fromRect:_page->editorState().postLayoutData().selectionRects[0].rect()];
1764     else if (_webSelectionAssistant && [_webSelectionAssistant respondsToSelector:@selector(showTextServiceFor:fromRect:)])
1765         [_webSelectionAssistant showTextServiceFor:[self selectedText] fromRect:_page->editorState().postLayoutData().selectionRects[0].rect()];
1766 }
1767
1768 - (NSString *)selectedText
1769 {
1770     return (NSString *)_page->editorState().postLayoutData().wordAtSelection;
1771 }
1772
1773 - (BOOL)isReplaceAllowed
1774 {
1775     return _page->editorState().postLayoutData().isReplaceAllowed;
1776 }
1777
1778 - (void)replaceText:(NSString *)text withText:(NSString *)word
1779 {
1780     _page->replaceSelectedText(text, word);
1781 }
1782
1783 - (void)selectWordBackward
1784 {
1785     _page->selectWordBackward();
1786 }
1787
1788 - (void)_promptForReplace:(id)sender
1789 {
1790     const auto& wordAtSelection = _page->editorState().postLayoutData().wordAtSelection;
1791     if (wordAtSelection.isEmpty())
1792         return;
1793
1794     if ([_textSelectionAssistant respondsToSelector:@selector(scheduleReplacementsForText:)])
1795         [_textSelectionAssistant scheduleReplacementsForText:wordAtSelection];
1796 }
1797
1798 - (void)_transliterateChinese:(id)sender
1799 {
1800     if ([_textSelectionAssistant respondsToSelector:@selector(scheduleChineseTransliterationForText:)])
1801         [_textSelectionAssistant scheduleChineseTransliterationForText:_page->editorState().postLayoutData().wordAtSelection];
1802 }
1803
1804 - (void)_reanalyze:(id)sender
1805 {
1806     [_textSelectionAssistant scheduleReanalysis];
1807 }
1808
1809 - (void)replace:(id)sender
1810 {
1811     [[UIKeyboardImpl sharedInstance] replaceText:sender];
1812 }
1813
1814 - (NSDictionary *)textStylingAtPosition:(UITextPosition *)position inDirection:(UITextStorageDirection)direction
1815 {
1816     if (!position || !_page->editorState().isContentRichlyEditable)
1817         return nil;
1818
1819     NSMutableDictionary* result = [NSMutableDictionary dictionary];
1820
1821     auto typingAttributes = _page->editorState().postLayoutData().typingAttributes;
1822     CTFontSymbolicTraits symbolicTraits = 0;
1823     if (typingAttributes & AttributeBold)
1824         symbolicTraits |= kCTFontBoldTrait;
1825     if (typingAttributes & AttributeItalics)
1826         symbolicTraits |= kCTFontTraitItalic;
1827
1828     // We chose a random font family and size.
1829     // What matters are the traits but the caller expects a font object
1830     // in the dictionary for NSFontAttributeName.
1831     RetainPtr<CTFontDescriptorRef> fontDescriptor = adoptCF(CTFontDescriptorCreateWithNameAndSize(CFSTR("Helvetica"), 10));
1832     if (symbolicTraits)
1833         fontDescriptor = adoptCF(CTFontDescriptorCreateCopyWithSymbolicTraits(fontDescriptor.get(), symbolicTraits, symbolicTraits));
1834     
1835     RetainPtr<CTFontRef> font = adoptCF(CTFontCreateWithFontDescriptor(fontDescriptor.get(), 10, nullptr));
1836     if (font)
1837         [result setObject:(id)font.get() forKey:NSFontAttributeName];
1838     
1839     if (typingAttributes & AttributeUnderline)
1840         [result setObject:[NSNumber numberWithInt:NSUnderlineStyleSingle] forKey:NSUnderlineStyleAttributeName];
1841
1842     return result;
1843 }
1844
1845 - (BOOL)canPerformAction:(SEL)action withSender:(id)sender
1846 {
1847     BOOL hasWebSelection = _webSelectionAssistant && !CGRectIsEmpty(_webSelectionAssistant.get().selectionFrame);
1848
1849     if (action == @selector(_arrowKey:))
1850         return [self isFirstResponder];
1851         
1852     if (action == @selector(_showTextStyleOptions:))
1853         return _page->editorState().isContentRichlyEditable && _page->editorState().selectionIsRange && !_showingTextStyleOptions;
1854     if (_showingTextStyleOptions)
1855         return (action == @selector(toggleBoldface:) || action == @selector(toggleItalics:) || action == @selector(toggleUnderline:));
1856     if (action == @selector(toggleBoldface:) || action == @selector(toggleItalics:) || action == @selector(toggleUnderline:))
1857         return _page->editorState().isContentRichlyEditable;
1858     if (action == @selector(cut:))
1859         return !_page->editorState().isInPasswordField && _page->editorState().isContentEditable && _page->editorState().selectionIsRange;
1860     
1861     if (action == @selector(paste:)) {
1862         if (_page->editorState().selectionIsNone || !_page->editorState().isContentEditable)
1863             return NO;
1864         UIPasteboard *pasteboard = [UIPasteboard generalPasteboard];
1865         NSArray *types = [self supportedPasteboardTypesForCurrentSelection];
1866         NSIndexSet *indices = [NSIndexSet indexSetWithIndexesInRange:NSMakeRange(0, [pasteboard numberOfItems])];
1867         return [pasteboard containsPasteboardTypes:types inItemSet:indices];
1868     }
1869
1870     if (action == @selector(copy:)) {
1871         if (_page->editorState().isInPasswordField)
1872             return NO;
1873         return hasWebSelection || _page->editorState().selectionIsRange;
1874     }
1875
1876     if (action == @selector(_define:)) {
1877         if (_page->editorState().isInPasswordField || !(hasWebSelection || _page->editorState().selectionIsRange))
1878             return NO;
1879
1880         NSUInteger textLength = _page->editorState().postLayoutData().selectedTextLength;
1881         // FIXME: We should be calling UIReferenceLibraryViewController to check if the length is
1882         // acceptable, but the interface takes a string.
1883         // <rdar://problem/15254406>
1884         if (!textLength || textLength > 200)
1885             return NO;
1886
1887         if ([[getMCProfileConnectionClass() sharedConnection] effectiveBoolValueForSetting:MCFeatureDefinitionLookupAllowed] == MCRestrictedBoolExplicitNo)
1888             return NO;
1889             
1890         return YES;
1891     }
1892
1893     if (action == @selector(_lookup:)) {
1894         if (_page->editorState().isInPasswordField)
1895             return NO;
1896         
1897         if ([[getMCProfileConnectionClass() sharedConnection] effectiveBoolValueForSetting:MCFeatureDefinitionLookupAllowed] == MCRestrictedBoolExplicitNo)
1898             return NO;
1899         
1900         return YES;
1901     }
1902
1903     if (action == @selector(_share:)) {
1904         if (_page->editorState().isInPasswordField || !(hasWebSelection || _page->editorState().selectionIsRange))
1905             return NO;
1906
1907         return _page->editorState().postLayoutData().selectedTextLength > 0;
1908     }
1909
1910     if (action == @selector(_addShortcut:)) {
1911         if (_page->editorState().isInPasswordField || !(hasWebSelection || _page->editorState().selectionIsRange))
1912             return NO;
1913
1914         NSString *selectedText = [self selectedText];
1915         if (![selectedText length])
1916             return NO;
1917
1918         if (!UIKeyboardEnabledInputModesAllowOneToManyShortcuts())
1919             return NO;
1920         if (![selectedText _containsCJScripts])
1921             return NO;
1922         return YES;
1923     }
1924
1925     if (action == @selector(_promptForReplace:)) {
1926         if (!_page->editorState().selectionIsRange || !_page->editorState().postLayoutData().isReplaceAllowed || ![[UIKeyboardImpl activeInstance] autocorrectSpellingEnabled])
1927             return NO;
1928         if ([[self selectedText] _containsCJScriptsOnly])
1929             return NO;
1930         return YES;
1931     }
1932
1933     if (action == @selector(_transliterateChinese:)) {
1934         if (!_page->editorState().selectionIsRange || !_page->editorState().postLayoutData().isReplaceAllowed || ![[UIKeyboardImpl activeInstance] autocorrectSpellingEnabled])
1935             return NO;
1936         return UIKeyboardEnabledInputModesAllowChineseTransliterationForText([self selectedText]);
1937     }
1938
1939     if (action == @selector(_reanalyze:)) {
1940         if (!_page->editorState().selectionIsRange || !_page->editorState().postLayoutData().isReplaceAllowed || ![[UIKeyboardImpl activeInstance] autocorrectSpellingEnabled])
1941             return NO;
1942         return UIKeyboardCurrentInputModeAllowsChineseOrJapaneseReanalysisForText([self selectedText]);
1943     }
1944
1945     if (action == @selector(select:)) {
1946         // Disable select in password fields so that you can't see word boundaries.
1947         return !_page->editorState().isInPasswordField && [self hasContent] && !_page->editorState().selectionIsNone && !_page->editorState().selectionIsRange;
1948     }
1949
1950     if (action == @selector(selectAll:)) {
1951         if (_page->editorState().selectionIsNone || ![self hasContent])
1952             return NO;
1953         if (!_page->editorState().selectionIsRange)
1954             return YES;
1955         // Enable selectAll for non-editable text, where the user can't access
1956         // this command via long-press to get a caret.
1957         if (_page->editorState().isContentEditable)
1958             return NO;
1959         // Don't attempt selectAll with general web content.
1960         if (hasWebSelection)
1961             return NO;
1962         // FIXME: Only enable if the selection doesn't already span the entire document.
1963         return YES;
1964     }
1965
1966     if (action == @selector(replace:))
1967         return _page->editorState().isContentEditable && !_page->editorState().isInPasswordField;
1968
1969     return [super canPerformAction:action withSender:sender];
1970 }
1971
1972 - (void)_resetShowingTextStyle:(NSNotification *)notification
1973 {
1974     _showingTextStyleOptions = NO;
1975     [_textSelectionAssistant hideTextStyleOptions];
1976 }
1977
1978 - (void)copy:(id)sender
1979 {
1980     _page->executeEditCommand(ASCIILiteral("copy"));
1981 }
1982
1983 - (void)cut:(id)sender
1984 {
1985     _page->executeEditCommand(ASCIILiteral("cut"));
1986 }
1987
1988 - (void)paste:(id)sender
1989 {
1990     _page->executeEditCommand(ASCIILiteral("paste"));
1991 }
1992
1993 - (void)select:(id)sender
1994 {
1995     [_textSelectionAssistant selectWord];
1996     // We cannot use selectWord command, because we want to be able to select the word even when it is the last in the paragraph.
1997     _page->extendSelection(WordGranularity);
1998 }
1999
2000 - (void)selectAll:(id)sender
2001 {
2002     [_textSelectionAssistant selectAll:sender];
2003     _page->executeEditCommand(ASCIILiteral("selectAll"));
2004 }
2005
2006 - (void)toggleBoldface:(id)sender
2007 {
2008     if (!_page->editorState().isContentRichlyEditable)
2009         return;
2010
2011     [self executeEditCommandWithCallback:@"toggleBold"];
2012 }
2013
2014 - (void)toggleItalics:(id)sender
2015 {
2016     if (!_page->editorState().isContentRichlyEditable)
2017         return;
2018
2019     [self executeEditCommandWithCallback:@"toggleItalic"];
2020 }
2021
2022 - (void)toggleUnderline:(id)sender
2023 {
2024     if (!_page->editorState().isContentRichlyEditable)
2025         return;
2026
2027     [self executeEditCommandWithCallback:@"toggleUnderline"];
2028 }
2029
2030 - (void)_showTextStyleOptions:(id)sender
2031 {
2032     _showingTextStyleOptions = YES;
2033     [_textSelectionAssistant showTextStyleOptions];
2034 }
2035
2036 - (void)_showDictionary:(NSString *)text
2037 {
2038     CGRect presentationRect = _page->editorState().postLayoutData().selectionRects[0].rect();
2039     if (_textSelectionAssistant)
2040         [_textSelectionAssistant showDictionaryFor:text fromRect:presentationRect];
2041     else
2042         [_webSelectionAssistant showDictionaryFor:text fromRect:presentationRect];
2043 }
2044
2045 - (void)_define:(id)sender
2046 {
2047     if ([[getMCProfileConnectionClass() sharedConnection] effectiveBoolValueForSetting:MCFeatureDefinitionLookupAllowed] == MCRestrictedBoolExplicitNo)
2048         return;
2049
2050     RetainPtr<WKContentView> view = self;
2051     _page->getSelectionOrContentsAsString([view](const String& string, WebKit::CallbackBase::Error error) {
2052         if (error != WebKit::CallbackBase::Error::None)
2053             return;
2054         if (!string)
2055             return;
2056
2057         [view _showDictionary:string];
2058     });
2059 }
2060
2061 - (void)accessibilityRetrieveSpeakSelectionContent
2062 {
2063     RetainPtr<WKContentView> view = self;
2064     _page->getSelectionOrContentsAsString([view](const String& string, WebKit::CallbackBase::Error error) {
2065         if (error != WebKit::CallbackBase::Error::None)
2066             return;
2067         if ([view respondsToSelector:@selector(accessibilitySpeakSelectionSetContent:)])
2068             [view accessibilitySpeakSelectionSetContent:string];
2069     });
2070 }
2071
2072 // UIWKInteractionViewProtocol
2073
2074 static inline GestureType toGestureType(UIWKGestureType gestureType)
2075 {
2076     switch (gestureType) {
2077     case UIWKGestureLoupe:
2078         return GestureType::Loupe;
2079     case UIWKGestureOneFingerTap:
2080         return GestureType::OneFingerTap;
2081     case UIWKGestureTapAndAHalf:
2082         return GestureType::TapAndAHalf;
2083     case UIWKGestureDoubleTap:
2084         return GestureType::DoubleTap;
2085     case UIWKGestureTapAndHalf:
2086         return GestureType::TapAndHalf;
2087     case UIWKGestureDoubleTapInUneditable:
2088         return GestureType::DoubleTapInUneditable;
2089     case UIWKGestureOneFingerTapInUneditable:
2090         return GestureType::OneFingerTapInUneditable;
2091     case UIWKGestureOneFingerTapSelectsAll:
2092         return GestureType::OneFingerTapSelectsAll;
2093     case UIWKGestureOneFingerDoubleTap:
2094         return GestureType::OneFingerDoubleTap;
2095     case UIWKGestureOneFingerTripleTap:
2096         return GestureType::OneFingerTripleTap;
2097     case UIWKGestureTwoFingerSingleTap:
2098         return GestureType::TwoFingerSingleTap;
2099     case UIWKGestureTwoFingerRangedSelectGesture:
2100         return GestureType::TwoFingerRangedSelectGesture;
2101     case UIWKGestureTapOnLinkWithGesture:
2102         return GestureType::TapOnLinkWithGesture;
2103     case UIWKGestureMakeWebSelection:
2104         return GestureType::MakeWebSelection;
2105     case UIWKGesturePhraseBoundary:
2106         return GestureType::PhraseBoundary;
2107     }
2108     ASSERT_NOT_REACHED();
2109     return GestureType::Loupe;
2110 }
2111
2112 static inline UIWKGestureType toUIWKGestureType(GestureType gestureType)
2113 {
2114     switch (gestureType) {
2115     case GestureType::Loupe:
2116         return UIWKGestureLoupe;
2117     case GestureType::OneFingerTap:
2118         return UIWKGestureOneFingerTap;
2119     case GestureType::TapAndAHalf:
2120         return UIWKGestureTapAndAHalf;
2121     case GestureType::DoubleTap:
2122         return UIWKGestureDoubleTap;
2123     case GestureType::TapAndHalf:
2124         return UIWKGestureTapAndHalf;
2125     case GestureType::DoubleTapInUneditable:
2126         return UIWKGestureDoubleTapInUneditable;
2127     case GestureType::OneFingerTapInUneditable:
2128         return UIWKGestureOneFingerTapInUneditable;
2129     case GestureType::OneFingerTapSelectsAll:
2130         return UIWKGestureOneFingerTapSelectsAll;
2131     case GestureType::OneFingerDoubleTap:
2132         return UIWKGestureOneFingerDoubleTap;
2133     case GestureType::OneFingerTripleTap:
2134         return UIWKGestureOneFingerTripleTap;
2135     case GestureType::TwoFingerSingleTap:
2136         return UIWKGestureTwoFingerSingleTap;
2137     case GestureType::TwoFingerRangedSelectGesture:
2138         return UIWKGestureTwoFingerRangedSelectGesture;
2139     case GestureType::TapOnLinkWithGesture:
2140         return UIWKGestureTapOnLinkWithGesture;
2141     case GestureType::MakeWebSelection:
2142         return UIWKGestureMakeWebSelection;
2143     case GestureType::PhraseBoundary:
2144         return UIWKGesturePhraseBoundary;
2145     }
2146 }
2147
2148 static inline SelectionTouch toSelectionTouch(UIWKSelectionTouch touch)
2149 {
2150     switch (touch) {
2151     case UIWKSelectionTouchStarted:
2152         return SelectionTouch::Started;
2153     case UIWKSelectionTouchMoved:
2154         return SelectionTouch::Moved;
2155     case UIWKSelectionTouchEnded:
2156         return SelectionTouch::Ended;
2157     case UIWKSelectionTouchEndedMovingForward:
2158         return SelectionTouch::EndedMovingForward;
2159     case UIWKSelectionTouchEndedMovingBackward:
2160         return SelectionTouch::EndedMovingBackward;
2161     case UIWKSelectionTouchEndedNotMoving:
2162         return SelectionTouch::EndedNotMoving;
2163     }
2164     ASSERT_NOT_REACHED();
2165     return SelectionTouch::Ended;
2166 }
2167
2168 static inline UIWKSelectionTouch toUIWKSelectionTouch(SelectionTouch touch)
2169 {
2170     switch (touch) {
2171     case SelectionTouch::Started:
2172         return UIWKSelectionTouchStarted;
2173     case SelectionTouch::Moved:
2174         return UIWKSelectionTouchMoved;
2175     case SelectionTouch::Ended:
2176         return UIWKSelectionTouchEnded;
2177     case SelectionTouch::EndedMovingForward:
2178         return UIWKSelectionTouchEndedMovingForward;
2179     case SelectionTouch::EndedMovingBackward:
2180         return UIWKSelectionTouchEndedMovingBackward;
2181     case SelectionTouch::EndedNotMoving:
2182         return UIWKSelectionTouchEndedNotMoving;
2183     }
2184 }
2185
2186 static inline GestureRecognizerState toGestureRecognizerState(UIGestureRecognizerState state)
2187 {
2188     switch (state) {
2189     case UIGestureRecognizerStatePossible:
2190         return GestureRecognizerState::Possible;
2191     case UIGestureRecognizerStateBegan:
2192         return GestureRecognizerState::Began;
2193     case UIGestureRecognizerStateChanged:
2194         return GestureRecognizerState::Changed;
2195     case UIGestureRecognizerStateCancelled:
2196         return GestureRecognizerState::Cancelled;
2197     case UIGestureRecognizerStateEnded:
2198         return GestureRecognizerState::Ended;
2199     case UIGestureRecognizerStateFailed:
2200         return GestureRecognizerState::Failed;
2201     }
2202 }
2203
2204 static inline UIGestureRecognizerState toUIGestureRecognizerState(GestureRecognizerState state)
2205 {
2206     switch (state) {
2207     case GestureRecognizerState::Possible:
2208         return UIGestureRecognizerStatePossible;
2209     case GestureRecognizerState::Began:
2210         return UIGestureRecognizerStateBegan;
2211     case GestureRecognizerState::Changed:
2212         return UIGestureRecognizerStateChanged;
2213     case GestureRecognizerState::Cancelled:
2214         return UIGestureRecognizerStateCancelled;
2215     case GestureRecognizerState::Ended:
2216         return UIGestureRecognizerStateEnded;
2217     case GestureRecognizerState::Failed:
2218         return UIGestureRecognizerStateFailed;
2219     }
2220 }
2221
2222 static inline UIWKSelectionFlags toUIWKSelectionFlags(SelectionFlags flags)
2223 {
2224     NSInteger uiFlags = UIWKNone;
2225     if (flags & WordIsNearTap)
2226         uiFlags |= UIWKWordIsNearTap;
2227     if (flags & IsBlockSelection)
2228         uiFlags |= UIWKIsBlockSelection;
2229     if (flags & PhraseBoundaryChanged)
2230         uiFlags |= UIWKPhraseBoundaryChanged;
2231
2232     return static_cast<UIWKSelectionFlags>(uiFlags);
2233 }
2234
2235 static inline SelectionHandlePosition toSelectionHandlePosition(UIWKHandlePosition position)
2236 {
2237     switch (position) {
2238     case UIWKHandleTop:
2239         return SelectionHandlePosition::Top;
2240     case UIWKHandleRight:
2241         return SelectionHandlePosition::Right;
2242     case UIWKHandleBottom:
2243         return SelectionHandlePosition::Bottom;
2244     case UIWKHandleLeft:
2245         return SelectionHandlePosition::Left;
2246     }
2247 }
2248
2249 static inline WebCore::TextGranularity toWKTextGranularity(UITextGranularity granularity)
2250 {
2251     switch (granularity) {
2252     case UITextGranularityCharacter:
2253         return CharacterGranularity;
2254     case UITextGranularityWord:
2255         return WordGranularity;
2256     case UITextGranularitySentence:
2257         return SentenceGranularity;
2258     case UITextGranularityParagraph:
2259         return ParagraphGranularity;
2260     case UITextGranularityLine:
2261         return LineGranularity;
2262     case UITextGranularityDocument:
2263         return DocumentGranularity;
2264     }
2265 }
2266
2267 static inline WebCore::SelectionDirection toWKSelectionDirection(UITextDirection direction)
2268 {
2269     switch (direction) {
2270     case UITextLayoutDirectionDown:
2271     case UITextLayoutDirectionRight:
2272         return DirectionRight;
2273     case UITextLayoutDirectionUp:
2274     case UITextLayoutDirectionLeft:
2275         return DirectionLeft;
2276     default:
2277         // UITextDirection is not an enum, but we only want to accept values from UITextLayoutDirection.
2278         ASSERT_NOT_REACHED();
2279         return DirectionRight;
2280     }
2281 }
2282
2283 static void selectionChangedWithGesture(WKContentView *view, const WebCore::IntPoint& point, uint32_t gestureType, uint32_t gestureState, uint32_t flags, WebKit::CallbackBase::Error error)
2284 {
2285     if (error != WebKit::CallbackBase::Error::None) {
2286         ASSERT_NOT_REACHED();
2287         return;
2288     }
2289     if ([view webSelectionAssistant])
2290         [(UIWKSelectionAssistant *)[view webSelectionAssistant] selectionChangedWithGestureAt:(CGPoint)point withGesture:toUIWKGestureType((GestureType)gestureType) withState:toUIGestureRecognizerState(static_cast<GestureRecognizerState>(gestureState)) withFlags:(toUIWKSelectionFlags((SelectionFlags)flags))];
2291     else
2292         [(UIWKTextInteractionAssistant *)[view interactionAssistant] selectionChangedWithGestureAt:(CGPoint)point withGesture:toUIWKGestureType((GestureType)gestureType) withState:toUIGestureRecognizerState(static_cast<GestureRecognizerState>(gestureState)) withFlags:(toUIWKSelectionFlags((SelectionFlags)flags))];
2293 }
2294
2295 static void selectionChangedWithTouch(WKContentView *view, const WebCore::IntPoint& point, uint32_t touch, uint32_t flags, WebKit::CallbackBase::Error error)
2296 {
2297     if (error != WebKit::CallbackBase::Error::None) {
2298         ASSERT_NOT_REACHED();
2299         return;
2300     }
2301     if ([view webSelectionAssistant])
2302         [(UIWKSelectionAssistant *)[view webSelectionAssistant] selectionChangedWithTouchAt:(CGPoint)point withSelectionTouch:toUIWKSelectionTouch((SelectionTouch)touch) withFlags:static_cast<UIWKSelectionFlags>(flags)];
2303     else
2304         [(UIWKTextInteractionAssistant *)[view interactionAssistant] selectionChangedWithTouchAt:(CGPoint)point withSelectionTouch:toUIWKSelectionTouch((SelectionTouch)touch)];
2305 }
2306
2307 - (void)_didUpdateBlockSelectionWithTouch:(SelectionTouch)touch withFlags:(SelectionFlags)flags growThreshold:(CGFloat)growThreshold shrinkThreshold:(CGFloat)shrinkThreshold
2308 {
2309     [_webSelectionAssistant blockSelectionChangedWithTouch:toUIWKSelectionTouch(touch) withFlags:toUIWKSelectionFlags(flags) growThreshold:growThreshold shrinkThreshold:shrinkThreshold];
2310     if (touch != SelectionTouch::Started && touch != SelectionTouch::Moved)
2311         _usingGestureForSelection = NO;
2312 }
2313
2314 - (BOOL)_isInteractingWithAssistedNode
2315 {
2316     return _textSelectionAssistant != nil;
2317 }
2318
2319 - (void)changeSelectionWithGestureAt:(CGPoint)point withGesture:(UIWKGestureType)gestureType withState:(UIGestureRecognizerState)state
2320 {
2321     _usingGestureForSelection = YES;
2322     _page->selectWithGesture(WebCore::IntPoint(point), CharacterGranularity, static_cast<uint32_t>(toGestureType(gestureType)), static_cast<uint32_t>(toGestureRecognizerState(state)), [self _isInteractingWithAssistedNode], [self, state](const WebCore::IntPoint& point, uint32_t gestureType, uint32_t gestureState, uint32_t flags, WebKit::CallbackBase::Error error) {
2323         selectionChangedWithGesture(self, point, gestureType, gestureState, flags, error);
2324         if (state == UIGestureRecognizerStateEnded || state == UIGestureRecognizerStateCancelled)
2325             _usingGestureForSelection = NO;
2326     });
2327 }
2328
2329 - (void)changeSelectionWithTouchAt:(CGPoint)point withSelectionTouch:(UIWKSelectionTouch)touch baseIsStart:(BOOL)baseIsStart
2330 {
2331     _usingGestureForSelection = YES;
2332     _page->updateSelectionWithTouches(WebCore::IntPoint(point), static_cast<uint32_t>(toSelectionTouch(touch)), baseIsStart, [self, touch](const WebCore::IntPoint& point, uint32_t touch, uint32_t flags, WebKit::CallbackBase::Error error) {
2333         selectionChangedWithTouch(self, point, touch, flags, error);
2334         if (touch != UIWKSelectionTouchStarted && touch != UIWKSelectionTouchMoved)
2335             _usingGestureForSelection = NO;
2336     });
2337 }
2338
2339 - (void)changeSelectionWithTouchesFrom:(CGPoint)from to:(CGPoint)to withGesture:(UIWKGestureType)gestureType withState:(UIGestureRecognizerState)gestureState
2340 {
2341     _usingGestureForSelection = YES;
2342     _page->selectWithTwoTouches(WebCore::IntPoint(from), WebCore::IntPoint(to), static_cast<uint32_t>(toGestureType(gestureType)), static_cast<uint32_t>(toGestureRecognizerState(gestureState)), [self, gestureState](const WebCore::IntPoint& point, uint32_t gestureType, uint32_t gestureState, uint32_t flags, WebKit::CallbackBase::Error error) {
2343         selectionChangedWithGesture(self, point, gestureType, gestureState, flags, error);
2344         if (gestureState == UIGestureRecognizerStateEnded || gestureState == UIGestureRecognizerStateCancelled)
2345             _usingGestureForSelection = NO;
2346     });
2347 }
2348
2349 - (void)changeBlockSelectionWithTouchAt:(CGPoint)point withSelectionTouch:(UIWKSelectionTouch)touch forHandle:(UIWKHandlePosition)handle
2350 {
2351     _usingGestureForSelection = YES;
2352     _page->updateBlockSelectionWithTouch(WebCore::IntPoint(point), static_cast<uint32_t>(toSelectionTouch(touch)), static_cast<uint32_t>(toSelectionHandlePosition(handle)));
2353 }
2354
2355 - (void)moveByOffset:(NSInteger)offset
2356 {
2357     if (!offset)
2358         return;
2359     
2360     [self beginSelectionChange];
2361     RetainPtr<WKContentView> view = self;
2362     _page->moveSelectionByOffset(offset, [view](WebKit::CallbackBase::Error) {
2363         [view endSelectionChange];
2364     });
2365 }
2366
2367 - (const WKAutoCorrectionData&)autocorrectionData
2368 {
2369     return _autocorrectionData;
2370 }
2371
2372 // The completion handler can pass nil if input does not match the actual text preceding the insertion point.
2373 - (void)requestAutocorrectionRectsForString:(NSString *)input withCompletionHandler:(void (^)(UIWKAutocorrectionRects *rectsForInput))completionHandler
2374 {
2375     if (!input || ![input length]) {
2376         completionHandler(nil);
2377         return;
2378     }
2379
2380     RetainPtr<WKContentView> view = self;
2381     _autocorrectionData.autocorrectionHandler = [completionHandler copy];
2382     _page->requestAutocorrectionData(input, [view](const Vector<FloatRect>& rects, const String& fontName, double fontSize, uint64_t traits, WebKit::CallbackBase::Error) {
2383         CGRect firstRect = CGRectZero;
2384         CGRect lastRect = CGRectZero;
2385         if (rects.size()) {
2386             firstRect = rects[0];
2387             lastRect = rects[rects.size() - 1];
2388         }
2389         
2390         view->_autocorrectionData.fontName = fontName;
2391         view->_autocorrectionData.fontSize = fontSize;
2392         view->_autocorrectionData.fontTraits = traits;
2393         view->_autocorrectionData.textFirstRect = firstRect;
2394         view->_autocorrectionData.textLastRect = lastRect;
2395
2396         view->_autocorrectionData.autocorrectionHandler(rects.size() ? [WKAutocorrectionRects autocorrectionRectsWithRects:firstRect lastRect:lastRect] : nil);
2397         [view->_autocorrectionData.autocorrectionHandler release];
2398         view->_autocorrectionData.autocorrectionHandler = nil;
2399     });
2400 }
2401
2402 - (void)selectPositionAtPoint:(CGPoint)point completionHandler:(void (^)(void))completionHandler
2403 {
2404     _usingGestureForSelection = YES;
2405     UIWKSelectionCompletionHandler selectionHandler = [completionHandler copy];
2406     RetainPtr<WKContentView> view = self;
2407     
2408     _page->selectPositionAtPoint(WebCore::IntPoint(point), [self _isInteractingWithAssistedNode], [view, selectionHandler](WebKit::CallbackBase::Error error) {
2409         selectionHandler();
2410         view->_usingGestureForSelection = NO;
2411         [selectionHandler release];
2412     });
2413 }
2414
2415 - (void)selectPositionAtBoundary:(UITextGranularity)granularity inDirection:(UITextDirection)direction fromPoint:(CGPoint)point completionHandler:(void (^)(void))completionHandler
2416 {
2417     _usingGestureForSelection = YES;
2418     UIWKSelectionCompletionHandler selectionHandler = [completionHandler copy];
2419     RetainPtr<WKContentView> view = self;
2420     
2421     _page->selectPositionAtBoundaryWithDirection(WebCore::IntPoint(point), toWKTextGranularity(granularity), toWKSelectionDirection(direction), [self _isInteractingWithAssistedNode], [view, selectionHandler](WebKit::CallbackBase::Error error) {
2422         selectionHandler();
2423         view->_usingGestureForSelection = NO;
2424         [selectionHandler release];
2425     });
2426 }
2427
2428 - (void)moveSelectionAtBoundary:(UITextGranularity)granularity inDirection:(UITextDirection)direction completionHandler:(void (^)(void))completionHandler
2429 {
2430     _usingGestureForSelection = YES;
2431     UIWKSelectionCompletionHandler selectionHandler = [completionHandler copy];
2432     RetainPtr<WKContentView> view = self;
2433     
2434     _page->moveSelectionAtBoundaryWithDirection(toWKTextGranularity(granularity), toWKSelectionDirection(direction), [view, selectionHandler](WebKit::CallbackBase::Error error) {
2435         selectionHandler();
2436         view->_usingGestureForSelection = NO;
2437         [selectionHandler release];
2438     });
2439 }
2440
2441 - (void)selectTextWithGranularity:(UITextGranularity)granularity atPoint:(CGPoint)point completionHandler:(void (^)(void))completionHandler
2442 {
2443     _usingGestureForSelection = YES;
2444     UIWKSelectionCompletionHandler selectionHandler = [completionHandler copy];
2445     RetainPtr<WKContentView> view = self;
2446
2447     _page->selectTextWithGranularityAtPoint(WebCore::IntPoint(point), toWKTextGranularity(granularity), [self _isInteractingWithAssistedNode], [view, selectionHandler](WebKit::CallbackBase::Error error) {
2448         selectionHandler();
2449         view->_usingGestureForSelection = NO;
2450         [selectionHandler release];
2451     });
2452 }
2453
2454 - (void)beginSelectionInDirection:(UITextDirection)direction completionHandler:(void (^)(BOOL endIsMoving))completionHandler
2455 {
2456     UIWKSelectionWithDirectionCompletionHandler selectionHandler = [completionHandler copy];
2457
2458     _page->beginSelectionInDirection(toWKSelectionDirection(direction), [selectionHandler](bool endIsMoving, WebKit::CallbackBase::Error error) {
2459         selectionHandler(endIsMoving);
2460         [selectionHandler release];
2461     });
2462 }
2463
2464 - (void)updateSelectionWithExtentPoint:(CGPoint)point completionHandler:(void (^)(BOOL endIsMoving))completionHandler
2465 {
2466     UIWKSelectionWithDirectionCompletionHandler selectionHandler = [completionHandler copy];
2467     
2468     _page->updateSelectionWithExtentPoint(WebCore::IntPoint(point), [self _isInteractingWithAssistedNode], [selectionHandler](bool endIsMoving, WebKit::CallbackBase::Error error) {
2469         selectionHandler(endIsMoving);
2470         [selectionHandler release];
2471     });
2472 }
2473
2474 - (void)updateSelectionWithExtentPoint:(CGPoint)point withBoundary:(UITextGranularity)granularity completionHandler:(void (^)(BOOL selectionEndIsMoving))completionHandler
2475 {
2476     UIWKSelectionWithDirectionCompletionHandler selectionHandler = [completionHandler copy];
2477     
2478     _page->updateSelectionWithExtentPointAndBoundary(WebCore::IntPoint(point), toWKTextGranularity(granularity), [self _isInteractingWithAssistedNode], [selectionHandler](bool endIsMoving, WebKit::CallbackBase::Error error) {
2479         selectionHandler(endIsMoving);
2480         [selectionHandler release];
2481     });
2482 }
2483
2484 - (UTF32Char)_characterBeforeCaretSelection
2485 {
2486     return _page->editorState().postLayoutData().characterBeforeSelection;
2487 }
2488
2489 - (UTF32Char)_characterInRelationToCaretSelection:(int)amount
2490 {
2491     switch (amount) {
2492     case 0:
2493         return _page->editorState().postLayoutData().characterAfterSelection;
2494     case -1:
2495         return _page->editorState().postLayoutData().characterBeforeSelection;
2496     case -2:
2497         return _page->editorState().postLayoutData().twoCharacterBeforeSelection;
2498     default:
2499         return 0;
2500     }
2501 }
2502
2503 - (BOOL)_selectionAtDocumentStart
2504 {
2505     return !_page->editorState().postLayoutData().characterBeforeSelection;
2506 }
2507
2508 - (CGRect)textFirstRect
2509 {
2510     return (_page->editorState().hasComposition) ? _page->editorState().firstMarkedRect : _autocorrectionData.textFirstRect;
2511 }
2512
2513 - (CGRect)textLastRect
2514 {
2515     return (_page->editorState().hasComposition) ? _page->editorState().lastMarkedRect : _autocorrectionData.textLastRect;
2516 }
2517
2518 - (void)replaceDictatedText:(NSString*)oldText withText:(NSString *)newText
2519 {
2520     _page->replaceDictatedText(oldText, newText);
2521 }
2522
2523 - (void)requestDictationContext:(void (^)(NSString *selectedText, NSString *beforeText, NSString *afterText))completionHandler
2524 {
2525     UIWKDictationContextHandler dictationHandler = [completionHandler copy];
2526
2527     _page->requestDictationContext([dictationHandler](const String& selectedText, const String& beforeText, const String& afterText, WebKit::CallbackBase::Error) {
2528         dictationHandler(selectedText, beforeText, afterText);
2529         [dictationHandler release];
2530     });
2531 }
2532
2533 // The completion handler should pass the rect of the correction text after replacing the input text, or nil if the replacement could not be performed.
2534 - (void)applyAutocorrection:(NSString *)correction toString:(NSString *)input withCompletionHandler:(void (^)(UIWKAutocorrectionRects *rectsForCorrection))completionHandler
2535 {
2536     // FIXME: Remove the synchronous call when <rdar://problem/16207002> is fixed.
2537     const bool useSyncRequest = true;
2538
2539     if (useSyncRequest) {
2540         completionHandler(_page->applyAutocorrection(correction, input) ? [WKAutocorrectionRects autocorrectionRectsWithRects:_autocorrectionData.textFirstRect lastRect:_autocorrectionData.textLastRect] : nil);
2541         return;
2542     }
2543     _autocorrectionData.autocorrectionHandler = [completionHandler copy];
2544     RetainPtr<WKContentView> view = self;
2545     _page->applyAutocorrection(correction, input, [view](const String& string, WebKit::CallbackBase::Error error) {
2546         view->_autocorrectionData.autocorrectionHandler(!string.isNull() ? [WKAutocorrectionRects autocorrectionRectsWithRects:view->_autocorrectionData.textFirstRect lastRect:view->_autocorrectionData.textLastRect] : nil);
2547         [view->_autocorrectionData.autocorrectionHandler release];
2548         view->_autocorrectionData.autocorrectionHandler = nil;
2549     });
2550 }
2551
2552 - (void)requestAutocorrectionContextWithCompletionHandler:(void (^)(UIWKAutocorrectionContext *autocorrectionContext))completionHandler
2553 {
2554     // FIXME: Remove the synchronous call when <rdar://problem/16207002> is fixed.
2555     const bool useSyncRequest = true;
2556
2557     if (useSyncRequest) {
2558         String beforeText;
2559         String markedText;
2560         String selectedText;
2561         String afterText;
2562         uint64_t location;
2563         uint64_t length;
2564         _page->getAutocorrectionContext(beforeText, markedText, selectedText, afterText, location, length);
2565         completionHandler([WKAutocorrectionContext autocorrectionContextWithData:beforeText markedText:markedText selectedText:selectedText afterText:afterText selectedRangeInMarkedText:NSMakeRange(location, length)]);
2566     } else {
2567         _autocorrectionData.autocorrectionContextHandler = [completionHandler copy];
2568         RetainPtr<WKContentView> view = self;
2569         _page->requestAutocorrectionContext([view](const String& beforeText, const String& markedText, const String& selectedText, const String& afterText, uint64_t location, uint64_t length, WebKit::CallbackBase::Error) {
2570             view->_autocorrectionData.autocorrectionContextHandler([WKAutocorrectionContext autocorrectionContextWithData:beforeText markedText:markedText selectedText:selectedText afterText:afterText selectedRangeInMarkedText:NSMakeRange(location, length)]);
2571         });
2572     }
2573 }
2574
2575 // UIWebFormAccessoryDelegate
2576 - (void)accessoryDone
2577 {
2578     [self resignFirstResponder];
2579 }
2580
2581 - (NSArray *)keyCommands
2582 {
2583     static NSArray* nonEditableKeyCommands = [@[
2584        [UIKeyCommand keyCommandWithInput:UIKeyInputUpArrow modifierFlags:0 action:@selector(_arrowKey:)],
2585        [UIKeyCommand keyCommandWithInput:UIKeyInputDownArrow modifierFlags:0 action:@selector(_arrowKey:)],
2586        [UIKeyCommand keyCommandWithInput:UIKeyInputLeftArrow modifierFlags:0 action:@selector(_arrowKey:)],
2587        [UIKeyCommand keyCommandWithInput:UIKeyInputRightArrow modifierFlags:0 action:@selector(_arrowKey:)],
2588        
2589        [UIKeyCommand keyCommandWithInput:UIKeyInputUpArrow modifierFlags:UIKeyModifierCommand action:@selector(_arrowKey:)],
2590        [UIKeyCommand keyCommandWithInput:UIKeyInputDownArrow modifierFlags:UIKeyModifierCommand action:@selector(_arrowKey:)],
2591        
2592        [UIKeyCommand keyCommandWithInput:UIKeyInputUpArrow modifierFlags:UIKeyModifierShift action:@selector(_arrowKey:)],
2593        [UIKeyCommand keyCommandWithInput:UIKeyInputDownArrow modifierFlags:UIKeyModifierShift action:@selector(_arrowKey:)],
2594        [UIKeyCommand keyCommandWithInput:UIKeyInputLeftArrow modifierFlags:UIKeyModifierShift action:@selector(_arrowKey:)],
2595        [UIKeyCommand keyCommandWithInput:UIKeyInputRightArrow modifierFlags:UIKeyModifierShift action:@selector(_arrowKey:)],
2596        
2597        [UIKeyCommand keyCommandWithInput:UIKeyInputUpArrow modifierFlags:UIKeyModifierAlternate action:@selector(_arrowKey:)],
2598        [UIKeyCommand keyCommandWithInput:UIKeyInputDownArrow modifierFlags:UIKeyModifierAlternate action:@selector(_arrowKey:)],
2599        [UIKeyCommand keyCommandWithInput:UIKeyInputLeftArrow modifierFlags:UIKeyModifierAlternate action:@selector(_arrowKey:)],
2600        [UIKeyCommand keyCommandWithInput:UIKeyInputRightArrow modifierFlags:UIKeyModifierAlternate action:@selector(_arrowKey:)],
2601        
2602        [UIKeyCommand keyCommandWithInput:@" " modifierFlags:0 action:@selector(_arrowKey:)],
2603        [UIKeyCommand keyCommandWithInput:@" " modifierFlags:UIKeyModifierShift action:@selector(_arrowKey:)],
2604        
2605        [UIKeyCommand keyCommandWithInput:UIKeyInputPageDown modifierFlags:0 action:@selector(_arrowKey:)],
2606        [UIKeyCommand keyCommandWithInput:UIKeyInputPageDown modifierFlags:0 action:@selector(_arrowKey:)],
2607     ] retain];
2608
2609     static NSArray* editableKeyCommands = [@[
2610        [UIKeyCommand keyCommandWithInput:@"\t" modifierFlags:0 action:@selector(_nextAccessoryTab:)],
2611        [UIKeyCommand keyCommandWithInput:@"\t" modifierFlags:UIKeyModifierShift action:@selector(_prevAccessoryTab:)]
2612     ] retain];
2613     
2614     return (_page->editorState().isContentEditable) ? editableKeyCommands : nonEditableKeyCommands;
2615 }
2616
2617 - (void)_arrowKey:(id)sender
2618 {
2619     UIKeyCommand* command = sender;
2620     [self handleKeyEvent:command._triggeringEvent];
2621 }
2622
2623 - (void)_nextAccessoryTab:(id)sender
2624 {
2625     [self accessoryTab:YES];
2626 }
2627
2628 - (void)_prevAccessoryTab:(id)sender
2629 {
2630     [self accessoryTab:NO];
2631 }
2632
2633 - (void)accessoryTab:(BOOL)isNext
2634 {
2635     [_inputPeripheral endEditing];
2636     _inputPeripheral = nil;
2637
2638     _didAccessoryTabInitiateFocus = YES; // Will be cleared in either -_displayFormNodeInputView or -cleanupInteraction.
2639     [self beginSelectionChange];
2640     RetainPtr<WKContentView> view = self;
2641     _page->focusNextAssistedNode(isNext, [view](WebKit::CallbackBase::Error) {
2642         [view endSelectionChange];
2643         [view reloadInputViews];
2644     });
2645
2646 }
2647
2648 - (void)_becomeFirstResponderWithSelectionMovingForward:(BOOL)selectingForward completionHandler:(void (^)(BOOL didBecomeFirstResponder))completionHandler
2649 {
2650     auto completionHandlerCopy = Block_copy(completionHandler);
2651     RetainPtr<WKContentView> view = self;
2652     _page->setInitialFocus(selectingForward, false, WebKit::WebKeyboardEvent(), [view, completionHandlerCopy](WebKit::CallbackBase::Error) {
2653         BOOL didBecomeFirstResponder = view->_assistedNodeInformation.elementType != InputType::None && [view becomeFirstResponder];
2654         completionHandlerCopy(didBecomeFirstResponder);
2655         Block_release(completionHandlerCopy);
2656     });
2657 }
2658
2659 - (WebCore::Color)_tapHighlightColorForFastClick:(BOOL)forFastClick
2660 {
2661     ASSERT(_showDebugTapHighlightsForFastClicking);
2662     return forFastClick ? WebCore::Color(0, 225, 0, 127) : WebCore::Color(225, 0, 0, 127);
2663 }
2664
2665 - (void)_setDoubleTapGesturesEnabled:(BOOL)enabled
2666 {
2667     if (enabled && ![_doubleTapGestureRecognizer isEnabled]) {
2668         // The first tap recognized after re-enabling double tap gestures will not wait for the
2669         // second tap before committing. To fix this, we use a new double tap gesture recognizer.
2670         [self removeGestureRecognizer:_doubleTapGestureRecognizer.get()];
2671         [_doubleTapGestureRecognizer setDelegate:nil];
2672         [self _createAndConfigureDoubleTapGestureRecognizer];
2673     }
2674
2675     if (_showDebugTapHighlightsForFastClicking && !enabled)
2676         _tapHighlightInformation.color = [self _tapHighlightColorForFastClick:YES];
2677
2678     [_doubleTapGestureRecognizer setEnabled:enabled];
2679     [_nonBlockingDoubleTapGestureRecognizer setEnabled:!enabled];
2680     [self _resetIsDoubleTapPending];
2681 }
2682
2683 - (void)accessoryAutoFill
2684 {
2685     id <_WKInputDelegate> inputDelegate = [_webView _inputDelegate];
2686     if ([inputDelegate respondsToSelector:@selector(_webView:accessoryViewCustomButtonTappedInFormInputSession:)])
2687         [inputDelegate _webView:_webView accessoryViewCustomButtonTappedInFormInputSession:_formInputSession.get()];
2688 }
2689
2690 - (void)accessoryClear
2691 {
2692     _page->setAssistedNodeValue(String());
2693 }
2694
2695 - (void)_updateAccessory
2696 {
2697     [_formAccessoryView setNextEnabled:_assistedNodeInformation.hasNextNode];
2698     [_formAccessoryView setPreviousEnabled:_assistedNodeInformation.hasPreviousNode];
2699
2700     if (UICurrentUserInterfaceIdiomIsPad())
2701         [_formAccessoryView setClearVisible:NO];
2702     else {
2703         switch (_assistedNodeInformation.elementType) {
2704         case InputType::Date:
2705         case InputType::Month:
2706         case InputType::DateTimeLocal:
2707         case InputType::Time:
2708             [_formAccessoryView setClearVisible:YES];
2709             break;
2710         default:
2711             [_formAccessoryView setClearVisible:NO];
2712             break;
2713         }
2714     }
2715
2716     // FIXME: hide or show the AutoFill button as needed.
2717 }
2718
2719 // Keyboard interaction
2720 // UITextInput protocol implementation
2721
2722 - (void)beginSelectionChange
2723 {
2724     [self.inputDelegate selectionWillChange:self];
2725 }
2726
2727 - (void)endSelectionChange
2728 {
2729     [self.inputDelegate selectionDidChange:self];
2730 }
2731     
2732 #if __IPHONE_OS_VERSION_MIN_REQUIRED >= 100000
2733 - (void)insertTextSuggestion:(UITextSuggestion *)textSuggestion
2734 {
2735     id <_WKInputDelegate> inputDelegate = [_webView _inputDelegate];
2736     if ([inputDelegate respondsToSelector:@selector(_webView:insertTextSuggestion:inInputSession:)])
2737         [inputDelegate _webView:_webView insertTextSuggestion:textSuggestion inInputSession:_formInputSession.get()];
2738 }
2739 #endif
2740
2741 - (NSString *)textInRange:(UITextRange *)range
2742 {
2743     return nil;
2744 }
2745
2746 - (void)replaceRange:(UITextRange *)range withText:(NSString *)text
2747 {
2748 }
2749
2750 - (UITextRange *)selectedTextRange
2751 {
2752     if (_page->editorState().selectionIsNone)
2753         return nil;
2754     auto& postLayoutEditorStateData = _page->editorState().postLayoutData();
2755     FloatRect startRect = postLayoutEditorStateData.caretRectAtStart;
2756     FloatRect endRect = postLayoutEditorStateData.caretRectAtEnd;
2757     double inverseScale = [self inverseScale];
2758     // We want to keep the original caret width, while the height scales with
2759     // the content taking orientation into account.
2760     // We achieve this by scaling the width with the inverse
2761     // scale factor. This way, when it is converted from the content view
2762     // the width remains unchanged.
2763     if (startRect.width() < startRect.height())
2764         startRect.setWidth(startRect.width() * inverseScale);
2765     else
2766         startRect.setHeight(startRect.height() * inverseScale);
2767     if (endRect.width() < endRect.height()) {
2768         double delta = endRect.width();
2769         endRect.setWidth(endRect.width() * inverseScale);
2770         delta = endRect.width() - delta;
2771         endRect.move(delta, 0);
2772     } else {
2773         double delta = endRect.height();
2774         endRect.setHeight(endRect.height() * inverseScale);
2775         delta = endRect.height() - delta;
2776         endRect.move(0, delta);
2777     }
2778     return [WKTextRange textRangeWithState:_page->editorState().selectionIsNone
2779                                    isRange:_page->editorState().selectionIsRange
2780                                 isEditable:_page->editorState().isContentEditable
2781                                  startRect:startRect
2782                                    endRect:endRect
2783                             selectionRects:[self webSelectionRects]
2784                         selectedTextLength:postLayoutEditorStateData.selectedTextLength];
2785 }
2786
2787 - (CGRect)caretRectForPosition:(UITextPosition *)position
2788 {
2789     return ((WKTextPosition *)position).positionRect;
2790 }
2791
2792 - (NSArray *)selectionRectsForRange:(UITextRange *)range
2793 {
2794     return [WKTextSelectionRect textSelectionRectsWithWebRects:((WKTextRange *)range).selectionRects];
2795 }
2796
2797 - (void)setSelectedTextRange:(UITextRange *)range
2798 {
2799 }
2800
2801 - (BOOL)hasMarkedText
2802 {
2803     return [_markedText length];
2804 }
2805
2806 - (NSString *)markedText
2807 {
2808     return _markedText.get();
2809 }
2810
2811 - (UITextRange *)markedTextRange
2812 {
2813     return nil;
2814 }
2815
2816 - (NSDictionary *)markedTextStyle
2817 {
2818     return nil;
2819 }
2820
2821 - (void)setMarkedTextStyle:(NSDictionary *)styleDictionary
2822 {
2823 }
2824
2825 - (void)setMarkedText:(NSString *)markedText selectedRange:(NSRange)selectedRange
2826 {
2827     _markedText = markedText;
2828     _page->setCompositionAsync(markedText, Vector<WebCore::CompositionUnderline>(), selectedRange, EditingRange());
2829 }
2830
2831 - (void)unmarkText
2832 {
2833     _markedText = nil;
2834     _page->confirmCompositionAsync();
2835 }
2836
2837 - (UITextPosition *)beginningOfDocument
2838 {
2839     return nil;
2840 }
2841
2842 - (UITextPosition *)endOfDocument
2843 {
2844     return nil;
2845 }
2846
2847 - (UITextRange *)textRangeFromPosition:(UITextPosition *)fromPosition toPosition:(UITextPosition *)toPosition
2848 {
2849     return nil;
2850 }
2851
2852 - (UITextPosition *)positionFromPosition:(UITextPosition *)position offset:(NSInteger)offset
2853 {
2854     return nil;
2855 }
2856
2857 - (UITextPosition *)positionFromPosition:(UITextPosition *)position inDirection:(UITextLayoutDirection)direction offset:(NSInteger)offset
2858 {
2859     return nil;
2860 }
2861
2862 - (NSComparisonResult)comparePosition:(UITextPosition *)position toPosition:(UITextPosition *)other
2863 {
2864     return NSOrderedSame;
2865 }
2866
2867 - (NSInteger)offsetFromPosition:(UITextPosition *)from toPosition:(UITextPosition *)toPosition
2868 {
2869     return 0;
2870 }
2871
2872 - (id <UITextInputTokenizer>)tokenizer
2873 {
2874     return nil;
2875 }
2876
2877 - (UITextPosition *)positionWithinRange:(UITextRange *)range farthestInDirection:(UITextLayoutDirection)direction
2878 {
2879     return nil;
2880 }
2881
2882 - (UITextRange *)characterRangeByExtendingPosition:(UITextPosition *)position inDirection:(UITextLayoutDirection)direction
2883 {
2884     return nil;
2885 }
2886
2887 - (UITextWritingDirection)baseWritingDirectionForPosition:(UITextPosition *)position inDirection:(UITextStorageDirection)direction
2888 {
2889     return UITextWritingDirectionLeftToRight;
2890 }
2891
2892 - (void)setBaseWritingDirection:(UITextWritingDirection)writingDirection forRange:(UITextRange *)range
2893 {
2894 }
2895
2896 - (CGRect)firstRectForRange:(UITextRange *)range
2897 {
2898     return CGRectZero;
2899 }
2900
2901 /* Hit testing. */
2902 - (UITextPosition *)closestPositionToPoint:(CGPoint)point
2903 {
2904     return nil;
2905 }
2906
2907 - (UITextPosition *)closestPositionToPoint:(CGPoint)point withinRange:(UITextRange *)range
2908 {
2909     return nil;
2910 }
2911
2912 - (UITextRange *)characterRangeAtPoint:(CGPoint)point
2913 {
2914     return nil;
2915 }
2916
2917 - (void)deleteBackward
2918 {
2919     _page->executeEditCommand(ASCIILiteral("deleteBackward"));
2920 }
2921
2922 // Inserts the given string, replacing any selected or marked text.
2923 - (void)insertText:(NSString *)aStringValue
2924 {
2925     _page->insertTextAsync(aStringValue, EditingRange());
2926 }
2927
2928 - (BOOL)hasText
2929 {
2930     return YES;
2931 }
2932
2933 // end of UITextInput protocol implementation
2934
2935 static UITextAutocapitalizationType toUITextAutocapitalize(AutocapitalizeType webkitType)
2936 {
2937     switch (webkitType) {
2938     case AutocapitalizeTypeDefault:
2939         return UITextAutocapitalizationTypeSentences;
2940     case AutocapitalizeTypeNone:
2941         return UITextAutocapitalizationTypeNone;
2942     case AutocapitalizeTypeWords:
2943         return UITextAutocapitalizationTypeWords;
2944     case AutocapitalizeTypeSentences:
2945         return UITextAutocapitalizationTypeSentences;
2946     case AutocapitalizeTypeAllCharacters:
2947         return UITextAutocapitalizationTypeAllCharacters;
2948     }
2949
2950     return UITextAutocapitalizationTypeSentences;
2951 }
2952
2953 #if __IPHONE_OS_VERSION_MIN_REQUIRED >= 100000
2954 static NSString *contentTypeFromFieldName(WebCore::AutofillFieldName fieldName)
2955 {
2956     switch (fieldName) {
2957     case WebCore::AutofillFieldName::Name:
2958         return UITextContentTypeName;
2959     case WebCore::AutofillFieldName::HonorificPrefix:
2960         return UITextContentTypeNamePrefix;
2961     case WebCore::AutofillFieldName::GivenName:
2962         return UITextContentTypeMiddleName;
2963     case WebCore::AutofillFieldName::AdditionalName:
2964         return UITextContentTypeMiddleName;
2965     case WebCore::AutofillFieldName::FamilyName:
2966         return UITextContentTypeFamilyName;
2967     case WebCore::AutofillFieldName::HonorificSuffix:
2968         return UITextContentTypeNameSuffix;
2969     case WebCore::AutofillFieldName::Nickname:
2970         return UITextContentTypeNickname;
2971     case WebCore::AutofillFieldName::OrganizationTitle:
2972         return UITextContentTypeJobTitle;
2973     case WebCore::AutofillFieldName::Organization:
2974         return UITextContentTypeOrganizationName;
2975     case WebCore::AutofillFieldName::StreetAddress:
2976         return UITextContentTypeFullStreetAddress;
2977     case WebCore::AutofillFieldName::AddressLine1:
2978         return UITextContentTypeStreetAddressLine1;
2979     case WebCore::AutofillFieldName::AddressLine2:
2980         return UITextContentTypeStreetAddressLine2;
2981     case WebCore::AutofillFieldName::AddressLevel3:
2982         return UITextContentTypeSublocality;
2983     case WebCore::AutofillFieldName::AddressLevel2:
2984         return UITextContentTypeAddressCity;
2985     case WebCore::AutofillFieldName::AddressLevel1:
2986         return UITextContentTypeAddressState;
2987     case WebCore::AutofillFieldName::CountryName:
2988         return UITextContentTypeCountryName;
2989     case WebCore::AutofillFieldName::PostalCode:
2990         return UITextContentTypePostalCode;
2991     case WebCore::AutofillFieldName::Tel:
2992         return UITextContentTypeTelephoneNumber;
2993     case WebCore::AutofillFieldName::Email:
2994         return UITextContentTypeEmailAddress;
2995     case WebCore::AutofillFieldName::URL:
2996         return UITextContentTypeURL;
2997     case WebCore::AutofillFieldName::None:
2998     case WebCore::AutofillFieldName::Username:
2999     case WebCore::AutofillFieldName::NewPassword:
3000     case WebCore::AutofillFieldName::CurrentPassword:
3001     case WebCore::AutofillFieldName::AddressLine3:
3002     case WebCore::AutofillFieldName::AddressLevel4:
3003     case WebCore::AutofillFieldName::Country:
3004     case WebCore::AutofillFieldName::CcName:
3005     case WebCore::AutofillFieldName::CcGivenName:
3006     case WebCore::AutofillFieldName::CcAdditionalName:
3007     case WebCore::AutofillFieldName::CcFamilyName:
3008     case WebCore::AutofillFieldName::CcNumber:
3009     case WebCore::AutofillFieldName::CcExp:
3010     case WebCore::AutofillFieldName::CcExpMonth:
3011     case WebCore::AutofillFieldName::CcExpYear:
3012     case WebCore::AutofillFieldName::CcCsc:
3013     case WebCore::AutofillFieldName::CcType:
3014     case WebCore::AutofillFieldName::TransactionCurrency:
3015     case WebCore::AutofillFieldName::TransactionAmount:
3016     case WebCore::AutofillFieldName::Language:
3017     case WebCore::AutofillFieldName::Bday:
3018     case WebCore::AutofillFieldName::BdayDay:
3019     case WebCore::AutofillFieldName::BdayMonth:
3020     case WebCore::AutofillFieldName::BdayYear:
3021     case WebCore::AutofillFieldName::Sex:
3022     case WebCore::AutofillFieldName::Photo:
3023     case WebCore::AutofillFieldName::TelCountryCode:
3024     case WebCore::AutofillFieldName::TelNational:
3025     case WebCore::AutofillFieldName::TelAreaCode:
3026     case WebCore::AutofillFieldName::TelLocal:
3027     case WebCore::AutofillFieldName::TelLocalPrefix:
3028     case WebCore::AutofillFieldName::TelLocalSuffix:
3029     case WebCore::AutofillFieldName::TelExtension:
3030     case WebCore::AutofillFieldName::Impp:
3031         break;
3032     };
3033
3034     return nil;
3035 }
3036 #endif
3037
3038 // UITextInputPrivate protocol
3039 // Direct access to the (private) UITextInputTraits object.
3040 - (UITextInputTraits *)textInputTraits
3041 {
3042     if (!_traits)
3043         _traits = adoptNS([[UITextInputTraits alloc] init]);
3044
3045     [_traits setSecureTextEntry:_assistedNodeInformation.elementType == InputType::Password || [_formInputSession forceSecureTextEntry]];
3046     [_traits setShortcutConversionType:_assistedNodeInformation.elementType == InputType::Password ? UITextShortcutConversionTypeNo : UITextShortcutConversionTypeDefault];
3047
3048     if (!_assistedNodeInformation.formAction.isEmpty())
3049         [_traits setReturnKeyType:(_assistedNodeInformation.elementType == InputType::Search) ? UIReturnKeySearch : UIReturnKeyGo];
3050
3051     if (_assistedNodeInformation.elementType == InputType::Password || _assistedNodeInformation.elementType == InputType::Email || _assistedNodeInformation.elementType == InputType::URL || _assistedNodeInformation.formAction.contains("login")) {
3052         [_traits setAutocapitalizationType:UITextAutocapitalizationTypeNone];
3053         [_traits setAutocorrectionType:UITextAutocorrectionTypeNo];
3054     } else {
3055         [_traits setAutocapitalizationType:toUITextAutocapitalize(_assistedNodeInformation.autocapitalizeType)];
3056         [_traits setAutocorrectionType:_assistedNodeInformation.isAutocorrect ? UITextAutocorrectionTypeYes : UITextAutocorrectionTypeNo];
3057     }
3058
3059     switch (_assistedNodeInformation.elementType) {
3060     case InputType::Phone:
3061         [_traits setKeyboardType:UIKeyboardTypePhonePad];
3062         break;
3063     case InputType::URL:
3064         [_traits setKeyboardType:UIKeyboardTypeURL];
3065         break;
3066     case InputType::Email:
3067         [_traits setKeyboardType:UIKeyboardTypeEmailAddress];
3068         break;
3069     case InputType::Number:
3070         [_traits setKeyboardType:UIKeyboardTypeNumbersAndPunctuation];
3071         break;
3072     case InputType::NumberPad:
3073         [_traits setKeyboardType:UIKeyboardTypeNumberPad];
3074         break;
3075     default:
3076         [_traits setKeyboardType:UIKeyboardTypeDefault];
3077     }
3078
3079 #if __IPHONE_OS_VERSION_MIN_REQUIRED >= 100000
3080     [_traits setTextContentType:contentTypeFromFieldName(_assistedNodeInformation.autofillFieldName)];
3081 #endif
3082
3083     return _traits.get();
3084 }
3085
3086 - (UITextInteractionAssistant *)interactionAssistant
3087 {
3088     return _textSelectionAssistant.get();
3089 }
3090
3091 - (UIWebSelectionAssistant *)webSelectionAssistant
3092 {
3093     return _webSelectionAssistant.get();
3094 }
3095
3096 - (id<UISelectionInteractionAssistant>)selectionInteractionAssistant
3097 {
3098     if ([_webSelectionAssistant conformsToProtocol:@protocol(UISelectionInteractionAssistant)])
3099         return (id<UISelectionInteractionAssistant>)_webSelectionAssistant.get();
3100     return nil;
3101 }
3102
3103 // NSRange support.  Would like to deprecate to the extent possible, although some support
3104 // (i.e. selectionRange) has shipped as API.
3105 - (NSRange)selectionRange
3106 {
3107     return NSMakeRange(NSNotFound, 0);
3108 }
3109
3110 - (CGRect)rectForNSRange:(NSRange)range
3111 {
3112     return CGRectZero;
3113 }
3114
3115 - (NSRange)_markedTextNSRange
3116 {
3117     return NSMakeRange(NSNotFound, 0);
3118 }
3119
3120 // DOM range support.
3121 - (DOMRange *)selectedDOMRange
3122 {
3123     return nil;
3124 }
3125
3126 - (void)setSelectedDOMRange:(DOMRange *)range affinityDownstream:(BOOL)affinityDownstream
3127 {
3128 }
3129
3130 // Modify text without starting a new undo grouping.
3131 - (void)replaceRangeWithTextWithoutClosingTyping:(UITextRange *)range replacementText:(NSString *)text
3132 {
3133 }
3134
3135 // Caret rect support.  Shouldn't be necessary, but firstRectForRange doesn't offer precisely
3136 // the same functionality.
3137 - (CGRect)rectContainingCaretSelection
3138 {
3139     return CGRectZero;
3140 }
3141
3142 // Web events.
3143 - (BOOL)requiresKeyEvents
3144 {
3145     return YES;
3146 }
3147
3148 - (void)_handleKeyUIEvent:(::UIEvent *)event
3149 {
3150     // We only want to handle key event from the hardware keyboard when we are
3151     // first responder and we are not interacting with editable content.
3152     if ([self isFirstResponder] && event._hidEvent && !_page->editorState().isContentEditable) {
3153         [self handleKeyEvent:event];
3154         return;
3155     }
3156
3157     [super _handleKeyUIEvent:event];
3158 }
3159
3160 - (void)handleKeyEvent:(::UIEvent *)event
3161 {
3162     // WebCore has already seen the event, no need for custom processing.
3163     if (event == _uiEventBeingResent)
3164         return;
3165
3166     WKWebEvent *webEvent = [[[WKWebEvent alloc] initWithKeyEventType:(event._isKeyDown) ? WebEventKeyDown : WebEventKeyUp
3167                                                            timeStamp:event.timestamp
3168                                                           characters:event._modifiedInput
3169                                          charactersIgnoringModifiers:event._unmodifiedInput
3170                                                            modifiers:event._modifierFlags
3171                                                          isRepeating:(event._inputFlags & kUIKeyboardInputRepeat)
3172                                                            withFlags:event._inputFlags
3173                                                              keyCode:0
3174                                                             isTabKey:[event._modifiedInput isEqualToString:@"\t"]
3175                                                         characterSet:WebEventCharacterSetUnicode] autorelease];
3176     webEvent.uiEvent = event;
3177     
3178     [self handleKeyWebEvent:webEvent];    
3179 }
3180
3181 - (void)handleKeyWebEvent:(WebIOSEvent *)theEvent
3182 {
3183     _page->handleKeyboardEvent(NativeWebKeyboardEvent(theEvent));
3184 }
3185
3186 - (void)handleKeyWebEvent:(WebIOSEvent *)theEvent withCompletionHandler:(void (^)(WebIOSEvent *theEvent, BOOL wasHandled))completionHandler
3187 {
3188     _keyWebEventHandler = [completionHandler copy];
3189     _page->handleKeyboardEvent(NativeWebKeyboardEvent(theEvent));
3190 }
3191
3192 - (void)_didHandleKeyEvent:(WebIOSEvent *)event eventWasHandled:(BOOL)eventWasHandled
3193 {
3194     if (_keyWebEventHandler) {
3195         _keyWebEventHandler(event, eventWasHandled);
3196         [_keyWebEventHandler release];
3197         _keyWebEventHandler = nil;
3198         return;
3199     }
3200         
3201     if (event.type == WebEventKeyDown) {
3202         // FIXME: This is only for staging purposes.
3203         if ([[UIKeyboardImpl sharedInstance] respondsToSelector:@selector(didHandleWebKeyEvent:)])
3204             [[UIKeyboardImpl sharedInstance] didHandleWebKeyEvent:event];
3205         else
3206             [[UIKeyboardImpl sharedInstance] didHandleWebKeyEvent];
3207     }
3208
3209     // If we aren't interacting with editable content, we still need to call [super _handleKeyUIEvent:]
3210     // so that keyboard repeat will work correctly. If we are interacting with editable content,
3211     // we already did so in _handleKeyUIEvent.
3212     if (eventWasHandled && _page->editorState().isContentEditable)
3213         return;
3214
3215     if (![event isKindOfClass:[WKWebEvent class]])
3216         return;
3217
3218     // Resending the event may destroy this WKContentView.
3219     RetainPtr<WKContentView> protector(self);
3220
3221     // We keep here the event when resending it to the application to distinguish
3222     // the case of a new event from one that has been already sent to WebCore.
3223     ASSERT(!_uiEventBeingResent);
3224     _uiEventBeingResent = [(WKWebEvent *)event uiEvent];
3225     [super _handleKeyUIEvent:_uiEventBeingResent.get()];
3226     _uiEventBeingResent = nil;
3227 }
3228
3229 - (std::optional<FloatPoint>)_scrollOffsetForEvent:(WebIOSEvent *)event
3230 {
3231     static const unsigned kWebSpaceKey = 0x20;
3232
3233     if (_page->editorState().isContentEditable)
3234         return std::nullopt;
3235
3236     NSString *charactersIgnoringModifiers = event.charactersIgnoringModifiers;
3237     if (!charactersIgnoringModifiers.length)
3238         return std::nullopt;
3239
3240     enum ScrollingIncrement { Document, Page, Line };
3241     enum ScrollingDirection { Up, Down, Left, Right };
3242
3243     auto computeOffset = ^(ScrollingIncrement increment, ScrollingDirection direction) {
3244         bool isHorizontal = (direction == Left || direction == Right);
3245
3246         CGFloat scrollDistance = ^ CGFloat {
3247             switch (increment) {
3248             case Document:
3249                 ASSERT(!isHorizontal);
3250                 return self.bounds.size.height;
3251             case Page:
3252                 ASSERT(!isHorizontal);
3253                 return Scrollbar::pageStep(_page->unobscuredContentRect().height(), self.bounds.size.height);
3254             case Line:
3255                 return Scrollbar::pixelsPerLineStep();
3256             }
3257             ASSERT_NOT_REACHED();
3258             return 0;
3259         }();
3260
3261         if (direction == Up || direction == Left)
3262             scrollDistance = -scrollDistance;
3263         
3264         return (isHorizontal ? FloatPoint(scrollDistance, 0) : FloatPoint(0, scrollDistance));
3265     };
3266
3267     if ([charactersIgnoringModifiers isEqualToString:UIKeyInputLeftArrow])
3268         return computeOffset(Line, Left);
3269     if ([charactersIgnoringModifiers isEqualToString:UIKeyInputRightArrow])
3270         return computeOffset(Line, Right);
3271
3272     ScrollingIncrement incrementForVerticalArrowKey = Line;
3273     if (event.modifierFlags & WebEventFlagMaskAlternate)
3274         incrementForVerticalArrowKey = Page;
3275     else if (event.modifierFlags & WebEventFlagMaskCommand)
3276         incrementForVerticalArrowKey = Document;
3277     if ([charactersIgnoringModifiers isEqualToString:UIKeyInputUpArrow])
3278         return computeOffset(incrementForVerticalArrowKey, Up);
3279     if ([charactersIgnoringModifiers isEqualToString:UIKeyInputDownArrow])
3280         return computeOffset(incrementForVerticalArrowKey, Down);
3281
3282     if ([charactersIgnoringModifiers isEqualToString:UIKeyInputPageDown])
3283         return computeOffset(Page, Down);
3284     if ([charactersIgnoringModifiers isEqualToString:UIKeyInputPageUp])
3285         return computeOffset(Page, Up);
3286
3287     if ([charactersIgnoringModifiers characterAtIndex:0] == kWebSpaceKey)
3288         return computeOffset(Page, (event.modifierFlags & WebEventFlagMaskShift) ? Up : Down);
3289
3290     return std::nullopt;
3291 }
3292
3293 - (BOOL)_interpretKeyEvent:(WebIOSEvent *)event isCharEvent:(BOOL)isCharEvent
3294 {
3295     static const unsigned kWebEnterKey = 0x0003;
3296     static const unsigned kWebBackspaceKey = 0x0008;
3297     static const unsigned kWebReturnKey = 0x000D;
3298     static const unsigned kWebDeleteKey = 0x007F;
3299     static const unsigned kWebDeleteForwardKey = 0xF728;
3300     static const unsigned kWebSpaceKey = 0x20;
3301
3302     BOOL contentEditable = _page->editorState().isContentEditable;
3303
3304     if (!contentEditable && event.isTabKey)
3305         return NO;
3306
3307     if (std::optional<FloatPoint> scrollOffset = [self _scrollOffsetForEvent:event]) {
3308         [_webView _scrollByContentOffset:*scrollOffset];
3309         return YES;
3310     }
3311
3312     UIKeyboardImpl *keyboard = [UIKeyboardImpl sharedInstance];
3313     NSString *characters = event.characters;
3314     
3315     if (!characters.length)
3316         return NO;
3317
3318     switch ([characters characterAtIndex:0]) {
3319     case kWebBackspaceKey:
3320     case kWebDeleteKey:
3321         if (contentEditable) {
3322             // FIXME: remove deleteFromInput once UIKit adopts deleteFromInputWithFlags
3323             if ([keyboard respondsToSelector:@selector(deleteFromInputWithFlags:)])
3324                 [keyboard deleteFromInputWithFlags:event.keyboardFlags];
3325             else
3326                 [keyboard deleteFromInput];
3327             return YES;
3328         }
3329         break;
3330
3331     case kWebSpaceKey:
3332         if (contentEditable && isCharEvent) {
3333             if ([keyboard respondsToSelector:@selector(addInputString:withFlags:withInputManagerHint:)])
3334                 [keyboard addInputString:event.characters withFlags:event.keyboardFlags withInputManagerHint:event.inputManagerHint];
3335             else
3336                 [keyboard addInputString:event.characters withFlags:event.keyboardFlags];
3337             return YES;
3338         }
3339         break;
3340
3341     case kWebEnterKey:
3342     case kWebReturnKey:
3343         if (contentEditable && isCharEvent) {
3344             // Map \r from HW keyboard to \n to match the behavior of the soft keyboard.
3345             [keyboard addInputString:@"\n" withFlags:0];
3346             return YES;
3347         }
3348         break;
3349
3350     case kWebDeleteForwardKey:
3351         _page->executeEditCommand(ASCIILiteral("deleteForward"));
3352         return YES;
3353
3354     default:
3355         if (contentEditable && isCharEvent) {
3356             if ([keyboard respondsToSelector:@selector(addInputString:withFlags:withInputManagerHint:)])
3357                 [keyboard addInputString:event.characters withFlags:event.keyboardFlags withInputManagerHint:event.inputManagerHint];
3358             else
3359                 [keyboard addInputString:event.characters withFlags:event.keyboardFlags];
3360             return YES;
3361         }
3362         break;
3363     }
3364
3365     return NO;
3366 }
3367
3368 - (void)executeEditCommandWithCallback:(NSString *)commandName
3369 {
3370     [self beginSelectionChange];
3371     RetainPtr<WKContentView> view = self;
3372     _page->executeEditCommand(commandName, [view](WebKit::CallbackBase::Error) {
3373         [view endSelectionChange];
3374     });
3375 }
3376
3377 - (UITextInputArrowKeyHistory *)_moveUp:(BOOL)extending withHistory:(UITextInputArrowKeyHistory *)history
3378 {
3379     [self executeEditCommandWithCallback:extending ? @"moveUpAndModifySelection" : @"moveUp"];
3380     return nil;
3381 }
3382
3383 - (UITextInputArrowKeyHistory *)_moveDown:(BOOL)extending withHistory:(UITextInputArrowKeyHistory *)history
3384 {
3385     [self executeEditCommandWithCallback:extending ? @"moveDownAndModifySelection" : @"moveDown"];
3386     return nil;
3387 }
3388
3389 - (UITextInputArrowKeyHistory *)_moveLeft:(BOOL)extending withHistory:(UITextInputArrowKeyHistory *)history
3390 {
3391     [self executeEditCommandWithCallback:extending? @"moveLeftAndModifySelection" : @"moveLeft"];
3392     return nil;
3393 }
3394
3395 - (UITextInputArrowKeyHistory *)_moveRight:(BOOL)extending withHistory:(UITextInputArrowKeyHistory *)history
3396 {
3397     [self executeEditCommandWithCallback:extending ? @"moveRightAndModifySelection" : @"moveRight"];
3398     return nil;
3399 }
3400
3401 - (UITextInputArrowKeyHistory *)_moveToStartOfWord:(BOOL)extending withHistory:(UITextInputArrowKeyHistory *)history
3402 {
3403     [self executeEditCommandWithCallback:extending ? @"moveWordBackwardAndModifySelection" : @"moveWordBackward"];
3404     return nil;
3405 }
3406
3407 - (UITextInputArrowKeyHistory *)_moveToStartOfParagraph:(BOOL)extending withHistory:(UITextInputArrowKeyHistory *)history
3408 {
3409     [self executeEditCommandWithCallback:extending ? @"moveToBeginningOfParagraphAndModifySelection" : @"moveToBeginningOfParagraph"];
3410     return nil;
3411 }
3412
3413 - (UITextInputArrowKeyHistory *)_moveToStartOfLine:(BOOL)extending withHistory:(UITextInputArrowKeyHistory *)history
3414 {
3415     [self executeEditCommandWithCallback:extending ? @"moveToBeginningOfLineAndModifySelection" : @"moveToBeginningOfLine"];
3416     return nil;
3417 }
3418
3419 - (UITextInputArrowKeyHistory *)_moveToStartOfDocument:(BOOL)extending withHistory:(UITextInputArrowKeyHistory *)history
3420 {
3421     [self executeEditCommandWithCallback:extending ? @"moveToBeginningOfDocumentAndModifySelection" : @"moveToBeginningOfDocument"];
3422     return nil;
3423 }
3424
3425 - (UITextInputArrowKeyHistory *)_moveToEndOfWord:(BOOL)extending withHistory:(UITextInputArrowKeyHistory *)history
3426 {
3427     [self executeEditCommandWithCallback:extending ? @"moveWordForwardAndModifySelection" : @"moveWordForward"];
3428     return nil;
3429 }
3430
3431 - (UITextInputArrowKeyHistory *)_moveToEndOfParagraph:(BOOL)extending withHistory:(UITextInputArrowKeyHistory *)history
3432 {
3433     [self executeEditCommandWithCallback:extending ? @"moveToEndOfParagraphAndModifySelection" : @"moveToEndOfParagraph"];
3434     return nil;
3435 }
3436
3437 - (UITextInputArrowKeyHistory *)_moveToEndOfLine:(BOOL)extending withHistory:(UITextInputArrowKeyHistory *)history
3438 {
3439     [self executeEditCommandWithCallback:extending ? @"moveToEndOfLineAndModifySelection" : @"moveToEndOfLine"];
3440     return nil;
3441 }
3442
3443 - (UITextInputArrowKeyHistory *)_moveToEndOfDocument:(BOOL)extending withHistory:(UITextInputArrowKeyHistory *)history
3444 {
3445     [self executeEditCommandWithCallback:extending ? @"moveToEndOfDocumentAndModifySelection" : @"moveToEndOfDocument"];
3446     return nil;
3447 }
3448
3449 // Sets a buffer to make room for autocorrection views
3450 - (void)setBottomBufferHeight:(CGFloat)bottomBuffer
3451 {
3452 }
3453
3454 - (UIView *)automaticallySelectedOverlay
3455 {
3456     return self;
3457 }
3458
3459 - (UITextGranularity)selectionGranularity
3460 {
3461     return UITextGranularityCharacter;
3462 }
3463
3464 // Should return an array of NSDictionary objects that key/value paries for the final text, correction identifier and
3465 // alternative selection counts using the keys defined at the top of this header.
3466 - (NSArray *)metadataDictionariesForDictationResults
3467 {
3468     return nil;
3469 }
3470
3471 // Returns the dictation result boundaries from position so that text that was not dictated can be excluded from logging.
3472 // If these are not implemented, no text will be logged.
3473 - (UITextPosition *)previousUnperturbedDictationResultBoundaryFromPosition:(UITextPosition *)position
3474 {
3475     return nil;
3476 }
3477
3478 - (UITextPosition *)nextUnperturbedDictationResultBoundaryFromPosition:(UITextPosition *)position
3479 {
3480     return nil;
3481 }
3482
3483 // The can all be (and have been) trivially implemented in terms of UITextInput.  Deprecate and remove.
3484 - (void)moveBackward:(unsigned)count
3485 {
3486 }
3487
3488 - (void)moveForward:(unsigned)count
3489 {
3490 }
3491
3492 - (unichar)characterBeforeCaretSelection
3493 {
3494     return 0;
3495 }
3496
3497 - (NSString *)wordContainingCaretSelection
3498 {
3499     return nil;
3500 }
3501
3502 - (DOMRange *)wordRangeContainingCaretSelection
3503 {
3504     return nil;
3505 }
3506
3507 - (void)setMarkedText:(NSString *)text
3508 {
3509 }
3510
3511 - (BOOL)hasContent
3512 {
3513     return _page->editorState().postLayoutData().hasContent;
3514 }
3515
3516 - (void)selectAll
3517 {
3518 }
3519
3520 - (UIColor *)textColorForCaretSelection
3521 {
3522     return [UIColor blackColor];
3523 }
3524
3525 - (UIFont *)fontForCaretSelection
3526 {
3527     CGFloat zoomScale = 1.0;    // FIXME: retrieve the actual document scale factor.
3528     CGFloat scaledSize = _autocorrectionData.fontSize;
3529     if (CGFAbs(zoomScale - 1.0) > FLT_EPSILON)
3530         scaledSize *= zoomScale;
3531     return [UIFont fontWithFamilyName:_autocorrectionData.fontName traits:(UIFontTrait)_autocorrectionData.fontTraits size:scaledSize];
3532 }
3533
3534 - (BOOL)hasSelection
3535 {
3536     return NO;
3537 }
3538
3539 - (BOOL)isPosition:(UITextPosition *)position atBoundary:(UITextGranularity)granularity inDirection:(UITextDirection)direction
3540 {
3541     return NO;
3542 }
3543
3544 - (UITextPosition *)positionFromPosition:(UITextPosition *)position toBoundary:(UITextGranularity)granularity inDirection:(UITextDirection)direction
3545 {
3546     return nil;
3547 }
3548
3549 - (BOOL)isPosition:(UITextPosition *)position withinTextUnit:(UITextGranularity)granularity inDirection:(UITextDirection)direction
3550 {
3551     return NO;
3552 }
3553
3554 - (UITextRange *)rangeEnclosingPosition:(UITextPosition *)position withGranularity:(UITextGranularity)granularity inDirection:(UITextDirection)direction
3555 {
3556     return nil;
3557 }
3558
3559 - (void)takeTraitsFrom:(UITextInputTraits *)traits
3560 {
3561     [[self textInputTraits] takeTraitsFrom:traits];
3562 }
3563
3564 // FIXME: I want to change the name of these functions, but I'm leaving it for now
3565 // to make it easier to look up the corresponding functions in UIKit.
3566
3567 - (void)_startAssistingKeyboard
3568 {
3569     [self useSelectionAssistantWithMode:UIWebSelectionModeTextOnly];
3570     [self reloadInputViews];
3571 }
3572
3573 - (void)_stopAssistingKeyboard
3574 {
3575     [self useSelectionAssistantWithMode:toUIWebSelectionMode([_webView _selectionGranularity])];
3576 }
3577
3578 - (const AssistedNodeInformation&)assistedNodeInformation
3579 {
3580     return _assistedNodeInformation;
3581 }
3582
3583 - (Vector<OptionItem>&)assistedNodeSelectOptions
3584 {
3585     return _assistedNodeInformation.selectOptions;
3586 }
3587
3588 - (UIWebFormAccessory *)formAccessoryView
3589 {
3590     [self _ensureFormAccessoryView];
3591     return _formAccessoryView.get();
3592 }
3593
3594 static bool isAssistableInputType(InputType type)
3595 {
3596     switch (type) {
3597     case InputType::ContentEditable:
3598     case InputType::Text:
3599     case InputType::Password:
3600     case InputType::TextArea:
3601     case InputType::Search:
3602     case InputType::Email:
3603     case InputType::URL:
3604     case InputType::Phone:
3605     case InputType::Number:
3606     case InputType::NumberPad:
3607     case InputType::Date:
3608     case InputType::DateTime:
3609     case InputType::DateTimeLocal:
3610     case InputType::Month:
3611     case InputType::Week:
3612     case InputType::Time:
3613     case InputType::Select:
3614         return true;
3615
3616     case InputType::None:
3617         return false;
3618     }
3619
3620     ASSERT_NOT_REACHED();
3621     return false;
3622 }
3623
3624 - (void)_startAssistingNode:(const AssistedNodeInformation&)information userIsInteracting:(BOOL)userIsInteracting blurPreviousNode:(BOOL)blurPreviousNode userObject:(NSObject <NSSecureCoding> *)userObject
3625 {
3626     id <_WKInputDelegate> inputDelegate = [_webView _inputDelegate];
3627     RetainPtr<WKFocusedElementInfo> focusedElementInfo = adoptNS([[WKFocusedElementInfo alloc] initWithAssistedNodeInformation:information isUserInitiated:userIsInteracting]);
3628     BOOL shouldShowKeyboard;
3629
3630     if ([inputDelegate respondsToSelector:@selector(_webView:focusShouldStartInputSession:)])
3631         shouldShowKeyboard = [inputDelegate _webView:_webView focusShouldStartInputSession:focusedElementInfo.get()];
3632     else {
3633         // The default behavior is to allow node assistance if the user is interacting or the keyboard is already active.
3634         shouldShowKeyboard = userIsInteracting || _textSelectionAssistant;
3635     }
3636     if (!shouldShowKeyboard)
3637         return;
3638
3639     if (blurPreviousNode)
3640         [self _stopAssistingNode];
3641
3642     if (!isAssistableInputType(information.elementType))
3643         return;
3644
3645     // FIXME: We should remove this check when we manage to send StartAssistingNode from the WebProcess
3646     // only when it is truly time to show the keyboard.
3647     if (_assistedNodeInformation.elementType == information.elementType && _assistedNodeInformation.elementRect == information.elementRect)
3648         return;
3649
3650     BOOL editableChanged = [self setIsEditable:YES];
3651     _assistedNodeInformation = information;
3652     _inputPeripheral = nil;
3653     _traits = nil;
3654     if (![self isFirstResponder])
3655         [self becomeFirstResponder];
3656
3657     [self reloadInputViews];
3658     
3659     switch (information.elementType) {
3660     case InputType::Select:
3661     case InputType::DateTimeLocal:
3662     case InputType::Time:
3663     case InputType::Month:
3664     case InputType::Date:
3665         break;
3666     default:
3667         [self _startAssistingKeyboard];
3668         break;
3669     }
3670     
3671     // The custom fixed position rect behavior is affected by -isAssistingNode, so if that changes we need to recompute rects.
3672     if (editableChanged)
3673         [_webView _updateVisibleContentRects];
3674     
3675     [self _displayFormNodeInputView];
3676
3677     // _inputPeripheral has been initialized in inputView called by reloadInputViews.
3678     [_inputPeripheral beginEditing];
3679
3680     if ([inputDelegate respondsToSelector:@selector(_webView:didStartInputSession:)]) {
3681         _formInputSession = adoptNS([[WKFormInputSession alloc] initWithContentView:self focusedElementInfo:focusedElementInfo.get() userObject:userObject]);
3682         [inputDelegate _webView:_webView didStartInputSession:_formInputSession.get()];
3683     }
3684     
3685     [_webView didStartFormControlInteraction];
3686 }
3687
3688 - (void)_stopAssistingNode
3689 {
3690     [_formInputSession invalidate];
3691     _formInputSession = nil;
3692
3693     BOOL editableChanged = [self setIsEditable:NO];
3694
3695     _assistedNodeInformation.elementType = InputType::None;
3696     _inputPeripheral = nil;
3697
3698     [self _stopAssistingKeyboard];
3699     [_formAccessoryView hideAutoFillButton];
3700     [self reloadInputViews];
3701     [self _updateAccessory];
3702     // The name is misleading, but this actually clears the selection views and removes any selection.
3703     [_webSelectionAssistant resignedFirstResponder];
3704
3705     // The custom fixed position rect behavior is affected by -isAssistingNode, so if that changes we need to recompute rects.
3706     if (editableChanged)
3707         [_webView _updateVisibleContentRects];
3708
3709     [_webView didEndFormControlInteraction];
3710 }
3711
3712 - (void)_selectionChanged
3713 {
3714     _selectionNeedsUpdate = YES;
3715     // If we are changing the selection with a gesture there is no need
3716     // to wait to paint the selection.
3717     if (_usingGestureForSelection)
3718         [self _updateChangedSelection];
3719 }
3720
3721 - (void)selectWordForReplacement
3722 {
3723     _page->extendSelection(WordGranularity);
3724 }
3725
3726 - (void)_updateChangedSelection
3727 {
3728     [self _updateChangedSelection:NO];
3729 }
3730
3731 - (void)_updateChangedSelection:(BOOL)force
3732 {
3733     if (!_selectionNeedsUpdate)
3734         return;
3735
3736     WKSelectionDrawingInfo selectionDrawingInfo(_page->editorState());
3737     if (!force && selectionDrawingInfo == _lastSelectionDrawingInfo)
3738         return;
3739
3740     LOG_WITH_STREAM(Selection, stream << "_updateChangedSelection " << selectionDrawingInfo);
3741
3742     _lastSelectionDrawingInfo = selectionDrawingInfo;
3743
3744     // FIXME: We need to figure out what to do if the selection is changed by Javascript.
3745     if (_textSelectionAssistant) {
3746         _markedText = (_page->editorState().hasComposition) ? _page->editorState().markedText : String();
3747         if (!_showingTextStyleOptions)
3748             [_textSelectionAssistant selectionChanged];
3749     } else if (!_page->editorState().isContentEditable)
3750         [_webSelectionAssistant selectionChanged];
3751     _selectionNeedsUpdate = NO;
3752     if (_shouldRestoreSelection) {
3753         [_webSelectionAssistant didEndScrollingOverflow];
3754         [_textSelectionAssistant didEndScrollingOverflow];
3755         _shouldRestoreSelection = NO;
3756     }
3757 }
3758
3759 - (void)_showPlaybackTargetPicker:(BOOL)hasVideo fromRect:(const IntRect&)elementRect
3760 {
3761     if (!_airPlayRoutePicker)
3762         _airPlayRoutePicker = adoptNS([[WKAirPlayRoutePicker alloc] initWithView:self]);
3763     [_airPlayRoutePicker show:hasVideo fromRect:elementRect];
3764 }
3765
3766 - (void)_showRunOpenPanel:(API::OpenPanelParameters*)parameters resultListener:(WebOpenPanelResultListenerProxy*)listener
3767 {
3768     ASSERT(!_fileUploadPanel);
3769     if (_fileUploadPanel)
3770         return;
3771
3772     _fileUploadPanel = adoptNS([[WKFileUploadPanel alloc] initWithView:self]);
3773     [_fileUploadPanel setDelegate:self];
3774     [_fileUploadPanel presentWithParameters:parameters resultListener:listener];
3775 }
3776
3777 - (void)fileUploadPanelDidDismiss:(WKFileUploadPanel *)fileUploadPanel
3778 {
3779     ASSERT(_fileUploadPanel.get() == fileUploadPanel);
3780
3781     [_fileUploadPanel setDelegate:nil];
3782     _fileUploadPanel = nil;
3783 }
3784
3785 #pragma mark - Implementation of UIWebTouchEventsGestureRecognizerDelegate.
3786
3787 - (BOOL)shouldIgnoreWebTouch
3788 {
3789     return NO;
3790 }
3791
3792 - (BOOL)isAnyTouchOverActiveArea:(NSSet *)touches
3793 {
3794     return YES;
3795 }
3796
3797 #pragma mark - Implementation of WKActionSheetAssistantDelegate.
3798
3799 - (const WebKit::InteractionInformationAtPosition&)positionInformationForActionSheetAssistant:(WKActionSheetAssistant *)assistant
3800 {
3801     // FIXME: This should be more asynchronous, since we control the presentation of the action sheet.
3802     InteractionInformationRequest request(_positionInformation.request.point);
3803     request.includeSnapshot = true;
3804     [self ensurePositionInformationIsUpToDate:request];
3805
3806     return _positionInformation;
3807 }
3808
3809 - (void)updatePositionInformationForActionSheetAssistant:(WKActionSheetAssistant *)assistant
3810 {
3811     _hasValidPositionInformation = NO;
3812     InteractionInformationRequest request(_positionInformation.request.point);
3813     request.includeSnapshot = true;
3814
3815     [self requestAsynchronousPositionInformationUpdate:request];
3816 }
3817
3818 - (void)actionSheetAssistant:(WKActionSheetAssistant *)assistant performAction:(WebKit::SheetAction)action
3819 {
3820     _page->performActionOnElement((uint32_t)action);
3821 }
3822
3823 - (void)actionSheetAssistant:(WKActionSheetAssistant *)assistant openElementAtLocation:(CGPoint)location
3824 {
3825     [self _attemptClickAtLocation:location];
3826 }
3827
3828 - (void)actionSheetAssistant:(WKActionSheetAssistant *)assistant shareElementWithURL:(NSURL *)url rect:(CGRect)boundingRect
3829 {
3830     if (_textSelectionAssistant)
3831         [_textSelectionAssistant showShareSheetFor:userVisibleString(url) fromRect:boundingRect];
3832     else if (_webSelectionAssistant)
3833         [_webSelectionAssistant showShareSheetFor:userVisibleString(url) fromRect:boundingRect];
3834 }
3835
3836 #if HAVE(APP_LINKS)
3837 - (BOOL)actionSheetAssistant:(WKActionSheetAssistant *)assistant shouldIncludeAppLinkActionsForElement:(_WKActivatedElementInfo *)element
3838 {
3839     return _page->uiClient().shouldIncludeAppLinkActionsForElement(element);
3840 }
3841 #endif
3842
3843 - (BOOL)actionSheetAssistant:(WKActionSheetAssistant *)assistant showCustomSheetForElement:(_WKActivatedElementInfo *)element
3844 {
3845     id <WKUIDelegatePrivate> uiDelegate = static_cast<id <WKUIDelegatePrivate>>([_webView UIDelegate]);
3846     
3847     if ([uiDelegate respondsToSelector:@selector(_webView:showCustomSheetForElement:)]) {
3848         if ([uiDelegate _webView:_webView showCustomSheetForElement:element]) {
3849             // Prevent tap-and-hold and drag.
3850             [UIApp _cancelAllTouches];
3851             return YES;
3852         }
3853     }
3854
3855     return NO;
3856 }
3857
3858 - (RetainPtr<NSArray>)actionSheetAssistant:(WKActionSheetAssistant *)assistant decideActionsForElement:(_WKActivatedElementInfo *)element defaultActions:(RetainPtr<NSArray>)defaultActions
3859 {
3860     return _page->uiClient().actionsForElement(element, WTFMove(defaultActions));
3861 }
3862
3863 - (void)actionSheetAssistant:(WKActionSheetAssistant *)assistant willStartInteractionWithElement:(_WKActivatedElementInfo *)element
3864 {
3865     _page->startInteractionWithElementAtPosition(_positionInformation.request.point);
3866 }
3867
3868 - (void)actionSheetAssistantDidStopInteraction:(WKActionSheetAssistant *)assistant
3869 {
3870     _page->stopInteraction();
3871 }
3872
3873 - (NSDictionary *)dataDetectionContextForActionSheetAssistant:(WKActionSheetAssistant *)assistant
3874 {
3875     NSDictionary *context = nil;
3876     id <WKUIDelegatePrivate> uiDelegate = static_cast<id <WKUIDelegatePrivate>>([_webView UIDelegate]);
3877     if ([uiDelegate respondsToSelector:@selector(_dataDetectionContextForWebView:)])
3878         context = [uiDelegate _dataDetectionContextForWebView:_webView];
3879     return context;
3880 }
3881
3882 - (NSString *)selectedTextForActionSheetAssistant:(WKActionSheetAssistant *)assistant
3883 {
3884     return [self selectedText];
3885 }
3886
3887 @end
3888
3889 @implementation WKContentView (WKTesting)
3890
3891 - (void)selectFormAccessoryPickerRow:(NSInteger)rowIndex
3892 {
3893     if ([_inputPeripheral isKindOfClass:[WKFormSelectControl self]])
3894         [(WKFormSelectControl *)_inputPeripheral selectRow:rowIndex inComponent:0 extendingSelection:NO];
3895 }
3896
3897 - (NSDictionary *)_contentsOfUserInterfaceItem:(NSString *)userInterfaceItem
3898 {
3899     if ([userInterfaceItem isEqualToString:@"actionSheet"])
3900         return @{ userInterfaceItem: [_actionSheetAssistant currentAvailableActionTitles] };
3901
3902 #if HAVE(LINK_PREVIEW)
3903     if ([userInterfaceItem isEqualToString:@"linkPreviewPopoverContents"]) {
3904         NSString *url = [_previewItemController previewData][UIPreviewDataLink];
3905         return @{ userInterfaceItem: @{ @"pageURL": url } };
3906     }
3907 #endif
3908     
3909     return nil;
3910 }
3911
3912 @end
3913
3914 #if HAVE(LINK_PREVIEW)
3915
3916 @implementation WKContentView (WKInteractionPreview)
3917
3918 - (void)_registerPreview
3919 {
3920     if (!_webView.allowsLinkPreview)
3921         return;
3922
3923     _previewItemController = adoptNS([[UIPreviewItemController alloc] initWithView:self]);
3924     [_previewItemController setDelegate:self];
3925     _previewGestureRecognizer = _previewItemController.get().presentationGestureRecognizer;
3926     if ([_previewItemController respondsToSelector:@selector(presentationSecondaryGestureRecognizer)])
3927         _previewSecondaryGestureRecognizer = _previewItemController.get().presentationSecondaryGestureRecognizer;
3928 }
3929
3930 - (void)_unregisterPreview
3931 {
3932     [_previewItemController setDelegate:nil];
3933<