/* * Copyright (C) 2012-2019 Apple Inc. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF * THE POSSIBILITY OF SUCH DAMAGE. */ #import "config.h" #import "WKContentViewInteraction.h" #if PLATFORM(IOS_FAMILY) #import "APIUIClient.h" #import "CompletionHandlerCallChecker.h" #import "DocumentEditingContext.h" #import "EditableImageController.h" #import "InputViewUpdateDeferrer.h" #import "InsertTextOptions.h" #import "Logging.h" #import "NativeWebKeyboardEvent.h" #import "NativeWebTouchEvent.h" #import "RemoteLayerTreeDrawingAreaProxy.h" #import "RemoteLayerTreeViews.h" #import "SmartMagnificationController.h" #import "TextInputSPI.h" #import "UIKitSPI.h" #import "VersionChecks.h" #import "WKActionSheetAssistant.h" #import "WKContextMenuElementInfoInternal.h" #import "WKDatePickerViewController.h" #import "WKDrawingCoordinator.h" #import "WKError.h" #import "WKFocusedFormControlView.h" #import "WKFormInputControl.h" #import "WKFormSelectControl.h" #import "WKHighlightLongPressGestureRecognizer.h" #import "WKImagePreviewViewController.h" #import "WKInspectorNodeSearchGestureRecognizer.h" #import "WKNSURLExtras.h" #import "WKPreviewActionItemIdentifiers.h" #import "WKPreviewActionItemInternal.h" #import "WKPreviewElementInfoInternal.h" #import "WKQuickboardListViewController.h" #import "WKSelectMenuListViewController.h" #import "WKSyntheticFlagsChangedWebEvent.h" #import "WKTextInputListViewController.h" #import "WKTimePickerViewController.h" #import "WKUIDelegatePrivate.h" #import "WKWebViewConfiguration.h" #import "WKWebViewConfigurationPrivate.h" #import "WKWebViewInternal.h" #import "WKWebViewPrivate.h" #import "WebAutocorrectionContext.h" #import "WebAutocorrectionData.h" #import "WebDataListSuggestionsDropdownIOS.h" #import "WebEvent.h" #import "WebIOSEventFactory.h" #import "WebPageMessages.h" #import "WebProcessProxy.h" #import "_WKActivatedElementInfoInternal.h" #import "_WKElementAction.h" #import "_WKElementActionInternal.h" #import "_WKFocusedElementInfo.h" #import "_WKInputDelegate.h" #import "_WKTextInputContextInternal.h" #import #import #import #import #import #import #import #import #import #import #import #import #import #import #import #import #import #import #import #import #import #import #import #import #import #import // FIXME: WK2 should not include WebKit headers! #import #import #import #import #import #import #import #import #import #import #import #import #import #if ENABLE(DRAG_SUPPORT) #import #import #import #import #endif #if PLATFORM(MACCATALYST) #import "NativeWebMouseEvent.h" #import #import #endif #if ENABLE(INPUT_TYPE_COLOR) #import "WKFormColorControl.h" #endif #if !USE(UIKIT_KEYBOARD_ADDITIONS) #import "WKWebEvent.h" #endif #if USE(APPLE_INTERNAL_SDK) && __has_include() #import #endif #if ENABLE(POINTER_EVENTS) #import "RemoteScrollingCoordinatorProxy.h" #import #endif #if !PLATFORM(MACCATALYST) #import "ManagedConfigurationSPI.h" #import SOFT_LINK_PRIVATE_FRAMEWORK(ManagedConfiguration); SOFT_LINK_CLASS(ManagedConfiguration, MCProfileConnection); SOFT_LINK_CONSTANT(ManagedConfiguration, MCFeatureDefinitionLookupAllowed, NSString *) #endif #if HAVE(LINK_PREVIEW) && USE(UICONTEXTMENU) static NSString * const webkitShowLinkPreviewsPreferenceKey = @"WebKitShowLinkPreviews"; #endif #if PLATFORM(WATCHOS) @interface WKContentView (WatchSupport) @end #endif // For staging with UIKit, will be removed ASAP. @interface WKUIWKTextInteractionAssistant : UIWKTextInteractionAssistant @end @implementation WKUIWKTextInteractionAssistant - (void)scrollSelectionToVisible { } @end #define UIWKWordNearTapIsMisspelled ((UIWKSelectionFlags)8) namespace WebKit { using namespace WebCore; using namespace WebKit; WKSelectionDrawingInfo::WKSelectionDrawingInfo() : type(SelectionType::None) { } WKSelectionDrawingInfo::WKSelectionDrawingInfo(const EditorState& editorState) { if (editorState.selectionIsNone) { type = SelectionType::None; return; } if (editorState.isInPlugin) { type = SelectionType::Plugin; return; } type = SelectionType::Range; auto& postLayoutData = editorState.postLayoutData(); caretRect = postLayoutData.caretRectAtEnd; selectionRects = postLayoutData.selectionRects; selectionClipRect = postLayoutData.focusedElementRect; } inline bool operator==(const WKSelectionDrawingInfo& a, const WKSelectionDrawingInfo& b) { if (a.type != b.type) return false; if (a.type == WKSelectionDrawingInfo::SelectionType::Range) { if (a.caretRect != b.caretRect) return false; if (a.selectionRects.size() != b.selectionRects.size()) return false; for (unsigned i = 0; i < a.selectionRects.size(); ++i) { if (a.selectionRects[i].rect() != b.selectionRects[i].rect()) return false; } } if (a.type != WKSelectionDrawingInfo::SelectionType::None && a.selectionClipRect != b.selectionClipRect) return false; return true; } inline bool operator!=(const WKSelectionDrawingInfo& a, const WKSelectionDrawingInfo& b) { return !(a == b); } static TextStream& operator<<(TextStream& stream, WKSelectionDrawingInfo::SelectionType type) { switch (type) { case WKSelectionDrawingInfo::SelectionType::None: stream << "none"; break; case WKSelectionDrawingInfo::SelectionType::Plugin: stream << "plugin"; break; case WKSelectionDrawingInfo::SelectionType::Range: stream << "range"; break; } return stream; } TextStream& operator<<(TextStream& stream, const WKSelectionDrawingInfo& info) { TextStream::GroupScope group(stream); stream.dumpProperty("type", info.type); stream.dumpProperty("caret rect", info.caretRect); stream.dumpProperty("selection rects", info.selectionRects); stream.dumpProperty("selection clip rect", info.selectionClipRect); return stream; } } // namespace WebKit constexpr float highlightDelay = 0.12; constexpr float tapAndHoldDelay = 0.75; constexpr CGFloat minimumTapHighlightRadius = 2.0; constexpr double fasterTapSignificantZoomThreshold = 0.8; @interface WKTextRange : UITextRange { CGRect _startRect; CGRect _endRect; BOOL _isNone; BOOL _isRange; BOOL _isEditable; NSArray *_selectionRects; NSUInteger _selectedTextLength; } @property (nonatomic) CGRect startRect; @property (nonatomic) CGRect endRect; @property (nonatomic) BOOL isNone; @property (nonatomic) BOOL isRange; @property (nonatomic) BOOL isEditable; @property (nonatomic) NSUInteger selectedTextLength; @property (copy, nonatomic) NSArray *selectionRects; + (WKTextRange *)textRangeWithState:(BOOL)isNone isRange:(BOOL)isRange isEditable:(BOOL)isEditable startRect:(CGRect)startRect endRect:(CGRect)endRect selectionRects:(NSArray *)selectionRects selectedTextLength:(NSUInteger)selectedTextLength; @end @interface WKTextPosition : UITextPosition { CGRect _positionRect; } @property (nonatomic) CGRect positionRect; + (WKTextPosition *)textPositionWithRect:(CGRect)positionRect; @end @interface WKTextSelectionRect : UITextSelectionRect @property (nonatomic, retain) WebSelectionRect *webRect; + (NSArray *)textSelectionRectsWithWebRects:(NSArray *)webRects; @end @interface WKAutocorrectionRects : UIWKAutocorrectionRects + (WKAutocorrectionRects *)autocorrectionRectsWithFirstCGRect:(CGRect)firstRect lastCGRect:(CGRect)lastRect; @end @interface WKAutocorrectionContext : UIWKAutocorrectionContext + (WKAutocorrectionContext *)emptyAutocorrectionContext; + (WKAutocorrectionContext *)autocorrectionContextWithWebContext:(const WebKit::WebAutocorrectionContext&)context; @end @interface UITextInteractionAssistant (UITextInteractionAssistant_Internal) // FIXME: this needs to be moved from the internal header to the private. - (id)initWithView:(UIResponder *)view; - (void)selectWord; @end @interface UIView (UIViewInternalHack) + (BOOL)_addCompletion:(void(^)(BOOL))completion; @end @protocol UISelectionInteractionAssistant; @interface WKFocusedElementInfo : NSObject <_WKFocusedElementInfo> - (instancetype)initWithFocusedElementInformation:(const WebKit::FocusedElementInformation&)information isUserInitiated:(BOOL)isUserInitiated userObject:(NSObject *)userObject; @end @implementation WKFormInputSession { WeakObjCPtr _contentView; RetainPtr _focusedElementInfo; RetainPtr _customInputView; RetainPtr _customInputAccessoryView; RetainPtr> _suggestions; BOOL _accessoryViewShouldNotShow; BOOL _forceSecureTextEntry; BOOL _requiresStrongPasswordAssistance; } - (instancetype)initWithContentView:(WKContentView *)view focusedElementInfo:(WKFocusedElementInfo *)elementInfo requiresStrongPasswordAssistance:(BOOL)requiresStrongPasswordAssistance { if (!(self = [super init])) return nil; _contentView = view; _focusedElementInfo = elementInfo; _requiresStrongPasswordAssistance = requiresStrongPasswordAssistance; return self; } - (id <_WKFocusedElementInfo>)focusedElementInfo { return _focusedElementInfo.get(); } - (NSObject *)userObject { return [_focusedElementInfo userObject]; } - (BOOL)isValid { return !!_contentView; } - (NSString *)accessoryViewCustomButtonTitle { return [[[_contentView formAccessoryView] _autofill] title]; } - (void)setAccessoryViewCustomButtonTitle:(NSString *)title { if (title.length) [[_contentView formAccessoryView] showAutoFillButtonWithTitle:title]; else [[_contentView formAccessoryView] hideAutoFillButton]; if (currentUserInterfaceIdiomIsPad()) [_contentView reloadInputViews]; } - (BOOL)accessoryViewShouldNotShow { return _accessoryViewShouldNotShow; } - (void)setAccessoryViewShouldNotShow:(BOOL)accessoryViewShouldNotShow { if (_accessoryViewShouldNotShow == accessoryViewShouldNotShow) return; _accessoryViewShouldNotShow = accessoryViewShouldNotShow; [_contentView reloadInputViews]; } - (BOOL)forceSecureTextEntry { return _forceSecureTextEntry; } - (void)setForceSecureTextEntry:(BOOL)forceSecureTextEntry { if (_forceSecureTextEntry == forceSecureTextEntry) return; _forceSecureTextEntry = forceSecureTextEntry; [_contentView reloadInputViews]; } - (UIView *)customInputView { return _customInputView.get(); } - (void)setCustomInputView:(UIView *)customInputView { if (customInputView == _customInputView) return; _customInputView = customInputView; [_contentView reloadInputViews]; } - (UIView *)customInputAccessoryView { return _customInputAccessoryView.get(); } - (void)setCustomInputAccessoryView:(UIView *)customInputAccessoryView { if (_customInputAccessoryView == customInputAccessoryView) return; _customInputAccessoryView = customInputAccessoryView; [_contentView reloadInputViews]; } - (void)endEditing { if ([_customInputView conformsToProtocol:@protocol(WKFormControl)]) [(id)_customInputView.get() controlEndEditing]; } - (NSArray *)suggestions { return _suggestions.get(); } - (void)setSuggestions:(NSArray *)suggestions { if (suggestions == _suggestions || [suggestions isEqualToArray:_suggestions.get()]) return; _suggestions = adoptNS([suggestions copy]); [_contentView updateTextSuggestionsForInputDelegate]; } - (BOOL)requiresStrongPasswordAssistance { return _requiresStrongPasswordAssistance; } - (void)invalidate { id suggestionDelegate = (id )[_contentView inputDelegate]; [suggestionDelegate setSuggestions:nil]; _contentView = nil; } - (void)reloadFocusedElementContextView { [_contentView reloadContextViewForPresentedListViewController]; } @end @implementation WKFocusedElementInfo { WKInputType _type; RetainPtr _value; BOOL _isUserInitiated; RetainPtr> _userObject; RetainPtr _placeholder; RetainPtr _label; } - (instancetype)initWithFocusedElementInformation:(const WebKit::FocusedElementInformation&)information isUserInitiated:(BOOL)isUserInitiated userObject:(NSObject *)userObject { if (!(self = [super init])) return nil; switch (information.elementType) { case WebKit::InputType::ContentEditable: _type = WKInputTypeContentEditable; break; case WebKit::InputType::Text: _type = WKInputTypeText; break; case WebKit::InputType::Password: _type = WKInputTypePassword; break; case WebKit::InputType::TextArea: _type = WKInputTypeTextArea; break; case WebKit::InputType::Search: _type = WKInputTypeSearch; break; case WebKit::InputType::Email: _type = WKInputTypeEmail; break; case WebKit::InputType::URL: _type = WKInputTypeURL; break; case WebKit::InputType::Phone: _type = WKInputTypePhone; break; case WebKit::InputType::Number: _type = WKInputTypeNumber; break; case WebKit::InputType::NumberPad: _type = WKInputTypeNumberPad; break; case WebKit::InputType::Date: _type = WKInputTypeDate; break; case WebKit::InputType::DateTime: _type = WKInputTypeDateTime; break; case WebKit::InputType::DateTimeLocal: _type = WKInputTypeDateTimeLocal; break; case WebKit::InputType::Month: _type = WKInputTypeMonth; break; case WebKit::InputType::Week: _type = WKInputTypeWeek; break; case WebKit::InputType::Time: _type = WKInputTypeTime; break; case WebKit::InputType::Select: _type = WKInputTypeSelect; break; case WebKit::InputType::Drawing: _type = WKInputTypeDrawing; break; #if ENABLE(INPUT_TYPE_COLOR) case WebKit::InputType::Color: _type = WKInputTypeColor; break; #endif case WebKit::InputType::None: _type = WKInputTypeNone; break; } _value = information.value; _isUserInitiated = isUserInitiated; _userObject = userObject; _placeholder = information.placeholder; _label = information.label; return self; } - (WKInputType)type { return _type; } - (NSString *)value { return _value.get(); } - (BOOL)isUserInitiated { return _isUserInitiated; } - (NSObject *)userObject { return _userObject.get(); } - (NSString *)label { return _label.get(); } - (NSString *)placeholder { return _placeholder.get(); } @end #if ENABLE(DRAG_SUPPORT) @interface WKDragSessionContext : NSObject - (void)addTemporaryDirectory:(NSString *)temporaryDirectory; - (void)cleanUpTemporaryDirectories; @end @implementation WKDragSessionContext { RetainPtr _temporaryDirectories; } - (void)addTemporaryDirectory:(NSString *)temporaryDirectory { if (!_temporaryDirectories) _temporaryDirectories = adoptNS([NSMutableArray new]); [_temporaryDirectories addObject:temporaryDirectory]; } - (void)cleanUpTemporaryDirectories { for (NSString *directory in _temporaryDirectories.get()) { NSError *error = nil; [[NSFileManager defaultManager] removeItemAtPath:directory error:&error]; RELEASE_LOG(DragAndDrop, "Removed temporary download directory: %@ with error: %@", directory, error); } _temporaryDirectories = nil; } @end static WKDragSessionContext *existingLocalDragSessionContext(id session) { return [session.localContext isKindOfClass:[WKDragSessionContext class]] ? (WKDragSessionContext *)session.localContext : nil; } static WKDragSessionContext *ensureLocalDragSessionContext(id session) { if (WKDragSessionContext *existingContext = existingLocalDragSessionContext(session)) return existingContext; if (session.localContext) { RELEASE_LOG(DragAndDrop, "Overriding existing local context: %@ on session: %@", session.localContext, session); ASSERT_NOT_REACHED(); } session.localContext = [[[WKDragSessionContext alloc] init] autorelease]; return (WKDragSessionContext *)session.localContext; } #endif // ENABLE(DRAG_SUPPORT) @interface WKContentView (WKInteractionPrivate) - (void)accessibilitySpeakSelectionSetContent:(NSString *)string; - (NSArray *)webSelectionRectsForSelectionRects:(const Vector&)selectionRects; - (void)_accessibilityDidGetSelectionRects:(NSArray *)selectionRects withGranularity:(UITextGranularity)granularity atOffset:(NSInteger)offset; @end @implementation WKContentView (WKInteraction) static inline bool hasFocusedElement(WebKit::FocusedElementInformation focusedElementInformation) { return (focusedElementInformation.elementType != WebKit::InputType::None); } #if ENABLE(POINTER_EVENTS) - (BOOL)preventsPanningInXAxis { return _preventsPanningInXAxis; } - (BOOL)preventsPanningInYAxis { return _preventsPanningInYAxis; } #endif - (WKFormInputSession *)_formInputSession { return _formInputSession.get(); } - (void)_createAndConfigureDoubleTapGestureRecognizer { if (_doubleTapGestureRecognizer) { [self removeGestureRecognizer:_doubleTapGestureRecognizer.get()]; [_doubleTapGestureRecognizer setDelegate:nil]; [_doubleTapGestureRecognizer setGestureFailedTarget:nil action:nil]; } _doubleTapGestureRecognizer = adoptNS([[WKSyntheticTapGestureRecognizer alloc] initWithTarget:self action:@selector(_doubleTapRecognized:)]); [_doubleTapGestureRecognizer setGestureFailedTarget:self action:@selector(_doubleTapDidFail:)]; [_doubleTapGestureRecognizer setNumberOfTapsRequired:2]; [_doubleTapGestureRecognizer setDelegate:self]; [self addGestureRecognizer:_doubleTapGestureRecognizer.get()]; [_singleTapGestureRecognizer requireGestureRecognizerToFail:_doubleTapGestureRecognizer.get()]; } - (void)_createAndConfigureLongPressGestureRecognizer { _longPressGestureRecognizer = adoptNS([[UILongPressGestureRecognizer alloc] initWithTarget:self action:@selector(_longPressRecognized:)]); [_longPressGestureRecognizer setDelay:tapAndHoldDelay]; [_longPressGestureRecognizer setDelegate:self]; [_longPressGestureRecognizer _setRequiresQuietImpulse:YES]; [self addGestureRecognizer:_longPressGestureRecognizer.get()]; } - (void)setupInteraction { // If the page is not valid yet then delay interaction setup until the process is launched/relaunched. if (!_page->hasRunningProcess()) return; if (_hasSetUpInteractions) return; if (!_interactionViewsContainerView) { _interactionViewsContainerView = adoptNS([[UIView alloc] init]); [_interactionViewsContainerView layer].name = @"InteractionViewsContainer"; [_interactionViewsContainerView setOpaque:NO]; [_interactionViewsContainerView layer].anchorPoint = CGPointZero; [self.superview addSubview:_interactionViewsContainerView.get()]; } _keyboardScrollingAnimator = adoptNS([[WKKeyboardScrollViewAnimator alloc] initWithScrollView:_webView.scrollView]); [_keyboardScrollingAnimator setDelegate:self]; [self.layer addObserver:self forKeyPath:@"transform" options:NSKeyValueObservingOptionInitial context:nil]; #if ENABLE(POINTER_EVENTS) _touchActionLeftSwipeGestureRecognizer = adoptNS([[UISwipeGestureRecognizer alloc] initWithTarget:nil action:nil]); [_touchActionLeftSwipeGestureRecognizer setDirection:UISwipeGestureRecognizerDirectionLeft]; [_touchActionLeftSwipeGestureRecognizer setDelegate:self]; [self addGestureRecognizer:_touchActionLeftSwipeGestureRecognizer.get()]; _touchActionRightSwipeGestureRecognizer = adoptNS([[UISwipeGestureRecognizer alloc] initWithTarget:nil action:nil]); [_touchActionRightSwipeGestureRecognizer setDirection:UISwipeGestureRecognizerDirectionRight]; [_touchActionRightSwipeGestureRecognizer setDelegate:self]; [self addGestureRecognizer:_touchActionRightSwipeGestureRecognizer.get()]; _touchActionUpSwipeGestureRecognizer = adoptNS([[UISwipeGestureRecognizer alloc] initWithTarget:nil action:nil]); [_touchActionUpSwipeGestureRecognizer setDirection:UISwipeGestureRecognizerDirectionUp]; [_touchActionUpSwipeGestureRecognizer setDelegate:self]; [self addGestureRecognizer:_touchActionUpSwipeGestureRecognizer.get()]; _touchActionDownSwipeGestureRecognizer = adoptNS([[UISwipeGestureRecognizer alloc] initWithTarget:nil action:nil]); [_touchActionDownSwipeGestureRecognizer setDirection:UISwipeGestureRecognizerDirectionDown]; [_touchActionDownSwipeGestureRecognizer setDelegate:self]; [self addGestureRecognizer:_touchActionDownSwipeGestureRecognizer.get()]; #endif _touchEventGestureRecognizer = adoptNS([[UIWebTouchEventsGestureRecognizer alloc] initWithTarget:self action:@selector(_webTouchEventsRecognized:) touchDelegate:self]); [_touchEventGestureRecognizer setDelegate:self]; [self addGestureRecognizer:_touchEventGestureRecognizer.get()]; #if PLATFORM(MACCATALYST) _hoverGestureRecognizer = adoptNS([[UIHoverGestureRecognizer alloc] initWithTarget:self action:@selector(_hoverGestureRecognizerChanged:)]); [_hoverGestureRecognizer setDelegate:self]; [self addGestureRecognizer:_hoverGestureRecognizer.get()]; _lookupGestureRecognizer = adoptNS([[_UILookupGestureRecognizer alloc] initWithTarget:self action:@selector(_lookupGestureRecognized:)]); [_lookupGestureRecognizer setDelegate:self]; [self addGestureRecognizer:_lookupGestureRecognizer.get()]; #endif _singleTapGestureRecognizer = adoptNS([[WKSyntheticTapGestureRecognizer alloc] initWithTarget:self action:@selector(_singleTapRecognized:)]); [_singleTapGestureRecognizer setDelegate:self]; [_singleTapGestureRecognizer setGestureIdentifiedTarget:self action:@selector(_singleTapIdentified:)]; [_singleTapGestureRecognizer setResetTarget:self action:@selector(_singleTapDidReset:)]; #if ENABLE(POINTER_EVENTS) [_singleTapGestureRecognizer setSupportingWebTouchEventsGestureRecognizer:_touchEventGestureRecognizer.get()]; #endif [self addGestureRecognizer:_singleTapGestureRecognizer.get()]; _nonBlockingDoubleTapGestureRecognizer = adoptNS([[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(_nonBlockingDoubleTapRecognized:)]); [_nonBlockingDoubleTapGestureRecognizer setNumberOfTapsRequired:2]; [_nonBlockingDoubleTapGestureRecognizer setDelegate:self]; [_nonBlockingDoubleTapGestureRecognizer setEnabled:NO]; [self addGestureRecognizer:_nonBlockingDoubleTapGestureRecognizer.get()]; _doubleTapGestureRecognizerForDoubleClick = adoptNS([[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(_doubleTapRecognizedForDoubleClick:)]); [_doubleTapGestureRecognizerForDoubleClick setNumberOfTapsRequired:2]; [_doubleTapGestureRecognizerForDoubleClick setDelegate:self]; [self addGestureRecognizer:_doubleTapGestureRecognizerForDoubleClick.get()]; [self _createAndConfigureDoubleTapGestureRecognizer]; _twoFingerDoubleTapGestureRecognizer = adoptNS([[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(_twoFingerDoubleTapRecognized:)]); [_twoFingerDoubleTapGestureRecognizer setNumberOfTapsRequired:2]; [_twoFingerDoubleTapGestureRecognizer setNumberOfTouchesRequired:2]; [_twoFingerDoubleTapGestureRecognizer setDelegate:self]; [self addGestureRecognizer:_twoFingerDoubleTapGestureRecognizer.get()]; _highlightLongPressGestureRecognizer = adoptNS([[WKHighlightLongPressGestureRecognizer alloc] initWithTarget:self action:@selector(_highlightLongPressRecognized:)]); [_highlightLongPressGestureRecognizer setDelay:highlightDelay]; [_highlightLongPressGestureRecognizer setDelegate:self]; [self addGestureRecognizer:_highlightLongPressGestureRecognizer.get()]; [self _createAndConfigureLongPressGestureRecognizer]; #if HAVE(LINK_PREVIEW) [self _updateLongPressAndHighlightLongPressGestures]; #endif #if ENABLE(DATA_INTERACTION) [self setupDragAndDropInteractions]; #endif _twoFingerSingleTapGestureRecognizer = adoptNS([[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(_twoFingerSingleTapGestureRecognized:)]); [_twoFingerSingleTapGestureRecognizer setAllowableMovement:60]; [_twoFingerSingleTapGestureRecognizer _setAllowableSeparation:150]; [_twoFingerSingleTapGestureRecognizer setNumberOfTapsRequired:1]; [_twoFingerSingleTapGestureRecognizer setNumberOfTouchesRequired:2]; [_twoFingerSingleTapGestureRecognizer setDelaysTouchesEnded:NO]; [_twoFingerSingleTapGestureRecognizer setDelegate:self]; [_twoFingerSingleTapGestureRecognizer setEnabled:!_webView._editable]; [self addGestureRecognizer:_twoFingerSingleTapGestureRecognizer.get()]; _stylusSingleTapGestureRecognizer = adoptNS([[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(_stylusSingleTapRecognized:)]); [_stylusSingleTapGestureRecognizer setNumberOfTapsRequired:1]; [_stylusSingleTapGestureRecognizer setDelegate:self]; [_stylusSingleTapGestureRecognizer setAllowedTouchTypes:@[ @(UITouchTypePencil) ]]; [self addGestureRecognizer:_stylusSingleTapGestureRecognizer.get()]; #if ENABLE(POINTER_EVENTS) _touchActionGestureRecognizer = adoptNS([[WKTouchActionGestureRecognizer alloc] initWithTouchActionDelegate:self]); [self addGestureRecognizer:_touchActionGestureRecognizer.get()]; #endif #if HAVE(LINK_PREVIEW) [self _registerPreview]; #endif NSNotificationCenter *center = [NSNotificationCenter defaultCenter]; [center addObserver:self selector:@selector(_willHideMenu:) name:UIMenuControllerWillHideMenuNotification object:nil]; [center addObserver:self selector:@selector(_didHideMenu:) name:UIMenuControllerDidHideMenuNotification object:nil]; [center addObserver:self selector:@selector(_keyboardDidRequestDismissal:) name:UIKeyboardPrivateDidRequestDismissalNotification object:nil]; _showingTextStyleOptions = NO; // FIXME: This should be called when we get notified that loading has completed. [self setUpTextSelectionAssistant]; _actionSheetAssistant = adoptNS([[WKActionSheetAssistant alloc] initWithView:self]); [_actionSheetAssistant setDelegate:self]; _smartMagnificationController = std::make_unique(self); _isExpectingFastSingleTapCommit = NO; _potentialTapInProgress = NO; _isDoubleTapPending = NO; _showDebugTapHighlightsForFastClicking = [[NSUserDefaults standardUserDefaults] boolForKey:@"WebKitShowFastClickDebugTapHighlights"]; _needsDeferredEndScrollingSelectionUpdate = NO; _isChangingFocus = NO; _isBlurringFocusedElement = NO; #if ENABLE(DATALIST_ELEMENT) _dataListTextSuggestionsInputView = nil; _dataListTextSuggestions = nil; #endif #if ENABLE(PLATFORM_DRIVEN_TEXT_CHECKING) _textCheckingController = std::make_unique(*_page); #endif _hasSetUpInteractions = YES; } - (void)cleanupInteraction { if (!_hasSetUpInteractions) return; _textSelectionAssistant = nil; [_actionSheetAssistant cleanupSheet]; _actionSheetAssistant = nil; _smartMagnificationController = nil; _didAccessoryTabInitiateFocus = NO; _isChangingFocusUsingAccessoryTab = NO; _isExpectingFastSingleTapCommit = NO; _needsDeferredEndScrollingSelectionUpdate = NO; [_formInputSession invalidate]; _formInputSession = nil; [_highlightView removeFromSuperview]; _outstandingPositionInformationRequest = WTF::nullopt; _focusRequiresStrongPasswordAssistance = NO; _waitingForEditDragSnapshot = NO; #if USE(UIKIT_KEYBOARD_ADDITIONS) _candidateViewNeedsUpdate = NO; _seenHardwareKeyDownInNonEditableElement = NO; #endif if (_interactionViewsContainerView) { [self.layer removeObserver:self forKeyPath:@"transform"]; [_interactionViewsContainerView removeFromSuperview]; _interactionViewsContainerView = nil; } [_touchEventGestureRecognizer setDelegate:nil]; [self removeGestureRecognizer:_touchEventGestureRecognizer.get()]; #if PLATFORM(MACCATALYST) [_hoverGestureRecognizer setDelegate:nil]; [self removeGestureRecognizer:_hoverGestureRecognizer.get()]; [_lookupGestureRecognizer setDelegate:nil]; [self removeGestureRecognizer:_lookupGestureRecognizer.get()]; #endif [_singleTapGestureRecognizer setDelegate:nil]; [_singleTapGestureRecognizer setGestureIdentifiedTarget:nil action:nil]; [_singleTapGestureRecognizer setResetTarget:nil action:nil]; #if ENABLE(POINTER_EVENTS) [_singleTapGestureRecognizer setSupportingWebTouchEventsGestureRecognizer:nil]; #endif [self removeGestureRecognizer:_singleTapGestureRecognizer.get()]; [_highlightLongPressGestureRecognizer setDelegate:nil]; [self removeGestureRecognizer:_highlightLongPressGestureRecognizer.get()]; [_longPressGestureRecognizer setDelegate:nil]; [self removeGestureRecognizer:_longPressGestureRecognizer.get()]; [_doubleTapGestureRecognizer setDelegate:nil]; [self removeGestureRecognizer:_doubleTapGestureRecognizer.get()]; [_nonBlockingDoubleTapGestureRecognizer setDelegate:nil]; [self removeGestureRecognizer:_nonBlockingDoubleTapGestureRecognizer.get()]; [_doubleTapGestureRecognizerForDoubleClick setDelegate:nil]; [self removeGestureRecognizer:_doubleTapGestureRecognizerForDoubleClick.get()]; [_twoFingerDoubleTapGestureRecognizer setDelegate:nil]; [self removeGestureRecognizer:_twoFingerDoubleTapGestureRecognizer.get()]; [_twoFingerSingleTapGestureRecognizer setDelegate:nil]; [self removeGestureRecognizer:_twoFingerSingleTapGestureRecognizer.get()]; [_stylusSingleTapGestureRecognizer setDelegate:nil]; [self removeGestureRecognizer:_stylusSingleTapGestureRecognizer.get()]; #if ENABLE(POINTER_EVENTS) [self removeGestureRecognizer:_touchActionGestureRecognizer.get()]; [self removeGestureRecognizer:_touchActionLeftSwipeGestureRecognizer.get()]; [self removeGestureRecognizer:_touchActionRightSwipeGestureRecognizer.get()]; [self removeGestureRecognizer:_touchActionUpSwipeGestureRecognizer.get()]; [self removeGestureRecognizer:_touchActionDownSwipeGestureRecognizer.get()]; #endif _layerTreeTransactionIdAtLastTouchStart = 0; #if ENABLE(DATA_INTERACTION) [existingLocalDragSessionContext(_dragDropInteractionState.dragSession()) cleanUpTemporaryDirectories]; [self teardownDragAndDropInteractions]; #endif _inspectorNodeSearchEnabled = NO; if (_inspectorNodeSearchGestureRecognizer) { [_inspectorNodeSearchGestureRecognizer setDelegate:nil]; [self removeGestureRecognizer:_inspectorNodeSearchGestureRecognizer.get()]; _inspectorNodeSearchGestureRecognizer = nil; } #if HAVE(LINK_PREVIEW) [self _unregisterPreview]; #endif if (_fileUploadPanel) { [_fileUploadPanel setDelegate:nil]; [_fileUploadPanel dismiss]; _fileUploadPanel = nil; } #if !PLATFORM(WATCHOS) && !PLATFORM(APPLETV) if (_shareSheet) { [_shareSheet setDelegate:nil]; [_shareSheet dismiss]; _shareSheet = nil; } #endif [self _resetInputViewDeferral]; _focusedElementInformation = { }; [_keyboardScrollingAnimator invalidate]; _keyboardScrollingAnimator = nil; #if HAVE(PENCILKIT) _drawingCoordinator = nil; #endif #if ENABLE(DATALIST_ELEMENT) _dataListTextSuggestionsInputView = nil; _dataListTextSuggestions = nil; #endif _hasSetUpInteractions = NO; _suppressSelectionAssistantReasons = { }; #if ENABLE(POINTER_EVENTS) [self _resetPanningPreventionFlags]; #endif [self _handleDOMPasteRequestWithResult:WebCore::DOMPasteAccessResponse::DeniedForGesture]; } - (void)_removeDefaultGestureRecognizers { [self removeGestureRecognizer:_touchEventGestureRecognizer.get()]; [self removeGestureRecognizer:_singleTapGestureRecognizer.get()]; [self removeGestureRecognizer:_highlightLongPressGestureRecognizer.get()]; [self removeGestureRecognizer:_doubleTapGestureRecognizer.get()]; [self removeGestureRecognizer:_nonBlockingDoubleTapGestureRecognizer.get()]; [self removeGestureRecognizer:_doubleTapGestureRecognizerForDoubleClick.get()]; [self removeGestureRecognizer:_twoFingerDoubleTapGestureRecognizer.get()]; [self removeGestureRecognizer:_twoFingerSingleTapGestureRecognizer.get()]; [self removeGestureRecognizer:_stylusSingleTapGestureRecognizer.get()]; #if PLATFORM(MACCATALYST) [self removeGestureRecognizer:_hoverGestureRecognizer.get()]; [self removeGestureRecognizer:_lookupGestureRecognizer.get()]; #endif #if ENABLE(POINTER_EVENTS) [self removeGestureRecognizer:_touchActionGestureRecognizer.get()]; [self removeGestureRecognizer:_touchActionLeftSwipeGestureRecognizer.get()]; [self removeGestureRecognizer:_touchActionRightSwipeGestureRecognizer.get()]; [self removeGestureRecognizer:_touchActionUpSwipeGestureRecognizer.get()]; [self removeGestureRecognizer:_touchActionDownSwipeGestureRecognizer.get()]; #endif } - (void)_addDefaultGestureRecognizers { [self addGestureRecognizer:_touchEventGestureRecognizer.get()]; [self addGestureRecognizer:_singleTapGestureRecognizer.get()]; [self addGestureRecognizer:_highlightLongPressGestureRecognizer.get()]; [self addGestureRecognizer:_doubleTapGestureRecognizer.get()]; [self addGestureRecognizer:_nonBlockingDoubleTapGestureRecognizer.get()]; [self addGestureRecognizer:_doubleTapGestureRecognizerForDoubleClick.get()]; [self addGestureRecognizer:_twoFingerDoubleTapGestureRecognizer.get()]; [self addGestureRecognizer:_twoFingerSingleTapGestureRecognizer.get()]; [self addGestureRecognizer:_stylusSingleTapGestureRecognizer.get()]; #if PLATFORM(MACCATALYST) [self addGestureRecognizer:_hoverGestureRecognizer.get()]; [self addGestureRecognizer:_lookupGestureRecognizer.get()]; #endif #if ENABLE(POINTER_EVENTS) [self addGestureRecognizer:_touchActionGestureRecognizer.get()]; [self addGestureRecognizer:_touchActionLeftSwipeGestureRecognizer.get()]; [self addGestureRecognizer:_touchActionRightSwipeGestureRecognizer.get()]; [self addGestureRecognizer:_touchActionUpSwipeGestureRecognizer.get()]; [self addGestureRecognizer:_touchActionDownSwipeGestureRecognizer.get()]; #endif } - (void)_didChangeLinkPreviewAvailability { [self _updateLongPressAndHighlightLongPressGestures]; } - (void)_updateLongPressAndHighlightLongPressGestures { // We only disable the highlight long press gesture in the case where UIContextMenu is available and we // also allow link previews, since the context menu interaction's gestures need to take precedence over // highlight long press gestures. [_highlightLongPressGestureRecognizer setEnabled:!self._shouldUseContextMenus || !_webView.allowsLinkPreview]; // We only enable the long press gesture in the case where the app is linked on iOS 12 or earlier (and // therefore prefers the legacy action sheet over context menus), and link previews are also enabled. [_longPressGestureRecognizer setEnabled:!self._shouldUseContextMenus && _webView.allowsLinkPreview]; } - (UIView *)unscaledView { return _interactionViewsContainerView.get(); } - (CGFloat)inverseScale { return 1 / [[self layer] transform].m11; } - (UIScrollView *)_scroller { return [_webView scrollView]; } - (CGRect)unobscuredContentRect { return _page->unobscuredContentRect(); } #pragma mark - UITextAutoscrolling - (void)startAutoscroll:(CGPoint)pointInDocument { _page->startAutoscrollAtPosition(pointInDocument); } - (void)cancelAutoscroll { _page->cancelAutoscroll(); } - (void)scrollSelectionToVisible:(BOOL)animated { // Used to scroll selection on keyboard up; we already scroll to visible. } - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context { ASSERT([keyPath isEqualToString:@"transform"]); ASSERT(object == self.layer); if ([UIView _isInAnimationBlock] && _page->editorState().selectionIsNone) { // If the utility views are not already visible, we don't want them to become visible during the animation since // they could not start from a reasonable state. // This is not perfect since views could also get updated during the animation, in practice this is rare and the end state // remains correct. [self _cancelInteraction]; [_interactionViewsContainerView setHidden:YES]; [UIView _addCompletion:^(BOOL){ [_interactionViewsContainerView setHidden:NO]; }]; } [self _updateTapHighlight]; _selectionNeedsUpdate = YES; [self _updateChangedSelection:YES]; } - (void)_enableInspectorNodeSearch { _inspectorNodeSearchEnabled = YES; [self _cancelInteraction]; [self _removeDefaultGestureRecognizers]; _inspectorNodeSearchGestureRecognizer = adoptNS([[WKInspectorNodeSearchGestureRecognizer alloc] initWithTarget:self action:@selector(_inspectorNodeSearchRecognized:)]); [self addGestureRecognizer:_inspectorNodeSearchGestureRecognizer.get()]; } - (void)_disableInspectorNodeSearch { _inspectorNodeSearchEnabled = NO; [self _addDefaultGestureRecognizers]; [self removeGestureRecognizer:_inspectorNodeSearchGestureRecognizer.get()]; _inspectorNodeSearchGestureRecognizer = nil; } - (UIView *)hitTest:(CGPoint)point withEvent:(::UIEvent *)event { for (UIView *subView in [_interactionViewsContainerView.get() subviews]) { UIView *hitView = [subView hitTest:[subView convertPoint:point fromView:self] withEvent:event]; if (hitView) { LOG_WITH_STREAM(UIHitTesting, stream << self << "hitTest at " << WebCore::FloatPoint(point) << " found interaction view " << hitView); return hitView; } } LOG_WITH_STREAM(UIHitTesting, stream << "hit-testing WKContentView subviews " << [[self recursiveDescription] UTF8String]); UIView* hitView = [super hitTest:point withEvent:event]; LOG_WITH_STREAM(UIHitTesting, stream << " found view " << [hitView class] << " " << (void*)hitView); return hitView; } - (const WebKit::InteractionInformationAtPosition&)positionInformation { return _positionInformation; } - (void)setInputDelegate:(id )inputDelegate { _inputDelegate = inputDelegate; } - (id )inputDelegate { return _inputDelegate.getAutoreleased(); } - (CGPoint)lastInteractionLocation { return _lastInteractionLocation; } - (BOOL)shouldHideSelectionWhenScrolling { if (_isEditable) return _focusedElementInformation.insideFixedPosition; auto& editorState = _page->editorState(); return !editorState.isMissingPostLayoutData && editorState.postLayoutData().insideFixedPosition; } - (BOOL)isEditable { return _isEditable; } - (BOOL)setIsEditable:(BOOL)isEditable { if (isEditable == _isEditable) return NO; _isEditable = isEditable; return YES; } - (void)_endEditing { [_inputPeripheral endEditing]; [_formInputSession endEditing]; #if ENABLE(DATALIST_ELEMENT) [_dataListTextSuggestionsInputView controlEndEditing]; #endif } - (void)_cancelPreviousResetInputViewDeferralRequest { [NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(_resetInputViewDeferral) object:nil]; } - (void)_scheduleResetInputViewDeferralAfterBecomingFirstResponder { [self _cancelPreviousResetInputViewDeferralRequest]; const NSTimeInterval inputViewDeferralWatchdogTimerDuration = 0.5; [self performSelector:@selector(_resetInputViewDeferral) withObject:self afterDelay:inputViewDeferralWatchdogTimerDuration]; } - (void)_resetInputViewDeferral { [self _cancelPreviousResetInputViewDeferralRequest]; _inputViewUpdateDeferrer = nullptr; } - (BOOL)canBecomeFirstResponder { return _becomingFirstResponder; } - (BOOL)canBecomeFirstResponderForWebView { if (_resigningFirstResponder) return NO; // We might want to return something else // if we decide to enable/disable interaction programmatically. return YES; } - (BOOL)becomeFirstResponder { return [_webView becomeFirstResponder]; } - (BOOL)becomeFirstResponderForWebView { if (_resigningFirstResponder) return NO; if (!_inputViewUpdateDeferrer) _inputViewUpdateDeferrer = std::make_unique(self); BOOL didBecomeFirstResponder; { SetForScope becomingFirstResponder { _becomingFirstResponder, YES }; didBecomeFirstResponder = [super becomeFirstResponder]; } if (didBecomeFirstResponder) { _page->installActivityStateChangeCompletionHandler([weakSelf = WeakObjCPtr(self)] { if (!weakSelf) return; auto strongSelf = weakSelf.get(); [strongSelf _resetInputViewDeferral]; }); _page->activityStateDidChange(WebCore::ActivityState::IsFocused); if ([self canShowNonEmptySelectionView]) [_textSelectionAssistant activateSelection]; [self _scheduleResetInputViewDeferralAfterBecomingFirstResponder]; } else [self _resetInputViewDeferral]; return didBecomeFirstResponder; } - (BOOL)resignFirstResponder { return [_webView resignFirstResponder]; } typedef NS_ENUM(NSInteger, EndEditingReason) { EndEditingReasonAccessoryDone, EndEditingReasonResigningFirstResponder, }; - (void)endEditingAndUpdateFocusAppearanceWithReason:(EndEditingReason)reason { if (!_webView._retainingActiveFocusedState) { // We need to complete the editing operation before we blur the element. [self _endEditing]; if ((reason == EndEditingReasonAccessoryDone && !currentUserInterfaceIdiomIsPad()) || _keyboardDidRequestDismissal || self._shouldUseLegacySelectPopoverDismissalBehavior) { _page->blurFocusedElement(); // Don't wait for WebPageProxy::blurFocusedElement() to round-trip back to us to hide the keyboard // because we know that the user explicitly requested us to do so. [self _elementDidBlur]; } } [self _cancelInteraction]; [_textSelectionAssistant deactivateSelection]; [self _resetInputViewDeferral]; } - (BOOL)resignFirstResponderForWebView { // FIXME: Maybe we should call resignFirstResponder on the superclass // and do nothing if the return value is NO. SetForScope resigningFirstResponderScope { _resigningFirstResponder, YES }; [self endEditingAndUpdateFocusAppearanceWithReason:EndEditingReasonResigningFirstResponder]; // If the user explicitly dismissed the keyboard then we will lose first responder // status only to gain it back again. Just don't resign in that case. if (_keyboardDidRequestDismissal) { _keyboardDidRequestDismissal = NO; return NO; } bool superDidResign = [super resignFirstResponder]; if (superDidResign) { [self _handleDOMPasteRequestWithResult:WebCore::DOMPasteAccessResponse::DeniedForGesture]; _page->activityStateDidChange(WebCore::ActivityState::IsFocused); } return superDidResign; } #if ENABLE(POINTER_EVENTS) - (void)cancelPointersForGestureRecognizer:(UIGestureRecognizer *)gestureRecognizer { NSMapTable *activeTouches = [_touchEventGestureRecognizer activeTouchesByIdentifier]; for (NSNumber *touchIdentifier in activeTouches) { UITouch *touch = [activeTouches objectForKey:touchIdentifier]; if ([touch.gestureRecognizers containsObject:gestureRecognizer]) _page->cancelPointer([touchIdentifier unsignedIntValue], WebCore::roundedIntPoint([touch locationInView:self])); } } - (WTF::Optional)activeTouchIdentifierForGestureRecognizer:(UIGestureRecognizer *)gestureRecognizer { NSMapTable *activeTouches = [_touchEventGestureRecognizer activeTouchesByIdentifier]; for (NSNumber *touchIdentifier in activeTouches) { UITouch *touch = [activeTouches objectForKey:touchIdentifier]; if ([touch.gestureRecognizers containsObject:gestureRecognizer]) return [touchIdentifier unsignedIntValue]; } return WTF::nullopt; } #endif inline static UIKeyModifierFlags gestureRecognizerModifierFlags(UIGestureRecognizer *recognizer) { return [recognizer respondsToSelector:@selector(_modifierFlags)] ? recognizer.modifierFlags : 0; } - (void)_webTouchEventsRecognized:(UIWebTouchEventsGestureRecognizer *)gestureRecognizer { if (!_page->hasRunningProcess()) return; const _UIWebTouchEvent* lastTouchEvent = gestureRecognizer.lastTouchEvent; _lastInteractionLocation = lastTouchEvent->locationInDocumentCoordinates; if (lastTouchEvent->type == UIWebTouchEventTouchBegin) { [self _handleDOMPasteRequestWithResult:WebCore::DOMPasteAccessResponse::DeniedForGesture]; _layerTreeTransactionIdAtLastTouchStart = downcast(*_page->drawingArea()).lastCommittedLayerTreeTransactionID(); #if ENABLE(TOUCH_EVENTS) _page->resetPotentialTapSecurityOrigin(); #endif WebKit::InteractionInformationRequest positionInformationRequest { WebCore::IntPoint(_lastInteractionLocation) }; [self doAfterPositionInformationUpdate:[assistant = WeakObjCPtr(_actionSheetAssistant.get())] (WebKit::InteractionInformationAtPosition information) { [assistant interactionDidStartWithPositionInformation:information]; } forRequest:positionInformationRequest]; } #if ENABLE(TOUCH_EVENTS) WebKit::NativeWebTouchEvent nativeWebTouchEvent { lastTouchEvent, gestureRecognizerModifierFlags(gestureRecognizer) }; nativeWebTouchEvent.setCanPreventNativeGestures(!_canSendTouchEventsAsynchronously || [gestureRecognizer isDefaultPrevented]); #if ENABLE(POINTER_EVENTS) [self _handleTouchActionsForTouchEvent:nativeWebTouchEvent]; #endif if (_canSendTouchEventsAsynchronously) _page->handleTouchEventAsynchronously(nativeWebTouchEvent); else _page->handleTouchEventSynchronously(nativeWebTouchEvent); if (nativeWebTouchEvent.allTouchPointsAreReleased()) { _canSendTouchEventsAsynchronously = NO; #if ENABLE(POINTER_EVENTS) if (!_page->isScrollingOrZooming()) [self _resetPanningPreventionFlags]; #endif } #endif } #if ENABLE(POINTER_EVENTS) #if ENABLE(TOUCH_EVENTS) - (void)_handleTouchActionsForTouchEvent:(const WebKit::NativeWebTouchEvent&)touchEvent { auto* scrollingCoordinator = _page->scrollingCoordinatorProxy(); if (!scrollingCoordinator) return; for (const auto& touchPoint : touchEvent.touchPoints()) { auto phase = touchPoint.phase(); if (phase == WebKit::WebPlatformTouchPoint::TouchPressed) { auto touchActions = WebKit::touchActionsForPoint(self, touchPoint.location()); LOG_WITH_STREAM(UIHitTesting, stream << "touchActionsForPoint " << touchPoint.location() << " found " << touchActions); if (!touchActions || touchActions.contains(WebCore::TouchAction::Auto)) continue; [_touchActionGestureRecognizer setTouchActions:touchActions forTouchIdentifier:touchPoint.identifier()]; scrollingCoordinator->setTouchActionsForTouchIdentifier(touchActions, touchPoint.identifier()); _preventsPanningInXAxis = !touchActions.containsAny({ WebCore::TouchAction::PanX, WebCore::TouchAction::Manipulation }); _preventsPanningInYAxis = !touchActions.containsAny({ WebCore::TouchAction::PanY, WebCore::TouchAction::Manipulation }); } else if (phase == WebKit::WebPlatformTouchPoint::TouchReleased || phase == WebKit::WebPlatformTouchPoint::TouchCancelled) { [_touchActionGestureRecognizer clearTouchActionsForTouchIdentifier:touchPoint.identifier()]; scrollingCoordinator->clearTouchActionsForTouchIdentifier(touchPoint.identifier()); } } } - (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch { if (gestureRecognizer != _touchActionLeftSwipeGestureRecognizer && gestureRecognizer != _touchActionRightSwipeGestureRecognizer && gestureRecognizer != _touchActionUpSwipeGestureRecognizer && gestureRecognizer != _touchActionDownSwipeGestureRecognizer) return true; // We update the enabled state of the various swipe gesture recognizers such that if we have a unidirectional touch-action // specified (only pan-x or only pan-y) we enable the two recognizers in the opposite axis to prevent scrolling from starting // if the initial gesture is such a swipe. Since the recognizers are specified to use a single finger for recognition, we don't // need to worry about the case where there may be more than a single touch for a given UIScrollView. auto touchActions = WebKit::touchActionsForPoint(self, WebCore::roundedIntPoint([touch locationInView:self])); if (gestureRecognizer == _touchActionLeftSwipeGestureRecognizer || gestureRecognizer == _touchActionRightSwipeGestureRecognizer) return touchActions == WebCore::TouchAction::PanY; return touchActions == WebCore::TouchAction::PanX; } #endif #pragma mark - WKTouchActionGestureRecognizerDelegate implementation - (BOOL)gestureRecognizerMayPanWebView:(UIGestureRecognizer *)gestureRecognizer { // The gesture recognizer is the main UIScrollView's UIPanGestureRecognizer. if (gestureRecognizer == [_webView scrollView].panGestureRecognizer) return YES; // The gesture recognizer is a child UIScrollView's UIPanGestureRecognizer created by WebKit. if (gestureRecognizer.view && [gestureRecognizer.view isKindOfClass:[WKChildScrollView class]]) return YES; return NO; } - (BOOL)gestureRecognizerMayPinchToZoomWebView:(UIGestureRecognizer *)gestureRecognizer { // The gesture recognizer is the main UIScrollView's UIPinchGestureRecognizer. if (gestureRecognizer == [_webView scrollView].pinchGestureRecognizer) return YES; // The gesture recognizer is another UIPichGestureRecognizer known to lead to pinch-to-zoom. if ([gestureRecognizer isKindOfClass:[UIPinchGestureRecognizer class]]) { if (auto uiDelegate = static_cast>(_webView.UIDelegate)) { if ([uiDelegate respondsToSelector:@selector(_webView:gestureRecognizerCouldPinch:)]) return [uiDelegate _webView:_webView gestureRecognizerCouldPinch:gestureRecognizer]; } } return NO; } - (BOOL)gestureRecognizerMayDoubleTapToZoomWebView:(UIGestureRecognizer *)gestureRecognizer { return gestureRecognizer == _doubleTapGestureRecognizer || gestureRecognizer == _twoFingerDoubleTapGestureRecognizer; } - (NSMapTable *)touchActionActiveTouches { return [_touchEventGestureRecognizer activeTouchesByIdentifier]; } - (void)_resetPanningPreventionFlags { _preventsPanningInXAxis = NO; _preventsPanningInYAxis = NO; } #endif - (void)_inspectorNodeSearchRecognized:(UIGestureRecognizer *)gestureRecognizer { ASSERT(_inspectorNodeSearchEnabled); [self _resetIsDoubleTapPending]; CGPoint point = [gestureRecognizer locationInView:self]; switch (gestureRecognizer.state) { case UIGestureRecognizerStateBegan: case UIGestureRecognizerStateChanged: _page->inspectorNodeSearchMovedToPosition(point); break; case UIGestureRecognizerStateEnded: case UIGestureRecognizerStateCancelled: default: // To ensure we turn off node search. _page->inspectorNodeSearchEndedAtPosition(point); break; } } static WebCore::FloatQuad inflateQuad(const WebCore::FloatQuad& quad, float inflateSize) { // We sort the output points like this (as expected by the highlight view): // p2------p3 // | | // p1------p4 // 1) Sort the points horizontally. WebCore::FloatPoint points[4] = { quad.p1(), quad.p4(), quad.p2(), quad.p3() }; if (points[0].x() > points[1].x()) std::swap(points[0], points[1]); if (points[2].x() > points[3].x()) std::swap(points[2], points[3]); if (points[0].x() > points[2].x()) std::swap(points[0], points[2]); if (points[1].x() > points[3].x()) std::swap(points[1], points[3]); if (points[1].x() > points[2].x()) std::swap(points[1], points[2]); // 2) Swap them vertically to have the output points [p2, p1, p3, p4]. if (points[1].y() < points[0].y()) std::swap(points[0], points[1]); if (points[3].y() < points[2].y()) std::swap(points[2], points[3]); // 3) Adjust the positions. points[0].move(-inflateSize, -inflateSize); points[1].move(-inflateSize, inflateSize); points[2].move(inflateSize, -inflateSize); points[3].move(inflateSize, inflateSize); return WebCore::FloatQuad(points[1], points[0], points[2], points[3]); } #if ENABLE(TOUCH_EVENTS) - (void)_webTouchEvent:(const WebKit::NativeWebTouchEvent&)touchEvent preventsNativeGestures:(BOOL)preventsNativeGesture { if (preventsNativeGesture) { _longPressCanClick = NO; _canSendTouchEventsAsynchronously = YES; [_touchEventGestureRecognizer setDefaultPrevented:YES]; } } #endif static NSValue *nsSizeForTapHighlightBorderRadius(WebCore::IntSize borderRadius, CGFloat borderRadiusScale) { return [NSValue valueWithCGSize:CGSizeMake((borderRadius.width() * borderRadiusScale) + minimumTapHighlightRadius, (borderRadius.height() * borderRadiusScale) + minimumTapHighlightRadius)]; } - (void)_updateTapHighlight { if (![_highlightView superview]) return; { RetainPtr highlightUIKitColor = adoptNS([[UIColor alloc] initWithCGColor:cachedCGColor(_tapHighlightInformation.color)]); [_highlightView setColor:highlightUIKitColor.get()]; } CGFloat selfScale = self.layer.transform.m11; bool allHighlightRectsAreRectilinear = true; float deviceScaleFactor = _page->deviceScaleFactor(); const Vector& highlightedQuads = _tapHighlightInformation.quads; const size_t quadCount = highlightedQuads.size(); RetainPtr rects = adoptNS([[NSMutableArray alloc] initWithCapacity:static_cast(quadCount)]); for (size_t i = 0; i < quadCount; ++i) { const WebCore::FloatQuad& quad = highlightedQuads[i]; if (quad.isRectilinear()) { WebCore::FloatRect boundingBox = quad.boundingBox(); boundingBox.scale(selfScale); boundingBox.inflate(minimumTapHighlightRadius); CGRect pixelAlignedRect = static_cast(encloseRectToDevicePixels(boundingBox, deviceScaleFactor)); [rects addObject:[NSValue valueWithCGRect:pixelAlignedRect]]; } else { allHighlightRectsAreRectilinear = false; rects.clear(); break; } } if (allHighlightRectsAreRectilinear) [_highlightView setFrames:rects.get() boundaryRect:_page->exposedContentRect()]; else { RetainPtr quads = adoptNS([[NSMutableArray alloc] initWithCapacity:static_cast(quadCount)]); for (size_t i = 0; i < quadCount; ++i) { WebCore::FloatQuad quad = highlightedQuads[i]; quad.scale(selfScale); WebCore::FloatQuad extendedQuad = inflateQuad(quad, minimumTapHighlightRadius); [quads addObject:[NSValue valueWithCGPoint:extendedQuad.p1()]]; [quads addObject:[NSValue valueWithCGPoint:extendedQuad.p2()]]; [quads addObject:[NSValue valueWithCGPoint:extendedQuad.p3()]]; [quads addObject:[NSValue valueWithCGPoint:extendedQuad.p4()]]; } [_highlightView setQuads:quads.get() boundaryRect:_page->exposedContentRect()]; } RetainPtr borderRadii = adoptNS([[NSMutableArray alloc] initWithCapacity:4]); [borderRadii addObject:nsSizeForTapHighlightBorderRadius(_tapHighlightInformation.topLeftRadius, selfScale)]; [borderRadii addObject:nsSizeForTapHighlightBorderRadius(_tapHighlightInformation.topRightRadius, selfScale)]; [borderRadii addObject:nsSizeForTapHighlightBorderRadius(_tapHighlightInformation.bottomLeftRadius, selfScale)]; [borderRadii addObject:nsSizeForTapHighlightBorderRadius(_tapHighlightInformation.bottomRightRadius, selfScale)]; [_highlightView setCornerRadii:borderRadii.get()]; } - (void)_showTapHighlight { auto shouldPaintTapHighlight = [&](const WebCore::FloatRect& rect) { #if PLATFORM(MACCATALYST) UNUSED_PARAM(rect); return NO; #else if (_tapHighlightInformation.nodeHasBuiltInClickHandling) return true; static const float highlightPaintThreshold = 0.3; // 30% float highlightArea = 0; for (auto highlightQuad : _tapHighlightInformation.quads) { auto boundingBox = highlightQuad.boundingBox(); highlightArea += boundingBox.area(); if (boundingBox.width() > (rect.width() * highlightPaintThreshold) || boundingBox.height() > (rect.height() * highlightPaintThreshold)) return false; } return highlightArea < rect.area() * highlightPaintThreshold; #endif }; if (!shouldPaintTapHighlight(_page->unobscuredContentRect()) && !_showDebugTapHighlightsForFastClicking) return; if (!_highlightView) { _highlightView = adoptNS([[_UIHighlightView alloc] initWithFrame:CGRectZero]); [_highlightView setUserInteractionEnabled:NO]; [_highlightView setOpaque:NO]; [_highlightView setCornerRadius:minimumTapHighlightRadius]; } [_highlightView layer].opacity = 1; [_interactionViewsContainerView addSubview:_highlightView.get()]; [self _updateTapHighlight]; } - (void)_didGetTapHighlightForRequest:(uint64_t)requestID color:(const WebCore::Color&)color quads:(const Vector&)highlightedQuads topLeftRadius:(const WebCore::IntSize&)topLeftRadius topRightRadius:(const WebCore::IntSize&)topRightRadius bottomLeftRadius:(const WebCore::IntSize&)bottomLeftRadius bottomRightRadius:(const WebCore::IntSize&)bottomRightRadius nodeHasBuiltInClickHandling:(BOOL)nodeHasBuiltInClickHandling { if (!_isTapHighlightIDValid || _latestTapID != requestID) return; if (hasFocusedElement(_focusedElementInformation) && _positionInformation.nodeAtPositionIsFocusedElement) return; _isTapHighlightIDValid = NO; _tapHighlightInformation.quads = highlightedQuads; _tapHighlightInformation.topLeftRadius = topLeftRadius; _tapHighlightInformation.topRightRadius = topRightRadius; _tapHighlightInformation.bottomLeftRadius = bottomLeftRadius; _tapHighlightInformation.bottomRightRadius = bottomRightRadius; _tapHighlightInformation.nodeHasBuiltInClickHandling = nodeHasBuiltInClickHandling; if (_showDebugTapHighlightsForFastClicking) _tapHighlightInformation.color = [self _tapHighlightColorForFastClick:![_doubleTapGestureRecognizer isEnabled]]; else _tapHighlightInformation.color = color; if (_potentialTapInProgress) { _hasTapHighlightForPotentialTap = YES; return; } [self _showTapHighlight]; if (_isExpectingFastSingleTapCommit) { [self _finishInteraction]; if (!_potentialTapInProgress) _isExpectingFastSingleTapCommit = NO; } } - (BOOL)_mayDisableDoubleTapGesturesDuringSingleTap { return _potentialTapInProgress; } - (void)_disableDoubleTapGesturesDuringTapIfNecessary:(uint64_t)requestID { if (_latestTapID != requestID) return; [self _setDoubleTapGesturesEnabled:NO]; } - (void)_handleSmartMagnificationInformationForPotentialTap:(uint64_t)requestID renderRect:(const WebCore::FloatRect&)renderRect fitEntireRect:(BOOL)fitEntireRect viewportMinimumScale:(double)viewportMinimumScale viewportMaximumScale:(double)viewportMaximumScale { ASSERT(_page->preferences().fasterClicksEnabled()); if (!_potentialTapInProgress) return; if (_page->preferences().fastClicksEverywhere() && _page->allowsFastClicksEverywhere()) { RELEASE_LOG(ViewGestures, "Potential tap found an element and fast taps are forced on. Trigger click. (%p)", self); [self _setDoubleTapGesturesEnabled:NO]; return; } auto targetScale = _smartMagnificationController->zoomFactorForTargetRect(renderRect, fitEntireRect, viewportMinimumScale, viewportMaximumScale); auto initialScale = [self _initialScaleFactor]; if (std::min(targetScale, initialScale) / std::max(targetScale, initialScale) > fasterTapSignificantZoomThreshold) { RELEASE_LOG(ViewGestures, "Potential tap would not cause a significant zoom. Trigger click. (%p)", self); [self _setDoubleTapGesturesEnabled:NO]; return; } RELEASE_LOG(ViewGestures, "Potential tap may cause significant zoom. Wait. (%p)", self); } - (void)_cancelLongPressGestureRecognizer { [_highlightLongPressGestureRecognizer cancel]; } - (void)_cancelTouchEventGestureRecognizer { #if HAVE(CANCEL_WEB_TOUCH_EVENTS_GESTURE) [_touchEventGestureRecognizer cancel]; #endif } - (void)_didScroll { [self _cancelLongPressGestureRecognizer]; [self _cancelInteraction]; } - (void)_scrollingNodeScrollingWillBegin { [_textSelectionAssistant willStartScrollingOverflow]; } - (void)_scrollingNodeScrollingDidEnd { // If scrolling ends before we've received a selection update, // we postpone showing the selection until the update is received. if (!_selectionNeedsUpdate) { _shouldRestoreSelection = YES; return; } [self _updateChangedSelection]; [_textSelectionAssistant didEndScrollingOverflow]; } - (BOOL)shouldShowAutomaticKeyboardUI { // FIXME: We should support inputmode="none" when the hardware keyboard is attached. // We currently refrain from doing so because that would prevent UIKit from showing // the language picker when pressing the globe key to change the input language. if (_focusedElementInformation.inputMode == WebCore::InputMode::None && !GSEventIsHardwareKeyboardAttached()) return NO; return [self _shouldShowAutomaticKeyboardUIIgnoringInputMode]; } - (BOOL)_shouldShowAutomaticKeyboardUIIgnoringInputMode { switch (_focusedElementInformation.elementType) { case WebKit::InputType::None: case WebKit::InputType::Drawing: return NO; case WebKit::InputType::Select: #if ENABLE(INPUT_TYPE_COLOR) case WebKit::InputType::Color: #endif case WebKit::InputType::Date: case WebKit::InputType::Month: case WebKit::InputType::DateTimeLocal: case WebKit::InputType::Time: return !currentUserInterfaceIdiomIsPad(); default: return !_focusedElementInformation.isReadOnly; } return NO; } #if USE(UIKIT_KEYBOARD_ADDITIONS) - (BOOL)_disableAutomaticKeyboardUI { // Always enable automatic keyboard UI if we are not the first responder to avoid // interfering with other focused views (e.g. Find-in-page). return [self isFirstResponder] && ![self shouldShowAutomaticKeyboardUI]; } #endif - (BOOL)_requiresKeyboardWhenFirstResponder { // FIXME: We should add the logic to handle keyboard visibility during focus redirects. return [self _shouldShowAutomaticKeyboardUIIgnoringInputMode] #if USE(UIKIT_KEYBOARD_ADDITIONS) || _seenHardwareKeyDownInNonEditableElement #endif ; } - (BOOL)_requiresKeyboardResetOnReload { return YES; } - (void)_zoomToRevealFocusedElement { if (_suppressSelectionAssistantReasons.contains(WebKit::EditableRootIsTransparentOrFullyClipped) || _suppressSelectionAssistantReasons.contains(WebKit::FocusedElementIsTooSmall)) return; // In case user scaling is force enabled, do not use that scaling when zooming in with an input field. // Zooming above the page's default scale factor should only happen when the user performs it. [self _zoomToFocusRect:_focusedElementInformation.elementRect selectionRect:_didAccessoryTabInitiateFocus ? WebCore::FloatRect() : rectToRevealWhenZoomingToFocusedElement(_focusedElementInformation, _page->editorState()) insideFixed:_focusedElementInformation.insideFixedPosition fontSize:_focusedElementInformation.nodeFontSize minimumScale:_focusedElementInformation.minimumScaleFactor maximumScale:_focusedElementInformation.maximumScaleFactorIgnoringAlwaysScalable allowScaling:_focusedElementInformation.allowsUserScalingIgnoringAlwaysScalable && !currentUserInterfaceIdiomIsPad() forceScroll:[self requiresAccessoryView]]; } - (UIView *)inputView { return [_webView inputView]; } - (UIView *)inputViewForWebView { if (!hasFocusedElement(_focusedElementInformation)) return nil; if (_inputPeripheral) { // FIXME: UIKit may invoke -[WKContentView inputView] at any time when WKContentView is the first responder; // as such, it doesn't make sense to change the enclosing scroll view's zoom scale and content offset to reveal // the focused element here. It seems this behavior was added to match logic in legacy WebKit (refer to // UIWebBrowserView). Instead, we should find the places where we currently assume that UIKit (or other clients) // invoke -inputView to zoom to the focused element, and either surface SPI for clients to zoom to the focused // element, or explicitly trigger the zoom from WebKit. // For instance, one use case that currently relies on this detail is adjusting the zoom scale and viewport upon // rotation, when a select element is focused. See for more information. [self _zoomToRevealFocusedElement]; [self _updateAccessory]; } if (UIView *customInputView = [_formInputSession customInputView]) return customInputView; #if ENABLE(DATALIST_ELEMENT) if (_dataListTextSuggestionsInputView) return _dataListTextSuggestionsInputView.get(); #endif return [_inputPeripheral assistantView]; } - (CGRect)_selectionClipRect { if (!hasFocusedElement(_focusedElementInformation)) return CGRectNull; if (_page->waitingForPostLayoutEditorStateUpdateAfterFocusingElement()) return _focusedElementInformation.elementRect; return _page->editorState().postLayoutData().focusedElementRect; } static BOOL isBuiltInScrollViewGestureRecognizer(UIGestureRecognizer *recognizer) { return ([recognizer isKindOfClass:NSClassFromString(@"UIScrollViewPanGestureRecognizer")] || [recognizer isKindOfClass:NSClassFromString(@"UIScrollViewPinchGestureRecognizer")] || [recognizer isKindOfClass:NSClassFromString(@"UIScrollViewKnobLongPressGestureRecognizer")] ); } - (BOOL)gestureRecognizer:(UIGestureRecognizer *)preventingGestureRecognizer canPreventGestureRecognizer:(UIGestureRecognizer *)preventedGestureRecognizer { // A long-press gesture can not be recognized while panning, but a pan can be recognized // during a long-press gesture. BOOL shouldNotPreventScrollViewGestures = preventingGestureRecognizer == _highlightLongPressGestureRecognizer || preventingGestureRecognizer == _longPressGestureRecognizer; return !(shouldNotPreventScrollViewGestures && isBuiltInScrollViewGestureRecognizer(preventedGestureRecognizer)); } - (BOOL)gestureRecognizer:(UIGestureRecognizer *)preventedGestureRecognizer canBePreventedByGestureRecognizer:(UIGestureRecognizer *)preventingGestureRecognizer { // 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. bool isForcePressGesture = NO; #if __IPHONE_OS_VERSION_MIN_REQUIRED >= 120000 isForcePressGesture = (preventingGestureRecognizer == _textSelectionAssistant.get().forcePressGesture); #endif #if PLATFORM(MACCATALYST) if ((preventingGestureRecognizer == _textSelectionAssistant.get().loupeGesture) && (preventedGestureRecognizer == _highlightLongPressGestureRecognizer || preventedGestureRecognizer == _longPressGestureRecognizer || preventedGestureRecognizer == _textSelectionAssistant.get().forcePressGesture)) return YES; #endif if ((preventingGestureRecognizer == _textSelectionAssistant.get().loupeGesture || isForcePressGesture) && (preventedGestureRecognizer == _highlightLongPressGestureRecognizer || preventedGestureRecognizer == _longPressGestureRecognizer)) return NO; return YES; } static inline bool isSamePair(UIGestureRecognizer *a, UIGestureRecognizer *b, UIGestureRecognizer *x, UIGestureRecognizer *y) { return (a == x && b == y) || (b == x && a == y); } - (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer*)otherGestureRecognizer { if (isSamePair(gestureRecognizer, otherGestureRecognizer, _highlightLongPressGestureRecognizer.get(), _longPressGestureRecognizer.get())) return YES; #if __IPHONE_OS_VERSION_MIN_REQUIRED >= 120000 #if PLATFORM(MACCATALYST) if (isSamePair(gestureRecognizer, otherGestureRecognizer, _textSelectionAssistant.get().loupeGesture, _textSelectionAssistant.get().forcePressGesture)) return YES; if (isSamePair(gestureRecognizer, otherGestureRecognizer, _singleTapGestureRecognizer.get(), _textSelectionAssistant.get().loupeGesture)) return YES; if ([gestureRecognizer isKindOfClass:[UIHoverGestureRecognizer class]] || [otherGestureRecognizer isKindOfClass:[UIHoverGestureRecognizer class]]) return YES; if (([gestureRecognizer isKindOfClass:[_UILookupGestureRecognizer class]] && [otherGestureRecognizer isKindOfClass:[UILongPressGestureRecognizer class]]) || ([otherGestureRecognizer isKindOfClass:[UILongPressGestureRecognizer class]] && [gestureRecognizer isKindOfClass:[_UILookupGestureRecognizer class]])) return YES; #endif if (isSamePair(gestureRecognizer, otherGestureRecognizer, _highlightLongPressGestureRecognizer.get(), _textSelectionAssistant.get().forcePressGesture)) return YES; #endif if (isSamePair(gestureRecognizer, otherGestureRecognizer, _singleTapGestureRecognizer.get(), _textSelectionAssistant.get().singleTapGesture)) return YES; if (isSamePair(gestureRecognizer, otherGestureRecognizer, _singleTapGestureRecognizer.get(), _nonBlockingDoubleTapGestureRecognizer.get())) return YES; if (isSamePair(gestureRecognizer, otherGestureRecognizer, _highlightLongPressGestureRecognizer.get(), _nonBlockingDoubleTapGestureRecognizer.get())) return YES; if (isSamePair(gestureRecognizer, otherGestureRecognizer, _highlightLongPressGestureRecognizer.get(), _previewSecondaryGestureRecognizer.get())) return YES; if (isSamePair(gestureRecognizer, otherGestureRecognizer, _highlightLongPressGestureRecognizer.get(), _previewGestureRecognizer.get())) return YES; if (isSamePair(gestureRecognizer, otherGestureRecognizer, _nonBlockingDoubleTapGestureRecognizer.get(), _doubleTapGestureRecognizerForDoubleClick.get())) return YES; if (isSamePair(gestureRecognizer, otherGestureRecognizer, _doubleTapGestureRecognizer.get(), _doubleTapGestureRecognizerForDoubleClick.get())) return YES; return NO; } - (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRequireFailureOfGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer { if (gestureRecognizer == _touchEventGestureRecognizer && [_webView _isNavigationSwipeGestureRecognizer:otherGestureRecognizer]) return YES; return NO; } - (void)_showImageSheet { [_actionSheetAssistant showImageSheet]; } - (void)_showAttachmentSheet { id uiDelegate = static_cast>([_webView UIDelegate]); if (![uiDelegate respondsToSelector:@selector(_webView:showCustomSheetForElement:)]) return; auto element = adoptNS([[_WKActivatedElementInfo alloc] _initWithType:_WKActivatedElementTypeAttachment URL:(NSURL *)_positionInformation.url imageURL:(NSURL *)_positionInformation.imageURL location:_positionInformation.request.point title:_positionInformation.title ID:_positionInformation.idAttribute rect:_positionInformation.bounds image:nil]); ALLOW_DEPRECATED_DECLARATIONS_BEGIN [uiDelegate _webView:_webView showCustomSheetForElement:element.get()]; ALLOW_DEPRECATED_DECLARATIONS_END } - (void)_showLinkSheet { [_actionSheetAssistant showLinkSheet]; } - (void)_showDataDetectorsSheet { [_actionSheetAssistant showDataDetectorsSheet]; } - (SEL)_actionForLongPressFromPositionInformation:(const WebKit::InteractionInformationAtPosition&)positionInformation { if (!_webView.configuration._longPressActionsEnabled) return nil; if (!positionInformation.touchCalloutEnabled) return nil; if (positionInformation.isImage) return @selector(_showImageSheet); if (positionInformation.isLink) { #if ENABLE(DATA_DETECTION) if (WebCore::DataDetection::canBePresentedByDataDetectors(positionInformation.url)) return @selector(_showDataDetectorsSheet); #endif return @selector(_showLinkSheet); } if (positionInformation.isAttachment) return @selector(_showAttachmentSheet); return nil; } - (SEL)_actionForLongPress { return [self _actionForLongPressFromPositionInformation:_positionInformation]; } - (WebKit::InteractionInformationAtPosition)currentPositionInformation { return _positionInformation; } - (void)doAfterPositionInformationUpdate:(void (^)(WebKit::InteractionInformationAtPosition))action forRequest:(WebKit::InteractionInformationRequest)request { if ([self _currentPositionInformationIsValidForRequest:request]) { // If the most recent position information is already valid, invoke the given action block immediately. action(_positionInformation); return; } _pendingPositionInformationHandlers.append(InteractionInformationRequestAndCallback(request, action)); if (![self _hasValidOutstandingPositionInformationRequest:request]) [self requestAsynchronousPositionInformationUpdate:request]; } - (BOOL)ensurePositionInformationIsUpToDate:(WebKit::InteractionInformationRequest)request { if ([self _currentPositionInformationIsValidForRequest:request]) return YES; auto* connection = _page->process().connection(); if (!connection) return NO; if ([self _hasValidOutstandingPositionInformationRequest:request]) return connection->waitForAndDispatchImmediately(_page->pageID(), 1_s, IPC::WaitForOption::InterruptWaitingIfSyncMessageArrives); bool receivedResponse = _page->process().sendSync(Messages::WebPage::GetPositionInformation(request), Messages::WebPage::GetPositionInformation::Reply(_positionInformation), _page->pageID(), 1_s, IPC::SendSyncOption::ForceDispatchWhenDestinationIsWaitingForUnboundedSyncReply); _hasValidPositionInformation = receivedResponse && _positionInformation.canBeValid; // FIXME: We need to clean up these handlers in the event that we are not able to collect data, or if the WebProcess crashes. if (_hasValidPositionInformation) [self _invokeAndRemovePendingHandlersValidForCurrentPositionInformation]; return _hasValidPositionInformation; } - (void)requestAsynchronousPositionInformationUpdate:(WebKit::InteractionInformationRequest)request { if ([self _currentPositionInformationIsValidForRequest:request]) return; _outstandingPositionInformationRequest = request; _page->requestPositionInformation(request); } - (BOOL)_currentPositionInformationIsValidForRequest:(const WebKit::InteractionInformationRequest&)request { return _hasValidPositionInformation && _positionInformation.request.isValidForRequest(request); } - (BOOL)_hasValidOutstandingPositionInformationRequest:(const WebKit::InteractionInformationRequest&)request { return _outstandingPositionInformationRequest && _outstandingPositionInformationRequest->isValidForRequest(request); } - (BOOL)_currentPositionInformationIsApproximatelyValidForRequest:(const WebKit::InteractionInformationRequest&)request radiusForApproximation:(int)radius { return _hasValidPositionInformation && _positionInformation.request.isApproximatelyValidForRequest(request, radius); } - (void)_invokeAndRemovePendingHandlersValidForCurrentPositionInformation { ASSERT(_hasValidPositionInformation); ++_positionInformationCallbackDepth; auto updatedPositionInformation = _positionInformation; for (size_t index = 0; index < _pendingPositionInformationHandlers.size(); ++index) { auto requestAndHandler = _pendingPositionInformationHandlers[index]; if (!requestAndHandler) continue; if (![self _currentPositionInformationIsValidForRequest:requestAndHandler->first]) continue; _pendingPositionInformationHandlers[index] = WTF::nullopt; if (requestAndHandler->second) requestAndHandler->second(updatedPositionInformation); } if (--_positionInformationCallbackDepth) return; for (int index = _pendingPositionInformationHandlers.size() - 1; index >= 0; --index) { if (!_pendingPositionInformationHandlers[index]) _pendingPositionInformationHandlers.remove(index); } } #if ENABLE(DATA_DETECTION) - (NSArray *)_dataDetectionResults { return _page->dataDetectionResults(); } #endif - (NSArray *)_uiTextSelectionRects { NSMutableArray *textSelectionRects = [NSMutableArray array]; if (_textSelectionAssistant) { for (WKTextSelectionRect *selectionRect in [_textSelectionAssistant valueForKeyPath:@"selectionView.selection.selectionRects"]) [textSelectionRects addObject:[NSValue valueWithCGRect:selectionRect.webRect.rect]]; } return textSelectionRects; } - (BOOL)_shouldToggleSelectionCommandsAfterTapAt:(CGPoint)point { if (_lastSelectionDrawingInfo.selectionRects.isEmpty()) return NO; WebCore::FloatRect selectionBoundingRect; BOOL pointIsInSelectionRect = NO; for (auto& rectInfo : _lastSelectionDrawingInfo.selectionRects) { auto rect = rectInfo.rect(); if (rect.isEmpty()) continue; pointIsInSelectionRect |= rect.contains(WebCore::roundedIntPoint(point)); selectionBoundingRect.unite(rect); } WebCore::FloatRect unobscuredContentRect = self.unobscuredContentRect; selectionBoundingRect.intersect(unobscuredContentRect); float unobscuredArea = unobscuredContentRect.area(); float ratioForConsideringSelectionRectToCoverVastMajorityOfContent = 0.75; if (!unobscuredArea || selectionBoundingRect.area() / unobscuredArea > ratioForConsideringSelectionRectToCoverVastMajorityOfContent) return NO; return pointIsInSelectionRect; } - (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer { CGPoint point = [gestureRecognizer locationInView:self]; if (gestureRecognizer == _stylusSingleTapGestureRecognizer) return _webView._stylusTapGestureShouldCreateEditableImage; auto isInterruptingDecelerationForScrollViewOrAncestor = [&] (UIScrollView *scrollView) { UIScrollView *mainScroller = _webView.scrollView; UIView *view = scrollView ?: mainScroller; while (view) { if ([view isKindOfClass:UIScrollView.class] && [(UIScrollView *)view _isInterruptingDeceleration]) return YES; if (mainScroller == view) break; view = view.superview; } return NO; }; if (gestureRecognizer == _singleTapGestureRecognizer) { if ([self _shouldToggleSelectionCommandsAfterTapAt:point]) return NO; return !isInterruptingDecelerationForScrollViewOrAncestor([_singleTapGestureRecognizer lastTouchedScrollView]); } if (gestureRecognizer == _doubleTapGestureRecognizerForDoubleClick) { // Do not start the double-tap-for-double-click gesture recognizer unless we've got a dblclick event handler on the node at the tap location. WebKit::InteractionInformationRequest request(WebCore::roundedIntPoint(point)); if ([self _currentPositionInformationIsApproximatelyValidForRequest:request radiusForApproximation:[_doubleTapGestureRecognizerForDoubleClick allowableMovement]]) return _positionInformation.nodeAtPositionHasDoubleClickHandler; if (![self ensurePositionInformationIsUpToDate:request]) return NO; return _positionInformation.nodeAtPositionHasDoubleClickHandler; } if (gestureRecognizer == _highlightLongPressGestureRecognizer || gestureRecognizer == _doubleTapGestureRecognizer || gestureRecognizer == _nonBlockingDoubleTapGestureRecognizer || gestureRecognizer == _twoFingerDoubleTapGestureRecognizer) { if (hasFocusedElement(_focusedElementInformation)) { // Request information about the position with sync message. // If the focused element is the same, prevent the gesture. if (![self ensurePositionInformationIsUpToDate:WebKit::InteractionInformationRequest(WebCore::roundedIntPoint(point))]) return NO; if (_positionInformation.nodeAtPositionIsFocusedElement) return NO; } } if (gestureRecognizer == _highlightLongPressGestureRecognizer) { if (isInterruptingDecelerationForScrollViewOrAncestor([_highlightLongPressGestureRecognizer lastTouchedScrollView])) return NO; if (hasFocusedElement(_focusedElementInformation)) { // This is a different element than the focused one. // Prevent the gesture if there is no node. // Allow the gesture if it is a node that wants highlight or if there is an action for it. if (!_positionInformation.isElement) return NO; return [self _actionForLongPress] != nil; } // We still have no idea about what is at the location. // Send an async message to find out. _hasValidPositionInformation = NO; WebKit::InteractionInformationRequest request(WebCore::roundedIntPoint(point)); // If 3D Touch is enabled, asynchronously collect snapshots in the hopes that // they'll arrive before we have to synchronously request them in // _interactionShouldBeginFromPreviewItemController. if (self.traitCollection.forceTouchCapability == UIForceTouchCapabilityAvailable) { request.includeSnapshot = true; request.includeLinkIndicator = true; request.linkIndicatorShouldHaveLegacyMargins = !self._shouldUseContextMenus; } [self requestAsynchronousPositionInformationUpdate:request]; return YES; } if (gestureRecognizer == _longPressGestureRecognizer) { // Use the information retrieved with one of the previous calls // to gestureRecognizerShouldBegin. // Force a sync call if not ready yet. WebKit::InteractionInformationRequest request(WebCore::roundedIntPoint(point)); if (![self ensurePositionInformationIsUpToDate:request]) return NO; if (hasFocusedElement(_focusedElementInformation)) { // Prevent the gesture if it is the same node. if (_positionInformation.nodeAtPositionIsFocusedElement) return NO; } else { // Prevent the gesture if there is no action for the node. return [self _actionForLongPress] != nil; } } return YES; } - (void)_cancelInteraction { _isTapHighlightIDValid = NO; [_highlightView removeFromSuperview]; } - (void)_finishInteraction { _isTapHighlightIDValid = NO; CGFloat tapHighlightFadeDuration = _showDebugTapHighlightsForFastClicking ? 0.25 : 0.1; [UIView animateWithDuration:tapHighlightFadeDuration animations:^{ [_highlightView layer].opacity = 0; } completion:^(BOOL finished){ if (finished) [_highlightView removeFromSuperview]; }]; } - (BOOL)canShowNonEmptySelectionView { if (_suppressSelectionAssistantReasons) return NO; auto& state = _page->editorState(); return !state.isMissingPostLayoutData && !state.selectionIsNone; } - (BOOL)hasSelectablePositionAtPoint:(CGPoint)point { if (!_webView.configuration._textInteractionGesturesEnabled) return NO; if (_suppressSelectionAssistantReasons) return NO; if (_inspectorNodeSearchEnabled) return NO; WebKit::InteractionInformationRequest request(WebCore::roundedIntPoint(point)); if (![self ensurePositionInformationIsUpToDate:request]) return NO; #if ENABLE(DRAG_SUPPORT) if (_positionInformation.hasSelectionAtPosition && self._allowedDragSourceActions & WebCore::DragSourceActionSelection) { // If the position might initiate a drag, we don't want to consider the content at this position to be selectable. // FIXME: This should be renamed to something more precise, such as textSelectionShouldRecognizeGestureAtPoint: return NO; } #endif return _positionInformation.isSelectable; } - (BOOL)pointIsNearMarkedText:(CGPoint)point { if (!_webView.configuration._textInteractionGesturesEnabled) return NO; if (_suppressSelectionAssistantReasons) return NO; WebKit::InteractionInformationRequest request(WebCore::roundedIntPoint(point)); if (![self ensurePositionInformationIsUpToDate:request]) return NO; return _positionInformation.isNearMarkedText; } - (BOOL)textInteractionGesture:(UIWKGestureType)gesture shouldBeginAtPoint:(CGPoint)point { if (!_webView.configuration._textInteractionGesturesEnabled) return NO; if (_domPasteRequestHandler) return NO; if (_suppressSelectionAssistantReasons) return NO; if (!self.isFocusingElement) { if (gesture == UIWKGestureDoubleTap) { // Don't allow double tap text gestures in noneditable content. return NO; } if (gesture == UIWKGestureOneFingerTap) { ASSERT(_suppressNonEditableSingleTapTextInteractionCount >= 0); if (_suppressNonEditableSingleTapTextInteractionCount > 0) return NO; switch ([_textSelectionAssistant loupeGesture].state) { case UIGestureRecognizerStateBegan: case UIGestureRecognizerStateChanged: case UIGestureRecognizerStateEnded: { // Avoid handling one-finger taps while the web process is processing certain selection changes. // This works around a scenario where UIKeyboardImpl blocks the main thread while handling a one- // finger tap, which subsequently prevents the UI process from handling any incoming IPC messages. return NO; } default: break; } } } WebKit::InteractionInformationRequest request(WebCore::roundedIntPoint(point)); if (![self ensurePositionInformationIsUpToDate:request]) return NO; #if ENABLE(DRAG_SUPPORT) if (_positionInformation.hasSelectionAtPosition && gesture == UIWKGestureLoupe && self._allowedDragSourceActions & WebCore::DragSourceActionSelection) { // If the position might initiate a drag, we don't want to change the selection. return NO; } #endif #if ENABLE(DATALIST_ELEMENT) if (_positionInformation.preventTextInteraction) return NO; #endif // If we're currently focusing an editable element, only allow the selection to move within that focused element. if (self.isFocusingElement) return _positionInformation.nodeAtPositionIsFocusedElement; // If we're selecting something, don't activate highlight. if (gesture == UIWKGestureLoupe && [self hasSelectablePositionAtPoint:point]) [self _cancelLongPressGestureRecognizer]; // Otherwise, if we're using a text interaction assistant outside of editing purposes (e.g. the selection mode // is character granularity) then allow text selection. return YES; } - (NSArray *)webSelectionRectsForSelectionRects:(const Vector&)selectionRects { unsigned size = selectionRects.size(); if (!size) return nil; NSMutableArray *webRects = [NSMutableArray arrayWithCapacity:size]; for (unsigned i = 0; i < size; i++) { const WebCore::SelectionRect& coreRect = selectionRects[i]; WebSelectionRect *webRect = [WebSelectionRect selectionRect]; webRect.rect = coreRect.rect(); webRect.writingDirection = coreRect.direction() == WebCore::TextDirection::LTR ? WKWritingDirectionLeftToRight : WKWritingDirectionRightToLeft; webRect.isLineBreak = coreRect.isLineBreak(); webRect.isFirstOnLine = coreRect.isFirstOnLine(); webRect.isLastOnLine = coreRect.isLastOnLine(); webRect.containsStart = coreRect.containsStart(); webRect.containsEnd = coreRect.containsEnd(); webRect.isInFixedPosition = coreRect.isInFixedPosition(); webRect.isHorizontal = coreRect.isHorizontal(); [webRects addObject:webRect]; } return webRects; } - (NSArray *)webSelectionRects { if (_page->editorState().isMissingPostLayoutData || _page->editorState().selectionIsNone) return nil; const auto& selectionRects = _page->editorState().postLayoutData().selectionRects; return [self webSelectionRectsForSelectionRects:selectionRects]; } - (void)_highlightLongPressRecognized:(UILongPressGestureRecognizer *)gestureRecognizer { ASSERT(gestureRecognizer == _highlightLongPressGestureRecognizer); [self _resetIsDoubleTapPending]; _lastInteractionLocation = gestureRecognizer.startPoint; switch ([gestureRecognizer state]) { case UIGestureRecognizerStateBegan: _longPressCanClick = YES; cancelPotentialTapIfNecessary(self); _page->tapHighlightAtPosition([gestureRecognizer startPoint], ++_latestTapID); _isTapHighlightIDValid = YES; break; case UIGestureRecognizerStateEnded: if (_longPressCanClick && _positionInformation.isElement) { [self _attemptClickAtLocation:gestureRecognizer.startPoint modifierFlags:gestureRecognizerModifierFlags(gestureRecognizer)]; [self _finishInteraction]; } else [self _cancelInteraction]; _longPressCanClick = NO; break; case UIGestureRecognizerStateCancelled: [self _cancelInteraction]; _longPressCanClick = NO; break; default: break; } } - (void)_doubleTapRecognizedForDoubleClick:(UITapGestureRecognizer *)gestureRecognizer { _page->handleDoubleTapForDoubleClickAtPoint(WebCore::IntPoint(gestureRecognizer.location), WebKit::webEventModifierFlags(gestureRecognizerModifierFlags(gestureRecognizer)), _layerTreeTransactionIdAtLastTouchStart); } - (void)_twoFingerSingleTapGestureRecognized:(UITapGestureRecognizer *)gestureRecognizer { _isTapHighlightIDValid = YES; _isExpectingFastSingleTapCommit = YES; _page->handleTwoFingerTapAtPoint(WebCore::roundedIntPoint(gestureRecognizer.centroid), WebKit::webEventModifierFlags(gestureRecognizerModifierFlags(gestureRecognizer) | UIKeyModifierCommand), ++_latestTapID); } - (void)_stylusSingleTapRecognized:(UITapGestureRecognizer *)gestureRecognizer { ASSERT(_webView._stylusTapGestureShouldCreateEditableImage); ASSERT(gestureRecognizer == _stylusSingleTapGestureRecognizer); _page->handleStylusSingleTapAtPoint(WebCore::roundedIntPoint(gestureRecognizer.location), ++_latestTapID); } - (void)_longPressRecognized:(UILongPressGestureRecognizer *)gestureRecognizer { ASSERT(gestureRecognizer == _longPressGestureRecognizer); [self _resetIsDoubleTapPending]; [self _cancelTouchEventGestureRecognizer]; _page->didRecognizeLongPress(); _lastInteractionLocation = gestureRecognizer.startPoint; if ([gestureRecognizer state] == UIGestureRecognizerStateBegan) { SEL action = [self _actionForLongPress]; if (action) { [self performSelector:action]; [self _cancelLongPressGestureRecognizer]; } } } - (void)_endPotentialTapAndEnableDoubleTapGesturesIfNecessary { if (_webView._allowsDoubleTapGestures) [self _setDoubleTapGesturesEnabled:YES]; _potentialTapInProgress = NO; } - (void)_singleTapIdentified:(UITapGestureRecognizer *)gestureRecognizer { ASSERT(gestureRecognizer == _singleTapGestureRecognizer); ASSERT(!_potentialTapInProgress); [self _resetIsDoubleTapPending]; bool shouldRequestMagnificationInformation = _page->preferences().fasterClicksEnabled(); if (shouldRequestMagnificationInformation) RELEASE_LOG(ViewGestures, "Single tap identified. Request details on potential zoom. (%p)", self); _page->potentialTapAtPosition(gestureRecognizer.location, shouldRequestMagnificationInformation, ++_latestTapID); _potentialTapInProgress = YES; _isTapHighlightIDValid = YES; _isExpectingFastSingleTapCommit = !_doubleTapGestureRecognizer.get().enabled; } static void cancelPotentialTapIfNecessary(WKContentView* contentView) { if (contentView->_potentialTapInProgress) { [contentView _endPotentialTapAndEnableDoubleTapGesturesIfNecessary]; [contentView _cancelInteraction]; contentView->_page->cancelPotentialTap(); } } - (void)_singleTapDidReset:(UITapGestureRecognizer *)gestureRecognizer { ASSERT(gestureRecognizer == _singleTapGestureRecognizer); cancelPotentialTapIfNecessary(self); #if ENABLE(POINTER_EVENTS) if (auto* singleTapTouchIdentifier = [_singleTapGestureRecognizer lastActiveTouchIdentifier]) { WebCore::PointerID pointerId = [singleTapTouchIdentifier unsignedIntValue]; if (m_commitPotentialTapPointerId != pointerId) _page->touchWithIdentifierWasRemoved(pointerId); } #endif } - (void)_doubleTapDidFail:(UITapGestureRecognizer *)gestureRecognizer { RELEASE_LOG(ViewGestures, "Double tap was not recognized. (%p)", self); ASSERT(gestureRecognizer == _doubleTapGestureRecognizer); } - (void)_commitPotentialTapFailed { #if ENABLE(POINTER_EVENTS) _page->touchWithIdentifierWasRemoved(m_commitPotentialTapPointerId); m_commitPotentialTapPointerId = 0; #endif [self _cancelInteraction]; [self _resetInputViewDeferral]; } - (void)_didNotHandleTapAsClick:(const WebCore::IntPoint&)point { [self _resetInputViewDeferral]; // FIXME: we should also take into account whether or not the UI delegate // has handled this notification. #if ENABLE(DATA_DETECTION) if (_hasValidPositionInformation && point == _positionInformation.request.point && _positionInformation.isDataDetectorLink) { [self _showDataDetectorsSheet]; return; } #endif if (!_isDoubleTapPending) return; _smartMagnificationController->handleSmartMagnificationGesture(_lastInteractionLocation); _isDoubleTapPending = NO; } - (void)_didCompleteSyntheticClick { #if ENABLE(POINTER_EVENTS) _page->touchWithIdentifierWasRemoved(m_commitPotentialTapPointerId); m_commitPotentialTapPointerId = 0; #endif RELEASE_LOG(ViewGestures, "Synthetic click completed. (%p)", self); [self _resetInputViewDeferral]; } - (void)_singleTapRecognized:(UITapGestureRecognizer *)gestureRecognizer { ASSERT(gestureRecognizer == _singleTapGestureRecognizer); if (![self isFirstResponder]) [self becomeFirstResponder]; ASSERT(_potentialTapInProgress); _lastInteractionLocation = gestureRecognizer.location; [self _endPotentialTapAndEnableDoubleTapGesturesIfNecessary]; if (_hasTapHighlightForPotentialTap) { [self _showTapHighlight]; _hasTapHighlightForPotentialTap = NO; } [_inputPeripheral endEditing]; RELEASE_LOG(ViewGestures, "Single tap recognized - commit potential tap (%p)", self); WebCore::PointerID pointerId = WebCore::mousePointerID; #if ENABLE(POINTER_EVENTS) if (auto* singleTapTouchIdentifier = [_singleTapGestureRecognizer lastActiveTouchIdentifier]) { pointerId = [singleTapTouchIdentifier unsignedIntValue]; m_commitPotentialTapPointerId = pointerId; } #endif _page->commitPotentialTap(WebKit::webEventModifierFlags(gestureRecognizerModifierFlags(gestureRecognizer)), _layerTreeTransactionIdAtLastTouchStart, pointerId); if (!_isExpectingFastSingleTapCommit) [self _finishInteraction]; } - (void)_doubleTapRecognized:(UITapGestureRecognizer *)gestureRecognizer { RELEASE_LOG(ViewGestures, "Identified a double tap (%p)", self); [self _resetIsDoubleTapPending]; _lastInteractionLocation = gestureRecognizer.location; _smartMagnificationController->handleSmartMagnificationGesture(gestureRecognizer.location); } - (void)_resetIsDoubleTapPending { _isDoubleTapPending = NO; } - (void)_nonBlockingDoubleTapRecognized:(UITapGestureRecognizer *)gestureRecognizer { _lastInteractionLocation = gestureRecognizer.location; _isDoubleTapPending = YES; } - (void)_twoFingerDoubleTapRecognized:(UITapGestureRecognizer *)gestureRecognizer { [self _resetIsDoubleTapPending]; _lastInteractionLocation = gestureRecognizer.location; _smartMagnificationController->handleResetMagnificationGesture(gestureRecognizer.location); } - (void)_attemptClickAtLocation:(CGPoint)location modifierFlags:(UIKeyModifierFlags)modifierFlags { if (![self isFirstResponder]) [self becomeFirstResponder]; [_inputPeripheral endEditing]; _page->handleTap(location, WebKit::webEventModifierFlags(modifierFlags), _layerTreeTransactionIdAtLastTouchStart); } - (void)setUpTextSelectionAssistant { if (!_textSelectionAssistant) _textSelectionAssistant = adoptNS([[WKUIWKTextInteractionAssistant alloc] initWithView:self]); else { // Reset the gesture recognizers in case editability has changed. [_textSelectionAssistant setGestureRecognizers]; } } - (void)pasteWithCompletionHandler:(void (^)(void))completionHandler { _page->executeEditCommand("Paste"_s, { }, [completion = makeBlockPtr(completionHandler)] (auto) { if (completion) completion(); }); } - (void)clearSelection { [self _elementDidBlur]; _page->clearSelection(); } - (void)_positionInformationDidChange:(const WebKit::InteractionInformationAtPosition&)info { _outstandingPositionInformationRequest = WTF::nullopt; WebKit::InteractionInformationAtPosition newInfo = info; newInfo.mergeCompatibleOptionalInformation(_positionInformation); _positionInformation = newInfo; _hasValidPositionInformation = _positionInformation.canBeValid; if (_actionSheetAssistant) [_actionSheetAssistant updateSheetPosition]; [self _invokeAndRemovePendingHandlersValidForCurrentPositionInformation]; } - (void)_willStartScrollingOrZooming { if ([_textSelectionAssistant respondsToSelector:@selector(willStartScrollingOrZooming)]) [_textSelectionAssistant willStartScrollingOrZooming]; else [_textSelectionAssistant willStartScrollingOverflow]; _page->setIsScrollingOrZooming(true); #if PLATFORM(WATCHOS) [_focusedFormControlView disengageFocusedFormControlNavigation]; #endif } - (void)scrollViewWillStartPanOrPinchGesture { _page->hideValidationMessage(); [_keyboardScrollingAnimator willStartInteractiveScroll]; _canSendTouchEventsAsynchronously = YES; } - (void)_didEndScrollingOrZooming { if (!_needsDeferredEndScrollingSelectionUpdate) { if ([_textSelectionAssistant respondsToSelector:@selector(didEndScrollingOrZooming)]) [_textSelectionAssistant didEndScrollingOrZooming]; else [_textSelectionAssistant didEndScrollingOverflow]; } _page->setIsScrollingOrZooming(false); #if ENABLE(POINTER_EVENTS) [self _resetPanningPreventionFlags]; #endif #if PLATFORM(WATCHOS) [_focusedFormControlView engageFocusedFormControlNavigation]; #endif } - (BOOL)requiresAccessoryView { if ([_formInputSession accessoryViewShouldNotShow]) return NO; if ([_formInputSession customInputAccessoryView]) return YES; switch (_focusedElementInformation.elementType) { case WebKit::InputType::None: case WebKit::InputType::Drawing: return NO; case WebKit::InputType::Text: case WebKit::InputType::Password: case WebKit::InputType::Search: case WebKit::InputType::Email: case WebKit::InputType::URL: case WebKit::InputType::Phone: case WebKit::InputType::Number: case WebKit::InputType::NumberPad: case WebKit::InputType::ContentEditable: case WebKit::InputType::TextArea: case WebKit::InputType::Select: case WebKit::InputType::Date: case WebKit::InputType::DateTime: case WebKit::InputType::DateTimeLocal: case WebKit::InputType::Month: case WebKit:: InputType::Week: case WebKit::InputType::Time: #if ENABLE(INPUT_TYPE_COLOR) case WebKit::InputType::Color: #endif return !currentUserInterfaceIdiomIsPad(); } } - (UITextInputAssistantItem *)inputAssistantItem { return [_webView inputAssistantItem]; } - (UITextInputAssistantItem *)inputAssistantItemForWebView { return [super inputAssistantItem]; } - (UIView *)inputAccessoryView { return [_webView inputAccessoryView]; } - (UIView *)inputAccessoryViewForWebView { if (![self requiresAccessoryView]) return nil; return [_formInputSession customInputAccessoryView] ?: self.formAccessoryView; } - (NSArray *)supportedPasteboardTypesForCurrentSelection { if (_page->editorState().selectionIsNone) return nil; static NSMutableArray *richTypes = nil; static NSMutableArray *plainTextTypes = nil; if (!plainTextTypes) { plainTextTypes = [[NSMutableArray alloc] init]; [plainTextTypes addObject:(id)kUTTypeURL]; [plainTextTypes addObjectsFromArray:UIPasteboardTypeListString]; richTypes = [[NSMutableArray alloc] init]; [richTypes addObject:WebCore::WebArchivePboardType]; [richTypes addObjectsFromArray:UIPasteboardTypeListImage]; [richTypes addObjectsFromArray:plainTextTypes]; } return (_page->editorState().isContentRichlyEditable) ? richTypes : plainTextTypes; } #define FORWARD_ACTION_TO_WKWEBVIEW(_action) \ - (void)_action:(id)sender \ { \ SEL action = @selector(_action:);\ [self _willPerformAction:action sender:sender];\ [_webView _action:sender]; \ [self _didPerformAction:action sender:sender];\ } FOR_EACH_WKCONTENTVIEW_ACTION(FORWARD_ACTION_TO_WKWEBVIEW) FOR_EACH_PRIVATE_WKCONTENTVIEW_ACTION(FORWARD_ACTION_TO_WKWEBVIEW) #undef FORWARD_ACTION_TO_WKWEBVIEW - (void)_lookupForWebView:(id)sender { RetainPtr view = self; _page->getSelectionContext([view](const String& selectedText, const String& textBefore, const String& textAfter, WebKit::CallbackBase::Error error) { if (error != WebKit::CallbackBase::Error::None) return; if (!selectedText) return; auto& editorState = view->_page->editorState(); auto& postLayoutData = editorState.postLayoutData(); CGRect presentationRect; if (editorState.selectionIsRange && !postLayoutData.selectionRects.isEmpty()) presentationRect = postLayoutData.selectionRects[0].rect(); else presentationRect = postLayoutData.caretRectAtStart; String selectionContext = textBefore + selectedText + textAfter; NSRange selectedRangeInContext = NSMakeRange(textBefore.length(), selectedText.length()); if (auto textSelectionAssistant = view->_textSelectionAssistant) [textSelectionAssistant lookup:selectionContext withRange:selectedRangeInContext fromRect:presentationRect]; }); } - (void)_shareForWebView:(id)sender { RetainPtr view = self; _page->getSelectionOrContentsAsString([view](const String& string, WebKit::CallbackBase::Error error) { if (error != WebKit::CallbackBase::Error::None) return; if (!view->_textSelectionAssistant || !string || view->_page->editorState().isMissingPostLayoutData) return; auto& selectionRects = view->_page->editorState().postLayoutData().selectionRects; if (selectionRects.isEmpty()) return; [view->_textSelectionAssistant showShareSheetFor:string fromRect:selectionRects.first().rect()]; }); } - (void)_addShortcutForWebView:(id)sender { if (_textSelectionAssistant) [_textSelectionAssistant showTextServiceFor:[self selectedText] fromRect:_page->editorState().postLayoutData().selectionRects[0].rect()]; } - (NSString *)selectedText { return (NSString *)_page->editorState().postLayoutData().wordAtSelection; } - (void)makeTextWritingDirectionNaturalForWebView:(id)sender { // Match platform behavior on iOS as well as legacy WebKit behavior by modifying the // base (paragraph) writing direction rather than the inline direction. _page->setBaseWritingDirection(WebCore::WritingDirection::Natural); } - (void)makeTextWritingDirectionLeftToRightForWebView:(id)sender { _page->setBaseWritingDirection(WebCore::WritingDirection::LeftToRight); } - (void)makeTextWritingDirectionRightToLeftForWebView:(id)sender { _page->setBaseWritingDirection(WebCore::WritingDirection::RightToLeft); } - (BOOL)isReplaceAllowed { return _page->editorState().postLayoutData().isReplaceAllowed; } - (void)replaceText:(NSString *)text withText:(NSString *)word { _page->replaceSelectedText(text, word); } - (void)selectWordBackward { _page->selectWordBackward(); } - (void)_promptForReplaceForWebView:(id)sender { const auto& wordAtSelection = _page->editorState().postLayoutData().wordAtSelection; if (wordAtSelection.isEmpty()) return; [_textSelectionAssistant scheduleReplacementsForText:wordAtSelection]; } - (void)_transliterateChineseForWebView:(id)sender { [_textSelectionAssistant scheduleChineseTransliterationForText:_page->editorState().postLayoutData().wordAtSelection]; } - (void)replaceForWebView:(id)sender { [[UIKeyboardImpl sharedInstance] replaceText:sender]; } #define WEBCORE_COMMAND_FOR_WEBVIEW(command) \ - (void)_ ## command ## ForWebView:(id)sender { _page->executeEditCommand(#command ## _s); } \ - (void)command ## ForWebView:(id)sender { [self _ ## command ## ForWebView:sender]; } WEBCORE_COMMAND_FOR_WEBVIEW(insertOrderedList); WEBCORE_COMMAND_FOR_WEBVIEW(insertUnorderedList); WEBCORE_COMMAND_FOR_WEBVIEW(insertNestedOrderedList); WEBCORE_COMMAND_FOR_WEBVIEW(insertNestedUnorderedList); WEBCORE_COMMAND_FOR_WEBVIEW(indent); WEBCORE_COMMAND_FOR_WEBVIEW(outdent); WEBCORE_COMMAND_FOR_WEBVIEW(alignLeft); WEBCORE_COMMAND_FOR_WEBVIEW(alignRight); WEBCORE_COMMAND_FOR_WEBVIEW(alignCenter); WEBCORE_COMMAND_FOR_WEBVIEW(alignJustified); WEBCORE_COMMAND_FOR_WEBVIEW(pasteAndMatchStyle); #undef WEBCORE_COMMAND_FOR_WEBVIEW - (void)_increaseListLevelForWebView:(id)sender { _page->increaseListLevel(); } - (void)_decreaseListLevelForWebView:(id)sender { _page->decreaseListLevel(); } - (void)_changeListTypeForWebView:(id)sender { _page->changeListType(); } - (void)_toggleStrikeThroughForWebView:(id)sender { _page->executeEditCommand("StrikeThrough"_s); } - (void)increaseSizeForWebView:(id)sender { _page->executeEditCommand("FontSizeDelta"_s, "1"_s); } - (void)decreaseSizeForWebView:(id)sender { _page->executeEditCommand("FontSizeDelta"_s, "-1"_s); } - (void)_setFontForWebView:(UIFont *)font sender:(id)sender { WebCore::FontChanges changes; changes.setFontFamily(font.familyName); changes.setFontName(font.fontName); changes.setFontSize(font.pointSize); changes.setBold(font.traits & UIFontTraitBold); changes.setItalic(font.traits & UIFontTraitItalic); _page->changeFont(WTFMove(changes)); } - (void)_setFontSizeForWebView:(CGFloat)fontSize sender:(id)sender { WebCore::FontChanges changes; changes.setFontSize(fontSize); _page->changeFont(WTFMove(changes)); } - (void)_setTextColorForWebView:(UIColor *)color sender:(id)sender { _page->executeEditCommand("ForeColor"_s, WebCore::Color(color.CGColor).serialized()); } - (void)toggleStrikeThroughForWebView:(id)sender { [self _toggleStrikeThroughForWebView:sender]; } - (NSDictionary *)textStylingAtPosition:(UITextPosition *)position inDirection:(UITextStorageDirection)direction { if (!position || !_page->editorState().isContentRichlyEditable) return nil; NSMutableDictionary* result = [NSMutableDictionary dictionary]; auto typingAttributes = _page->editorState().postLayoutData().typingAttributes; CTFontSymbolicTraits symbolicTraits = 0; if (typingAttributes & WebKit::AttributeBold) symbolicTraits |= kCTFontBoldTrait; if (typingAttributes & WebKit::AttributeItalics) symbolicTraits |= kCTFontTraitItalic; // We chose a random font family and size. // What matters are the traits but the caller expects a font object // in the dictionary for NSFontAttributeName. RetainPtr fontDescriptor = adoptCF(CTFontDescriptorCreateWithNameAndSize(CFSTR("Helvetica"), 10)); if (symbolicTraits) fontDescriptor = adoptCF(CTFontDescriptorCreateCopyWithSymbolicTraits(fontDescriptor.get(), symbolicTraits, symbolicTraits)); RetainPtr font = adoptCF(CTFontCreateWithFontDescriptor(fontDescriptor.get(), 10, nullptr)); if (font) [result setObject:(id)font.get() forKey:NSFontAttributeName]; if (typingAttributes & WebKit::AttributeUnderline) [result setObject:[NSNumber numberWithInt:NSUnderlineStyleSingle] forKey:NSUnderlineStyleAttributeName]; return result; } - (UIColor *)insertionPointColor { return [self.textInputTraits insertionPointColor]; } - (UIColor *)selectionBarColor { return [self.textInputTraits selectionBarColor]; } - (UIColor *)selectionHighlightColor { return [self.textInputTraits selectionHighlightColor]; } - (void)_updateInteractionTintColor { UIColor *tintColor = ^{ if (!_webView.configuration._textInteractionGesturesEnabled) return [UIColor clearColor]; if (!_page->editorState().isMissingPostLayoutData) { WebCore::Color caretColor = _page->editorState().postLayoutData().caretColor; if (caretColor.isValid()) return [UIColor colorWithCGColor:cachedCGColor(caretColor)]; } return [self _inheritedInteractionTintColor]; }(); [_traits _setColorsToMatchTintColor:tintColor]; } - (void)tintColorDidChange { [super tintColorDidChange]; BOOL shouldUpdateTextSelection = self.isFirstResponder && [self canShowNonEmptySelectionView]; if (shouldUpdateTextSelection) [_textSelectionAssistant deactivateSelection]; [self _updateInteractionTintColor]; if (shouldUpdateTextSelection) [_textSelectionAssistant activateSelection]; } - (BOOL)canPerformAction:(SEL)action withSender:(id)sender { if (_domPasteRequestHandler) return action == @selector(paste:); // These are UIKit IPI selectors. We don't want to forward them to the web view. auto editorState = _page->editorState(); if (action == @selector(_moveDown:withHistory:) || action == @selector(_moveLeft:withHistory:) || action == @selector(_moveRight:withHistory:) || action == @selector(_moveToEndOfDocument:withHistory:) || action == @selector(_moveToEndOfLine:withHistory:) || action == @selector(_moveToEndOfParagraph:withHistory:) || action == @selector(_moveToEndOfWord:withHistory:) || action == @selector(_moveToStartOfDocument:withHistory:) || action == @selector(_moveToStartOfLine:withHistory:) || action == @selector(_moveToStartOfParagraph:withHistory:) || action == @selector(_moveToStartOfWord:withHistory:) || action == @selector(_moveUp:withHistory:)) return !editorState.selectionIsNone; if (action == @selector(_deleteByWord) || action == @selector(_deleteForwardAndNotify:) || action == @selector(_deleteToEndOfParagraph) || action == @selector(_deleteToStartOfLine) || action == @selector(_transpose)) return editorState.isContentEditable; return [_webView canPerformAction:action withSender:sender]; } - (BOOL)canPerformActionForWebView:(SEL)action withSender:(id)sender { if (_domPasteRequestHandler) return action == @selector(paste:); if (action == @selector(_nextAccessoryTab:)) return hasFocusedElement(_focusedElementInformation) && _focusedElementInformation.hasNextNode; if (action == @selector(_previousAccessoryTab:)) return hasFocusedElement(_focusedElementInformation) && _focusedElementInformation.hasPreviousNode; auto editorState = _page->editorState(); if (action == @selector(_showTextStyleOptions:)) return editorState.isContentRichlyEditable && editorState.selectionIsRange && !_showingTextStyleOptions; if (_showingTextStyleOptions) return (action == @selector(toggleBoldface:) || action == @selector(toggleItalics:) || action == @selector(toggleUnderline:)); // FIXME: Some of the following checks should be removed once internal clients move to the underscore-prefixed versions. if (action == @selector(toggleBoldface:) || action == @selector(toggleItalics:) || action == @selector(toggleUnderline:) || action == @selector(_toggleStrikeThrough:) || action == @selector(_alignLeft:) || action == @selector(_alignRight:) || action == @selector(_alignCenter:) || action == @selector(_alignJustified:) || action == @selector(_setTextColor:sender:) || action == @selector(_setFont:sender:) || action == @selector(_setFontSize:sender:) || action == @selector(_insertOrderedList:) || action == @selector(_insertUnorderedList:) || action == @selector(_insertNestedOrderedList:) || action == @selector(_insertNestedUnorderedList:) || action == @selector(_increaseListLevel:) || action == @selector(_decreaseListLevel:) || action == @selector(_changeListType:) || action == @selector(_indent:) || action == @selector(_outdent:) || action == @selector(increaseSize:) || action == @selector(decreaseSize:) || action == @selector(makeTextWritingDirectionNatural:)) { // FIXME: This should be more nuanced in the future, rather than returning YES for all richly editable areas. For instance, outdent: should be disabled when the selection is already // at the outermost indentation level. return editorState.isContentRichlyEditable; } if (action == @selector(cut:)) return !editorState.isInPasswordField && editorState.isContentEditable && editorState.selectionIsRange; if (action == @selector(paste:) || action == @selector(_pasteAsQuotation:) || action == @selector(_pasteAndMatchStyle:) || action == @selector(pasteAndMatchStyle:)) { if (editorState.selectionIsNone || !editorState.isContentEditable) return NO; UIPasteboard *pasteboard = [UIPasteboard generalPasteboard]; NSArray *types = [self supportedPasteboardTypesForCurrentSelection]; NSIndexSet *indices = [NSIndexSet indexSetWithIndexesInRange:NSMakeRange(0, [pasteboard numberOfItems])]; if ([pasteboard containsPasteboardTypes:types inItemSet:indices]) return YES; #if PLATFORM(IOS) if (editorState.isContentRichlyEditable && _webView.configuration._attachmentElementEnabled) { for (NSItemProvider *itemProvider in pasteboard.itemProviders) { auto preferredPresentationStyle = itemProvider.preferredPresentationStyle; if (preferredPresentationStyle == UIPreferredPresentationStyleInline) continue; if (preferredPresentationStyle == UIPreferredPresentationStyleUnspecified && !itemProvider.suggestedName.length) continue; if (itemProvider.web_fileUploadContentTypes.count) return YES; } } #endif // PLATFORM(IOS) auto focusedDocumentOrigin = editorState.originIdentifierForPasteboard; if (focusedDocumentOrigin.isEmpty()) return NO; NSArray *allCustomPasteboardData = [pasteboard dataForPasteboardType:@(WebCore::PasteboardCustomData::cocoaType()) inItemSet:indices]; for (NSData *data in allCustomPasteboardData) { auto buffer = WebCore::SharedBuffer::create(data); if (WebCore::PasteboardCustomData::fromSharedBuffer(buffer.get()).origin == focusedDocumentOrigin) return YES; } return NO; } if (action == @selector(copy:)) { if (editorState.isInPasswordField) return NO; return editorState.selectionIsRange; } if (action == @selector(_define:)) { if (editorState.isInPasswordField || !editorState.selectionIsRange) return NO; NSUInteger textLength = editorState.postLayoutData().selectedTextLength; // FIXME: We should be calling UIReferenceLibraryViewController to check if the length is // acceptable, but the interface takes a string. // if (!textLength || textLength > 200) return NO; #if !PLATFORM(MACCATALYST) if ([[getMCProfileConnectionClass() sharedConnection] effectiveBoolValueForSetting:getMCFeatureDefinitionLookupAllowed()] == MCRestrictedBoolExplicitNo) return NO; #endif return YES; } if (action == @selector(_lookup:)) { if (editorState.isInPasswordField) return NO; #if !PLATFORM(MACCATALYST) if ([[getMCProfileConnectionClass() sharedConnection] effectiveBoolValueForSetting:getMCFeatureDefinitionLookupAllowed()] == MCRestrictedBoolExplicitNo) return NO; #endif return editorState.selectionIsRange; } if (action == @selector(_share:)) { if (editorState.isInPasswordField || !editorState.selectionIsRange) return NO; return editorState.postLayoutData().selectedTextLength > 0; } if (action == @selector(_addShortcut:)) { if (editorState.isInPasswordField || !editorState.selectionIsRange) return NO; NSString *selectedText = [self selectedText]; if (![selectedText length]) return NO; if (!UIKeyboardEnabledInputModesAllowOneToManyShortcuts()) return NO; if (![selectedText _containsCJScripts]) return NO; return YES; } if (action == @selector(_promptForReplace:)) { if (!editorState.selectionIsRange || !editorState.postLayoutData().isReplaceAllowed || ![[UIKeyboardImpl activeInstance] autocorrectSpellingEnabled]) return NO; if ([[self selectedText] _containsCJScriptsOnly]) return NO; return YES; } if (action == @selector(_transliterateChinese:)) { if (!editorState.selectionIsRange || !editorState.postLayoutData().isReplaceAllowed || ![[UIKeyboardImpl activeInstance] autocorrectSpellingEnabled]) return NO; return UIKeyboardEnabledInputModesAllowChineseTransliterationForText([self selectedText]); } if (action == @selector(select:)) { // Disable select in password fields so that you can't see word boundaries. return !editorState.isInPasswordField && [self hasContent] && !editorState.selectionIsNone && !editorState.selectionIsRange; } if (action == @selector(selectAll:)) { // By platform convention we don't show Select All in the callout menu for a range selection. if ([sender isKindOfClass:UIMenuController.class]) return !editorState.selectionIsNone && !editorState.selectionIsRange; #if USE(UIKIT_KEYBOARD_ADDITIONS) return YES; #else return !editorState.selectionIsNone && self.hasContent; #endif } if (action == @selector(replace:)) return editorState.isContentEditable && !editorState.isInPasswordField; if (action == @selector(makeTextWritingDirectionLeftToRight:) || action == @selector(makeTextWritingDirectionRightToLeft:)) { if (!editorState.isContentEditable) return NO; auto baseWritingDirection = editorState.postLayoutData().baseWritingDirection; if (baseWritingDirection == WebCore::WritingDirection::LeftToRight && !UIKeyboardIsRightToLeftInputModeActive()) { // A keyboard is considered "active" if it is available for the user to switch to. As such, this check prevents // text direction actions from showing up in the case where a user has only added left-to-right keyboards, and // is also not editing right-to-left content. return NO; } if (action == @selector(makeTextWritingDirectionLeftToRight:)) return baseWritingDirection != WebCore::WritingDirection::LeftToRight; return baseWritingDirection != WebCore::WritingDirection::RightToLeft; } return [super canPerformAction:action withSender:sender]; } - (id)targetForAction:(SEL)action withSender:(id)sender { return [_webView targetForAction:action withSender:sender]; } - (id)targetForActionForWebView:(SEL)action withSender:(id)sender { return [super targetForAction:action withSender:sender]; } - (void)_willHideMenu:(NSNotification *)notification { [self _handleDOMPasteRequestWithResult:WebCore::DOMPasteAccessResponse::DeniedForGesture]; } - (void)_didHideMenu:(NSNotification *)notification { _showingTextStyleOptions = NO; [_textSelectionAssistant hideTextStyleOptions]; } - (void)_keyboardDidRequestDismissal:(NSNotification *)notification { if (![self isFirstResponder]) return; _keyboardDidRequestDismissal = YES; } - (void)copyForWebView:(id)sender { _page->executeEditCommand("copy"_s); } - (void)cutForWebView:(id)sender { [self executeEditCommandWithCallback:@"cut"]; } - (void)pasteForWebView:(id)sender { if (sender == UIMenuController.sharedMenuController && [self _handleDOMPasteRequestWithResult:WebCore::DOMPasteAccessResponse::GrantedForGesture]) return; _page->executeEditCommand("paste"_s); } - (void)_pasteAsQuotationForWebView:(id)sender { _page->executeEditCommand("PasteAsQuotation"_s); } - (void)selectForWebView:(id)sender { [_textSelectionAssistant selectWord]; // We cannot use selectWord command, because we want to be able to select the word even when it is the last in the paragraph. _page->extendSelection(WebCore::WordGranularity); } - (void)selectAllForWebView:(id)sender { [_textSelectionAssistant selectAll:sender]; _page->selectAll(); } - (BOOL)shouldSynthesizeKeyEvents { if (_focusedElementInformation.shouldSynthesizeKeyEventsForEditing && self.hasHiddenContentEditable) return true; return false; } - (void)toggleBoldfaceForWebView:(id)sender { if (!_page->editorState().isContentRichlyEditable) return; [self executeEditCommandWithCallback:@"toggleBold"]; if (self.shouldSynthesizeKeyEvents) _page->generateSyntheticEditingCommand(WebKit::SyntheticEditingCommandType::ToggleBoldface); } - (void)toggleItalicsForWebView:(id)sender { if (!_page->editorState().isContentRichlyEditable) return; [self executeEditCommandWithCallback:@"toggleItalic"]; if (self.shouldSynthesizeKeyEvents) _page->generateSyntheticEditingCommand(WebKit::SyntheticEditingCommandType::ToggleItalic); } - (void)toggleUnderlineForWebView:(id)sender { if (!_page->editorState().isContentRichlyEditable) return; [self executeEditCommandWithCallback:@"toggleUnderline"]; if (self.shouldSynthesizeKeyEvents) _page->generateSyntheticEditingCommand(WebKit::SyntheticEditingCommandType::ToggleUnderline); } - (void)_showTextStyleOptionsForWebView:(id)sender { _showingTextStyleOptions = YES; [_textSelectionAssistant showTextStyleOptions]; } - (void)_showDictionary:(NSString *)text { CGRect presentationRect = _page->editorState().postLayoutData().selectionRects[0].rect(); if (_textSelectionAssistant) [_textSelectionAssistant showDictionaryFor:text fromRect:presentationRect]; } - (void)_defineForWebView:(id)sender { #if !PLATFORM(MACCATALYST) if ([[getMCProfileConnectionClass() sharedConnection] effectiveBoolValueForSetting:getMCFeatureDefinitionLookupAllowed()] == MCRestrictedBoolExplicitNo) return; #endif RetainPtr view = self; _page->getSelectionOrContentsAsString([view](const String& string, WebKit::CallbackBase::Error error) { if (error != WebKit::CallbackBase::Error::None) return; if (!string) return; [view _showDictionary:string]; }); } - (void)accessibilityRetrieveSpeakSelectionContent { RetainPtr view = self; RetainPtr webView = _webView; _page->getSelectionOrContentsAsString([view, webView](const String& string, WebKit::CallbackBase::Error error) { if (error != WebKit::CallbackBase::Error::None) return; [webView _accessibilityDidGetSpeakSelectionContent:string]; if ([view respondsToSelector:@selector(accessibilitySpeakSelectionSetContent:)]) [view accessibilitySpeakSelectionSetContent:string]; }); } - (void)_accessibilityRetrieveRectsEnclosingSelectionOffset:(NSInteger)offset withGranularity:(UITextGranularity)granularity { RetainPtr view = self; _page->requestRectsForGranularityWithSelectionOffset(toWKTextGranularity(granularity), offset , [view, offset, granularity](const Vector& selectionRects, WebKit::CallbackBase::Error error) { if (error != WebKit::CallbackBase::Error::None) return; if ([view respondsToSelector:@selector(_accessibilityDidGetSelectionRects:withGranularity:atOffset:)]) [view _accessibilityDidGetSelectionRects:[view webSelectionRectsForSelectionRects:selectionRects] withGranularity:granularity atOffset:offset]; }); } - (void)_accessibilityRetrieveRectsAtSelectionOffset:(NSInteger)offset withText:(NSString *)text { [self _accessibilityRetrieveRectsAtSelectionOffset:offset withText:text completionHandler:nil]; } - (void)_accessibilityRetrieveRectsAtSelectionOffset:(NSInteger)offset withText:(NSString *)text completionHandler:(void (^)(const Vector& rects))completionHandler { RetainPtr view = self; _page->requestRectsAtSelectionOffsetWithText(offset, text, [view, offset, capturedCompletionHandler = makeBlockPtr(completionHandler)](const Vector& selectionRects, WebKit::CallbackBase::Error error) { if (capturedCompletionHandler) capturedCompletionHandler(selectionRects); if (error != WebKit::CallbackBase::Error::None) return; if ([view respondsToSelector:@selector(_accessibilityDidGetSelectionRects:withGranularity:atOffset:)]) [view _accessibilityDidGetSelectionRects:[view webSelectionRectsForSelectionRects:selectionRects] withGranularity:UITextGranularityWord atOffset:offset]; }); } - (void)_accessibilityStoreSelection { _page->storeSelectionForAccessibility(true); } - (void)_accessibilityClearSelection { _page->storeSelectionForAccessibility(false); } - (BOOL)_handleDOMPasteRequestWithResult:(WebCore::DOMPasteAccessResponse)response { if (auto pasteHandler = WTFMove(_domPasteRequestHandler)) { [self hideGlobalMenuController]; pasteHandler(response); return YES; } return NO; } - (void)_willPerformAction:(SEL)action sender:(id)sender { if (action != @selector(paste:)) [self _handleDOMPasteRequestWithResult:WebCore::DOMPasteAccessResponse::DeniedForGesture]; } - (void)_didPerformAction:(SEL)action sender:(id)sender { if (action == @selector(paste:)) [self _handleDOMPasteRequestWithResult:WebCore::DOMPasteAccessResponse::DeniedForGesture]; } // UIWKInteractionViewProtocol static inline WebKit::GestureType toGestureType(UIWKGestureType gestureType) { switch (gestureType) { case UIWKGestureLoupe: return WebKit::GestureType::Loupe; case UIWKGestureOneFingerTap: return WebKit::GestureType::OneFingerTap; case UIWKGestureTapAndAHalf: return WebKit::GestureType::TapAndAHalf; case UIWKGestureDoubleTap: return WebKit::GestureType::DoubleTap; case UIWKGestureTapAndHalf: return WebKit::GestureType::TapAndHalf; case UIWKGestureDoubleTapInUneditable: return WebKit::GestureType::DoubleTapInUneditable; case UIWKGestureOneFingerTapInUneditable: return WebKit::GestureType::OneFingerTapInUneditable; case UIWKGestureOneFingerTapSelectsAll: return WebKit::GestureType::OneFingerTapSelectsAll; case UIWKGestureOneFingerDoubleTap: return WebKit::GestureType::OneFingerDoubleTap; case UIWKGestureOneFingerTripleTap: return WebKit::GestureType::OneFingerTripleTap; case UIWKGestureTwoFingerSingleTap: return WebKit::GestureType::TwoFingerSingleTap; case UIWKGestureTwoFingerRangedSelectGesture: return WebKit::GestureType::TwoFingerRangedSelectGesture; case UIWKGestureTapOnLinkWithGesture: return WebKit::GestureType::TapOnLinkWithGesture; case UIWKGesturePhraseBoundary: return WebKit::GestureType::PhraseBoundary; case UIWKGestureMakeWebSelection: ASSERT_NOT_REACHED(); return WebKit::GestureType::Loupe; } ASSERT_NOT_REACHED(); return WebKit::GestureType::Loupe; } static inline UIWKGestureType toUIWKGestureType(WebKit::GestureType gestureType) { switch (gestureType) { case WebKit::GestureType::Loupe: return UIWKGestureLoupe; case WebKit::GestureType::OneFingerTap: return UIWKGestureOneFingerTap; case WebKit::GestureType::TapAndAHalf: return UIWKGestureTapAndAHalf; case WebKit::GestureType::DoubleTap: return UIWKGestureDoubleTap; case WebKit::GestureType::TapAndHalf: return UIWKGestureTapAndHalf; case WebKit::GestureType::DoubleTapInUneditable: return UIWKGestureDoubleTapInUneditable; case WebKit::GestureType::OneFingerTapInUneditable: return UIWKGestureOneFingerTapInUneditable; case WebKit::GestureType::OneFingerTapSelectsAll: return UIWKGestureOneFingerTapSelectsAll; case WebKit::GestureType::OneFingerDoubleTap: return UIWKGestureOneFingerDoubleTap; case WebKit::GestureType::OneFingerTripleTap: return UIWKGestureOneFingerTripleTap; case WebKit::GestureType::TwoFingerSingleTap: return UIWKGestureTwoFingerSingleTap; case WebKit::GestureType::TwoFingerRangedSelectGesture: return UIWKGestureTwoFingerRangedSelectGesture; case WebKit::GestureType::TapOnLinkWithGesture: return UIWKGestureTapOnLinkWithGesture; case WebKit::GestureType::PhraseBoundary: return UIWKGesturePhraseBoundary; } } static inline WebKit::SelectionTouch toSelectionTouch(UIWKSelectionTouch touch) { switch (touch) { case UIWKSelectionTouchStarted: return WebKit::SelectionTouch::Started; case UIWKSelectionTouchMoved: return WebKit::SelectionTouch::Moved; case UIWKSelectionTouchEnded: return WebKit::SelectionTouch::Ended; case UIWKSelectionTouchEndedMovingForward: return WebKit::SelectionTouch::EndedMovingForward; case UIWKSelectionTouchEndedMovingBackward: return WebKit::SelectionTouch::EndedMovingBackward; case UIWKSelectionTouchEndedNotMoving: return WebKit::SelectionTouch::EndedNotMoving; } ASSERT_NOT_REACHED(); return WebKit::SelectionTouch::Ended; } static inline UIWKSelectionTouch toUIWKSelectionTouch(WebKit::SelectionTouch touch) { switch (touch) { case WebKit::SelectionTouch::Started: return UIWKSelectionTouchStarted; case WebKit::SelectionTouch::Moved: return UIWKSelectionTouchMoved; case WebKit::SelectionTouch::Ended: return UIWKSelectionTouchEnded; case WebKit::SelectionTouch::EndedMovingForward: return UIWKSelectionTouchEndedMovingForward; case WebKit::SelectionTouch::EndedMovingBackward: return UIWKSelectionTouchEndedMovingBackward; case WebKit::SelectionTouch::EndedNotMoving: return UIWKSelectionTouchEndedNotMoving; } } static inline WebKit::GestureRecognizerState toGestureRecognizerState(UIGestureRecognizerState state) { switch (state) { case UIGestureRecognizerStatePossible: return WebKit::GestureRecognizerState::Possible; case UIGestureRecognizerStateBegan: return WebKit::GestureRecognizerState::Began; case UIGestureRecognizerStateChanged: return WebKit::GestureRecognizerState::Changed; case UIGestureRecognizerStateCancelled: return WebKit::GestureRecognizerState::Cancelled; case UIGestureRecognizerStateEnded: return WebKit::GestureRecognizerState::Ended; case UIGestureRecognizerStateFailed: return WebKit::GestureRecognizerState::Failed; } } static inline UIGestureRecognizerState toUIGestureRecognizerState(WebKit::GestureRecognizerState state) { switch (state) { case WebKit::GestureRecognizerState::Possible: return UIGestureRecognizerStatePossible; case WebKit::GestureRecognizerState::Began: return UIGestureRecognizerStateBegan; case WebKit::GestureRecognizerState::Changed: return UIGestureRecognizerStateChanged; case WebKit::GestureRecognizerState::Cancelled: return UIGestureRecognizerStateCancelled; case WebKit::GestureRecognizerState::Ended: return UIGestureRecognizerStateEnded; case WebKit::GestureRecognizerState::Failed: return UIGestureRecognizerStateFailed; } } static inline UIWKSelectionFlags toUIWKSelectionFlags(WebKit::SelectionFlags flags) { NSInteger uiFlags = UIWKNone; if (flags & WebKit::WordIsNearTap) uiFlags |= UIWKWordIsNearTap; if (flags & WebKit::PhraseBoundaryChanged) uiFlags |= UIWKPhraseBoundaryChanged; if (flags & WebKit::WordNearTapIsMisspelled) uiFlags |= UIWKWordNearTapIsMisspelled; return static_cast(uiFlags); } static inline WebCore::TextGranularity toWKTextGranularity(UITextGranularity granularity) { switch (granularity) { case UITextGranularityCharacter: return WebCore::CharacterGranularity; case UITextGranularityWord: return WebCore::WordGranularity; case UITextGranularitySentence: return WebCore::SentenceGranularity; case UITextGranularityParagraph: return WebCore::ParagraphGranularity; case UITextGranularityLine: return WebCore::LineGranularity; case UITextGranularityDocument: return WebCore::DocumentGranularity; } } static inline WebCore::SelectionDirection toWKSelectionDirection(UITextDirection direction) { switch (direction) { case UITextLayoutDirectionDown: case UITextLayoutDirectionRight: return WebCore::DirectionRight; case UITextLayoutDirectionUp: case UITextLayoutDirectionLeft: return WebCore::DirectionLeft; default: // UITextDirection is not an enum, but we only want to accept values from UITextLayoutDirection. ASSERT_NOT_REACHED(); return WebCore::DirectionRight; } } static void selectionChangedWithGesture(WKContentView *view, const WebCore::IntPoint& point, uint32_t gestureType, uint32_t gestureState, uint32_t flags, WebKit::CallbackBase::Error error) { if (error != WebKit::CallbackBase::Error::None) { ASSERT_NOT_REACHED(); return; } [(UIWKTextInteractionAssistant *)[view interactionAssistant] selectionChangedWithGestureAt:(CGPoint)point withGesture:toUIWKGestureType((WebKit::GestureType)gestureType) withState:toUIGestureRecognizerState(static_cast(gestureState)) withFlags:(toUIWKSelectionFlags((WebKit::SelectionFlags)flags))]; } static void selectionChangedWithTouch(WKContentView *view, const WebCore::IntPoint& point, uint32_t touch, uint32_t flags, WebKit::CallbackBase::Error error) { if (error != WebKit::CallbackBase::Error::None) { ASSERT_NOT_REACHED(); return; } [(UIWKTextInteractionAssistant *)[view interactionAssistant] selectionChangedWithTouchAt:(CGPoint)point withSelectionTouch:toUIWKSelectionTouch((WebKit::SelectionTouch)touch) withFlags:static_cast(flags)]; } - (BOOL)_isInteractingWithFocusedElement { return hasFocusedElement(_focusedElementInformation); } - (void)changeSelectionWithGestureAt:(CGPoint)point withGesture:(UIWKGestureType)gestureType withState:(UIGestureRecognizerState)state { [self changeSelectionWithGestureAt:point withGesture:gestureType withState:state withFlags:UIWKNone]; } - (void)changeSelectionWithGestureAt:(CGPoint)point withGesture:(UIWKGestureType)gestureType withState:(UIGestureRecognizerState)state withFlags:(UIWKSelectionFlags)flags { _usingGestureForSelection = YES; _processingChangeSelectionWithGestureCount++; _page->selectWithGesture(WebCore::IntPoint(point), WebCore::CharacterGranularity, static_cast(toGestureType(gestureType)), static_cast(toGestureRecognizerState(state)), [self _isInteractingWithFocusedElement], [self, state, flags](const WebCore::IntPoint& point, uint32_t gestureType, uint32_t gestureState, uint32_t innerFlags, WebKit::CallbackBase::Error error) { selectionChangedWithGesture(self, point, gestureType, gestureState, flags | innerFlags, error); if (state == UIGestureRecognizerStateEnded || state == UIGestureRecognizerStateCancelled) _usingGestureForSelection = NO; _processingChangeSelectionWithGestureCount--; }); } - (void)changeSelectionWithTouchAt:(CGPoint)point withSelectionTouch:(UIWKSelectionTouch)touch baseIsStart:(BOOL)baseIsStart withFlags:(UIWKSelectionFlags)flags { _usingGestureForSelection = YES; _page->updateSelectionWithTouches(WebCore::IntPoint(point), static_cast(toSelectionTouch(touch)), baseIsStart, [self, flags](const WebCore::IntPoint& point, uint32_t touch, uint32_t innerFlags, WebKit::CallbackBase::Error error) { selectionChangedWithTouch(self, point, touch, flags | innerFlags, error); if (touch != UIWKSelectionTouchStarted && touch != UIWKSelectionTouchMoved) _usingGestureForSelection = NO; }); } - (void)changeSelectionWithTouchesFrom:(CGPoint)from to:(CGPoint)to withGesture:(UIWKGestureType)gestureType withState:(UIGestureRecognizerState)gestureState { _usingGestureForSelection = YES; _page->selectWithTwoTouches(WebCore::IntPoint(from), WebCore::IntPoint(to), static_cast(toGestureType(gestureType)), static_cast(toGestureRecognizerState(gestureState)), [self](const WebCore::IntPoint& point, uint32_t gestureType, uint32_t gestureState, uint32_t flags, WebKit::CallbackBase::Error error) { selectionChangedWithGesture(self, point, gestureType, gestureState, flags, error); if (gestureState == UIGestureRecognizerStateEnded || gestureState == UIGestureRecognizerStateCancelled) _usingGestureForSelection = NO; }); } - (void)moveByOffset:(NSInteger)offset { if (!offset) return; [self beginSelectionChange]; RetainPtr view = self; _page->moveSelectionByOffset(offset, [view](WebKit::CallbackBase::Error) { [view endSelectionChange]; }); } - (const WebKit::WKAutoCorrectionData&)autocorrectionData { return _autocorrectionData; } // The completion handler can pass nil if input does not match the actual text preceding the insertion point. - (void)requestAutocorrectionRectsForString:(NSString *)input withCompletionHandler:(void (^)(UIWKAutocorrectionRects *rectsForInput))completionHandler { if (!completionHandler) { [NSException raise:NSInvalidArgumentException format:@"Expected a nonnull completion handler in %s.", __PRETTY_FUNCTION__]; return; } if (!input || ![input length]) { completionHandler(nil); return; } _page->requestAutocorrectionData(input, [view = retainPtr(self), completion = makeBlockPtr(completionHandler)](auto data) { CGRect firstRect; CGRect lastRect; auto& rects = data.textRects; if (rects.isEmpty()) { firstRect = CGRectZero; lastRect = CGRectZero; } else { firstRect = rects.first(); lastRect = rects.last(); } view->_autocorrectionData.font = data.font; view->_autocorrectionData.textFirstRect = firstRect; view->_autocorrectionData.textLastRect = lastRect; completion(!rects.isEmpty() ? [WKAutocorrectionRects autocorrectionRectsWithFirstCGRect:firstRect lastCGRect:lastRect] : nil); }); } - (void)requestRectsToEvadeForSelectionCommandsWithCompletionHandler:(void(^)(NSArray *rects))completionHandler { if (!completionHandler) { [NSException raise:NSInvalidArgumentException format:@"Expected a nonnull completion handler in %s.", __PRETTY_FUNCTION__]; return; } if ([self _shouldSuppressSelectionCommands] || _webView._editable) { completionHandler(@[ ]); return; } if (_focusedElementInformation.elementType != WebKit::InputType::ContentEditable && _focusedElementInformation.elementType != WebKit::InputType::TextArea) { completionHandler(@[ ]); return; } // Give the page some time to present custom editing UI before attempting to detect and evade it. auto delayBeforeShowingCalloutBar = (0.25_s).nanoseconds(); dispatch_after(dispatch_time(DISPATCH_TIME_NOW, delayBeforeShowingCalloutBar), dispatch_get_main_queue(), [completion = makeBlockPtr(completionHandler), weakSelf = WeakObjCPtr(self)] () mutable { if (!weakSelf) { completion(@[ ]); return; } auto strongSelf = weakSelf.get(); if (!strongSelf->_page) { completion(@[ ]); return; } strongSelf->_page->requestEvasionRectsAboveSelection([completion = WTFMove(completion)] (auto& rects) { auto rectsAsValues = adoptNS([[NSMutableArray alloc] initWithCapacity:rects.size()]); for (auto& floatRect : rects) [rectsAsValues addObject:[NSValue valueWithCGRect:floatRect]]; completion(rectsAsValues.get()); }); }); } - (void)selectPositionAtPoint:(CGPoint)point completionHandler:(void (^)(void))completionHandler { _usingGestureForSelection = YES; UIWKSelectionCompletionHandler selectionHandler = [completionHandler copy]; RetainPtr view = self; _page->selectPositionAtPoint(WebCore::IntPoint(point), [self _isInteractingWithFocusedElement], [view, selectionHandler](WebKit::CallbackBase::Error error) { selectionHandler(); view->_usingGestureForSelection = NO; [selectionHandler release]; }); } - (void)selectPositionAtBoundary:(UITextGranularity)granularity inDirection:(UITextDirection)direction fromPoint:(CGPoint)point completionHandler:(void (^)(void))completionHandler { _usingGestureForSelection = YES; UIWKSelectionCompletionHandler selectionHandler = [completionHandler copy]; RetainPtr view = self; _page->selectPositionAtBoundaryWithDirection(WebCore::IntPoint(point), toWKTextGranularity(granularity), toWKSelectionDirection(direction), [self _isInteractingWithFocusedElement], [view, selectionHandler](WebKit::CallbackBase::Error error) { selectionHandler(); view->_usingGestureForSelection = NO; [selectionHandler release]; }); } - (void)moveSelectionAtBoundary:(UITextGranularity)granularity inDirection:(UITextDirection)direction completionHandler:(void (^)(void))completionHandler { _usingGestureForSelection = YES; UIWKSelectionCompletionHandler selectionHandler = [completionHandler copy]; RetainPtr view = self; _page->moveSelectionAtBoundaryWithDirection(toWKTextGranularity(granularity), toWKSelectionDirection(direction), [view, selectionHandler](WebKit::CallbackBase::Error error) { selectionHandler(); view->_usingGestureForSelection = NO; [selectionHandler release]; }); } - (void)selectTextWithGranularity:(UITextGranularity)granularity atPoint:(CGPoint)point completionHandler:(void (^)(void))completionHandler { _usingGestureForSelection = YES; ++_suppressNonEditableSingleTapTextInteractionCount; UIWKSelectionCompletionHandler selectionHandler = [completionHandler copy]; RetainPtr view = self; _page->selectTextWithGranularityAtPoint(WebCore::IntPoint(point), toWKTextGranularity(granularity), [self _isInteractingWithFocusedElement], [view, selectionHandler](WebKit::CallbackBase::Error error) { selectionHandler(); view->_usingGestureForSelection = NO; --view->_suppressNonEditableSingleTapTextInteractionCount; [selectionHandler release]; }); } - (void)beginSelectionInDirection:(UITextDirection)direction completionHandler:(void (^)(BOOL endIsMoving))completionHandler { UIWKSelectionWithDirectionCompletionHandler selectionHandler = [completionHandler copy]; _page->beginSelectionInDirection(toWKSelectionDirection(direction), [selectionHandler](bool endIsMoving, WebKit::CallbackBase::Error error) { selectionHandler(endIsMoving); [selectionHandler release]; }); } - (void)updateSelectionWithExtentPoint:(CGPoint)point completionHandler:(void (^)(BOOL endIsMoving))completionHandler { UIWKSelectionWithDirectionCompletionHandler selectionHandler = [completionHandler copy]; _page->updateSelectionWithExtentPoint(WebCore::IntPoint(point), [self _isInteractingWithFocusedElement], [selectionHandler](bool endIsMoving, WebKit::CallbackBase::Error error) { selectionHandler(endIsMoving); [selectionHandler release]; }); } - (void)updateSelectionWithExtentPoint:(CGPoint)point withBoundary:(UITextGranularity)granularity completionHandler:(void (^)(BOOL selectionEndIsMoving))completionHandler { UIWKSelectionWithDirectionCompletionHandler selectionHandler = [completionHandler copy]; ++_suppressNonEditableSingleTapTextInteractionCount; _page->updateSelectionWithExtentPointAndBoundary(WebCore::IntPoint(point), toWKTextGranularity(granularity), [self _isInteractingWithFocusedElement], [selectionHandler, protectedSelf = retainPtr(self)] (bool endIsMoving, WebKit::CallbackBase::Error error) { selectionHandler(endIsMoving); [selectionHandler release]; --protectedSelf->_suppressNonEditableSingleTapTextInteractionCount; }); } - (UTF32Char)_characterBeforeCaretSelection { return _page->editorState().postLayoutData().characterBeforeSelection; } - (UTF32Char)_characterInRelationToCaretSelection:(int)amount { switch (amount) { case 0: return _page->editorState().postLayoutData().characterAfterSelection; case -1: return _page->editorState().postLayoutData().characterBeforeSelection; case -2: return _page->editorState().postLayoutData().twoCharacterBeforeSelection; default: return 0; } } - (BOOL)_selectionAtDocumentStart { return !_page->editorState().postLayoutData().characterBeforeSelection; } - (CGRect)textFirstRect { return (_page->editorState().hasComposition) ? _page->editorState().firstMarkedRect : _autocorrectionData.textFirstRect; } - (CGRect)textLastRect { return (_page->editorState().hasComposition) ? _page->editorState().lastMarkedRect : _autocorrectionData.textLastRect; } - (void)replaceDictatedText:(NSString*)oldText withText:(NSString *)newText { _page->replaceDictatedText(oldText, newText); } - (void)requestDictationContext:(void (^)(NSString *selectedText, NSString *beforeText, NSString *afterText))completionHandler { UIWKDictationContextHandler dictationHandler = [completionHandler copy]; _page->requestDictationContext([dictationHandler](const String& selectedText, const String& beforeText, const String& afterText, WebKit::CallbackBase::Error) { dictationHandler(selectedText, beforeText, afterText); [dictationHandler release]; }); } // 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. - (void)applyAutocorrection:(NSString *)correction toString:(NSString *)input withCompletionHandler:(void (^)(UIWKAutocorrectionRects *rectsForCorrection))completionHandler { #if USE(UIKIT_KEYBOARD_ADDITIONS) if ([self _disableAutomaticKeyboardUI]) { if (completionHandler) completionHandler(nil); return; } #endif // FIXME: Remove the synchronous call when is fixed. const bool useSyncRequest = true; if (useSyncRequest) { if (completionHandler) completionHandler(_page->applyAutocorrection(correction, input) ? [WKAutocorrectionRects autocorrectionRectsWithFirstCGRect:_autocorrectionData.textFirstRect lastCGRect:_autocorrectionData.textLastRect] : nil); return; } _page->applyAutocorrection(correction, input, [view = retainPtr(self), completion = makeBlockPtr(completionHandler)](auto& string, auto error) { if (completion) completion(!string.isNull() ? [WKAutocorrectionRects autocorrectionRectsWithFirstCGRect:view->_autocorrectionData.textFirstRect lastCGRect:view->_autocorrectionData.textLastRect] : nil); }); } - (void)_invokePendingAutocorrectionContextHandler:(WKAutocorrectionContext *)context { if (auto handler = WTFMove(_pendingAutocorrectionContextHandler)) handler(context); } - (void)_cancelPendingAutocorrectionContextHandler { [self _invokePendingAutocorrectionContextHandler:WKAutocorrectionContext.emptyAutocorrectionContext]; } - (void)requestAutocorrectionContextWithCompletionHandler:(void (^)(UIWKAutocorrectionContext *autocorrectionContext))completionHandler { if (!completionHandler) { [NSException raise:NSInvalidArgumentException format:@"Expected a nonnull completion handler in %s.", __PRETTY_FUNCTION__]; return; } #if USE(UIKIT_KEYBOARD_ADDITIONS) if ([self _disableAutomaticKeyboardUI]) { completionHandler(WKAutocorrectionContext.emptyAutocorrectionContext); return; } #endif if (!_page->hasRunningProcess()) { completionHandler(WKAutocorrectionContext.emptyAutocorrectionContext); return; } // FIXME: Remove the synchronous call when is fixed. const bool useSyncRequest = true; if (useSyncRequest && _pendingAutocorrectionContextHandler) { completionHandler(WKAutocorrectionContext.emptyAutocorrectionContext); return; } _pendingAutocorrectionContextHandler = completionHandler; _page->requestAutocorrectionContext(); if (useSyncRequest) { _page->process().connection()->waitForAndDispatchImmediately(_page->pageID(), 1_s, IPC::WaitForOption::InterruptWaitingIfSyncMessageArrives); [self _cancelPendingAutocorrectionContextHandler]; } } - (void)_handleAutocorrectionContext:(const WebKit::WebAutocorrectionContext&)context { [self _invokePendingAutocorrectionContextHandler:[WKAutocorrectionContext autocorrectionContextWithWebContext:context]]; } - (void)_didStartProvisionalLoadForMainFrame { // Reset the double tap gesture recognizer to prevent any double click that is in the process of being recognized. [_doubleTapGestureRecognizerForDoubleClick setEnabled:NO]; [_doubleTapGestureRecognizerForDoubleClick setEnabled:YES]; // We also need to disable the double-tap gesture recognizers that are enabled for double-tap-to-zoom and which // are enabled when a single tap is first recognized. This avoids tests running in sequence and simulating taps // in the same location to trigger double-tap recognition. [self _setDoubleTapGesturesEnabled:NO]; } - (void)_didCommitLoadForMainFrame { #if USE(UIKIT_KEYBOARD_ADDITIONS) _seenHardwareKeyDownInNonEditableElement = NO; #endif [self _elementDidBlur]; [self _cancelLongPressGestureRecognizer]; [self _hideContextMenuHintContainer]; [_webView _didCommitLoadForMainFrame]; } #if !USE(UIKIT_KEYBOARD_ADDITIONS) - (NSArray *)keyCommands { if (!_page->editorState().isContentEditable) return nil; static NSArray* editableKeyCommands = [@[ [UIKeyCommand keyCommandWithInput:@"\t" modifierFlags:0 action:@selector(_nextAccessoryTab:)], [UIKeyCommand keyCommandWithInput:@"\t" modifierFlags:UIKeyModifierShift action:@selector(_previousAccessoryTab:)] ] retain]; return editableKeyCommands; } #endif - (void)_nextAccessoryTabForWebView:(id)sender { [self accessoryTab:YES]; } - (void)_previousAccessoryTabForWebView:(id)sender { [self accessoryTab:NO]; } - (void)_becomeFirstResponderWithSelectionMovingForward:(BOOL)selectingForward completionHandler:(void (^)(BOOL didBecomeFirstResponder))completionHandler { constexpr bool isKeyboardEventValid = false; _page->setInitialFocus(selectingForward, isKeyboardEventValid, { }, [protectedSelf = retainPtr(self), completionHandler = makeBlockPtr(completionHandler)] (auto) { completionHandler([protectedSelf becomeFirstResponder]); }); } - (WebCore::Color)_tapHighlightColorForFastClick:(BOOL)forFastClick { ASSERT(_showDebugTapHighlightsForFastClicking); return forFastClick ? WebCore::Color(0, 225, 0, 127) : WebCore::Color(225, 0, 0, 127); } - (void)_setDoubleTapGesturesEnabled:(BOOL)enabled { if (enabled && ![_doubleTapGestureRecognizer isEnabled]) { // The first tap recognized after re-enabling double tap gestures will not wait for the // second tap before committing. To fix this, we use a new double tap gesture recognizer. [self _createAndConfigureDoubleTapGestureRecognizer]; } if (_showDebugTapHighlightsForFastClicking && !enabled) _tapHighlightInformation.color = [self _tapHighlightColorForFastClick:YES]; [_doubleTapGestureRecognizer setEnabled:enabled]; [_nonBlockingDoubleTapGestureRecognizer setEnabled:!enabled]; [self _resetIsDoubleTapPending]; } // MARK: UIWebFormAccessoryDelegate protocol and accessory methods - (void)accessoryClear { _page->setFocusedElementValue({ }); } - (void)accessoryDone { [self endEditingAndUpdateFocusAppearanceWithReason:EndEditingReasonAccessoryDone]; _page->setIsShowingInputViewForFocusedElement(false); } - (void)accessoryTab:(BOOL)isNext { // The input peripheral may need to update the focused DOM node before we switch focus. The UI process does // not maintain a handle to the actual focused DOM node – only the web process has such a handle. So, we need // to end the editing session now before we tell the web process to switch focus. Once the web process tells // us the newly focused element we are no longer are in a position to effect the previously focused element. // See . [self _endEditing]; _inputPeripheral = nil; // Nullify so that we don't tell the input peripheral to end editing again in -_elementDidBlur. _isChangingFocusUsingAccessoryTab = YES; [self beginSelectionChange]; _page->focusNextFocusedElement(isNext, [protectedSelf = retainPtr(self)] (WebKit::CallbackBase::Error) { [protectedSelf endSelectionChange]; [protectedSelf reloadInputViews]; protectedSelf->_isChangingFocusUsingAccessoryTab = NO; }); } - (void)accessoryAutoFill { id <_WKInputDelegate> inputDelegate = [_webView _inputDelegate]; if ([inputDelegate respondsToSelector:@selector(_webView:accessoryViewCustomButtonTappedInFormInputSession:)]) [inputDelegate _webView:_webView accessoryViewCustomButtonTappedInFormInputSession:_formInputSession.get()]; } - (UIWebFormAccessory *)formAccessoryView { if (_formAccessoryView) return _formAccessoryView.get(); _formAccessoryView = adoptNS([[UIWebFormAccessory alloc] initWithInputAssistantItem:self.inputAssistantItem]); [_formAccessoryView setDelegate:self]; return _formAccessoryView.get(); } - (void)accessoryOpen { if (!_inputPeripheral) return; [self _zoomToRevealFocusedElement]; [self _updateAccessory]; [_inputPeripheral beginEditing]; } - (void)_updateAccessory { auto* accessoryView = self.formAccessoryView; // Creates one, if needed. if ([accessoryView respondsToSelector:@selector(setNextPreviousItemsVisible:)]) [accessoryView setNextPreviousItemsVisible:!_webView._editable]; [accessoryView setNextEnabled:_focusedElementInformation.hasNextNode]; [accessoryView setPreviousEnabled:_focusedElementInformation.hasPreviousNode]; if (currentUserInterfaceIdiomIsPad()) { [accessoryView setClearVisible:NO]; return; } switch (_focusedElementInformation.elementType) { case WebKit::InputType::Date: case WebKit::InputType::Month: case WebKit::InputType::DateTimeLocal: case WebKit::InputType::Time: [accessoryView setClearVisible:YES]; return; default: [accessoryView setClearVisible:NO]; return; } } // MARK: Keyboard interaction // UITextInput protocol implementation - (BOOL)_allowAnimatedUpdateSelectionRectViews { return NO; } - (void)beginSelectionChange { [self.inputDelegate selectionWillChange:self]; } - (void)endSelectionChange { [self.inputDelegate selectionDidChange:self]; } - (void)willFinishIgnoringCalloutBarFadeAfterPerformingAction { _ignoreSelectionCommandFadeCount++; _page->scheduleFullEditorStateUpdate(); _page->callAfterNextPresentationUpdate([weakSelf = WeakObjCPtr(self)] (auto) { if (auto strongSelf = weakSelf.get()) strongSelf->_ignoreSelectionCommandFadeCount--; }); } - (void)_didChangeWebViewEditability { if ([_formAccessoryView respondsToSelector:@selector(setNextPreviousItemsVisible:)]) [_formAccessoryView setNextPreviousItemsVisible:!_webView._editable]; [_twoFingerSingleTapGestureRecognizer setEnabled:!_webView._editable]; } - (void)insertTextSuggestion:(UITextSuggestion *)textSuggestion { // FIXME: Replace NSClassFromString with actual class as soon as UIKit submitted the new class into the iOS SDK. if ([textSuggestion isKindOfClass:[UITextAutofillSuggestion class]]) { _page->autofillLoginCredentials([(UITextAutofillSuggestion *)textSuggestion username], [(UITextAutofillSuggestion *)textSuggestion password]); return; } #if ENABLE(DATALIST_ELEMENT) if ([textSuggestion isKindOfClass:[WKDataListTextSuggestion class]]) { _page->setFocusedElementValue([textSuggestion inputText]); return; } #endif id <_WKInputDelegate> inputDelegate = [_webView _inputDelegate]; if ([inputDelegate respondsToSelector:@selector(_webView:insertTextSuggestion:inInputSession:)]) [inputDelegate _webView:_webView insertTextSuggestion:textSuggestion inInputSession:_formInputSession.get()]; } - (NSString *)textInRange:(UITextRange *)range { return nil; } - (void)replaceRange:(UITextRange *)range withText:(NSString *)text { } - (UITextRange *)selectedTextRange { if (_page->editorState().selectionIsNone || _page->editorState().isMissingPostLayoutData) return nil; // UIKit does not expect caret selections in noneditable content. if (!_page->editorState().isContentEditable && !_page->editorState().selectionIsRange) return nil; auto& postLayoutEditorStateData = _page->editorState().postLayoutData(); WebCore::FloatRect startRect = postLayoutEditorStateData.caretRectAtStart; WebCore::FloatRect endRect = postLayoutEditorStateData.caretRectAtEnd; double inverseScale = [self inverseScale]; // We want to keep the original caret width, while the height scales with // the content taking orientation into account. // We achieve this by scaling the width with the inverse // scale factor. This way, when it is converted from the content view // the width remains unchanged. if (startRect.width() < startRect.height()) startRect.setWidth(startRect.width() * inverseScale); else startRect.setHeight(startRect.height() * inverseScale); if (endRect.width() < endRect.height()) { double delta = endRect.width(); endRect.setWidth(endRect.width() * inverseScale); delta = endRect.width() - delta; endRect.move(delta, 0); } else { double delta = endRect.height(); endRect.setHeight(endRect.height() * inverseScale); delta = endRect.height() - delta; endRect.move(0, delta); } return [WKTextRange textRangeWithState:_page->editorState().selectionIsNone isRange:_page->editorState().selectionIsRange isEditable:_page->editorState().isContentEditable startRect:startRect endRect:endRect selectionRects:[self webSelectionRects] selectedTextLength:postLayoutEditorStateData.selectedTextLength]; } - (CGRect)caretRectForPosition:(UITextPosition *)position { return ((WKTextPosition *)position).positionRect; } - (NSArray *)selectionRectsForRange:(UITextRange *)range { return [WKTextSelectionRect textSelectionRectsWithWebRects:((WKTextRange *)range).selectionRects]; } - (void)setSelectedTextRange:(UITextRange *)range { if (range) return; #if !PLATFORM(MACCATALYST) if (!hasFocusedElement(_focusedElementInformation)) return; #endif [self clearSelection]; } - (BOOL)hasMarkedText { return [_markedText length]; } - (NSString *)markedText { return _markedText.get(); } - (UITextRange *)markedTextRange { return nil; } - (NSDictionary *)markedTextStyle { return nil; } - (void)setMarkedTextStyle:(NSDictionary *)styleDictionary { } - (void)setMarkedText:(NSString *)markedText selectedRange:(NSRange)selectedRange { #if USE(UIKIT_KEYBOARD_ADDITIONS) _candidateViewNeedsUpdate = !self.hasMarkedText; #endif _markedText = markedText; _page->setCompositionAsync(markedText, Vector(), selectedRange, WebKit::EditingRange()); } - (void)unmarkText { _markedText = nil; _page->confirmCompositionAsync(); } - (UITextPosition *)beginningOfDocument { return nil; } - (UITextPosition *)endOfDocument { return nil; } - (UITextRange *)textRangeFromPosition:(UITextPosition *)fromPosition toPosition:(UITextPosition *)toPosition { return nil; } - (UITextPosition *)positionFromPosition:(UITextPosition *)position offset:(NSInteger)offset { return nil; } - (UITextPosition *)positionFromPosition:(UITextPosition *)position inDirection:(UITextLayoutDirection)direction offset:(NSInteger)offset { return nil; } - (NSComparisonResult)comparePosition:(UITextPosition *)position toPosition:(UITextPosition *)other { return NSOrderedSame; } - (NSInteger)offsetFromPosition:(UITextPosition *)from toPosition:(UITextPosition *)toPosition { return 0; } - (id )tokenizer { return nil; } - (UITextPosition *)positionWithinRange:(UITextRange *)range farthestInDirection:(UITextLayoutDirection)direction { return nil; } - (UITextRange *)characterRangeByExtendingPosition:(UITextPosition *)position inDirection:(UITextLayoutDirection)direction { return nil; } - (NSWritingDirection)baseWritingDirectionForPosition:(UITextPosition *)position inDirection:(UITextStorageDirection)direction { return NSWritingDirectionLeftToRight; } static WebKit::WritingDirection coreWritingDirection(NSWritingDirection direction) { switch (direction) { case NSWritingDirectionNatural: return WebCore::WritingDirection::Natural; case NSWritingDirectionLeftToRight: return WebCore::WritingDirection::LeftToRight; case NSWritingDirectionRightToLeft: return WebCore::WritingDirection::RightToLeft; default: ASSERT_NOT_REACHED(); return WebCore::WritingDirection::Natural; } } - (void)setBaseWritingDirection:(NSWritingDirection)direction forRange:(UITextRange *)range { if (!_page->isEditable()) return; if (range && ![range isEqual:self.selectedTextRange]) { // We currently only support changing the base writing direction at the selection. return; } _page->setBaseWritingDirection(coreWritingDirection(direction)); } - (CGRect)firstRectForRange:(UITextRange *)range { return CGRectZero; } /* Hit testing. */ - (UITextPosition *)closestPositionToPoint:(CGPoint)point { #if PLATFORM(MACCATALYST) WebKit::InteractionInformationRequest request(WebCore::roundedIntPoint(point)); [self requestAsynchronousPositionInformationUpdate:request]; if ([self _currentPositionInformationIsApproximatelyValidForRequest:request radiusForApproximation:2] && _positionInformation.isSelectable) return [WKTextPosition textPositionWithRect:_positionInformation.caretRect]; #endif return nil; } - (UITextPosition *)closestPositionToPoint:(CGPoint)point withinRange:(UITextRange *)range { return nil; } - (UITextRange *)characterRangeAtPoint:(CGPoint)point { return nil; } - (void)deleteBackward { _page->executeEditCommand("deleteBackward"_s); } // Inserts the given string, replacing any selected or marked text. - (void)insertText:(NSString *)aStringValue { auto* keyboard = [UIKeyboardImpl sharedInstance]; WebKit::InsertTextOptions options; options.processingUserGesture = [keyboard respondsToSelector:@selector(isCallingInputDelegate)] && keyboard.isCallingInputDelegate; _page->insertTextAsync(aStringValue, WebKit::EditingRange(), WTFMove(options)); } - (BOOL)hasText { auto& editorState = _page->editorState(); return !editorState.isMissingPostLayoutData && editorState.postLayoutData().hasPlainText; } // end of UITextInput protocol implementation static UITextAutocapitalizationType toUITextAutocapitalize(AutocapitalizeType webkitType) { switch (webkitType) { case AutocapitalizeTypeDefault: return UITextAutocapitalizationTypeSentences; case AutocapitalizeTypeNone: return UITextAutocapitalizationTypeNone; case AutocapitalizeTypeWords: return UITextAutocapitalizationTypeWords; case AutocapitalizeTypeSentences: return UITextAutocapitalizationTypeSentences; case AutocapitalizeTypeAllCharacters: return UITextAutocapitalizationTypeAllCharacters; } return UITextAutocapitalizationTypeSentences; } static NSString *contentTypeFromFieldName(WebCore::AutofillFieldName fieldName) { switch (fieldName) { case WebCore::AutofillFieldName::Name: return UITextContentTypeName; case WebCore::AutofillFieldName::HonorificPrefix: return UITextContentTypeNamePrefix; case WebCore::AutofillFieldName::GivenName: return UITextContentTypeMiddleName; case WebCore::AutofillFieldName::AdditionalName: return UITextContentTypeMiddleName; case WebCore::AutofillFieldName::FamilyName: return UITextContentTypeFamilyName; case WebCore::AutofillFieldName::HonorificSuffix: return UITextContentTypeNameSuffix; case WebCore::AutofillFieldName::Nickname: return UITextContentTypeNickname; case WebCore::AutofillFieldName::OrganizationTitle: return UITextContentTypeJobTitle; case WebCore::AutofillFieldName::Organization: return UITextContentTypeOrganizationName; case WebCore::AutofillFieldName::StreetAddress: return UITextContentTypeFullStreetAddress; case WebCore::AutofillFieldName::AddressLine1: return UITextContentTypeStreetAddressLine1; case WebCore::AutofillFieldName::AddressLine2: return UITextContentTypeStreetAddressLine2; case WebCore::AutofillFieldName::AddressLevel3: return UITextContentTypeSublocality; case WebCore::AutofillFieldName::AddressLevel2: return UITextContentTypeAddressCity; case WebCore::AutofillFieldName::AddressLevel1: return UITextContentTypeAddressState; case WebCore::AutofillFieldName::CountryName: return UITextContentTypeCountryName; case WebCore::AutofillFieldName::PostalCode: return UITextContentTypePostalCode; case WebCore::AutofillFieldName::Tel: return UITextContentTypeTelephoneNumber; case WebCore::AutofillFieldName::Email: return UITextContentTypeEmailAddress; case WebCore::AutofillFieldName::URL: return UITextContentTypeURL; case WebCore::AutofillFieldName::Username: return UITextContentTypeUsername; case WebCore::AutofillFieldName::None: case WebCore::AutofillFieldName::NewPassword: case WebCore::AutofillFieldName::CurrentPassword: case WebCore::AutofillFieldName::AddressLine3: case WebCore::AutofillFieldName::AddressLevel4: case WebCore::AutofillFieldName::Country: case WebCore::AutofillFieldName::CcName: case WebCore::AutofillFieldName::CcGivenName: case WebCore::AutofillFieldName::CcAdditionalName: case WebCore::AutofillFieldName::CcFamilyName: case WebCore::AutofillFieldName::CcNumber: case WebCore::AutofillFieldName::CcExp: case WebCore::AutofillFieldName::CcExpMonth: case WebCore::AutofillFieldName::CcExpYear: case WebCore::AutofillFieldName::CcCsc: case WebCore::AutofillFieldName::CcType: case WebCore::AutofillFieldName::TransactionCurrency: case WebCore::AutofillFieldName::TransactionAmount: case WebCore::AutofillFieldName::Language: case WebCore::AutofillFieldName::Bday: case WebCore::AutofillFieldName::BdayDay: case WebCore::AutofillFieldName::BdayMonth: case WebCore::AutofillFieldName::BdayYear: case WebCore::AutofillFieldName::Sex: case WebCore::AutofillFieldName::Photo: case WebCore::AutofillFieldName::TelCountryCode: case WebCore::AutofillFieldName::TelNational: case WebCore::AutofillFieldName::TelAreaCode: case WebCore::AutofillFieldName::TelLocal: case WebCore::AutofillFieldName::TelLocalPrefix: case WebCore::AutofillFieldName::TelLocalSuffix: case WebCore::AutofillFieldName::TelExtension: case WebCore::AutofillFieldName::Impp: break; }; return nil; } // UITextInputPrivate protocol // Direct access to the (private) UITextInputTraits object. - (UITextInputTraits *)textInputTraits { if (!_traits) _traits = adoptNS([[UITextInputTraits alloc] init]); #if USE(UIKIT_KEYBOARD_ADDITIONS) // Do not change traits when dismissing the keyboard. if (_isBlurringFocusedElement) return _traits.get(); #endif [_traits setSecureTextEntry:_focusedElementInformation.elementType == WebKit::InputType::Password || [_formInputSession forceSecureTextEntry]]; [_traits setShortcutConversionType:_focusedElementInformation.elementType == WebKit::InputType::Password ? UITextShortcutConversionTypeNo : UITextShortcutConversionTypeDefault]; if (!_focusedElementInformation.formAction.isEmpty()) [_traits setReturnKeyType:(_focusedElementInformation.elementType == WebKit::InputType::Search) ? UIReturnKeySearch : UIReturnKeyGo]; if (_focusedElementInformation.elementType == WebKit::InputType::Password || _focusedElementInformation.elementType == WebKit::InputType::Email || _focusedElementInformation.elementType == WebKit::InputType::URL || _focusedElementInformation.formAction.contains("login")) { [_traits setAutocapitalizationType:UITextAutocapitalizationTypeNone]; [_traits setAutocorrectionType:UITextAutocorrectionTypeNo]; } else { [_traits setAutocapitalizationType:toUITextAutocapitalize(_focusedElementInformation.autocapitalizeType)]; [_traits setAutocorrectionType:_focusedElementInformation.isAutocorrect ? UITextAutocorrectionTypeYes : UITextAutocorrectionTypeNo]; } if (!_focusedElementInformation.isSpellCheckingEnabled) { [_traits setSmartQuotesType:UITextSmartQuotesTypeNo]; [_traits setSmartDashesType:UITextSmartDashesTypeNo]; } switch (_focusedElementInformation.inputMode) { case WebCore::InputMode::None: case WebCore::InputMode::Unspecified: switch (_focusedElementInformation.elementType) { case WebKit::InputType::Phone: [_traits setKeyboardType:UIKeyboardTypePhonePad]; break; case WebKit::InputType::URL: [_traits setKeyboardType:UIKeyboardTypeURL]; break; case WebKit::InputType::Email: [_traits setKeyboardType:UIKeyboardTypeEmailAddress]; break; case WebKit::InputType::Number: [_traits setKeyboardType:UIKeyboardTypeNumbersAndPunctuation]; break; case WebKit::InputType::NumberPad: [_traits setKeyboardType:UIKeyboardTypeNumberPad]; break; case WebKit::InputType::None: case WebKit::InputType::ContentEditable: case WebKit::InputType::Text: case WebKit::InputType::Password: case WebKit::InputType::TextArea: case WebKit::InputType::Search: case WebKit::InputType::Date: case WebKit::InputType::DateTime: case WebKit::InputType::DateTimeLocal: case WebKit::InputType::Month: case WebKit::InputType::Week: case WebKit::InputType::Time: case WebKit::InputType::Select: case WebKit::InputType::Drawing: #if ENABLE(INPUT_TYPE_COLOR) case WebKit::InputType::Color: #endif [_traits setKeyboardType:UIKeyboardTypeDefault]; } break; case WebCore::InputMode::Text: [_traits setKeyboardType:UIKeyboardTypeDefault]; break; case WebCore::InputMode::Telephone: [_traits setKeyboardType:UIKeyboardTypePhonePad]; break; case WebCore::InputMode::Url: [_traits setKeyboardType:UIKeyboardTypeURL]; break; case WebCore::InputMode::Email: [_traits setKeyboardType:UIKeyboardTypeEmailAddress]; break; case WebCore::InputMode::Numeric: [_traits setKeyboardType:UIKeyboardTypeNumberPad]; break; case WebCore::InputMode::Decimal: [_traits setKeyboardType:UIKeyboardTypeDecimalPad]; break; case WebCore::InputMode::Search: [_traits setKeyboardType:UIKeyboardTypeWebSearch]; break; } [_traits setTextContentType:contentTypeFromFieldName(_focusedElementInformation.autofillFieldName)]; [self _updateInteractionTintColor]; return _traits.get(); } - (UITextInteractionAssistant *)interactionAssistant { return _textSelectionAssistant.get(); } - (id)selectionInteractionAssistant { return nil; } // NSRange support. Would like to deprecate to the extent possible, although some support // (i.e. selectionRange) has shipped as API. - (NSRange)selectionRange { return NSMakeRange(NSNotFound, 0); } - (CGRect)rectForNSRange:(NSRange)range { return CGRectZero; } - (NSRange)_markedTextNSRange { return NSMakeRange(NSNotFound, 0); } // DOM range support. - (DOMRange *)selectedDOMRange { return nil; } - (void)setSelectedDOMRange:(DOMRange *)range affinityDownstream:(BOOL)affinityDownstream { } // Modify text without starting a new undo grouping. - (void)replaceRangeWithTextWithoutClosingTyping:(UITextRange *)range replacementText:(NSString *)text { } // Caret rect support. Shouldn't be necessary, but firstRectForRange doesn't offer precisely // the same functionality. - (CGRect)rectContainingCaretSelection { return CGRectZero; } #if USE(UIKIT_KEYBOARD_ADDITIONS) - (void)modifierFlagsDidChangeFrom:(UIKeyModifierFlags)oldFlags to:(UIKeyModifierFlags)newFlags { auto dispatchSyntheticFlagsChangedEvents = [&] (UIKeyModifierFlags flags, bool keyDown) { if (flags & UIKeyModifierShift) [self handleKeyWebEvent:adoptNS([[WKSyntheticFlagsChangedWebEvent alloc] initWithShiftState:keyDown]).get()]; if (flags & UIKeyModifierAlphaShift) [self handleKeyWebEvent:adoptNS([[WKSyntheticFlagsChangedWebEvent alloc] initWithCapsLockState:keyDown]).get()]; }; UIKeyModifierFlags removedFlags = oldFlags & ~newFlags; UIKeyModifierFlags addedFlags = newFlags & ~oldFlags; if (removedFlags) dispatchSyntheticFlagsChangedEvents(removedFlags, false); if (addedFlags) dispatchSyntheticFlagsChangedEvents(addedFlags, true); } - (BOOL)shouldSuppressUpdateCandidateView { return _candidateViewNeedsUpdate; } #endif // Web events. - (BOOL)requiresKeyEvents { return YES; } - (void)_handleKeyUIEvent:(::UIEvent *)event { bool isHardwareKeyboardEvent = !!event._hidEvent; // We only want to handle key event from the hardware keyboard when we are // first responder and we are not interacting with editable content. if ([self isFirstResponder] && isHardwareKeyboardEvent && (_inputPeripheral || !_page->editorState().isContentEditable)) { if ([_inputPeripheral respondsToSelector:@selector(handleKeyEvent:)]) { if ([_inputPeripheral handleKeyEvent:event]) return; } #if USE(UIKIT_KEYBOARD_ADDITIONS) if (!_seenHardwareKeyDownInNonEditableElement) { _seenHardwareKeyDownInNonEditableElement = YES; [self reloadInputViews]; } [super _handleKeyUIEvent:event]; #else [self handleKeyEvent:event]; #endif return; } [super _handleKeyUIEvent:event]; } - (void)generateSyntheticEditingCommand:(WebKit::SyntheticEditingCommandType)command { _page->generateSyntheticEditingCommand(command); } #if !USE(UIKIT_KEYBOARD_ADDITIONS) - (void)handleKeyEvent:(::UIEvent *)event { // WebCore has already seen the event, no need for custom processing. if (event == _uiEventBeingResent) return; auto webEvent = adoptNS([[WKWebEvent alloc] initWithEvent:event]); [self handleKeyWebEvent:webEvent.get()]; } #endif - (void)handleKeyWebEvent:(::WebEvent *)theEvent { _page->handleKeyboardEvent(WebKit::NativeWebKeyboardEvent(theEvent, WebKit::NativeWebKeyboardEvent::HandledByInputMethod::No)); } - (void)handleKeyWebEvent:(::WebEvent *)theEvent withCompletionHandler:(void (^)(::WebEvent *theEvent, BOOL wasHandled))completionHandler { [self _handleDOMPasteRequestWithResult:WebCore::DOMPasteAccessResponse::DeniedForGesture]; using HandledByInputMethod = WebKit::NativeWebKeyboardEvent::HandledByInputMethod; #if USE(UIKIT_KEYBOARD_ADDITIONS) auto* keyboard = [UIKeyboardImpl sharedInstance]; if (_page->editorState().isContentEditable && [keyboard respondsToSelector:@selector(handleKeyInputMethodCommandForCurrentEvent)] && [keyboard handleKeyInputMethodCommandForCurrentEvent]) { completionHandler(theEvent, YES); _page->handleKeyboardEvent(WebKit::NativeWebKeyboardEvent(theEvent, HandledByInputMethod::Yes)); return; } #endif _keyWebEventHandler = makeBlockPtr(completionHandler); _page->handleKeyboardEvent(WebKit::NativeWebKeyboardEvent(theEvent, HandledByInputMethod::No)); } - (void)_didHandleKeyEvent:(::WebEvent *)event eventWasHandled:(BOOL)eventWasHandled { #if USE(UIKIT_KEYBOARD_ADDITIONS) if ([event isKindOfClass:[WKSyntheticFlagsChangedWebEvent class]]) return; #endif if (!(event.keyboardFlags & WebEventKeyboardInputModifierFlagsChanged)) [_keyboardScrollingAnimator handleKeyEvent:event]; if (auto handler = WTFMove(_keyWebEventHandler)) { handler(event, eventWasHandled); return; } #if !USE(UIKIT_KEYBOARD_ADDITIONS) // If we aren't interacting with editable content, we still need to call [super _handleKeyUIEvent:] // so that keyboard repeat will work correctly. If we are interacting with editable content, // we already did so in _handleKeyUIEvent. if (eventWasHandled && _page->editorState().isContentEditable) return; if (![event isKindOfClass:[WKWebEvent class]]) return; // Resending the event may destroy this WKContentView. RetainPtr protector(self); // We keep here the event when resending it to the application to distinguish // the case of a new event from one that has been already sent to WebCore. ASSERT(!_uiEventBeingResent); _uiEventBeingResent = [(WKWebEvent *)event uiEvent]; [super _handleKeyUIEvent:_uiEventBeingResent.get()]; _uiEventBeingResent = nil; #endif } - (BOOL)_interpretKeyEvent:(::WebEvent *)event isCharEvent:(BOOL)isCharEvent { if (event.keyboardFlags & WebEventKeyboardInputModifierFlagsChanged) return NO; BOOL contentEditable = _page->editorState().isContentEditable; if (!contentEditable && event.isTabKey) return NO; if ([_keyboardScrollingAnimator beginWithEvent:event] || [_keyboardScrollingAnimator scrollTriggeringKeyIsPressed]) return YES; UIKeyboardImpl *keyboard = [UIKeyboardImpl sharedInstance]; #if __IPHONE_OS_VERSION_MIN_REQUIRED >= 130000 if (!isCharEvent && [keyboard respondsToSelector:@selector(handleKeyTextCommandForCurrentEvent)] && [keyboard handleKeyTextCommandForCurrentEvent]) return YES; if (isCharEvent && [keyboard respondsToSelector:@selector(handleKeyAppCommandForCurrentEvent)] && [keyboard handleKeyAppCommandForCurrentEvent]) return YES; #endif // Don't insert character for an unhandled Command-key key command. This matches iOS and Mac platform conventions. if (event.modifierFlags & WebEventFlagMaskCommandKey) return NO; NSString *characters = event.characters; if (!characters.length) return NO; switch ([characters characterAtIndex:0]) { case NSBackspaceCharacter: case NSDeleteCharacter: if (contentEditable) { [keyboard deleteFromInputWithFlags:event.keyboardFlags]; return YES; } break; case NSEnterCharacter: case NSCarriageReturnCharacter: if (contentEditable && isCharEvent) { // Map \r from HW keyboard to \n to match the behavior of the soft keyboard. [keyboard addInputString:@"\n" withFlags:0 withInputManagerHint:nil]; return YES; } break; default: if (contentEditable && isCharEvent) { [keyboard addInputString:event.characters withFlags:event.keyboardFlags withInputManagerHint:event.inputManagerHint]; return YES; } break; } return NO; } - (void)dismissFilePicker { [_fileUploadPanel dismiss]; } - (BOOL)isScrollableForKeyboardScrollViewAnimator:(WKKeyboardScrollViewAnimator *)animator { if (_page->editorState().isContentEditable) return NO; if (_focusedElementInformation.elementType == WebKit::InputType::Select) return NO; if (!_webView.scrollView.scrollEnabled) return NO; return YES; } - (CGFloat)keyboardScrollViewAnimator:(WKKeyboardScrollViewAnimator *)animator distanceForIncrement:(WebKit::ScrollingIncrement)increment inDirection:(WebKit::ScrollingDirection)direction { BOOL directionIsHorizontal = direction == WebKit::ScrollingDirection::Left || direction == WebKit::ScrollingDirection::Right; switch (increment) { case WebKit::ScrollingIncrement::Document: { CGSize documentSize = [self convertRect:self.bounds toView:_webView].size; return directionIsHorizontal ? documentSize.width : documentSize.height; } case WebKit::ScrollingIncrement::Page: { CGSize pageSize = [self convertSize:CGSizeMake(0, WebCore::Scrollbar::pageStep(_page->unobscuredContentRect().height(), self.bounds.size.height)) toView:_webView]; return directionIsHorizontal ? pageSize.width : pageSize.height; } case WebKit::ScrollingIncrement::Line: return [self convertSize:CGSizeMake(0, WebCore::Scrollbar::pixelsPerLineStep()) toView:_webView].height; } ASSERT_NOT_REACHED(); return 0; } - (void)keyboardScrollViewAnimatorWillScroll:(WKKeyboardScrollViewAnimator *)animator { [self willStartZoomOrScroll]; } - (void)keyboardScrollViewAnimatorDidFinishScrolling:(WKKeyboardScrollViewAnimator *)animator { [_webView _didFinishScrolling]; } - (void)executeEditCommandWithCallback:(NSString *)commandName { // FIXME: Editing commands are not considered by WebKit as user initiated even if they are the result // of keydown or keyup. We need to query the keyboard to determine if this was called from the keyboard // or not to know whether to tell WebKit to treat this command as user initiated or not. [self beginSelectionChange]; RetainPtr view = self; _page->executeEditCommand(commandName, { }, [view](WebKit::CallbackBase::Error) { [view endSelectionChange]; }); } - (void)_deleteByWord { [self executeEditCommandWithCallback:@"deleteWordBackward"]; } - (void)_deleteToStartOfLine { [self executeEditCommandWithCallback:@"deleteToBeginningOfLine"]; } - (void)_deleteToEndOfLine { [self executeEditCommandWithCallback:@"deleteToEndOfLine"]; } - (void)_deleteForwardAndNotify:(BOOL)notify { [self executeEditCommandWithCallback:@"deleteForward"]; } - (void)_deleteToEndOfParagraph { [self executeEditCommandWithCallback:@"deleteToEndOfParagraph"]; } - (void)_transpose { [self executeEditCommandWithCallback:@"transpose"]; } - (UITextInputArrowKeyHistory *)_moveUp:(BOOL)extending withHistory:(UITextInputArrowKeyHistory *)history { [self executeEditCommandWithCallback:extending ? @"moveUpAndModifySelection" : @"moveUp"]; return nil; } - (UITextInputArrowKeyHistory *)_moveDown:(BOOL)extending withHistory:(UITextInputArrowKeyHistory *)history { [self executeEditCommandWithCallback:extending ? @"moveDownAndModifySelection" : @"moveDown"]; return nil; } - (UITextInputArrowKeyHistory *)_moveLeft:(BOOL)extending withHistory:(UITextInputArrowKeyHistory *)history { [self executeEditCommandWithCallback:extending? @"moveLeftAndModifySelection" : @"moveLeft"]; return nil; } - (UITextInputArrowKeyHistory *)_moveRight:(BOOL)extending withHistory:(UITextInputArrowKeyHistory *)history { [self executeEditCommandWithCallback:extending ? @"moveRightAndModifySelection" : @"moveRight"]; return nil; } - (UITextInputArrowKeyHistory *)_moveToStartOfWord:(BOOL)extending withHistory:(UITextInputArrowKeyHistory *)history { [self executeEditCommandWithCallback:extending ? @"moveWordBackwardAndModifySelection" : @"moveWordBackward"]; return nil; } - (UITextInputArrowKeyHistory *)_moveToStartOfParagraph:(BOOL)extending withHistory:(UITextInputArrowKeyHistory *)history { [self executeEditCommandWithCallback:extending ? @"moveBackwardAndModifySelection" : @"moveBackward"]; [self executeEditCommandWithCallback:extending ? @"moveToBeginningOfParagraphAndModifySelection" : @"moveToBeginningOfParagraph"]; return nil; } - (UITextInputArrowKeyHistory *)_moveToStartOfLine:(BOOL)extending withHistory:(UITextInputArrowKeyHistory *)history { [self executeEditCommandWithCallback:extending ? @"moveToBeginningOfLineAndModifySelection" : @"moveToBeginningOfLine"]; return nil; } - (UITextInputArrowKeyHistory *)_moveToStartOfDocument:(BOOL)extending withHistory:(UITextInputArrowKeyHistory *)history { [self executeEditCommandWithCallback:extending ? @"moveToBeginningOfDocumentAndModifySelection" : @"moveToBeginningOfDocument"]; return nil; } - (UITextInputArrowKeyHistory *)_moveToEndOfWord:(BOOL)extending withHistory:(UITextInputArrowKeyHistory *)history { [self executeEditCommandWithCallback:extending ? @"moveWordForwardAndModifySelection" : @"moveWordForward"]; return nil; } - (UITextInputArrowKeyHistory *)_moveToEndOfParagraph:(BOOL)extending withHistory:(UITextInputArrowKeyHistory *)history { [self executeEditCommandWithCallback:extending ? @"moveForwardAndModifySelection" : @"moveForward"]; [self executeEditCommandWithCallback:extending ? @"moveToEndOfParagraphAndModifySelection" : @"moveToEndOfParagraph"]; return nil; } - (UITextInputArrowKeyHistory *)_moveToEndOfLine:(BOOL)extending withHistory:(UITextInputArrowKeyHistory *)history { [self executeEditCommandWithCallback:extending ? @"moveToEndOfLineAndModifySelection" : @"moveToEndOfLine"]; return nil; } - (UITextInputArrowKeyHistory *)_moveToEndOfDocument:(BOOL)extending withHistory:(UITextInputArrowKeyHistory *)history { [self executeEditCommandWithCallback:extending ? @"moveToEndOfDocumentAndModifySelection" : @"moveToEndOfDocument"]; return nil; } // Sets a buffer to make room for autocorrection views - (void)setBottomBufferHeight:(CGFloat)bottomBuffer { } - (UIView *)automaticallySelectedOverlay { return [self unscaledView]; } - (UITextGranularity)selectionGranularity { return UITextGranularityCharacter; } // Should return an array of NSDictionary objects that key/value paries for the final text, correction identifier and // alternative selection counts using the keys defined at the top of this header. - (NSArray *)metadataDictionariesForDictationResults { return nil; } // Returns the dictation result boundaries from position so that text that was not dictated can be excluded from logging. // If these are not implemented, no text will be logged. - (UITextPosition *)previousUnperturbedDictationResultBoundaryFromPosition:(UITextPosition *)position { return nil; } - (UITextPosition *)nextUnperturbedDictationResultBoundaryFromPosition:(UITextPosition *)position { return nil; } // The can all be (and have been) trivially implemented in terms of UITextInput. Deprecate and remove. - (void)moveBackward:(unsigned)count { } - (void)moveForward:(unsigned)count { } - (unichar)characterBeforeCaretSelection { return 0; } - (NSString *)wordContainingCaretSelection { return nil; } - (DOMRange *)wordRangeContainingCaretSelection { return nil; } - (void)setMarkedText:(NSString *)text { } - (BOOL)hasContent { return _page->editorState().postLayoutData().hasContent; } - (void)selectAll { } - (UIColor *)textColorForCaretSelection { return [UIColor blackColor]; } - (UIFont *)fontForCaretSelection { UIFont *font = _autocorrectionData.font.get(); double zoomScale = self._contentZoomScale; return std::abs(zoomScale - 1) > FLT_EPSILON ? [font fontWithSize:font.pointSize * zoomScale] : font; } - (BOOL)hasSelection { return NO; } - (BOOL)isPosition:(UITextPosition *)position atBoundary:(UITextGranularity)granularity inDirection:(UITextDirection)direction { if (granularity == UITextGranularityParagraph) { if (direction == UITextStorageDirectionBackward && [position isEqual:self.selectedTextRange.start]) return _page->editorState().postLayoutData().selectionStartIsAtParagraphBoundary; if (direction == UITextStorageDirectionForward && [position isEqual:self.selectedTextRange.end]) return _page->editorState().postLayoutData().selectionEndIsAtParagraphBoundary; } return NO; } - (UITextPosition *)positionFromPosition:(UITextPosition *)position toBoundary:(UITextGranularity)granularity inDirection:(UITextDirection)direction { return nil; } - (BOOL)isPosition:(UITextPosition *)position withinTextUnit:(UITextGranularity)granularity inDirection:(UITextDirection)direction { return NO; } - (UITextRange *)rangeEnclosingPosition:(UITextPosition *)position withGranularity:(UITextGranularity)granularity inDirection:(UITextDirection)direction { return nil; } - (void)takeTraitsFrom:(UITextInputTraits *)traits { [[self textInputTraits] takeTraitsFrom:traits]; } - (void)_showKeyboard { [self setUpTextSelectionAssistant]; if (self.isFirstResponder && !_suppressSelectionAssistantReasons) [_textSelectionAssistant activateSelection]; #if !PLATFORM(WATCHOS) [self reloadInputViews]; #endif } - (void)_hideKeyboard { self.inputDelegate = nil; [self setUpTextSelectionAssistant]; [_textSelectionAssistant deactivateSelection]; [_formAccessoryView hideAutoFillButton]; // FIXME: Does it make sense to call -reloadInputViews on watchOS? [self reloadInputViews]; if (_formAccessoryView) [self _updateAccessory]; } - (const WebKit::FocusedElementInformation&)focusedElementInformation { return _focusedElementInformation; } - (Vector&)focusedSelectElementOptions { return _focusedElementInformation.selectOptions; } // Note that selectability is also affected by the CSS property user-select. static bool mayContainSelectableText(WebKit::InputType type) { switch (type) { case WebKit::InputType::None: // The following types have custom UI and do not look or behave like a text field. #if ENABLE(INPUT_TYPE_COLOR) case WebKit::InputType::Color: #endif case WebKit::InputType::Date: case WebKit::InputType::DateTimeLocal: case WebKit::InputType::Drawing: case WebKit::InputType::Month: case WebKit::InputType::Select: case WebKit::InputType::Time: return false; // The following types look and behave like a text field. case WebKit::InputType::ContentEditable: case WebKit::InputType::DateTime: case WebKit::InputType::Email: case WebKit::InputType::Number: case WebKit::InputType::NumberPad: case WebKit::InputType::Password: case WebKit::InputType::Phone: case WebKit::InputType::Search: case WebKit::InputType::Text: case WebKit::InputType::TextArea: case WebKit::InputType::URL: case WebKit::InputType::Week: return true; } } static bool shouldShowKeyboardForElement(const WebKit::FocusedElementInformation& information) { if (information.inputMode == WebCore::InputMode::None) return false; if (information.elementType == WebKit::InputType::Drawing) return false; if (mayContainSelectableText(information.elementType)) return true; return !currentUserInterfaceIdiomIsPad(); } static WebCore::FloatRect rectToRevealWhenZoomingToFocusedElement(const WebKit::FocusedElementInformation& elementInfo, const WebKit::EditorState& editorState) { WebCore::IntRect elementInteractionRect; if (elementInfo.elementRect.contains(elementInfo.lastInteractionLocation)) elementInteractionRect = { elementInfo.lastInteractionLocation, { 1, 1 } }; if (!mayContainSelectableText(elementInfo.elementType)) return elementInteractionRect; if (editorState.isMissingPostLayoutData) { ASSERT_NOT_REACHED(); return elementInteractionRect; } if (editorState.selectionIsNone) return { }; WebCore::FloatRect selectionBoundingRect; auto& postLayoutData = editorState.postLayoutData(); if (editorState.selectionIsRange) { for (auto& rect : postLayoutData.selectionRects) selectionBoundingRect.unite(rect.rect()); } else selectionBoundingRect = postLayoutData.caretRectAtStart; selectionBoundingRect.intersect(elementInfo.elementRect); return selectionBoundingRect; } static RetainPtr> createInputPeripheralWithView(WebKit::InputType type, WKContentView *view) { switch (type) { case WebKit::InputType::Select: return adoptNS([[WKFormSelectControl alloc] initWithView:view]); #if ENABLE(INPUT_TYPE_COLOR) case WebKit::InputType::Color: return adoptNS([[WKFormColorControl alloc] initWithView:view]); #endif default: return adoptNS([[WKFormInputControl alloc] initWithView:view]); } } - (void)_elementDidFocus:(const WebKit::FocusedElementInformation&)information userIsInteracting:(BOOL)userIsInteracting blurPreviousNode:(BOOL)blurPreviousNode activityStateChanges:(OptionSet)activityStateChanges userObject:(NSObject *)userObject { SetForScope isChangingFocusForScope { _isChangingFocus, hasFocusedElement(_focusedElementInformation) }; SetForScope isFocusingElementWithKeyboardForScope { _isFocusingElementWithKeyboard, shouldShowKeyboardForElement(information) }; auto inputViewUpdateDeferrer = std::exchange(_inputViewUpdateDeferrer, nullptr); _didAccessoryTabInitiateFocus = _isChangingFocusUsingAccessoryTab; id <_WKInputDelegate> inputDelegate = [_webView _inputDelegate]; RetainPtr focusedElementInfo = adoptNS([[WKFocusedElementInfo alloc] initWithFocusedElementInformation:information isUserInitiated:userIsInteracting userObject:userObject]); _WKFocusStartsInputSessionPolicy startInputSessionPolicy = _WKFocusStartsInputSessionPolicyAuto; if ([inputDelegate respondsToSelector:@selector(_webView:focusShouldStartInputSession:)]) { if ([inputDelegate _webView:_webView focusShouldStartInputSession:focusedElementInfo.get()]) startInputSessionPolicy = _WKFocusStartsInputSessionPolicyAllow; else startInputSessionPolicy = _WKFocusStartsInputSessionPolicyDisallow; } if ([inputDelegate respondsToSelector:@selector(_webView:decidePolicyForFocusedElement:)]) startInputSessionPolicy = [inputDelegate _webView:_webView decidePolicyForFocusedElement:focusedElementInfo.get()]; BOOL shouldShowInputView = [&] { switch (startInputSessionPolicy) { case _WKFocusStartsInputSessionPolicyAuto: // The default behavior is to allow node assistance if the user is interacting. // We also allow node assistance if the keyboard already is showing, unless we're in extra zoom mode. if (userIsInteracting) return YES; if (self.isFirstResponder || _becomingFirstResponder) { // When the software keyboard is being used to enter an url, only the focus activity state is changing. // In this case, auto focus on the page being navigated to should be disabled, unless a hardware // keyboard is attached. if (activityStateChanges && activityStateChanges != WebCore::ActivityState::IsFocused) return YES; #if PLATFORM(WATCHOS) if (_isChangingFocus && ![_focusedFormControlView isHidden]) return YES; #else if (_isChangingFocus) return YES; if ([UIKeyboard isInHardwareKeyboardMode]) return YES; #endif } return NO; case _WKFocusStartsInputSessionPolicyAllow: return YES; case _WKFocusStartsInputSessionPolicyDisallow: return NO; default: ASSERT_NOT_REACHED(); return NO; } }(); if (blurPreviousNode) { // Defer view updates until the end of this function to avoid a noticeable flash when switching focus // between elements that require the keyboard. if (!inputViewUpdateDeferrer) inputViewUpdateDeferrer = std::make_unique(self); [self _elementDidBlur]; } #if HAVE(PENCILKIT) if (information.elementType == WebKit::InputType::Drawing) [_drawingCoordinator installInkPickerForDrawing:information.embeddedViewID]; #endif if (!shouldShowInputView || information.elementType == WebKit::InputType::None) { _page->setIsShowingInputViewForFocusedElement(false); return; } _page->setIsShowingInputViewForFocusedElement(true); // FIXME: We should remove this check when we manage to send ElementDidFocus from the WebProcess // only when it is truly time to show the keyboard. if (_focusedElementInformation.elementType == information.elementType && _focusedElementInformation.elementRect == information.elementRect) { if (_inputPeripheral) { if (!self.isFirstResponder) [self becomeFirstResponder]; [self accessoryOpen]; } return; } [_webView _resetFocusPreservationCount]; _focusRequiresStrongPasswordAssistance = NO; if ([inputDelegate respondsToSelector:@selector(_webView:focusRequiresStrongPasswordAssistance:)]) _focusRequiresStrongPasswordAssistance = [inputDelegate _webView:_webView focusRequiresStrongPasswordAssistance:focusedElementInfo.get()]; bool delegateImplementsWillStartInputSession = [inputDelegate respondsToSelector:@selector(_webView:willStartInputSession:)]; bool delegateImplementsDidStartInputSession = [inputDelegate respondsToSelector:@selector(_webView:didStartInputSession:)]; if (delegateImplementsWillStartInputSession || delegateImplementsDidStartInputSession) { [_formInputSession invalidate]; _formInputSession = adoptNS([[WKFormInputSession alloc] initWithContentView:self focusedElementInfo:focusedElementInfo.get() requiresStrongPasswordAssistance:_focusRequiresStrongPasswordAssistance]); } if (delegateImplementsWillStartInputSession) [inputDelegate _webView:_webView willStartInputSession:_formInputSession.get()]; BOOL isSelectable = mayContainSelectableText(information.elementType); BOOL editableChanged = [self setIsEditable:isSelectable]; _focusedElementInformation = information; _traits = nil; if (![self isFirstResponder]) [self becomeFirstResponder]; _inputPeripheral = createInputPeripheralWithView(_focusedElementInformation.elementType, self); #if PLATFORM(WATCHOS) [self addFocusedFormControlOverlay]; if (!_isChangingFocus) [self presentViewControllerForCurrentFocusedElement]; #else [self reloadInputViews]; #endif if (isSelectable) [self _showKeyboard]; // The custom fixed position rect behavior is affected by -isFocusingElement, so if that changes we need to recompute rects. if (editableChanged) [_webView _scheduleVisibleContentRectUpdate]; // For elements that have selectable content (e.g. text field) we need to wait for the web process to send an up-to-date // selection rect before we can zoom and reveal the selection. Non-selectable elements (e.g.