| /* |
| * Copyright (C) 2012-2017 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) |
| |
| #import "APIUIClient.h" |
| #import "EditingRange.h" |
| #import "InputViewUpdateDeferrer.h" |
| #import "Logging.h" |
| #import "ManagedConfigurationSPI.h" |
| #import "NativeWebKeyboardEvent.h" |
| #import "NativeWebTouchEvent.h" |
| #import "RemoteLayerTreeDrawingAreaProxy.h" |
| #import "SmartMagnificationController.h" |
| #import "TextInputSPI.h" |
| #import "UIKitSPI.h" |
| #import "WKActionSheetAssistant.h" |
| #import "WKDatePickerViewController.h" |
| #import "WKError.h" |
| #import "WKFocusedFormControlView.h" |
| #import "WKFormControlListViewController.h" |
| #import "WKFormInputControl.h" |
| #import "WKFormSelectControl.h" |
| #import "WKImagePreviewViewController.h" |
| #import "WKInspectorNodeSearchGestureRecognizer.h" |
| #import "WKNSURLExtras.h" |
| #import "WKPreviewActionItemIdentifiers.h" |
| #import "WKPreviewActionItemInternal.h" |
| #import "WKPreviewElementInfoInternal.h" |
| #import "WKSelectMenuListViewController.h" |
| #import "WKTextInputListViewController.h" |
| #import "WKTimePickerViewController.h" |
| #import "WKUIDelegatePrivate.h" |
| #import "WKWebViewConfiguration.h" |
| #import "WKWebViewConfigurationPrivate.h" |
| #import "WKWebViewInternal.h" |
| #import "WKWebViewPrivate.h" |
| #import "WeakObjCPtr.h" |
| #import "WebEvent.h" |
| #import "WebIOSEventFactory.h" |
| #import "WebPageMessages.h" |
| #import "WebProcessProxy.h" |
| #import "_WKActivatedElementInfoInternal.h" |
| #import "_WKElementAction.h" |
| #import "_WKFocusedElementInfo.h" |
| #import "_WKFormInputSession.h" |
| #import "_WKInputDelegate.h" |
| #import <CoreText/CTFont.h> |
| #import <CoreText/CTFontDescriptor.h> |
| #import <MobileCoreServices/UTCoreTypes.h> |
| #import <WebCore/Color.h> |
| #import <WebCore/DataDetection.h> |
| #import <WebCore/FloatQuad.h> |
| #import <WebCore/LocalizedStrings.h> |
| #import <WebCore/NotImplemented.h> |
| #import <WebCore/Pasteboard.h> |
| #import <WebCore/Path.h> |
| #import <WebCore/PathUtilities.h> |
| #import <WebCore/PromisedBlobInfo.h> |
| #import <WebCore/RuntimeApplicationChecks.h> |
| #import <WebCore/Scrollbar.h> |
| #import <WebCore/TextIndicator.h> |
| #import <WebCore/VisibleSelection.h> |
| #import <WebCore/WebCoreNSURLExtras.h> |
| #import <WebCore/WebEvent.h> |
| #import <WebKit/WebSelectionRect.h> // FIXME: WK2 should not include WebKit headers! |
| #import <pal/spi/cg/CoreGraphicsSPI.h> |
| #import <pal/spi/cocoa/DataDetectorsCoreSPI.h> |
| #import <pal/spi/ios/DataDetectorsUISPI.h> |
| #import <wtf/Optional.h> |
| #import <wtf/RetainPtr.h> |
| #import <wtf/SetForScope.h> |
| #import <wtf/SoftLinking.h> |
| #import <wtf/text/TextStream.h> |
| |
| #if ENABLE(DRAG_SUPPORT) |
| #import <WebCore/DragData.h> |
| #import <WebCore/DragItem.h> |
| #import <WebCore/PlatformPasteboard.h> |
| #import <WebCore/WebItemProviderPasteboard.h> |
| #endif |
| |
| #if USE(APPLE_INTERNAL_SDK) |
| #import <WebKitAdditions/WKContentViewInteractionAdditionsBefore.mm> |
| #endif |
| |
| @interface UIEvent(UIEventInternal) |
| @property (nonatomic, assign) UIKeyboardInputFlags _inputFlags; |
| @end |
| |
| @interface WKWebEvent : WebEvent |
| @property (nonatomic, retain) UIEvent *uiEvent; |
| @end |
| |
| @implementation WKWebEvent |
| |
| - (void)dealloc |
| { |
| [_uiEvent release]; |
| [super dealloc]; |
| } |
| |
| @end |
| |
| #if ENABLE(EXTRA_ZOOM_MODE) |
| |
| @interface WKContentView (ExtraZoomMode) <WKFocusedFormControlViewDelegate, WKSelectMenuListViewControllerDelegate, WKTextInputListViewControllerDelegate> |
| @end |
| |
| #endif |
| |
| using namespace WebCore; |
| using namespace WebKit; |
| |
| 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; |
| } |
| |
| 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; |
| } |
| } |
| |
| 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); |
| return stream; |
| } |
| |
| } // namespace WebKit |
| |
| static const float highlightDelay = 0.12; |
| static const float tapAndHoldDelay = 0.75; |
| const CGFloat minimumTapHighlightRadius = 2.0; |
| |
| @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 *)autocorrectionRectsWithRects:(CGRect)firstRect lastRect:(CGRect)lastRect; |
| @end |
| |
| @interface WKAutocorrectionContext : UIWKAutocorrectionContext |
| + (WKAutocorrectionContext *)autocorrectionContextWithData:(NSString *)beforeText markedText:(NSString *)markedText selectedText:(NSString *)selectedText afterText:(NSString *)afterText selectedRangeInMarkedText:(NSRange)range; |
| @end |
| |
| @interface UITextInteractionAssistant (UITextInteractionAssistant_Internal) |
| // FIXME: this needs to be moved from the internal header to the private. |
| - (id)initWithView:(UIResponder <UITextInput> *)view; |
| - (void)selectWord; |
| - (void)scheduleReanalysis; |
| @end |
| |
| @interface UIView (UIViewInternalHack) |
| + (BOOL)_addCompletion:(void(^)(BOOL))completion; |
| @end |
| |
| @protocol UISelectionInteractionAssistant; |
| |
| @interface WKFocusedElementInfo : NSObject <_WKFocusedElementInfo> |
| - (instancetype)initWithAssistedNodeInformation:(const AssistedNodeInformation&)information isUserInitiated:(BOOL)isUserInitiated userObject:(NSObject <NSSecureCoding> *)userObject; |
| @end |
| |
| @interface WKFormInputSession : NSObject <_WKFormInputSession> |
| |
| - (instancetype)initWithContentView:(WKContentView *)view focusedElementInfo:(WKFocusedElementInfo *)elementInfo requiresStrongPasswordAssistance:(BOOL)requiresStrongPasswordAssistance; |
| - (void)invalidate; |
| |
| @end |
| |
| @implementation WKFormInputSession { |
| WKContentView *_contentView; |
| RetainPtr<WKFocusedElementInfo> _focusedElementInfo; |
| RetainPtr<UIView> _customInputView; |
| RetainPtr<NSArray<UITextSuggestion *>> _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 <NSSecureCoding> *)userObject |
| { |
| return [_focusedElementInfo userObject]; |
| } |
| |
| - (BOOL)isValid |
| { |
| return _contentView != nil; |
| } |
| |
| - (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]; |
| } |
| |
| - (NSArray<UITextSuggestion *> *)suggestions |
| { |
| return _suggestions.get(); |
| } |
| |
| - (void)setSuggestions:(NSArray<UITextSuggestion *> *)suggestions |
| { |
| id <UITextInputSuggestionDelegate> suggestionDelegate = (id <UITextInputSuggestionDelegate>)_contentView.inputDelegate; |
| _suggestions = adoptNS([suggestions copy]); |
| [suggestionDelegate setSuggestions:suggestions]; |
| } |
| |
| - (BOOL)requiresStrongPasswordAssistance |
| { |
| return _requiresStrongPasswordAssistance; |
| } |
| |
| - (void)invalidate |
| { |
| id <UITextInputSuggestionDelegate> suggestionDelegate = (id <UITextInputSuggestionDelegate>)_contentView.inputDelegate; |
| [suggestionDelegate setSuggestions:nil]; |
| _contentView = nil; |
| } |
| |
| - (void)reloadFocusedElementContextView |
| { |
| [_contentView reloadContextViewForPresentedListViewController]; |
| } |
| |
| @end |
| |
| @implementation WKFocusedElementInfo { |
| WKInputType _type; |
| RetainPtr<NSString> _value; |
| BOOL _isUserInitiated; |
| RetainPtr<NSObject <NSSecureCoding>> _userObject; |
| RetainPtr<NSString> _placeholder; |
| RetainPtr<NSString> _label; |
| } |
| |
| - (instancetype)initWithAssistedNodeInformation:(const AssistedNodeInformation&)information isUserInitiated:(BOOL)isUserInitiated userObject:(NSObject <NSSecureCoding> *)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::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 <NSSecureCoding> *)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<NSMutableArray> _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 <UIDragSession> session) |
| { |
| return [session.localContext isKindOfClass:[WKDragSessionContext class]] ? (WKDragSessionContext *)session.localContext : nil; |
| } |
| |
| static WKDragSessionContext *ensureLocalDragSessionContext(id <UIDragSession> 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<WebCore::SelectionRect>&)selectionRects; |
| - (void)_accessibilityDidGetSelectionRects:(NSArray *)selectionRects withGranularity:(UITextGranularity)granularity atOffset:(NSInteger)offset; |
| @end |
| |
| @implementation WKContentView (WKInteraction) |
| |
| static inline bool hasAssistedNode(WebKit::AssistedNodeInformation assistedNodeInformation) |
| { |
| return (assistedNodeInformation.elementType != InputType::None); |
| } |
| |
| - (void)_createAndConfigureDoubleTapGestureRecognizer |
| { |
| _doubleTapGestureRecognizer = adoptNS([[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(_doubleTapRecognized:)]); |
| [_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 (!_interactionViewsContainerView) { |
| _interactionViewsContainerView = adoptNS([[UIView alloc] init]); |
| [_interactionViewsContainerView layer].name = @"InteractionViewsContainer"; |
| [_interactionViewsContainerView setOpaque:NO]; |
| [_interactionViewsContainerView layer].anchorPoint = CGPointZero; |
| [self.superview addSubview:_interactionViewsContainerView.get()]; |
| } |
| |
| [self.layer addObserver:self forKeyPath:@"transform" options:NSKeyValueObservingOptionInitial context:nil]; |
| |
| _touchEventGestureRecognizer = adoptNS([[UIWebTouchEventsGestureRecognizer alloc] initWithTarget:self action:@selector(_webTouchEventsRecognized:) touchDelegate:self]); |
| [_touchEventGestureRecognizer setDelegate:self]; |
| [self addGestureRecognizer:_touchEventGestureRecognizer.get()]; |
| |
| #if USE(APPLE_INTERNAL_SDK) |
| [self _internalSetupInteraction]; |
| #endif |
| |
| _singleTapGestureRecognizer = adoptNS([[WKSyntheticClickTapGestureRecognizer alloc] initWithTarget:self action:@selector(_singleTapCommited:)]); |
| [_singleTapGestureRecognizer setDelegate:self]; |
| [_singleTapGestureRecognizer setGestureRecognizedTarget:self action:@selector(_singleTapRecognized:)]; |
| [_singleTapGestureRecognizer setResetTarget:self action:@selector(_singleTapDidReset:)]; |
| [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()]; |
| |
| [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([[_UIWebHighlightLongPressGestureRecognizer alloc] initWithTarget:self action:@selector(_highlightLongPressRecognized:)]); |
| [_highlightLongPressGestureRecognizer setDelay:highlightDelay]; |
| [_highlightLongPressGestureRecognizer setDelegate:self]; |
| [self addGestureRecognizer:_highlightLongPressGestureRecognizer.get()]; |
| |
| [self _createAndConfigureLongPressGestureRecognizer]; |
| |
| #if ENABLE(DATA_INTERACTION) |
| [self setupDataInteractionDelegates]; |
| #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]; |
| [self addGestureRecognizer:_twoFingerSingleTapGestureRecognizer.get()]; |
| |
| #if HAVE(LINK_PREVIEW) |
| [self _registerPreview]; |
| #endif |
| |
| [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(_resetShowingTextStyle:) name:UIMenuControllerDidHideMenuNotification object:nil]; |
| _showingTextStyleOptions = NO; |
| |
| // FIXME: This should be called when we get notified that loading has completed. |
| [self useSelectionAssistantWithGranularity:_webView._selectionGranularity]; |
| |
| _actionSheetAssistant = adoptNS([[WKActionSheetAssistant alloc] initWithView:self]); |
| [_actionSheetAssistant setDelegate:self]; |
| _smartMagnificationController = std::make_unique<SmartMagnificationController>(self); |
| _isExpectingFastSingleTapCommit = NO; |
| _potentialTapInProgress = NO; |
| _isDoubleTapPending = NO; |
| _showDebugTapHighlightsForFastClicking = [[NSUserDefaults standardUserDefaults] boolForKey:@"WebKitShowFastClickDebugTapHighlights"]; |
| _needsDeferredEndScrollingSelectionUpdate = NO; |
| _isChangingFocus = NO; |
| _isBlurringFocusedNode = NO; |
| } |
| |
| - (void)cleanupInteraction |
| { |
| _webSelectionAssistant = nil; |
| _textSelectionAssistant = nil; |
| |
| [_actionSheetAssistant cleanupSheet]; |
| _actionSheetAssistant = nil; |
| |
| _smartMagnificationController = nil; |
| _didAccessoryTabInitiateFocus = NO; |
| _isExpectingFastSingleTapCommit = NO; |
| _needsDeferredEndScrollingSelectionUpdate = NO; |
| [_formInputSession invalidate]; |
| _formInputSession = nil; |
| [_highlightView removeFromSuperview]; |
| _outstandingPositionInformationRequest = std::nullopt; |
| |
| _focusRequiresStrongPasswordAssistance = NO; |
| |
| if (_interactionViewsContainerView) { |
| [self.layer removeObserver:self forKeyPath:@"transform"]; |
| [_interactionViewsContainerView removeFromSuperview]; |
| _interactionViewsContainerView = nil; |
| } |
| |
| [_touchEventGestureRecognizer setDelegate:nil]; |
| [self removeGestureRecognizer:_touchEventGestureRecognizer.get()]; |
| |
| #if USE(APPLE_INTERNAL_SDK) |
| [self _internalCleanupInteraction]; |
| #endif |
| |
| [_singleTapGestureRecognizer setDelegate:nil]; |
| [_singleTapGestureRecognizer setGestureRecognizedTarget:nil action:nil]; |
| [_singleTapGestureRecognizer setResetTarget:nil action:nil]; |
| [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()]; |
| |
| [_twoFingerDoubleTapGestureRecognizer setDelegate:nil]; |
| [self removeGestureRecognizer:_twoFingerDoubleTapGestureRecognizer.get()]; |
| |
| [_twoFingerSingleTapGestureRecognizer setDelegate:nil]; |
| [self removeGestureRecognizer:_twoFingerSingleTapGestureRecognizer.get()]; |
| |
| _layerTreeTransactionIdAtLastTouchStart = 0; |
| |
| #if ENABLE(DATA_INTERACTION) |
| [existingLocalDragSessionContext(_dragDropInteractionState.dragSession()) cleanUpTemporaryDirectories]; |
| [self teardownDataInteractionDelegates]; |
| #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; |
| } |
| |
| _inputViewUpdateDeferrer = nullptr; |
| _assistedNodeInformation = { }; |
| } |
| |
| - (void)_removeDefaultGestureRecognizers |
| { |
| [self removeGestureRecognizer:_touchEventGestureRecognizer.get()]; |
| [self removeGestureRecognizer:_singleTapGestureRecognizer.get()]; |
| [self removeGestureRecognizer:_highlightLongPressGestureRecognizer.get()]; |
| [self removeGestureRecognizer:_doubleTapGestureRecognizer.get()]; |
| [self removeGestureRecognizer:_nonBlockingDoubleTapGestureRecognizer.get()]; |
| [self removeGestureRecognizer:_twoFingerDoubleTapGestureRecognizer.get()]; |
| [self removeGestureRecognizer:_twoFingerSingleTapGestureRecognizer.get()]; |
| |
| #if USE(APPLE_INTERNAL_SDK) |
| [self _internalRemoveDefaultGestureRecognizers]; |
| #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:_twoFingerDoubleTapGestureRecognizer.get()]; |
| [self addGestureRecognizer:_twoFingerSingleTapGestureRecognizer.get()]; |
| |
| #if USE(APPLE_INTERNAL_SDK) |
| [self _internalAddDefaultGestureRecognizers]; |
| #endif |
| } |
| |
| - (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]; }]; |
| } |
| |
| _selectionNeedsUpdate = YES; |
| [self _updateChangedSelection:YES]; |
| [self _updateTapHighlight]; |
| } |
| |
| - (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) |
| return hitView; |
| } |
| return [super hitTest:point withEvent:event]; |
| } |
| |
| - (const InteractionInformationAtPosition&)positionInformation |
| { |
| return _positionInformation; |
| } |
| |
| - (void)setInputDelegate:(id <UITextInputDelegate>)inputDelegate |
| { |
| _inputDelegate = inputDelegate; |
| } |
| |
| - (id <UITextInputDelegate>)inputDelegate |
| { |
| return _inputDelegate; |
| } |
| |
| - (CGPoint)lastInteractionLocation |
| { |
| return _lastInteractionLocation; |
| } |
| |
| - (BOOL)shouldHideSelectionWhenScrolling |
| { |
| if (_isEditable) |
| return _assistedNodeInformation.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; |
| } |
| |
| - (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; |
| |
| BOOL didBecomeFirstResponder; |
| { |
| SetForScope<BOOL> becomingFirstResponder { _becomingFirstResponder, YES }; |
| didBecomeFirstResponder = [super becomeFirstResponder]; |
| } |
| |
| return didBecomeFirstResponder; |
| } |
| |
| - (BOOL)resignFirstResponder |
| { |
| return [_webView resignFirstResponder]; |
| } |
| |
| - (BOOL)resignFirstResponderForWebView |
| { |
| // FIXME: Maybe we should call resignFirstResponder on the superclass |
| // and do nothing if the return value is NO. |
| |
| _resigningFirstResponder = YES; |
| if (!_webView->_activeFocusedStateRetainCount) { |
| // We need to complete the editing operation before we blur the element. |
| [_inputPeripheral endEditing]; |
| _page->blurAssistedNode(); |
| } |
| |
| [self _cancelInteraction]; |
| [_webSelectionAssistant resignedFirstResponder]; |
| [_textSelectionAssistant deactivateSelection]; |
| |
| _inputViewUpdateDeferrer = nullptr; |
| |
| bool superDidResign = [super resignFirstResponder]; |
| |
| _resigningFirstResponder = NO; |
| |
| return superDidResign; |
| } |
| |
| - (void)_webTouchEventsRecognized:(UIWebTouchEventsGestureRecognizer *)gestureRecognizer |
| { |
| if (!_page->isValid()) |
| return; |
| |
| const _UIWebTouchEvent* lastTouchEvent = gestureRecognizer.lastTouchEvent; |
| |
| _lastInteractionLocation = lastTouchEvent->locationInDocumentCoordinates; |
| if (lastTouchEvent->type == UIWebTouchEventTouchBegin) |
| _layerTreeTransactionIdAtLastTouchStart = downcast<RemoteLayerTreeDrawingAreaProxy>(*_page->drawingArea()).lastCommittedLayerTreeTransactionID(); |
| |
| #if ENABLE(TOUCH_EVENTS) |
| NativeWebTouchEvent nativeWebTouchEvent(lastTouchEvent); |
| nativeWebTouchEvent.setCanPreventNativeGestures(!_canSendTouchEventsAsynchronously || [gestureRecognizer isDefaultPrevented]); |
| |
| if (_canSendTouchEventsAsynchronously) |
| _page->handleTouchEventAsynchronously(nativeWebTouchEvent); |
| else |
| _page->handleTouchEventSynchronously(nativeWebTouchEvent); |
| |
| if (nativeWebTouchEvent.allTouchPointsAreReleased()) |
| _canSendTouchEventsAsynchronously = 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 FloatQuad inflateQuad(const 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. |
| 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 FloatQuad(points[1], points[0], points[2], points[3]); |
| } |
| |
| #if ENABLE(TOUCH_EVENTS) |
| - (void)_webTouchEvent:(const WebKit::NativeWebTouchEvent&)touchEvent preventsNativeGestures:(BOOL)preventsNativeGesture |
| { |
| if (preventsNativeGesture) { |
| _highlightLongPressCanClick = NO; |
| |
| _canSendTouchEventsAsynchronously = YES; |
| [_touchEventGestureRecognizer setDefaultPrevented:YES]; |
| } |
| } |
| #endif |
| |
| static inline bool highlightedQuadsAreSmallerThanRect(const Vector<FloatQuad>& quads, const FloatRect& rect) |
| { |
| for (size_t i = 0; i < quads.size(); ++i) { |
| FloatRect boundingBox = quads[i].boundingBox(); |
| if (boundingBox.width() > rect.width() || boundingBox.height() > rect.height()) |
| return false; |
| } |
| return true; |
| } |
| |
| 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<UIColor> 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<WebCore::FloatQuad>& highlightedQuads = _tapHighlightInformation.quads; |
| const size_t quadCount = highlightedQuads.size(); |
| RetainPtr<NSMutableArray> rects = adoptNS([[NSMutableArray alloc] initWithCapacity:static_cast<const NSUInteger>(quadCount)]); |
| for (size_t i = 0; i < quadCount; ++i) { |
| const FloatQuad& quad = highlightedQuads[i]; |
| if (quad.isRectilinear()) { |
| FloatRect boundingBox = quad.boundingBox(); |
| boundingBox.scale(selfScale); |
| boundingBox.inflate(minimumTapHighlightRadius); |
| CGRect pixelAlignedRect = static_cast<CGRect>(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<NSMutableArray> quads = adoptNS([[NSMutableArray alloc] initWithCapacity:static_cast<const NSUInteger>(quadCount)]); |
| for (size_t i = 0; i < quadCount; ++i) { |
| FloatQuad quad = highlightedQuads[i]; |
| quad.scale(selfScale); |
| 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<NSMutableArray> 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 |
| { |
| if (!highlightedQuadsAreSmallerThanRect(_tapHighlightInformation.quads, _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<WebCore::FloatQuad>&)highlightedQuads topLeftRadius:(const WebCore::IntSize&)topLeftRadius topRightRadius:(const WebCore::IntSize&)topRightRadius bottomLeftRadius:(const WebCore::IntSize&)bottomLeftRadius bottomRightRadius:(const WebCore::IntSize&)bottomRightRadius |
| { |
| if (!_isTapHighlightIDValid || _latestTapID != requestID) |
| return; |
| |
| _isTapHighlightIDValid = NO; |
| |
| _tapHighlightInformation.quads = highlightedQuads; |
| _tapHighlightInformation.topLeftRadius = topLeftRadius; |
| _tapHighlightInformation.topRightRadius = topRightRadius; |
| _tapHighlightInformation.bottomLeftRadius = bottomLeftRadius; |
| _tapHighlightInformation.bottomRightRadius = bottomRightRadius; |
| 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)_cancelLongPressGestureRecognizer |
| { |
| [_highlightLongPressGestureRecognizer cancel]; |
| } |
| |
| - (void)_didScroll |
| { |
| [self _cancelLongPressGestureRecognizer]; |
| [self _cancelInteraction]; |
| } |
| |
| - (void)_overflowScrollingWillBegin |
| { |
| [_webSelectionAssistant willStartScrollingOverflow]; |
| [_textSelectionAssistant willStartScrollingOverflow]; |
| } |
| |
| - (void)_overflowScrollingDidEnd |
| { |
| // 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]; |
| [_webSelectionAssistant didEndScrollingOverflow]; |
| [_textSelectionAssistant didEndScrollingOverflow]; |
| } |
| |
| - (BOOL)_requiresKeyboardWhenFirstResponder |
| { |
| // FIXME: We should add the logic to handle keyboard visibility during focus redirects. |
| switch (_assistedNodeInformation.elementType) { |
| case InputType::None: |
| return NO; |
| case InputType::Select: |
| return !currentUserInterfaceIdiomIsPad(); |
| case InputType::Date: |
| case InputType::Month: |
| case InputType::DateTimeLocal: |
| case InputType::Time: |
| return !currentUserInterfaceIdiomIsPad(); |
| default: |
| return !_assistedNodeInformation.isReadOnly; |
| } |
| return NO; |
| } |
| |
| - (BOOL)_requiresKeyboardResetOnReload |
| { |
| return YES; |
| } |
| |
| - (void)_displayFormNodeInputView |
| { |
| // 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:_assistedNodeInformation.elementRect |
| selectionRect:_didAccessoryTabInitiateFocus ? IntRect() : _assistedNodeInformation.selectionRect |
| insideFixed:_assistedNodeInformation.insideFixedPosition |
| fontSize:_assistedNodeInformation.nodeFontSize |
| minimumScale:_assistedNodeInformation.minimumScaleFactor |
| maximumScale:_assistedNodeInformation.maximumScaleFactorIgnoringAlwaysScalable |
| allowScaling:_assistedNodeInformation.allowsUserScalingIgnoringAlwaysScalable && !currentUserInterfaceIdiomIsPad() |
| forceScroll:[self requiresAccessoryView]]; |
| |
| _didAccessoryTabInitiateFocus = NO; |
| [self _ensureFormAccessoryView]; |
| [self _updateAccessory]; |
| } |
| |
| - (UIView *)inputView |
| { |
| if (!hasAssistedNode(_assistedNodeInformation)) |
| return nil; |
| |
| if (!_inputPeripheral) |
| _inputPeripheral = adoptNS(_assistedNodeInformation.elementType == InputType::Select ? [[WKFormSelectControl alloc] initWithView:self] : [[WKFormInputControl alloc] initWithView:self]); |
| else |
| [self _displayFormNodeInputView]; |
| |
| return [_formInputSession customInputView] ?: [_inputPeripheral assistantView]; |
| } |
| |
| - (CGRect)_selectionClipRect |
| { |
| if (!hasAssistedNode(_assistedNodeInformation)) |
| return CGRectNull; |
| return _page->editorState().postLayoutData().selectionClipRect; |
| } |
| |
| - (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 |
| && ([preventedGestureRecognizer isKindOfClass:NSClassFromString(@"UIScrollViewPanGestureRecognizer")] || [preventedGestureRecognizer isKindOfClass:NSClassFromString(@"UIScrollViewPinchGestureRecognizer")])); |
| } |
| |
| - (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 ((preventingGestureRecognizer == _textSelectionAssistant.get().loupeGesture || isForcePressGesture || [_webSelectionAssistant isSelectionGestureRecognizer:preventingGestureRecognizer]) && (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 USE(APPLE_INTERNAL_SDK) |
| if ([self _internalGestureRecognizer:gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:otherGestureRecognizer]) |
| return YES; |
| #endif |
| |
| if (isSamePair(gestureRecognizer, otherGestureRecognizer, _highlightLongPressGestureRecognizer.get(), _longPressGestureRecognizer.get())) |
| return YES; |
| |
| if (isSamePair(gestureRecognizer, otherGestureRecognizer, _highlightLongPressGestureRecognizer.get(), _webSelectionAssistant.get().selectionLongPressRecognizer)) |
| return YES; |
| #if __IPHONE_OS_VERSION_MIN_REQUIRED >= 120000 |
| 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; |
| |
| 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 <WKUIDelegatePrivate> uiDelegate = static_cast<id <WKUIDelegatePrivate>>([_webView UIDelegate]); |
| if (![uiDelegate respondsToSelector:@selector(_webView:showCustomSheetForElement:)]) |
| return; |
| |
| auto element = adoptNS([[_WKActivatedElementInfo alloc] _initWithType:_WKActivatedElementTypeAttachment URL:(NSURL *)_positionInformation.url location:_positionInformation.request.point title:_positionInformation.title ID:_positionInformation.idAttribute rect:_positionInformation.bounds image:nil]); |
| [uiDelegate _webView:_webView showCustomSheetForElement:element.get()]; |
| } |
| |
| - (void)_showLinkSheet |
| { |
| [_actionSheetAssistant showLinkSheet]; |
| } |
| |
| - (void)_showDataDetectorsSheet |
| { |
| [_actionSheetAssistant showDataDetectorsSheet]; |
| } |
| |
| - (SEL)_actionForLongPressFromPositionInformation:(const 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 (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]; |
| } |
| |
| - (InteractionInformationAtPosition)currentPositionInformation |
| { |
| return _positionInformation; |
| } |
| |
| - (void)doAfterPositionInformationUpdate:(void (^)(InteractionInformationAtPosition))action forRequest:(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<Messages::WebPageProxy::DidReceivePositionInformation>(_page->pageID(), 1_s, IPC::WaitForOption::InterruptWaitingIfSyncMessageArrives); |
| |
| _hasValidPositionInformation = _page->process().sendSync(Messages::WebPage::GetPositionInformation(request), Messages::WebPage::GetPositionInformation::Reply(_positionInformation), _page->pageID(), 1_s); |
| |
| // 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 InteractionInformationRequest&)request |
| { |
| return _hasValidPositionInformation && _positionInformation.request.isValidForRequest(request); |
| } |
| |
| - (BOOL)_hasValidOutstandingPositionInformationRequest:(const InteractionInformationRequest&)request |
| { |
| return _outstandingPositionInformationRequest && _outstandingPositionInformationRequest->isValidForRequest(request); |
| } |
| |
| - (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] = std::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<NSValue *> *)_uiTextSelectionRects |
| { |
| NSMutableArray *textSelectionRects = [NSMutableArray array]; |
| |
| if (_textSelectionAssistant) { |
| for (WKTextSelectionRect *selectionRect in [_textSelectionAssistant valueForKeyPath:@"selectionView.selection.selectionRects"]) |
| [textSelectionRects addObject:[NSValue valueWithCGRect:selectionRect.webRect.rect]]; |
| } else if (_webSelectionAssistant) { |
| for (WebSelectionRect *selectionRect in [_webSelectionAssistant valueForKeyPath:@"selectionView.selectionRects"]) |
| [textSelectionRects addObject:[NSValue valueWithCGRect:selectionRect.rect]]; |
| } |
| |
| return textSelectionRects; |
| } |
| |
| - (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer |
| { |
| CGPoint point = [gestureRecognizer locationInView:self]; |
| |
| if (gestureRecognizer == _highlightLongPressGestureRecognizer |
| || gestureRecognizer == _doubleTapGestureRecognizer |
| || gestureRecognizer == _nonBlockingDoubleTapGestureRecognizer |
| || gestureRecognizer == _twoFingerDoubleTapGestureRecognizer |
| || gestureRecognizer == _singleTapGestureRecognizer) { |
| |
| if (hasAssistedNode(_assistedNodeInformation)) { |
| // Request information about the position with sync message. |
| // If the assisted node is the same, prevent the gesture. |
| if (![self ensurePositionInformationIsUpToDate:InteractionInformationRequest(roundedIntPoint(point))]) |
| return NO; |
| if (_positionInformation.nodeAtPositionIsAssistedNode) |
| return NO; |
| } |
| } |
| |
| if (gestureRecognizer == _highlightLongPressGestureRecognizer) { |
| if (hasAssistedNode(_assistedNodeInformation)) { |
| // This is a different node than the assisted 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; |
| InteractionInformationRequest request(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; |
| } |
| |
| [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. |
| InteractionInformationRequest request(roundedIntPoint(point)); |
| if (![self ensurePositionInformationIsUpToDate:request]) |
| return NO; |
| |
| if (hasAssistedNode(_assistedNodeInformation)) { |
| // Prevent the gesture if it is the same node. |
| if (_positionInformation.nodeAtPositionIsAssistedNode) |
| 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)hasSelectablePositionAtPoint:(CGPoint)point |
| { |
| if (!_webView.configuration._textInteractionGesturesEnabled) |
| return NO; |
| |
| if (_inspectorNodeSearchEnabled) |
| return NO; |
| |
| InteractionInformationRequest request(roundedIntPoint(point)); |
| if (![self ensurePositionInformationIsUpToDate:request]) |
| return NO; |
| |
| #if ENABLE(DATA_INTERACTION) |
| if (_positionInformation.hasSelectionAtPosition) { |
| // If the position might initiate a data interaction, 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; |
| |
| InteractionInformationRequest request(roundedIntPoint(point)); |
| if (![self ensurePositionInformationIsUpToDate:request]) |
| return NO; |
| return _positionInformation.isNearMarkedText; |
| } |
| |
| - (BOOL)textInteractionGesture:(UIWKGestureType)gesture shouldBeginAtPoint:(CGPoint)point |
| { |
| if (!_webView.configuration._textInteractionGesturesEnabled) |
| return NO; |
| |
| InteractionInformationRequest request(roundedIntPoint(point)); |
| if (![self ensurePositionInformationIsUpToDate:request]) |
| return NO; |
| |
| #if ENABLE(DATA_INTERACTION) |
| if (_positionInformation.hasSelectionAtPosition && gesture == UIWKGestureLoupe) { |
| // If the position might initiate data interaction, we don't want to change the selection. |
| return NO; |
| } |
| #endif |
| |
| // If we're currently editing an assisted node, only allow the selection to move within that assisted node. |
| if (self.isAssistingNode) |
| return _positionInformation.nodeAtPositionIsAssistedNode; |
| |
| // Don't allow double tap text gestures in noneditable content. |
| if (gesture == UIWKGestureDoubleTap) |
| return NO; |
| |
| // 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<WebCore::SelectionRect>&)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() == 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: |
| _highlightLongPressCanClick = YES; |
| cancelPotentialTapIfNecessary(self); |
| _page->tapHighlightAtPosition([gestureRecognizer startPoint], ++_latestTapID); |
| _isTapHighlightIDValid = YES; |
| break; |
| case UIGestureRecognizerStateEnded: |
| if (_highlightLongPressCanClick && _positionInformation.isElement) { |
| [self _attemptClickAtLocation:[gestureRecognizer startPoint]]; |
| [self _finishInteraction]; |
| } else |
| [self _cancelInteraction]; |
| _highlightLongPressCanClick = NO; |
| break; |
| case UIGestureRecognizerStateCancelled: |
| [self _cancelInteraction]; |
| _highlightLongPressCanClick = NO; |
| break; |
| default: |
| break; |
| } |
| } |
| |
| - (void)_twoFingerSingleTapGestureRecognized:(UITapGestureRecognizer *)gestureRecognizer |
| { |
| _isTapHighlightIDValid = YES; |
| _isExpectingFastSingleTapCommit = YES; |
| _page->handleTwoFingerTapAtPoint(roundedIntPoint(gestureRecognizer.centroid), ++_latestTapID); |
| } |
| |
| - (void)_longPressRecognized:(UILongPressGestureRecognizer *)gestureRecognizer |
| { |
| ASSERT(gestureRecognizer == _longPressGestureRecognizer); |
| [self _resetIsDoubleTapPending]; |
| |
| _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)_singleTapRecognized:(UITapGestureRecognizer *)gestureRecognizer |
| { |
| ASSERT(gestureRecognizer == _singleTapGestureRecognizer); |
| ASSERT(!_potentialTapInProgress); |
| [self _resetIsDoubleTapPending]; |
| |
| _page->potentialTapAtPosition(gestureRecognizer.location, ++_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); |
| } |
| |
| - (void)_commitPotentialTapFailed |
| { |
| [self _cancelInteraction]; |
| |
| _inputViewUpdateDeferrer = nullptr; |
| } |
| |
| - (void)_didNotHandleTapAsClick:(const WebCore::IntPoint&)point |
| { |
| _inputViewUpdateDeferrer = nullptr; |
| |
| // 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 |
| { |
| _inputViewUpdateDeferrer = nullptr; |
| } |
| |
| - (void)_singleTapCommited:(UITapGestureRecognizer *)gestureRecognizer |
| { |
| ASSERT(gestureRecognizer == _singleTapGestureRecognizer); |
| |
| if (![self isFirstResponder]) { |
| if (!_inputViewUpdateDeferrer) |
| _inputViewUpdateDeferrer = std::make_unique<InputViewUpdateDeferrer>(); |
| [self becomeFirstResponder]; |
| } |
| |
| if (_webSelectionAssistant && ![_webSelectionAssistant shouldHandleSingleTapAtPoint:gestureRecognizer.location]) { |
| [self _singleTapDidReset:gestureRecognizer]; |
| return; |
| } |
| |
| ASSERT(_potentialTapInProgress); |
| |
| // We don't want to clear the selection if it is in editable content. |
| // The selection could have been set by autofocusing on page load and not |
| // reflected in the UI process since the user was not interacting with the page. |
| if (!_page->editorState().isContentEditable) |
| [_webSelectionAssistant clearSelection]; |
| |
| _lastInteractionLocation = gestureRecognizer.location; |
| |
| [self _endPotentialTapAndEnableDoubleTapGesturesIfNecessary]; |
| |
| if (_hasTapHighlightForPotentialTap) { |
| [self _showTapHighlight]; |
| _hasTapHighlightForPotentialTap = NO; |
| } |
| |
| [_inputPeripheral endEditing]; |
| _page->commitPotentialTap(_layerTreeTransactionIdAtLastTouchStart); |
| |
| if (!_isExpectingFastSingleTapCommit) |
| [self _finishInteraction]; |
| } |
| |
| - (void)_doubleTapRecognized:(UITapGestureRecognizer *)gestureRecognizer |
| { |
| [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 |
| { |
| if (![self isFirstResponder]) { |
| if (!_inputViewUpdateDeferrer) |
| _inputViewUpdateDeferrer = std::make_unique<InputViewUpdateDeferrer>(); |
| [self becomeFirstResponder]; |
| } |
| |
| [_inputPeripheral endEditing]; |
| _page->handleTap(location, _layerTreeTransactionIdAtLastTouchStart); |
| } |
| |
| - (void)useSelectionAssistantWithGranularity:(WKSelectionGranularity)selectionGranularity |
| { |
| _webSelectionAssistant = nil; |
| |
| if (!_textSelectionAssistant) |
| _textSelectionAssistant = adoptNS([[UIWKTextInteractionAssistant alloc] initWithView:self]); |
| else { |
| // Reset the gesture recognizers in case editibility has changed. |
| [_textSelectionAssistant setGestureRecognizers]; |
| } |
| } |
| |
| - (void)clearSelection |
| { |
| [self _stopAssistingNode]; |
| _page->clearSelection(); |
| } |
| |
| - (void)_positionInformationDidChange:(const InteractionInformationAtPosition&)info |
| { |
| _outstandingPositionInformationRequest = std::nullopt; |
| |
| InteractionInformationAtPosition newInfo = info; |
| newInfo.mergeCompatibleOptionalInformation(_positionInformation); |
| |
| _positionInformation = newInfo; |
| _hasValidPositionInformation = YES; |
| if (_actionSheetAssistant) |
| [_actionSheetAssistant updateSheetPosition]; |
| [self _invokeAndRemovePendingHandlersValidForCurrentPositionInformation]; |
| } |
| |
| - (void)_willStartScrollingOrZooming |
| { |
| [_webSelectionAssistant willStartScrollingOrZoomingPage]; |
| [_textSelectionAssistant willStartScrollingOverflow]; |
| _page->setIsScrollingOrZooming(true); |
| |
| #if ENABLE(EXTRA_ZOOM_MODE) |
| [_focusedFormControlView disengageFocusedFormControlNavigation]; |
| #endif |
| } |
| |
| - (void)scrollViewWillStartPanOrPinchGesture |
| { |
| _page->hideValidationMessage(); |
| |
| _canSendTouchEventsAsynchronously = YES; |
| } |
| |
| - (void)_didEndScrollingOrZooming |
| { |
| if (!_needsDeferredEndScrollingSelectionUpdate) { |
| [_webSelectionAssistant didEndScrollingOrZoomingPage]; |
| [_textSelectionAssistant didEndScrollingOverflow]; |
| } |
| _page->setIsScrollingOrZooming(false); |
| |
| #if ENABLE(EXTRA_ZOOM_MODE) |
| [_focusedFormControlView engageFocusedFormControlNavigation]; |
| #endif |
| } |
| |
| - (BOOL)requiresAccessoryView |
| { |
| if ([_formInputSession accessoryViewShouldNotShow]) |
| return NO; |
| |
| switch (_assistedNodeInformation.elementType) { |
| case InputType::None: |
| return NO; |
| case InputType::Text: |
| case InputType::Password: |
| case InputType::Search: |
| case InputType::Email: |
| case InputType::URL: |
| case InputType::Phone: |
| case InputType::Number: |
| case InputType::NumberPad: |
| case InputType::ContentEditable: |
| case InputType::TextArea: |
| case InputType::Select: |
| case InputType::Date: |
| case InputType::DateTime: |
| case InputType::DateTimeLocal: |
| case InputType::Month: |
| case InputType::Week: |
| case InputType::Time: |
| return !currentUserInterfaceIdiomIsPad(); |
| } |
| } |
| |
| - (void)_ensureFormAccessoryView |
| { |
| if (_formAccessoryView) |
| return; |
| |
| _formAccessoryView = adoptNS([[UIWebFormAccessory alloc] initWithInputAssistantItem:self.inputAssistantItem]); |
| [_formAccessoryView setDelegate:self]; |
| } |
| |
| - (UIView *)inputAccessoryView |
| { |
| if (![self requiresAccessoryView]) |
| return nil; |
| |
| return 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:WebArchivePboardType]; |
| [richTypes addObjectsFromArray:UIPasteboardTypeListImage]; |
| [richTypes addObjectsFromArray:plainTextTypes]; |
| } |
| |
| return (_page->editorState().isContentRichlyEditable) ? richTypes : plainTextTypes; |
| } |
| |
| #define FORWARD_ACTION_TO_WKWEBVIEW(_action) \ |
| - (void)_action:(id)sender \ |
| { \ |
| [_webView _action:sender]; \ |
| } |
| |
| FOR_EACH_WKCONTENTVIEW_ACTION(FORWARD_ACTION_TO_WKWEBVIEW) |
| |
| #undef FORWARD_ACTION_TO_WKWEBVIEW |
| |
| - (void)_lookupForWebView:(id)sender |
| { |
| RetainPtr<WKContentView> view = self; |
| _page->getSelectionContext([view](const String& selectedText, const String& textBefore, const String& textAfter, CallbackBase::Error error) { |
| if (error != 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]; |
| else |
| [view->_webSelectionAssistant lookup:selectionContext withRange:selectedRangeInContext fromRect:presentationRect]; |
| }); |
| } |
| |
| - (void)_shareForWebView:(id)sender |
| { |
| RetainPtr<WKContentView> view = self; |
| _page->getSelectionOrContentsAsString([view](const String& string, CallbackBase::Error error) { |
| if (error != CallbackBase::Error::None) |
| return; |
| if (!string) |
| return; |
| |
| CGRect presentationRect = view->_page->editorState().postLayoutData().selectionRects[0].rect(); |
| |
| if (view->_textSelectionAssistant) |
| [view->_textSelectionAssistant showShareSheetFor:string fromRect:presentationRect]; |
| else if (view->_webSelectionAssistant) |
| [view->_webSelectionAssistant showShareSheetFor:string fromRect:presentationRect]; |
| }); |
| } |
| |
| - (void)_addShortcutForWebView:(id)sender |
| { |
| if (_textSelectionAssistant) |
| [_textSelectionAssistant showTextServiceFor:[self selectedText] fromRect:_page->editorState().postLayoutData().selectionRects[0].rect()]; |
| else if (_webSelectionAssistant) |
| [_webSelectionAssistant showTextServiceFor:[self selectedText] fromRect:_page->editorState().postLayoutData().selectionRects[0].rect()]; |
| } |
| |
| - (NSString *)selectedText |
| { |
| return (NSString *)_page->editorState().postLayoutData().wordAtSelection; |
| } |
| |
| - (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)_reanalyzeForWebView:(id)sender |
| { |
| [_textSelectionAssistant scheduleReanalysis]; |
| } |
| |
| - (void)replaceForWebView:(id)sender |
| { |
| [[UIKeyboardImpl sharedInstance] replaceText: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 & AttributeBold) |
| symbolicTraits |= kCTFontBoldTrait; |
| if (typingAttributes & 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<CTFontDescriptorRef> fontDescriptor = adoptCF(CTFontDescriptorCreateWithNameAndSize(CFSTR("Helvetica"), 10)); |
| if (symbolicTraits) |
| fontDescriptor = adoptCF(CTFontDescriptorCreateCopyWithSymbolicTraits(fontDescriptor.get(), symbolicTraits, symbolicTraits)); |
| |
| RetainPtr<CTFontRef> font = adoptCF(CTFontCreateWithFontDescriptor(fontDescriptor.get(), 10, nullptr)); |
| if (font) |
| [result setObject:(id)font.get() forKey:NSFontAttributeName]; |
| |
| if (typingAttributes & AttributeUnderline) |
| [result setObject:[NSNumber numberWithInt:NSUnderlineStyleSingle] forKey:NSUnderlineStyleAttributeName]; |
| |
| return result; |
| } |
| |
| - (UIColor *)insertionPointColor |
| { |
| 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 [UIColor insertionPointColor]; |
| } |
| |
| - (BOOL)canPerformAction:(SEL)action withSender:(id)sender |
| { |
| return [_webView canPerformAction:action withSender:sender]; |
| } |
| |
| - (BOOL)canPerformActionForWebView:(SEL)action withSender:(id)sender |
| { |
| BOOL hasWebSelection = _webSelectionAssistant && !CGRectIsEmpty(_webSelectionAssistant.get().selectionFrame); |
| |
| if (action == @selector(_arrowKey:)) |
| return [self isFirstResponder]; |
| |
| if (action == @selector(_showTextStyleOptions:)) |
| return _page->editorState().isContentRichlyEditable && _page->editorState().selectionIsRange && !_showingTextStyleOptions; |
| if (_showingTextStyleOptions) |
| return (action == @selector(toggleBoldface:) || action == @selector(toggleItalics:) || action == @selector(toggleUnderline:)); |
| if (action == @selector(toggleBoldface:) || action == @selector(toggleItalics:) || action == @selector(toggleUnderline:)) |
| return _page->editorState().isContentRichlyEditable; |
| if (action == @selector(cut:)) |
| return !_page->editorState().isInPasswordField && _page->editorState().isContentEditable && _page->editorState().selectionIsRange; |
| |
| if (action == @selector(paste:)) { |
| if (_page->editorState().selectionIsNone || !_page->editorState().isContentEditable) |
| return NO; |
| UIPasteboard *pasteboard = [UIPasteboard generalPasteboard]; |
| NSArray *types = [self supportedPasteboardTypesForCurrentSelection]; |
| NSIndexSet *indices = [NSIndexSet indexSetWithIndexesInRange:NSMakeRange(0, [pasteboard numberOfItems])]; |
| return [pasteboard containsPasteboardTypes:types inItemSet:indices]; |
| } |
| |
| if (action == @selector(copy:)) { |
| if (_page->editorState().isInPasswordField) |
| return NO; |
| return hasWebSelection || _page->editorState().selectionIsRange; |
| } |
| |
| if (action == @selector(_define:)) { |
| if (_page->editorState().isInPasswordField || !(hasWebSelection || _page->editorState().selectionIsRange)) |
| return NO; |
| |
| NSUInteger textLength = _page->editorState().postLayoutData().selectedTextLength; |
| // FIXME: We should be calling UIReferenceLibraryViewController to check if the length is |
| // acceptable, but the interface takes a string. |
| // <rdar://problem/15254406> |
| if (!textLength || textLength > 200) |
| return NO; |
| |
| #if !ENABLE(MINIMAL_SIMULATOR) |
| if ([[getMCProfileConnectionClass() sharedConnection] effectiveBoolValueForSetting:MCFeatureDefinitionLookupAllowed] == MCRestrictedBoolExplicitNo) |
| return NO; |
| #endif |
| |
| return YES; |
| } |
| |
| if (action == @selector(_lookup:)) { |
| if (_page->editorState().isInPasswordField) |
| return NO; |
| |
| #if !ENABLE(MINIMAL_SIMULATOR) |
| if ([[getMCProfileConnectionClass() sharedConnection] effectiveBoolValueForSetting:MCFeatureDefinitionLookupAllowed] == MCRestrictedBoolExplicitNo) |
| return NO; |
| #endif |
| |
| return hasWebSelection || _page->editorState().selectionIsRange; |
| } |
| |
| if (action == @selector(_share:)) { |
| if (_page->editorState().isInPasswordField || !(hasWebSelection || _page->editorState().selectionIsRange)) |
| return NO; |
| |
| return _page->editorState().postLayoutData().selectedTextLength > 0; |
| } |
| |
| if (action == @selector(_addShortcut:)) { |
| if (_page->editorState().isInPasswordField || !(hasWebSelection || _page->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 (!_page->editorState().selectionIsRange || !_page->editorState().postLayoutData().isReplaceAllowed || ![[UIKeyboardImpl activeInstance] autocorrectSpellingEnabled]) |
| return NO; |
| if ([[self selectedText] _containsCJScriptsOnly]) |
| return NO; |
| return YES; |
| } |
| |
| if (action == @selector(_transliterateChinese:)) { |
| if (!_page->editorState().selectionIsRange || !_page->editorState().postLayoutData().isReplaceAllowed || ![[UIKeyboardImpl activeInstance] autocorrectSpellingEnabled]) |
| return NO; |
| return UIKeyboardEnabledInputModesAllowChineseTransliterationForText([self selectedText]); |
| } |
| |
| if (action == @selector(_reanalyze:)) { |
| if (!_page->editorState().selectionIsRange || !_page->editorState().postLayoutData().isReplaceAllowed || ![[UIKeyboardImpl activeInstance] autocorrectSpellingEnabled]) |
| return NO; |
| return UIKeyboardCurrentInputModeAllowsChineseOrJapaneseReanalysisForText([self selectedText]); |
| } |
| |
| if (action == @selector(select:)) { |
| // Disable select in password fields so that you can't see word boundaries. |
| return !_page->editorState().isInPasswordField && [self hasContent] && !_page->editorState().selectionIsNone && !_page->editorState().selectionIsRange; |
| } |
| |
| if (action == @selector(selectAll:)) { |
| if (_page->editorState().selectionIsNone || ![self hasContent]) |
| return NO; |
| if (!_page->editorState().selectionIsRange) |
| return YES; |
| // Enable selectAll for non-editable text, where the user can't access |
| // this command via long-press to get a caret. |
| if (_page->editorState().isContentEditable) |
| return NO; |
| // Don't attempt selectAll with general web content. |
| if (hasWebSelection) |
| return NO; |
| // FIXME: Only enable if the selection doesn't already span the entire document. |
| return YES; |
| } |
| |
| if (action == @selector(replace:)) |
| return _page->editorState().isContentEditable && !_page->editorState().isInPasswordField; |
| |
| 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)_resetShowingTextStyle:(NSNotification *)notification |
| { |
| _showingTextStyleOptions = NO; |
| [_textSelectionAssistant hideTextStyleOptions]; |
| } |
| |
| - (void)copyForWebView:(id)sender |
| { |
| _page->executeEditCommand(ASCIILiteral("copy")); |
| } |
| |
| - (void)cutForWebView:(id)sender |
| { |
| _page->executeEditCommand(ASCIILiteral("cut")); |
| } |
| |
| - (void)pasteForWebView:(id)sender |
| { |
| _page->executeEditCommand(ASCIILiteral("paste")); |
| } |
| |
| - (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(WordGranularity); |
| } |
| |
| - (void)selectAllForWebView:(id)sender |
| { |
| [_textSelectionAssistant selectAll:sender]; |
| _page->executeEditCommand(ASCIILiteral("selectAll")); |
| } |
| |
| - (void)toggleBoldfaceForWebView:(id)sender |
| { |
| if (!_page->editorState().isContentRichlyEditable) |
| return; |
| |
| [self executeEditCommandWithCallback:@"toggleBold"]; |
| } |
| |
| - (void)toggleItalicsForWebView:(id)sender |
| { |
| if (!_page->editorState().isContentRichlyEditable) |
| return; |
| |
| [self executeEditCommandWithCallback:@"toggleItalic"]; |
| } |
| |
| - (void)toggleUnderlineForWebView:(id)sender |
| { |
| if (!_page->editorState().isContentRichlyEditable) |
| return; |
| |
| [self executeEditCommandWithCallback:@"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]; |
| else |
| [_webSelectionAssistant showDictionaryFor:text fromRect:presentationRect]; |
| } |
| |
| - (void)_defineForWebView:(id)sender |
| { |
| #if !ENABLE(MINIMAL_SIMULATOR) |
| if ([[getMCProfileConnectionClass() sharedConnection] effectiveBoolValueForSetting:MCFeatureDefinitionLookupAllowed] == MCRestrictedBoolExplicitNo) |
| return; |
| #endif |
| |
| RetainPtr<WKContentView> 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<WKContentView> view = self; |
| RetainPtr<WKWebView> 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<WKContentView> view = self; |
| _page->requestRectsForGranularityWithSelectionOffset(toWKTextGranularity(granularity), offset , [view, offset, granularity](const Vector<WebCore::SelectionRect>& selectionRects, 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<SelectionRect>& rects))completionHandler |
| { |
| RetainPtr<WKContentView> view = self; |
| _page->requestRectsAtSelectionOffsetWithText(offset, text, [view, offset, capturedCompletionHandler = makeBlockPtr(completionHandler)](const Vector<SelectionRect>& selectionRects, 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); |
| } |
| |
| // UIWKInteractionViewProtocol |
| |
| static inline GestureType toGestureType(UIWKGestureType gestureType) |
| { |
| switch (gestureType) { |
| case UIWKGestureLoupe: |
| return GestureType::Loupe; |
| case UIWKGestureOneFingerTap: |
| return GestureType::OneFingerTap; |
| case UIWKGestureTapAndAHalf: |
| return GestureType::TapAndAHalf; |
| case UIWKGestureDoubleTap: |
| return GestureType::DoubleTap; |
| case UIWKGestureTapAndHalf: |
| return GestureType::TapAndHalf; |
| case UIWKGestureDoubleTapInUneditable: |
| return GestureType::DoubleTapInUneditable; |
| case UIWKGestureOneFingerTapInUneditable: |
| return GestureType::OneFingerTapInUneditable; |
| case UIWKGestureOneFingerTapSelectsAll: |
| return GestureType::OneFingerTapSelectsAll; |
| case UIWKGestureOneFingerDoubleTap: |
| return GestureType::OneFingerDoubleTap; |
| case UIWKGestureOneFingerTripleTap: |
| return GestureType::OneFingerTripleTap; |
| case UIWKGestureTwoFingerSingleTap: |
| return GestureType::TwoFingerSingleTap; |
| case UIWKGestureTwoFingerRangedSelectGesture: |
| return GestureType::TwoFingerRangedSelectGesture; |
| case UIWKGestureTapOnLinkWithGesture: |
| return GestureType::TapOnLinkWithGesture; |
| case UIWKGestureMakeWebSelection: |
| return GestureType::MakeWebSelection; |
| case UIWKGesturePhraseBoundary: |
| return GestureType::PhraseBoundary; |
| } |
| ASSERT_NOT_REACHED(); |
| return GestureType::Loupe; |
| } |
| |
| static inline UIWKGestureType toUIWKGestureType(GestureType gestureType) |
| { |
| switch (gestureType) { |
| case GestureType::Loupe: |
| return UIWKGestureLoupe; |
| case GestureType::OneFingerTap: |
| return UIWKGestureOneFingerTap; |
| case GestureType::TapAndAHalf: |
| return UIWKGestureTapAndAHalf; |
| case GestureType::DoubleTap: |
| return UIWKGestureDoubleTap; |
| case GestureType::TapAndHalf: |
| return UIWKGestureTapAndHalf; |
| case GestureType::DoubleTapInUneditable: |
| return UIWKGestureDoubleTapInUneditable; |
| case GestureType::OneFingerTapInUneditable: |
| return UIWKGestureOneFingerTapInUneditable; |
| case GestureType::OneFingerTapSelectsAll: |
| return UIWKGestureOneFingerTapSelectsAll; |
| case GestureType::OneFingerDoubleTap: |
| return UIWKGestureOneFingerDoubleTap; |
| case GestureType::OneFingerTripleTap: |
| return UIWKGestureOneFingerTripleTap; |
| case GestureType::TwoFingerSingleTap: |
| return UIWKGestureTwoFingerSingleTap; |
| case GestureType::TwoFingerRangedSelectGesture: |
| return UIWKGestureTwoFingerRangedSelectGesture; |
| case GestureType::TapOnLinkWithGesture: |
| return UIWKGestureTapOnLinkWithGesture; |
| case GestureType::MakeWebSelection: |
| return UIWKGestureMakeWebSelection; |
| case GestureType::PhraseBoundary: |
| return UIWKGesturePhraseBoundary; |
| } |
| } |
| |
| static inline SelectionTouch toSelectionTouch(UIWKSelectionTouch touch) |
| { |
| switch (touch) { |
| case UIWKSelectionTouchStarted: |
| return SelectionTouch::Started; |
| case UIWKSelectionTouchMoved: |
| return SelectionTouch::Moved; |
| case UIWKSelectionTouchEnded: |
| return SelectionTouch::Ended; |
| case UIWKSelectionTouchEndedMovingForward: |
| return SelectionTouch::EndedMovingForward; |
| case UIWKSelectionTouchEndedMovingBackward: |
| return SelectionTouch::EndedMovingBackward; |
| case UIWKSelectionTouchEndedNotMoving: |
| return SelectionTouch::EndedNotMoving; |
| } |
| ASSERT_NOT_REACHED(); |
| return SelectionTouch::Ended; |
| } |
| |
| static inline UIWKSelectionTouch toUIWKSelectionTouch(SelectionTouch touch) |
| { |
| switch (touch) { |
| case SelectionTouch::Started: |
| return UIWKSelectionTouchStarted; |
| case SelectionTouch::Moved: |
| return UIWKSelectionTouchMoved; |
| case SelectionTouch::Ended: |
| return UIWKSelectionTouchEnded; |
| case SelectionTouch::EndedMovingForward: |
| return UIWKSelectionTouchEndedMovingForward; |
| case SelectionTouch::EndedMovingBackward: |
| return UIWKSelectionTouchEndedMovingBackward; |
| case SelectionTouch::EndedNotMoving: |
| return UIWKSelectionTouchEndedNotMoving; |
| } |
| } |
| |
| static inline GestureRecognizerState toGestureRecognizerState(UIGestureRecognizerState state) |
| { |
| switch (state) { |
| case UIGestureRecognizerStatePossible: |
| return GestureRecognizerState::Possible; |
| case UIGestureRecognizerStateBegan: |
| return GestureRecognizerState::Began; |
| case UIGestureRecognizerStateChanged: |
| return GestureRecognizerState::Changed; |
| case UIGestureRecognizerStateCancelled: |
| return GestureRecognizerState::Cancelled; |
| case UIGestureRecognizerStateEnded: |
| return GestureRecognizerState::Ended; |
| case UIGestureRecognizerStateFailed: |
| return GestureRecognizerState::Failed; |
| } |
| } |
| |
| static inline UIGestureRecognizerState toUIGestureRecognizerState(GestureRecognizerState state) |
| { |
| switch (state) { |
| case GestureRecognizerState::Possible: |
| return UIGestureRecognizerStatePossible; |
| case GestureRecognizerState::Began: |
| return UIGestureRecognizerStateBegan; |
| case GestureRecognizerState::Changed: |
| return UIGestureRecognizerStateChanged; |
| case GestureRecognizerState::Cancelled: |
| return UIGestureRecognizerStateCancelled; |
| case GestureRecognizerState::Ended: |
| return UIGestureRecognizerStateEnded; |
| case GestureRecognizerState::Failed: |
| return UIGestureRecognizerStateFailed; |
| } |
| } |
| |
| static inline UIWKSelectionFlags toUIWKSelectionFlags(SelectionFlags flags) |
| { |
| NSInteger uiFlags = UIWKNone; |
| if (flags & WordIsNearTap) |
| uiFlags |= UIWKWordIsNearTap; |
| if (flags & PhraseBoundaryChanged) |
| uiFlags |= UIWKPhraseBoundaryChanged; |
| |
| return static_cast<UIWKSelectionFlags>(uiFlags); |
| } |
| |
| static inline WebCore::TextGranularity toWKTextGranularity(UITextGranularity granularity) |
| { |
| switch (granularity) { |
| case UITextGranularityCharacter: |
| return CharacterGranularity; |
| case UITextGranularityWord: |
| return WordGranularity; |
| case UITextGranularitySentence: |
| return SentenceGranularity; |
| case UITextGranularityParagraph: |
| return ParagraphGranularity; |
| case UITextGranularityLine: |
| return LineGranularity; |
| case UITextGranularityDocument: |
| return DocumentGranularity; |
| } |
| } |
| |
| static inline WebCore::SelectionDirection toWKSelectionDirection(UITextDirection direction) |
| { |
| switch (direction) { |
| case UITextLayoutDirectionDown: |
| case UITextLayoutDirectionRight: |
| return DirectionRight; |
| case UITextLayoutDirectionUp: |
| case UITextLayoutDirectionLeft: |
| return DirectionLeft; |
| default: |
| // UITextDirection is not an enum, but we only want to accept values from UITextLayoutDirection. |
| ASSERT_NOT_REACHED(); |
| return 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; |
| } |
| if ([view webSelectionAssistant]) |
| [(UIWKSelectionAssistant *)[view webSelectionAssistant] selectionChangedWithGestureAt:(CGPoint)point withGesture:toUIWKGestureType((GestureType)gestureType) withState:toUIGestureRecognizerState(static_cast<GestureRecognizerState>(gestureState)) withFlags:(toUIWKSelectionFlags((SelectionFlags)flags))]; |
| else |
| [(UIWKTextInteractionAssistant *)[view interactionAssistant] selectionChangedWithGestureAt:(CGPoint)point withGesture:toUIWKGestureType((GestureType)gestureType) withState:toUIGestureRecognizerState(static_cast<GestureRecognizerState>(gestureState)) withFlags:(toUIWKSelectionFlags((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; |
| } |
| if ([view webSelectionAssistant]) |
| [(UIWKSelectionAssistant *)[view webSelectionAssistant] selectionChangedWithTouchAt:(CGPoint)point withSelectionTouch:toUIWKSelectionTouch((SelectionTouch)touch) withFlags:static_cast<UIWKSelectionFlags>(flags)]; |
| else |
| [(UIWKTextInteractionAssistant *)[view interactionAssistant] selectionChangedWithTouchAt:(CGPoint)point withSelectionTouch:toUIWKSelectionTouch((SelectionTouch)touch) withFlags:static_cast<UIWKSelectionFlags>(flags)]; |
| } |
| |
| - (BOOL)_isInteractingWithAssistedNode |
| { |
| return hasAssistedNode(_assistedNodeInformation); |
| } |
| |
| - (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; |
| _page->selectWithGesture(WebCore::IntPoint(point), CharacterGranularity, static_cast<uint32_t>(toGestureType(gestureType)), static_cast<uint32_t>(toGestureRecognizerState(state)), [self _isInteractingWithAssistedNode], [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; |
| }); |
| } |
| |
| #if __IPHONE_OS_VERSION_MAX_ALLOWED < 120000 |
| - (void)changeSelectionWithTouchAt:(CGPoint)point withSelectionTouch:(UIWKSelectionTouch)touch baseIsStart:(BOOL)baseIsStart |
| { |
| [self changeSelectionWithTouchAt:point withSelectionTouch:touch baseIsStart:baseIsStart withFlags:UIWKNone]; |
| } |
| #endif |
| |
| - (void)changeSelectionWithTouchAt:(CGPoint)point withSelectionTouch:(UIWKSelectionTouch)touch baseIsStart:(BOOL)baseIsStart withFlags:(UIWKSelectionFlags)flags |
| { |
| _usingGestureForSelection = YES; |
| _page->updateSelectionWithTouches(WebCore::IntPoint(point), static_cast<uint32_t>(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<uint32_t>(toGestureType(gestureType)), static_cast<uint32_t>(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<WKContentView> view = self; |
| _page->moveSelectionByOffset(offset, [view](WebKit::CallbackBase::Error) { |
| [view endSelectionChange]; |
| }); |
| } |
| |
| - (const 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 (!input || ![input length]) { |
| completionHandler(nil); |
| return; |
| } |
| |
| RetainPtr<WKContentView> view = self; |
| _autocorrectionData.autocorrectionHandler = [completionHandler copy]; |
| _page->requestAutocorrectionData(input, [view](const Vector<FloatRect>& rects, const String& fontName, double fontSize, uint64_t traits, WebKit::CallbackBase::Error) { |
| CGRect firstRect = CGRectZero; |
| CGRect lastRect = CGRectZero; |
| if (rects.size()) { |
| firstRect = rects[0]; |
| lastRect = rects[rects.size() - 1]; |
| } |
| |
| view->_autocorrectionData.fontName = fontName; |
| view->_autocorrectionData.fontSize = fontSize; |
| view->_autocorrectionData.fontTraits = traits; |
| view->_autocorrectionData.textFirstRect = firstRect; |
| view->_autocorrectionData.textLastRect = lastRect; |
| |
| view->_autocorrectionData.autocorrectionHandler(rects.size() ? [WKAutocorrectionRects autocorrectionRectsWithRects:firstRect lastRect:lastRect] : nil); |
| [view->_autocorrectionData.autocorrectionHandler release]; |
| view->_autocorrectionData.autocorrectionHandler = nil; |
| }); |
| } |
| |
| - (void)selectPositionAtPoint:(CGPoint)point completionHandler:(void (^)(void))completionHandler |
| { |
| _usingGestureForSelection = YES; |
| UIWKSelectionCompletionHandler selectionHandler = [completionHandler copy]; |
| RetainPtr<WKContentView> view = self; |
| |
| _page->selectPositionAtPoint(WebCore::IntPoint(point), [self _isInteractingWithAssistedNode], [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<WKContentView> view = self; |
| |
| _page->selectPositionAtBoundaryWithDirection(WebCore::IntPoint(point), toWKTextGranularity(granularity), toWKSelectionDirection(direction), [self _isInteractingWithAssistedNode], [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<WKContentView> 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; |
| UIWKSelectionCompletionHandler selectionHandler = [completionHandler copy]; |
| RetainPtr<WKContentView> view = self; |
| |
| _page->selectTextWithGranularityAtPoint(WebCore::IntPoint(point), toWKTextGranularity(granularity), [self _isInteractingWithAssistedNode], [view, selectionHandler](WebKit::CallbackBase::Error error) { |
| selectionHandler(); |
| view->_usingGestureForSelection = NO; |
| [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 _isInteractingWithAssistedNode], [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]; |
| |
| _page->updateSelectionWithExtentPointAndBoundary(WebCore::IntPoint(point), toWKTextGranularity(granularity), [self _isInteractingWithAssistedNode], [selectionHandler](bool endIsMoving, WebKit::CallbackBase::Error error) { |
| selectionHandler(endIsMoving); |
| [selectionHandler release]; |
| }); |
| } |
| |
| - (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 |
| { |
| // FIXME: Remove the synchronous call when <rdar://problem/16207002> is fixed. |
| const bool useSyncRequest = true; |
| |
| if (useSyncRequest) { |
| completionHandler(_page->applyAutocorrection(correction, input) ? [WKAutocorrectionRects autocorrectionRectsWithRects:_autocorrectionData.textFirstRect lastRect:_autocorrectionData.textLastRect] : nil); |
| return; |
| } |
| _autocorrectionData.autocorrectionHandler = [completionHandler copy]; |
| RetainPtr<WKContentView> view = self; |
| _page->applyAutocorrection(correction, input, [view](const String& string, WebKit::CallbackBase::Error error) { |
| view->_autocorrectionData.autocorrectionHandler(!string.isNull() ? [WKAutocorrectionRects autocorrectionRectsWithRects:view->_autocorrectionData.textFirstRect lastRect:view->_autocorrectionData.textLastRect] : nil); |
| [view->_autocorrectionData.autocorrectionHandler release]; |
| view->_autocorrectionData.autocorrectionHandler = nil; |
| }); |
| } |
| |
| - (void)requestAutocorrectionContextWithCompletionHandler:(void (^)(UIWKAutocorrectionContext *autocorrectionContext))completionHandler |
| { |
| // FIXME: Remove the synchronous call when <rdar://problem/16207002> is fixed. |
| const bool useSyncRequest = true; |
| |
| if (useSyncRequest) { |
| String beforeText; |
| String markedText; |
| String selectedText; |
| String afterText; |
| uint64_t location; |
| uint64_t length; |
| _page->getAutocorrectionContext(beforeText, markedText, selectedText, afterText, location, length); |
| completionHandler([WKAutocorrectionContext autocorrectionContextWithData:beforeText markedText:markedText selectedText:selectedText afterText:afterText selectedRangeInMarkedText:NSMakeRange(location, length)]); |
| } else { |
| _autocorrectionData.autocorrectionContextHandler = [completionHandler copy]; |
| RetainPtr<WKContentView> view = self; |
| _page->requestAutocorrectionContext([view](const String& beforeText, const String& markedText, const String& selectedText, const String& afterText, uint64_t location, uint64_t length, WebKit::CallbackBase::Error) { |
| view->_autocorrectionData.autocorrectionContextHandler([WKAutocorrectionContext autocorrectionContextWithData:beforeText markedText:markedText selectedText:selectedText afterText:afterText selectedRangeInMarkedText:NSMakeRange(location, length)]); |
| }); |
| } |
| } |
| |
| // UIWebFormAccessoryDelegate |
| - (void)accessoryDone |
| { |
| [self resignFirstResponder]; |
| } |
| |
| - (NSArray *)keyCommands |
| { |
| static NSArray* nonEditableKeyCommands = [@[ |
| [UIKeyCommand keyCommandWithInput:UIKeyInputUpArrow modifierFlags:0 action:@selector(_arrowKey:)], |
| [UIKeyCommand keyCommandWithInput:UIKeyInputDownArrow modifierFlags:0 action:@selector(_arrowKey:)], |
| [UIKeyCommand keyCommandWithInput:UIKeyInputLeftArrow modifierFlags:0 action:@selector(_arrowKey:)], |
| [UIKeyCommand keyCommandWithInput:UIKeyInputRightArrow modifierFlags:0 action:@selector(_arrowKey:)], |
| |
| [UIKeyCommand keyCommandWithInput:UIKeyInputUpArrow modifierFlags:UIKeyModifierCommand action:@selector(_arrowKey:)], |
| [UIKeyCommand keyCommandWithInput:UIKeyInputDownArrow modifierFlags:UIKeyModifierCommand action:@selector(_arrowKey:)], |
| |
| [UIKeyCommand keyCommandWithInput:UIKeyInputUpArrow modifierFlags:UIKeyModifierShift action:@selector(_arrowKey:)], |
| [UIKeyCommand keyCommandWithInput:UIKeyInputDownArrow modifierFlags:UIKeyModifierShift action:@selector(_arrowKey:)], |
| [UIKeyCommand keyCommandWithInput:UIKeyInputLeftArrow modifierFlags:UIKeyModifierShift action:@selector(_arrowKey:)], |
| [UIKeyCommand keyCommandWithInput:UIKeyInputRightArrow modifierFlags:UIKeyModifierShift action:@selector(_arrowKey:)], |
| |
| [UIKeyCommand keyCommandWithInput:UIKeyInputUpArrow modifierFlags:UIKeyModifierAlternate action:@selector(_arrowKey:)], |
| [UIKeyCommand keyCommandWithInput:UIKeyInputDownArrow modifierFlags:UIKeyModifierAlternate action:@selector(_arrowKey:)], |
| [UIKeyCommand keyCommandWithInput:UIKeyInputLeftArrow modifierFlags:UIKeyModifierAlternate action:@selector(_arrowKey:)], |
| [UIKeyCommand keyCommandWithInput:UIKeyInputRightArrow modifierFlags:UIKeyModifierAlternate action:@selector(_arrowKey:)], |
| |
| [UIKeyCommand keyCommandWithInput:@" " modifierFlags:0 action:@selector(_arrowKey:)], |
| [UIKeyCommand keyCommandWithInput:@" " modifierFlags:UIKeyModifierShift action:@selector(_arrowKey:)], |
| |
| [UIKeyCommand keyCommandWithInput:UIKeyInputPageDown modifierFlags:0 action:@selector(_arrowKey:)], |
| [UIKeyCommand keyCommandWithInput:UIKeyInputPageDown modifierFlags:0 action:@selector(_arrowKey:)], |
| ] retain]; |
| |
| static NSArray* editableKeyCommands = [@[ |
| [UIKeyCommand keyCommandWithInput:@"\t" modifierFlags:0 action:@selector(_nextAccessoryTab:)], |
| [UIKeyCommand keyCommandWithInput:@"\t" modifierFlags:UIKeyModifierShift action:@selector(_prevAccessoryTab:)] |
| ] retain]; |
| |
| return (_page->editorState().isContentEditable) ? editableKeyCommands : nonEditableKeyCommands; |
| } |
| |
| - (void)_arrowKeyForWebView:(id)sender |
| { |
| UIKeyCommand* command = sender; |
| [self handleKeyEvent:command._triggeringEvent]; |
| } |
| |
| - (void)_nextAccessoryTab:(id)sender |
| { |
| [self accessoryTab:YES]; |
| } |
| |
| - (void)_prevAccessoryTab:(id)sender |
| { |
| [self accessoryTab:NO]; |
| } |
| |
| - (void)accessoryTab:(BOOL)isNext |
| { |
| [_inputPeripheral endEditing]; |
| _inputPeripheral = nil; |
| |
| _didAccessoryTabInitiateFocus = YES; // Will be cleared in either -_displayFormNodeInputView or -cleanupInteraction. |
| [self beginSelectionChange]; |
| RetainPtr<WKContentView> view = self; |
| _page->focusNextAssistedNode(isNext, [view](WebKit::CallbackBase::Error) { |
| [view endSelectionChange]; |
| [view reloadInputViews]; |
| }); |
| |
| } |
| |
| - (void)_becomeFirstResponderWithSelectionMovingForward:(BOOL)selectingForward completionHandler:(void (^)(BOOL didBecomeFirstResponder))completionHandler |
| { |
| auto completionHandlerCopy = Block_copy(completionHandler); |
| RetainPtr<WKContentView> view = self; |
| _page->setInitialFocus(selectingForward, false, WebKit::WebKeyboardEvent(), [view, completionHandlerCopy](WebKit::CallbackBase::Error) { |
| BOOL didBecomeFirstResponder = view->_assistedNodeInformation.elementType != InputType::None && [view becomeFirstResponder]; |
| completionHandlerCopy(didBecomeFirstResponder); |
| Block_release(completionHandlerCopy); |
| }); |
| } |
| |
| - (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 removeGestureRecognizer:_doubleTapGestureRecognizer.get()]; |
| [_doubleTapGestureRecognizer setDelegate:nil]; |
| [self _createAndConfigureDoubleTapGestureRecognizer]; |
| } |
| |
| if (_showDebugTapHighlightsForFastClicking && !enabled) |
| _tapHighlightInformation.color = [self _tapHighlightColorForFastClick:YES]; |
| |
| [_doubleTapGestureRecognizer setEnabled:enabled]; |
| [_nonBlockingDoubleTapGestureRecognizer setEnabled:!enabled]; |
| [self _resetIsDoubleTapPending]; |
| } |
| |
| - (void)accessoryAutoFill |
| { |
| id <_WKInputDelegate> inputDelegate = [_webView _inputDelegate]; |
| if ([inputDelegate respondsToSelector:@selector(_webView:accessoryViewCustomButtonTappedInFormInputSession:)]) |
| [inputDelegate _webView:_webView accessoryViewCustomButtonTappedInFormInputSession:_formInputSession.get()]; |
| } |
| |
| - (void)accessoryClear |
| { |
| _page->setAssistedNodeValue(String()); |
| } |
| |
| - (void)_updateAccessory |
| { |
| [_formAccessoryView setNextEnabled:_assistedNodeInformation.hasNextNode]; |
| [_formAccessoryView setPreviousEnabled:_assistedNodeInformation.hasPreviousNode]; |
| |
| if (currentUserInterfaceIdiomIsPad()) |
| [_formAccessoryView setClearVisible:NO]; |
| else { |
| switch (_assistedNodeInformation.elementType) { |
| case InputType::Date: |
| case InputType::Month: |
| case InputType::DateTimeLocal: |
| case InputType::Time: |
| [_formAccessoryView setClearVisible:YES]; |
| break; |
| default: |
| [_formAccessoryView setClearVisible:NO]; |
| break; |
| } |
| } |
| |
| // FIXME: hide or show the AutoFill button as needed. |
| } |
| |
| // Keyboard interaction |
| // UITextInput protocol implementation |
| |
| - (BOOL)_allowAnimatedUpdateSelectionRectViews |
| { |
| return NO; |
| } |
| |
| - (void)beginSelectionChange |
| { |
| [self.inputDelegate selectionWillChange:self]; |
| } |
| |
| - (void)endSelectionChange |
| { |
| [self.inputDelegate selectionDidChange:self]; |
| } |
| |
| - (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:NSClassFromString(@"UITextAutofillSuggestion")]) { |
| _page->autofillLoginCredentials([(UITextAutofillSuggestion *)textSuggestion username], [(UITextAutofillSuggestion *)textSuggestion password]); |
| return; |
| } |
| 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(); |
| FloatRect startRect = postLayoutEditorStateData.caretRectAtStart; |
| 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 (_textSelectionAssistant && !range) |
| [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 |
| { |
| _markedText = markedText; |
| _page->setCompositionAsync(markedText, Vector<WebCore::CompositionUnderline>(), selectedRange, 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 <UITextInputTokenizer>)tokenizer |
| { |
| return nil; |
| } |
| |
| - (UITextPosition *)positionWithinRange:(UITextRange *)range farthestInDirection:(UITextLayoutDirection)direction |
| { |
| return nil; |
| } |
| |
| - (UITextRange *)characterRangeByExtendingPosition:(UITextPosition *)position inDirection:(UITextLayoutDirection)direction |
| { |
| return nil; |
| } |
| |
| - (UITextWritingDirection)baseWritingDirectionForPosition:(UITextPosition *)position inDirection:(UITextStorageDirection)direction |
| { |
| return UITextWritingDirectionLeftToRight; |
| } |
| |
| - (void)setBaseWritingDirection:(UITextWritingDirection)writingDirection forRange:(UITextRange *)range |
| { |
| } |
| |
| - (CGRect)firstRectForRange:(UITextRange *)range |
| { |
| return CGRectZero; |
| } |
| |
| /* Hit testing. */ |
| - (UITextPosition *)closestPositionToPoint:(CGPoint)point |
| { |
| return nil; |
| } |
| |
| - (UITextPosition *)closestPositionToPoint:(CGPoint)point withinRange:(UITextRange *)range |
| { |
| return nil; |
| } |
| |
| - (UITextRange *)characterRangeAtPoint:(CGPoint)point |
| { |
| return nil; |
| } |
| |
| - (void)deleteBackward |
| { |
| _page->executeEditCommand(ASCIILiteral("deleteBackward")); |
| } |
| |
| // Inserts the given string, replacing any selected or marked text. |
| - (void)insertText:(NSString *)aStringValue |
| { |
| _page->insertTextAsync(aStringValue, EditingRange()); |
| } |
| |
| - (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::None: |
| case WebCore::AutofillFieldName::Username: |
| 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]); |
| |
| [_traits setSecureTextEntry:_assistedNodeInformation.elementType == InputType::Password || [_formInputSession forceSecureTextEntry]]; |
| [_traits setShortcutConversionType:_assistedNodeInformation.elementType == InputType::Password ? UITextShortcutConversionTypeNo : UITextShortcutConversionTypeDefault]; |
| |
| if (!_assistedNodeInformation.formAction.isEmpty()) |
| [_traits setReturnKeyType:(_assistedNodeInformation.elementType == InputType::Search) ? UIReturnKeySearch : UIReturnKeyGo]; |
| |
| if (_assistedNodeInformation.elementType == InputType::Password || _assistedNodeInformation.elementType == InputType::Email || _assistedNodeInformation.elementType == InputType::URL || _assistedNodeInformation.formAction.contains("login")) { |
| [_traits setAutocapitalizationType:UITextAutocapitalizationTypeNone]; |
| [_traits setAutocorrectionType:UITextAutocorrectionTypeNo]; |
| } else { |
| [_traits setAutocapitalizationType:toUITextAutocapitalize(_assistedNodeInformation.autocapitalizeType)]; |
| [_traits setAutocorrectionType:_assistedNodeInformation.isAutocorrect ? UITextAutocorrectionTypeYes : UITextAutocorrectionTypeNo]; |
| } |
| |
| switch (_assistedNodeInformation.elementType) { |
| case InputType::Phone: |
| [_traits setKeyboardType:UIKeyboardTypePhonePad]; |
| break; |
| case InputType::URL: |
| [_traits setKeyboardType:UIKeyboardTypeURL]; |
| break; |
| case InputType::Email: |
| [_traits setKeyboardType:UIKeyboardTypeEmailAddress]; |
| break; |
| case InputType::Number: |
| [_traits setKeyboardType:UIKeyboardTypeNumbersAndPunctuation]; |
| break; |
| case InputType::NumberPad: |
| [_traits setKeyboardType:UIKeyboardTypeNumberPad]; |
| break; |
| default: |
| [_traits setKeyboardType:UIKeyboardTypeDefault]; |
| } |
| |
| [_traits setTextContentType:contentTypeFromFieldName(_assistedNodeInformation.autofillFieldName)]; |
| |
| return _traits.get(); |
| } |
| |
| - (UITextInteractionAssistant *)interactionAssistant |
| { |
| return _textSelectionAssistant.get(); |
| } |
| |
| - (UIWebSelectionAssistant *)webSelectionAssistant |
| { |
| return _webSelectionAssistant.get(); |
| } |
| |
| - (id<UISelectionInteractionAssistant>)selectionInteractionAssistant |
| { |
| if ([_webSelectionAssistant conformsToProtocol:@protocol(UISelectionInteractionAssistant)]) |
| return (id<UISelectionInteractionAssistant>)_webSelectionAssistant.get(); |
| 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; |
| } |
| |
| // Web events. |
| - (BOOL)requiresKeyEvents |
| { |
| return YES; |
| } |
| |
| - (void)_handleKeyUIEvent:(::UIEvent *)event |
| { |
| // 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] && event._hidEvent && !_page->editorState().isContentEditable) { |
| [self handleKeyEvent:event]; |
| return; |
| } |
| |
| [super _handleKeyUIEvent:event]; |
| } |
| |
| - (void)handleKeyEvent:(::UIEvent *)event |
| { |
| // WebCore has already seen the event, no need for custom processing. |
| if (event == _uiEventBeingResent) |
| return; |
| |
| WKWebEvent *webEvent = [[[WKWebEvent alloc] initWithKeyEventType:(event._isKeyDown) ? WebEventKeyDown : WebEventKeyUp |
| timeStamp:event.timestamp |
| characters:event._modifiedInput |
| charactersIgnoringModifiers:event._unmodifiedInput |
| modifiers:event._modifierFlags |
| isRepeating:(event._inputFlags & kUIKeyboardInputRepeat) |
| withFlags:event._inputFlags |
| keyCode:0 |
| isTabKey:[event._modifiedInput isEqualToString:@"\t"] |
| characterSet:WebEventCharacterSetUnicode] autorelease]; |
| webEvent.uiEvent = event; |
| |
| [self handleKeyWebEvent:webEvent]; |
| } |
| |
| - (void)handleKeyWebEvent:(::WebEvent *)theEvent |
| { |
| _page->handleKeyboardEvent(NativeWebKeyboardEvent(theEvent)); |
| } |
| |
| - (void)handleKeyWebEvent:(::WebEvent *)theEvent withCompletionHandler:(void (^)(::WebEvent *theEvent, BOOL wasHandled))completionHandler |
| { |
| _keyWebEventHandler = [completionHandler copy]; |
| _page->handleKeyboardEvent(NativeWebKeyboardEvent(theEvent)); |
| } |
| |
| - (void)_didHandleKeyEvent:(::WebEvent *)event eventWasHandled:(BOOL)eventWasHandled |
| { |
| if (_keyWebEventHandler) { |
| _keyWebEventHandler(event, eventWasHandled); |
| [_keyWebEventHandler release]; |
| _keyWebEventHandler = nil; |
| return; |
| } |
| |
| // 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<WKContentView> 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; |
| } |
| |
| - (std::optional<FloatPoint>)_scrollOffsetForEvent:(::WebEvent *)event |
| { |
| static const unsigned kWebSpaceKey = 0x20; |
| |
| if (_page->editorState().isContentEditable) |
| return std::nullopt; |
| |
| NSString *charactersIgnoringModifiers = event.charactersIgnoringModifiers; |
| if (!charactersIgnoringModifiers.length) |
| return std::nullopt; |
| |
| enum ScrollingIncrement { Document, Page, Line }; |
| enum ScrollingDirection { Up, Down, Left, Right }; |
| |
| auto computeOffset = ^(ScrollingIncrement increment, ScrollingDirection direction) { |
| bool isHorizontal = (direction == Left || direction == Right); |
| |
| CGFloat scrollDistance = ^ CGFloat { |
| switch (increment) { |
| case Document: |
| ASSERT(!isHorizontal); |
| return self.bounds.size.height; |
| case Page: |
| ASSERT(!isHorizontal); |
| return Scrollbar::pageStep(_page->unobscuredContentRect().height(), self.bounds.size.height); |
| case Line: |
| return Scrollbar::pixelsPerLineStep(); |
| } |
| ASSERT_NOT_REACHED(); |
| return 0; |
| }(); |
| |
| if (direction == Up || direction == Left) |
| scrollDistance = -scrollDistance; |
| |
| return (isHorizontal ? FloatPoint(scrollDistance, 0) : FloatPoint(0, scrollDistance)); |
| }; |
| |
| if ([charactersIgnoringModifiers isEqualToString:UIKeyInputLeftArrow]) |
| return computeOffset(Line, Left); |
| if ([charactersIgnoringModifiers isEqualToString:UIKeyInputRightArrow]) |
| return computeOffset(Line, Right); |
| |
| ScrollingIncrement incrementForVerticalArrowKey = Line; |
| if (event.modifierFlags & WebEventFlagMaskAlternate) |
| incrementForVerticalArrowKey = Page; |
| else if (event.modifierFlags & WebEventFlagMaskCommand) |
| incrementForVerticalArrowKey = Document; |
| if ([charactersIgnoringModifiers isEqualToString:UIKeyInputUpArrow]) |
| return computeOffset(incrementForVerticalArrowKey, Up); |
| if ([charactersIgnoringModifiers isEqualToString:UIKeyInputDownArrow]) |
| return computeOffset(incrementForVerticalArrowKey, Down); |
| |
| if ([charactersIgnoringModifiers isEqualToString:UIKeyInputPageDown]) |
| return computeOffset(Page, Down); |
| if ([charactersIgnoringModifiers isEqualToString:UIKeyInputPageUp]) |
| return computeOffset(Page, Up); |
| |
| if ([charactersIgnoringModifiers characterAtIndex:0] == kWebSpaceKey) |
| return computeOffset(Page, (event.modifierFlags & WebEventFlagMaskShift) ? Up : Down); |
| |
| return std::nullopt; |
| } |
| |
| - (BOOL)_interpretKeyEvent:(::WebEvent *)event isCharEvent:(BOOL)isCharEvent |
| { |
| static const unsigned kWebEnterKey = 0x0003; |
| static const unsigned kWebBackspaceKey = 0x0008; |
| static const unsigned kWebReturnKey = 0x000D; |
| static const unsigned kWebDeleteKey = 0x007F; |
| static const unsigned kWebDeleteForwardKey = 0xF728; |
| static const unsigned kWebSpaceKey = 0x20; |
| |
| BOOL contentEditable = _page->editorState().isContentEditable; |
| |
| if (!contentEditable && event.isTabKey) |
| return NO; |
| |
| if (std::optional<FloatPoint> scrollOffset = [self _scrollOffsetForEvent:event]) { |
| [_webView _scrollByContentOffset:*scrollOffset]; |
| return YES; |
| } |
| |
| UIKeyboardImpl *keyboard = [UIKeyboardImpl sharedInstance]; |
| NSString *characters = event.characters; |
| |
| if (!characters.length) |
| return NO; |
| |
| switch ([characters characterAtIndex:0]) { |
| case kWebBackspaceKey: |
| case kWebDeleteKey: |
| if (contentEditable) { |
| [keyboard deleteFromInputWithFlags:event.keyboardFlags]; |
| return YES; |
| } |
| break; |
| |
| case kWebSpaceKey: |
| if (contentEditable && isCharEvent) { |
| [keyboard addInputString:event.characters withFlags:event.keyboardFlags withInputManagerHint:event.inputManagerHint]; |
| return YES; |
| } |
| break; |
| |
| case kWebEnterKey: |
| case kWebReturnKey: |
| if (contentEditable && isCharEvent) { |
| // Map \r from HW keyboard to \n to match the behavior of the soft keyboard. |
| [keyboard addInputString:@"\n" withFlags:0]; |
| return YES; |
| } |
| break; |
| |
| case kWebDeleteForwardKey: |
| _page->executeEditCommand(ASCIILiteral("deleteForward")); |
| return YES; |
| |
| default: |
| if (contentEditable && isCharEvent) { |
| [keyboard addInputString:event.characters withFlags:event.keyboardFlags withInputManagerHint:event.inputManagerHint]; |
| return YES; |
| } |
| break; |
| } |
| |
| return NO; |
| } |
| |
| - (void)executeEditCommandWithCallback:(NSString *)commandName |
| { |
| [self beginSelectionChange]; |
| RetainPtr<WKContentView> view = self; |
| _page->executeEditCommand(commandName, { }, [view](WebKit::CallbackBase::Error) { |
| [view endSelectionChange]; |
| }); |
| } |
| |
| - (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 ? @"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 ? @"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 |
| { |
| CGFloat zoomScale = 1.0; // FIXME: retrieve the actual document scale factor. |
| CGFloat scaledSize = _autocorrectionData.fontSize; |
| if (CGFAbs(zoomScale - 1.0) > FLT_EPSILON) |
| scaledSize *= zoomScale; |
| return [UIFont fontWithFamilyName:_autocorrectionData.fontName traits:(UIFontTrait)_autocorrectionData.fontTraits size:scaledSize]; |
| } |
| |
| - (BOOL)hasSelection |
| { |
| return NO; |
| } |
| |
| - (BOOL)isPosition:(UITextPosition *)position atBoundary:(UITextGranularity)granularity inDirection:(UITextDirection)direction |
| { |
| 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]; |
| } |
| |
| // FIXME: I want to change the name of these functions, but I'm leaving it for now |
| // to make it easier to look up the corresponding functions in UIKit. |
| |
| - (void)_startAssistingKeyboard |
| { |
| [self useSelectionAssistantWithGranularity:WKSelectionGranularityCharacter]; |
| |
| if (self.isFirstResponder && !self.suppressAssistantSelectionView) |
| [_textSelectionAssistant activateSelection]; |
| |
| #if !ENABLE(EXTRA_ZOOM_MODE) |
| [self reloadInputViews]; |
| #endif |
| } |
| |
| - (void)_stopAssistingKeyboard |
| { |
| [self useSelectionAssistantWithGranularity:_webView._selectionGranularity]; |
| |
| [_textSelectionAssistant deactivateSelection]; |
| } |
| |
| - (const AssistedNodeInformation&)assistedNodeInformation |
| { |
| return _assistedNodeInformation; |
| } |
| |
| - (Vector<OptionItem>&)assistedNodeSelectOptions |
| { |
| return _assistedNodeInformation.selectOptions; |
| } |
| |
| - (UIWebFormAccessory *)formAccessoryView |
| { |
| [self _ensureFormAccessoryView]; |
| return _formAccessoryView.get(); |
| } |
| |
| static bool isAssistableInputType(InputType type) |
| { |
| switch (type) { |
| case InputType::ContentEditable: |
| case InputType::Text: |
| case InputType::Password: |
| case InputType::TextArea: |
| case InputType::Search: |
| case InputType::Email: |
| case InputType::URL: |
| case InputType::Phone: |
| case InputType::Number: |
| case InputType::NumberPad: |
| case InputType::Date: |
| case InputType::DateTime: |
| case InputType::DateTimeLocal: |
| case InputType::Month: |
| case InputType::Week: |
| case InputType::Time: |
| case InputType::Select: |
| return true; |
| |
| case InputType::None: |
| return false; |
| } |
| |
| ASSERT_NOT_REACHED(); |
| return false; |
| } |
| |
| - (void)_startAssistingNode:(const AssistedNodeInformation&)information userIsInteracting:(BOOL)userIsInteracting blurPreviousNode:(BOOL)blurPreviousNode changingActivityState:(BOOL)changingActivityState userObject:(NSObject <NSSecureCoding> *)userObject |
| { |
| SetForScope<BOOL> isChangingFocusForScope { _isChangingFocus, hasAssistedNode(_assistedNodeInformation) }; |
| _inputViewUpdateDeferrer = nullptr; |
| |
| id <_WKInputDelegate> inputDelegate = [_webView _inputDelegate]; |
| RetainPtr<WKFocusedElementInfo> focusedElementInfo = adoptNS([[WKFocusedElementInfo alloc] initWithAssistedNodeInformation:information isUserInitiated:userIsInteracting userObject:userObject]); |
| |
| BOOL shouldShowKeyboard = NO; |
| _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()]; |
| |
| 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. |
| shouldShowKeyboard = userIsInteracting |
| #if ENABLE(EXTRA_ZOOM_MODE) |
| || (_isChangingFocus && ![_focusedFormControlView isHidden]) |
| #else |
| || _isChangingFocus |
| #endif |
| #if ENABLE(DRAG_SUPPORT) |
| || _dragDropInteractionState.isPerformingDrop() |
| #endif |
| || changingActivityState; |
| break; |
| case _WKFocusStartsInputSessionPolicyAllow: |
| shouldShowKeyboard = YES; |
| break; |
| case _WKFocusStartsInputSessionPolicyDisallow: |
| shouldShowKeyboard = NO; |
| break; |
| default: |
| ASSERT_NOT_REACHED(); |
| } |
| |
| if (blurPreviousNode) |
| [self _stopAssistingNode]; |
| |
| if (!shouldShowKeyboard) |
| return; |
| |
| if (!isAssistableInputType(information.elementType)) |
| return; |
| |
| // FIXME: We should remove this check when we manage to send StartAssistingNode from the WebProcess |
| // only when it is truly time to show the keyboard. |
| if (_assistedNodeInformation.elementType == information.elementType && _assistedNodeInformation.elementRect == information.elementRect) |
| return; |
| |
| _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 = adoptNS([[WKFormInputSession alloc] initWithContentView:self focusedElementInfo:focusedElementInfo.get() requiresStrongPasswordAssistance:_focusRequiresStrongPasswordAssistance]); |
| |
| if (delegateImplementsWillStartInputSession) |
| [inputDelegate _webView:_webView willStartInputSession:_formInputSession.get()]; |
| |
| BOOL editableChanged = [self setIsEditable:YES]; |
| _assistedNodeInformation = information; |
| _inputPeripheral = nil; |
| _traits = nil; |
| |
| if (![self isFirstResponder]) |
| [self becomeFirstResponder]; |
| |
| #if ENABLE(EXTRA_ZOOM_MODE) |
| [self addFocusedFormControlOverlay]; |
| if (!_isChangingFocus) |
| [self presentViewControllerForCurrentAssistedNode]; |
| #else |
| [self reloadInputViews]; |
| #endif |
| |
| switch (information.elementType) { |
| case InputType::Select: |
| case InputType::DateTimeLocal: |
| case InputType::Time: |
| case InputType::Month: |
| case InputType::Date: |
| break; |
| default: |
| [self _startAssistingKeyboard]; |
| break; |
| } |
| |
| // The custom fixed position rect behavior is affected by -isAssistingNode, so if that changes we need to recompute rects. |
| if (editableChanged) |
| [_webView _scheduleVisibleContentRectUpdate]; |
| |
| [self _displayFormNodeInputView]; |
| |
| #if ENABLE(EXTRA_ZOOM_MODE) |
| if (_isChangingFocus) |
| [_focusedFormControlView reloadData:YES]; |
| #endif |
| |
| // _inputPeripheral has been initialized in inputView called by reloadInputViews. |
| [_inputPeripheral beginEditing]; |
| |
| if (delegateImplementsDidStartInputSession) |
| [inputDelegate _webView:_webView didStartInputSession:_formInputSession.get()]; |
| |
| [_webView didStartFormControlInteraction]; |
| } |
| |
| - (void)_stopAssistingNode |
| { |
| SetForScope<BOOL> isBlurringFocusedNodeForScope { _isBlurringFocusedNode, YES }; |
| |
| [_formInputSession invalidate]; |
| _formInputSession = nil; |
| |
| BOOL editableChanged = [self setIsEditable:NO]; |
| |
| _assistedNodeInformation.elementType = InputType::None; |
| _inputPeripheral = nil; |
| _focusRequiresStrongPasswordAssistance = NO; |
| |
| [self _stopAssistingKeyboard]; |
| [_formAccessoryView hideAutoFillButton]; |
| [self reloadInputViews]; |
| [self _updateAccessory]; |
| // The name is misleading, but this actually clears the selection views and removes any selection. |
| [_webSelectionAssistant resignedFirstResponder]; |
| |
| #if ENABLE(EXTRA_ZOOM_MODE) |
| [self dismissAllInputViewControllers:YES]; |
| if (!_isChangingFocus) |
| [self removeFocusedFormControlOverlay]; |
| #endif |
| |
| // The custom fixed position rect behavior is affected by -isAssistingNode, so if that changes we need to recompute rects. |
| if (editableChanged) |
| [_webView _scheduleVisibleContentRectUpdate]; |
| |
| [_webView didEndFormControlInteraction]; |
| } |
| |
| - (void)updateCurrentAssistedNodeInformation:(Function<void(bool didUpdate)>&&)callback |
| { |
| WeakObjCPtr<WKContentView> weakSelf { self }; |
| auto identifierBeforeUpdate = _assistedNodeInformation.assistedNodeIdentifier; |
| _page->requestAssistedNodeInformation([callback = WTFMove(callback), identifierBeforeUpdate, weakSelf] (auto& info, auto error) { |
| if (!weakSelf || error != CallbackBase::Error::None || info.assistedNodeIdentifier != identifierBeforeUpdate) { |
| // If the assisted node may have changed in the meantime, don't overwrite assisted node information. |
| callback(false); |
| return; |
| } |
| |
| weakSelf.get()->_assistedNodeInformation = info; |
| callback(true); |
| }); |
| } |
| |
| - (void)reloadContextViewForPresentedListViewController |
| { |
| #if ENABLE(EXTRA_ZOOM_MODE) |
| if ([_presentedFullScreenInputViewController isKindOfClass:[WKTextInputListViewController class]]) |
| [(WKTextInputListViewController *)_presentedFullScreenInputViewController.get() reloadContextView]; |
| #endif |
| } |
| |
| #if ENABLE(EXTRA_ZOOM_MODE) |
| |
| - (void)addFocusedFormControlOverlay |
| { |
| if (_focusedFormControlView) |
| return; |
| |
| ++_webView->_activeFocusedStateRetainCount; |
| |
| _focusedFormControlView = adoptNS([[WKFocusedFormControlView alloc] initWithFrame:_webView.bounds delegate:self]); |
| [_focusedFormControlView hide:NO]; |
| [_webView addSubview:_focusedFormControlView.get()]; |
| [self setInputDelegate:_focusedFormControlView.get()]; |
| } |
| |
| - (void)removeFocusedFormControlOverlay |
| { |
| if (!_focusedFormControlView) |
| return; |
| |
| --_webView->_activeFocusedStateRetainCount; |
| |
| [_focusedFormControlView removeFromSuperview]; |
| _focusedFormControlView = nil; |
| [self setInputDelegate:nil]; |
| } |
| |
| - (void)presentViewControllerForCurrentAssistedNode |
| { |
| [self dismissAllInputViewControllers:NO]; |
| |
| _shouldRestoreFirstResponderStatusAfterLosingFocus = self.isFirstResponder; |
| UIViewController *presentingViewController = [UIViewController _viewControllerForFullScreenPresentationFromView:self]; |
| |
| ASSERT(!_presentedFullScreenInputViewController); |
| |
| BOOL prefersModalPresentation = NO; |
| |
| switch (_assistedNodeInformation.elementType) { |
| case InputType::Select: |
| _presentedFullScreenInputViewController = adoptNS([[WKSelectMenuListViewController alloc] initWithDelegate:self]); |
| break; |
| case InputType::Time: |
| // Time inputs are special, in that the only UI affordances for dismissal are push buttons rather than status bar chevrons. |
| // As such, modal presentation and dismissal is preferred even if a navigation stack exists. |
| prefersModalPresentation = YES; |
| _presentedFullScreenInputViewController = adoptNS([[WKTimePickerViewController alloc] initWithDelegate:self]); |
| break; |
| case InputType::Date: |
| _presentedFullScreenInputViewController = adoptNS([[WKDatePickerViewController alloc] initWithDelegate:self]); |
| break; |
| case InputType::None: |
| break; |
| default: |
| _presentedFullScreenInputViewController = adoptNS([[WKTextInputListViewController alloc] initWithDelegate:self]); |
| break; |
| } |
| |
| ASSERT(_presentedFullScreenInputViewController); |
| ASSERT(presentingViewController); |
| |
| if (!prefersModalPresentation && [presentingViewController isKindOfClass:[UINavigationController class]]) |
| _inputNavigationViewControllerForFullScreenInputs = (UINavigationController *)presentingViewController; |
| else |
| _inputNavigationViewControllerForFullScreenInputs = nil; |
| |
| // Present the input view controller on an existing navigation stack, if possible. If there is no navigation stack we can use, fall back to presenting modally. |
| // This is because the HI specification (for certain scenarios) calls for navigation-style view controller presentation, but WKWebView can't make any guarantees |
| // about clients' view controller hierarchies, so we can only try our best to avoid presenting modally. Clients can implicitly opt in to specced behavior by using |
| // UINavigationController to present the web view. |
| if (_inputNavigationViewControllerForFullScreenInputs) |
| [_inputNavigationViewControllerForFullScreenInputs pushViewController:_presentedFullScreenInputViewController.get() animated:YES]; |
| else |
| [presentingViewController presentViewController:_presentedFullScreenInputViewController.get() animated:YES completion:nil]; |
| |
| // Presenting a fullscreen input view controller fully obscures the web view. Without taking this token, the web content process will get backgrounded. |
| _page->process().takeBackgroundActivityTokenForFullscreenInput(); |
| |
| [presentingViewController.transitionCoordinator animateAlongsideTransition:nil completion:[weakWebView = WeakObjCPtr<WKWebView>(_webView), controller = _presentedFullScreenInputViewController] (id <UIViewControllerTransitionCoordinatorContext>) { |
| auto strongWebView = weakWebView.get(); |
| id <WKUIDelegatePrivate> uiDelegate = static_cast<id <WKUIDelegatePrivate>>([strongWebView UIDelegate]); |
| if ([uiDelegate respondsToSelector:@selector(_webView:didPresentFocusedElementViewController:)]) |
| [uiDelegate _webView:strongWebView.get() didPresentFocusedElementViewController:controller.get()]; |
| }]; |
| } |
| |
| - (void)dismissAllInputViewControllers:(BOOL)animated |
| { |
| auto navigationController = WTFMove(_inputNavigationViewControllerForFullScreenInputs); |
| auto presentedController = WTFMove(_presentedFullScreenInputViewController); |
| |
| if (!presentedController) |
| return; |
| |
| if ([navigationController viewControllers].lastObject == presentedController.get()) |
| [navigationController popViewControllerAnimated:animated]; |
| else |
| [presentedController dismissViewControllerAnimated:animated completion:nil]; |
| |
| [[presentedController transitionCoordinator] animateAlongsideTransition:nil completion:[weakWebView = WeakObjCPtr<WKWebView>(_webView), controller = presentedController] (id <UIViewControllerTransitionCoordinatorContext>) { |
| auto strongWebView = weakWebView.get(); |
| id <WKUIDelegatePrivate> uiDelegate = static_cast<id <WKUIDelegatePrivate>>([strongWebView UIDelegate]); |
| if ([uiDelegate respondsToSelector:@selector(_webView:didDismissFocusedElementViewController:)]) |
| [uiDelegate _webView:strongWebView.get() didDismissFocusedElementViewController:controller.get()]; |
| }]; |
| |
| if (_shouldRestoreFirstResponderStatusAfterLosingFocus) { |
| _shouldRestoreFirstResponderStatusAfterLosingFocus = NO; |
| if (!self.isFirstResponder) |
| [self becomeFirstResponder]; |
| } |
| |
| _page->process().releaseBackgroundActivityTokenForFullscreenInput(); |
| } |
| |
| - (void)focusedFormControlViewDidSubmit:(WKFocusedFormControlView *)view |
| { |
| [self insertText:@"\n"]; |
| _page->blurAssistedNode(); |
| } |
| |
| - (void)focusedFormControlViewDidCancel:(WKFocusedFormControlView *)view |
| { |
| _page->blurAssistedNode(); |
| } |
| |
| - (void)focusedFormControlViewDidBeginEditing:(WKFocusedFormControlView *)view |
| { |
| [self updateCurrentAssistedNodeInformation:[weakSelf = WeakObjCPtr<WKContentView>(self)] (bool didUpdate) { |
| if (!didUpdate) |
| return; |
| |
| auto strongSelf = weakSelf.get(); |
| [strongSelf presentViewControllerForCurrentAssistedNode]; |
| [strongSelf->_focusedFormControlView hide:YES]; |
| }]; |
| } |
| |
| - (CGRect)rectForFocusedFormControlView:(WKFocusedFormControlView *)view |
| { |
| return [self convertRect:_assistedNodeInformation.elementRect toView:view]; |
| } |
| |
| - (CGRect)nextRectForFocusedFormControlView:(WKFocusedFormControlView *)view |
| { |
| if (!_assistedNodeInformation.hasNextNode) |
| return CGRectNull; |
| |
| return [self convertRect:_assistedNodeInformation.nextNodeRect toView:view]; |
| } |
| |
| - (CGRect)previousRectForFocusedFormControlView:(WKFocusedFormControlView *)view |
| { |
| if (!_assistedNodeInformation.hasPreviousNode) |
| return CGRectNull; |
| |
| return [self convertRect:_assistedNodeInformation.previousNodeRect toView:view]; |
| } |
| |
| - (UIScrollView *)scrollViewForFocusedFormControlView:(WKFocusedFormControlView *)view |
| { |
| return self._scroller; |
| } |
| |
| - (NSString *)actionNameForFocusedFormControlView:(WKFocusedFormControlView *)view |
| { |
| if (_assistedNodeInformation.formAction.isEmpty()) |
| return nil; |
| |
| switch (_assistedNodeInformation.elementType) { |
| case InputType::Select: |
| case InputType::Time: |
| case InputType::Date: |
| return nil; |
| case InputType::Search: |
| return formControlSearchButtonTitle(); |
| default: |
| return formControlGoButtonTitle(); |
| } |
| } |
| |
| - (void)focusedFormControlViewDidRequestNextNode:(WKFocusedFormControlView *)view |
| { |
| if (_assistedNodeInformation.hasNextNode) |
| _page->focusNextAssistedNode(true); |
| } |
| |
| - (void)focusedFormControlViewDidRequestPreviousNode:(WKFocusedFormControlView *)view |
| { |
| if (_assistedNodeInformation.hasPreviousNode) |
| _page->focusNextAssistedNode(false); |
| } |
| |
| - (BOOL)hasNextNodeForFocusedFormControlView:(WKFocusedFormControlView *)view |
| { |
| return _assistedNodeInformation.hasNextNode; |
| } |
| |
| - (BOOL)hasPreviousNodeForFocusedFormControlView:(WKFocusedFormControlView *)view |
| { |
| return _assistedNodeInformation.hasPreviousNode; |
| } |
| |
| - (void)focusedFormControllerDidUpdateSuggestions:(WKFocusedFormControlView *)view |
| { |
| if (_isBlurringFocusedNode || ![_presentedFullScreenInputViewController isKindOfClass:[WKTextInputListViewController class]]) |
| return; |
| |
| [(WKTextInputListViewController *)_presentedFullScreenInputViewController reloadTextSuggestions]; |
| } |
| |
| #pragma mark - WKSelectMenuListViewControllerDelegate |
| |
| - (void)selectMenu:(WKSelectMenuListViewController *)selectMenu didSelectItemAtIndex:(NSUInteger)index |
| { |
| ASSERT(!_assistedNodeInformation.isMultiSelect); |
| _page->setAssistedNodeSelectedIndex(index, false); |
| } |
| |
| - (NSUInteger)numberOfItemsInSelectMenu:(WKSelectMenuListViewController *)selectMenu |
| { |
| return self.assistedNodeSelectOptions.size(); |
| } |
| |
| - (NSString *)selectMenu:(WKSelectMenuListViewController *)selectMenu displayTextForItemAtIndex:(NSUInteger)index |
| { |
| auto& options = self.assistedNodeSelectOptions; |
| if (index >= options.size()) { |
| ASSERT_NOT_REACHED(); |
| return @""; |
| } |
| |
| return options[index].text; |
| } |
| |
| - (void)selectMenu:(WKSelectMenuListViewController *)selectMenu didCheckItemAtIndex:(NSUInteger)index checked:(BOOL)checked |
| { |
| ASSERT(_assistedNodeInformation.isMultiSelect); |
| if (index >= self.assistedNodeSelectOptions.size()) { |
| ASSERT_NOT_REACHED(); |
| return; |
| } |
| |
| auto& option = self.assistedNodeSelectOptions[index]; |
| if (option.isSelected == checked) { |
| ASSERT_NOT_REACHED(); |
| return; |
| } |
| |
| _page->setAssistedNodeSelectedIndex(index, true); |
| option.isSelected = checked; |
| } |
| |
| - (BOOL)selectMenuUsesMultipleSelection:(WKSelectMenuListViewController *)selectMenu |
| { |
| return _assistedNodeInformation.isMultiSelect; |
| } |
| |
| - (BOOL)selectMenu:(WKSelectMenuListViewController *)selectMenu hasSelectedOptionAtIndex:(NSUInteger)index |
| { |
| if (index >= self.assistedNodeSelectOptions.size()) { |
| ASSERT_NOT_REACHED(); |
| return NO; |
| } |
| |
| return self.assistedNodeSelectOptions[index].isSelected; |
| } |
| |
| #endif // ENABLE(EXTRA_ZOOM_MODE) |
| |
| - (void)_wheelChangedWithEvent:(UIEvent *)event |
| { |
| #if ENABLE(EXTRA_ZOOM_MODE) |
| if ([_focusedFormControlView handleWheelEvent:event]) |
| return; |
| #endif |
| [super _wheelChangedWithEvent:event]; |
| } |
| |
| - (void)_selectionChanged |
| { |
| _selectionNeedsUpdate = YES; |
| // If we are changing the selection with a gesture there is no need |
| // to wait to paint the selection. |
| if (_usingGestureForSelection) |
| [self _updateChangedSelection]; |
| |
| [_webView _didChangeEditorState]; |
| } |
| |
| - (void)selectWordForReplacement |
| { |
| _page->extendSelection(WordGranularity); |
| } |
| |
| - (void)_updateChangedSelection |
| { |
| [self _updateChangedSelection:NO]; |
| } |
| |
| - (void)_updateChangedSelection:(BOOL)force |
| { |
| if (!_selectionNeedsUpdate || _page->editorState().isMissingPostLayoutData) |
| return; |
| |
| WKSelectionDrawingInfo selectionDrawingInfo(_page->editorState()); |
| if (force || selectionDrawingInfo != _lastSelectionDrawingInfo) { |
| LOG_WITH_STREAM(Selection, stream << "_updateChangedSelection " << selectionDrawingInfo); |
| |
| _lastSelectionDrawingInfo = selectionDrawingInfo; |
| |
| // FIXME: We need to figure out what to do if the selection is changed by Javascript. |
| if (_textSelectionAssistant) { |
| _markedText = (_page->editorState().hasComposition) ? _page->editorState().markedText : String(); |
| if (!_showingTextStyleOptions) |
| [_textSelectionAssistant selectionChanged]; |
| } else if (!_page->editorState().isContentEditable) |
| [_webSelectionAssistant selectionChanged]; |
| |
| _selectionNeedsUpdate = NO; |
| if (_shouldRestoreSelection) { |
| [_webSelectionAssistant didEndScrollingOverflow]; |
| [_textSelectionAssistant didEndScrollingOverflow]; |
| _shouldRestoreSelection = NO; |
| } |
| } |
| |
| auto& state = _page->editorState(); |
| if (!state.isMissingPostLayoutData && state.postLayoutData().isStableStateUpdate && _needsDeferredEndScrollingSelectionUpdate && _page->inStableState()) { |
| [[self selectionInteractionAssistant] showSelectionCommands]; |
| [_webSelectionAssistant didEndScrollingOrZoomingPage]; |
| #if !ENABLE(MINIMAL_SIMULATOR) |
| [[_webSelectionAssistant selectionView] setHidden:NO]; |
| #endif |
| |
| if (!self.suppressAssistantSelectionView) |
| [_textSelectionAssistant activateSelection]; |
| |
| [_textSelectionAssistant didEndScrollingOverflow]; |
| |
| _needsDeferredEndScrollingSelectionUpdate = NO; |
| } |
| } |
| |
| - (BOOL)suppressAssistantSelectionView |
| { |
| return _suppressAssistantSelectionView; |
| } |
| |
| - (void)setSuppressAssistantSelectionView:(BOOL)suppressAssistantSelectionView |
| { |
| if (_suppressAssistantSelectionView == suppressAssistantSelectionView) |
| return; |
| |
| _suppressAssistantSelectionView = suppressAssistantSelectionView; |
| if (!_textSelectionAssistant) |
| return; |
| |
| if (suppressAssistantSelectionView) |
| [_textSelectionAssistant deactivateSelection]; |
| else |
| [_textSelectionAssistant activateSelection]; |
| } |
| |
| - (void)_showPlaybackTargetPicker:(BOOL)hasVideo fromRect:(const IntRect&)elementRect |
| { |
| #if ENABLE(AIRPLAY_PICKER) |
| #if __IPHONE_OS_VERSION_MIN_REQUIRED >= 110000 && !PLATFORM(WATCHOS) && !PLATFORM(APPLETV) |
| if (!_airPlayRoutePicker) |
| _airPlayRoutePicker = adoptNS([[WKAirPlayRoutePicker alloc] init]); |
| [_airPlayRoutePicker showFromView:self]; |
| #else |
| if (!_airPlayRoutePicker) |
| _airPlayRoutePicker = adoptNS([[WKAirPlayRoutePicker alloc] initWithView:self]); |
| [_airPlayRoutePicker show:hasVideo fromRect:elementRect]; |
| #endif |
| #endif |
| } |
| |
| - (void)_showRunOpenPanel:(API::OpenPanelParameters*)parameters resultListener:(WebOpenPanelResultListenerProxy*)listener |
| { |
| ASSERT(!_fileUploadPanel); |
| if (_fileUploadPanel) |
| return; |
| |
| _fileUploadPanel = adoptNS([[WKFileUploadPanel alloc] initWithView:self]); |
| [_fileUploadPanel setDelegate:self]; |
| [_fileUploadPanel presentWithParameters:parameters resultListener:listener]; |
| } |
| |
| - (void)fileUploadPanelDidDismiss:(WKFileUploadPanel *)fileUploadPanel |
| { |
| ASSERT(_fileUploadPanel.get() == fileUploadPanel); |
| |
| [_fileUploadPanel setDelegate:nil]; |
| _fileUploadPanel = nil; |
| } |
| |
| #pragma mark - UITextInputMultiDocument |
| |
| - (void)_restoreFocusWithToken:(id <NSCopying, NSSecureCoding>)token |
| { |
| ASSERT(!_focusStateStack.isEmpty()); |
| |
| if (_focusStateStack.takeLast()) { |
| ASSERT(_webView->_activeFocusedStateRetainCount); |
| --_webView->_activeFocusedStateRetainCount; |
| } |
| } |
| |
| - (void)_preserveFocusWithToken:(id <NSCopying, NSSecureCoding>)token destructively:(BOOL)destructively |
| { |
| if (!_inputPeripheral) { |
| ++_webView->_activeFocusedStateRetainCount; |
| _focusStateStack.append(true); |
| } else |
| _focusStateStack.append(false); |
| } |
| |
| #pragma mark - Implementation of UIWebTouchEventsGestureRecognizerDelegate. |
| |
| // FIXME: Remove once -gestureRecognizer:shouldIgnoreWebTouchWithEvent: is in UIWebTouchEventsGestureRecognizer.h. Refer to <rdar://problem/33217525> for more details. |
| - (BOOL)shouldIgnoreWebTouch |
| { |
| return NO; |
| } |
| |
| - (BOOL)gestureRecognizer:(UIWebTouchEventsGestureRecognizer *)gestureRecognizer shouldIgnoreWebTouchWithEvent:(UIEvent *)event |
| { |
| _canSendTouchEventsAsynchronously = NO; |
| |
| NSSet<UITouch *> *touches = [event touchesForGestureRecognizer:gestureRecognizer]; |
| for (UITouch *touch in touches) { |
| if ([touch.view isKindOfClass:[UIScrollView class]] && [(UIScrollView *)touch.view _isInterruptingDeceleration]) |
| return YES; |
| } |
| return self._scroller._isInterruptingDeceleration; |
| } |
| |
| - (BOOL)isAnyTouchOverActiveArea:(NSSet *)touches |
| { |
| return YES; |
| } |
| |
| #pragma mark - Implementation of WKActionSheetAssistantDelegate. |
| |
| - (std::optional<WebKit::InteractionInformationAtPosition>)positionInformationForActionSheetAssistant:(WKActionSheetAssistant *)assistant |
| { |
| InteractionInformationRequest request(_positionInformation.request.point); |
| request.includeSnapshot = true; |
| request.includeLinkIndicator = assistant.needsLinkIndicator; |
| if (![self ensurePositionInformationIsUpToDate:request]) |
| return std::nullopt; |
| |
| return _positionInformation; |
| } |
| |
| - (void)updatePositionInformationForActionSheetAssistant:(WKActionSheetAssistant *)assistant |
| { |
| _hasValidPositionInformation = NO; |
| InteractionInformationRequest request(_positionInformation.request.point); |
| request.includeSnapshot = true; |
| request.includeLinkIndicator = assistant.needsLinkIndicator; |
| |
| [self requestAsynchronousPositionInformationUpdate:request]; |
| } |
| |
| - (void)actionSheetAssistant:(WKActionSheetAssistant *)assistant performAction:(WebKit::SheetAction)action |
| { |
| _page->performActionOnElement((uint32_t)action); |
| } |
| |
| - (void)actionSheetAssistant:(WKActionSheetAssistant *)assistant openElementAtLocation:(CGPoint)location |
| { |
| [self _attemptClickAtLocation:location]; |
| } |
| |
| - (void)actionSheetAssistant:(WKActionSheetAssistant *)assistant shareElementWithURL:(NSURL *)url rect:(CGRect)boundingRect |
| { |
| if (_textSelectionAssistant) |
| [_textSelectionAssistant showShareSheetFor:userVisibleString(url) fromRect:boundingRect]; |
| else if (_webSelectionAssistant) |
| [_webSelectionAssistant showShareSheetFor:userVisibleString(url) fromRect:boundingRect]; |
| } |
| |
| #if HAVE(APP_LINKS) |
| - (BOOL)actionSheetAssistant:(WKActionSheetAssistant *)assistant shouldIncludeAppLinkActionsForElement:(_WKActivatedElementInfo *)element |
| { |
| return _page->uiClient().shouldIncludeAppLinkActionsForElement(element); |
| } |
| #endif |
| |
| - (BOOL)actionSheetAssistant:(WKActionSheetAssistant *)assistant showCustomSheetForElement:(_WKActivatedElementInfo *)element |
| { |
| id <WKUIDelegatePrivate> uiDelegate = static_cast<id <WKUIDelegatePrivate>>([_webView UIDelegate]); |
| |
| if ([uiDelegate respondsToSelector:@selector(_webView:showCustomSheetForElement:)]) { |
| if ([uiDelegate _webView:_webView showCustomSheetForElement:element]) { |
| #if ENABLE(DATA_INTERACTION) |
| BOOL shouldCancelAllTouches = !_dragDropInteractionState.dragSession(); |
| #else |
| BOOL shouldCancelAllTouches = YES; |
| #endif |
| // Prevent tap-and-hold and panning. |
| if (shouldCancelAllTouches) |
| [UIApp _cancelAllTouches]; |
| |
| return YES; |
| } |
| } |
| |
| return NO; |
| } |
| |
| - (RetainPtr<NSArray>)actionSheetAssistant:(WKActionSheetAssistant *)assistant decideActionsForElement:(_WKActivatedElementInfo *)element defaultActions:(RetainPtr<NSArray>)defaultActions |
| { |
| return _page->uiClient().actionsForElement(element, WTFMove(defaultActions)); |
| } |
| |
| - (void)actionSheetAssistant:(WKActionSheetAssistant *)assistant willStartInteractionWithElement:(_WKActivatedElementInfo *)element |
| { |
| _page->startInteractionWithElementAtPosition(_positionInformation.request.point); |
| } |
| |
| - (void)actionSheetAssistantDidStopInteraction:(WKActionSheetAssistant *)assistant |
| { |
| _page->stopInteraction(); |
| } |
| |
| - (NSDictionary *)dataDetectionContextForActionSheetAssistant:(WKActionSheetAssistant *)assistant |
| { |
| NSDictionary *context = nil; |
| id <WKUIDelegatePrivate> uiDelegate = static_cast<id <WKUIDelegatePrivate>>([_webView UIDelegate]); |
| if ([uiDelegate respondsToSelector:@selector(_dataDetectionContextForWebView:)]) |
| context = [uiDelegate _dataDetectionContextForWebView:_webView]; |
| return context; |
| } |
| |
| - (NSString *)selectedTextForActionSheetAssistant:(WKActionSheetAssistant *)assistant |
| { |
| return [self selectedText]; |
| } |
| |
| - (void)actionSheetAssistant:(WKActionSheetAssistant *)assistant getAlternateURLForImage:(UIImage *)image completion:(void (^)(NSURL *alternateURL, NSDictionary *userInfo))completion |
| { |
| id <WKUIDelegatePrivate> uiDelegate = static_cast<id <WKUIDelegatePrivate>>([_webView UIDelegate]); |
| if ([uiDelegate respondsToSelector:@selector(_webView:getAlternateURLFromImage:completionHandler:)]) { |
| [uiDelegate _webView:_webView getAlternateURLFromImage:image completionHandler:^(NSURL *alternateURL, NSDictionary *userInfo) { |
| completion(alternateURL, userInfo); |
| }]; |
| } else |
| completion(nil, nil); |
| } |
| |
| #if ENABLE(DRAG_SUPPORT) |
| |
| static BOOL shouldEnableDragInteractionForPolicy(_WKDragInteractionPolicy policy) |
| { |
| switch (policy) { |
| case _WKDragInteractionPolicyAlwaysEnable: |
| return YES; |
| case _WKDragInteractionPolicyAlwaysDisable: |
| return NO; |
| default: |
| return [UIDragInteraction isEnabledByDefault]; |
| } |
| } |
| |
| - (void)_didChangeDragInteractionPolicy |
| { |
| [_dragInteraction setEnabled:shouldEnableDragInteractionForPolicy(_webView._dragInteractionPolicy)]; |
| } |
| |
| - (NSTimeInterval)dragLiftDelay |
| { |
| static const NSTimeInterval mediumDragLiftDelay = 0.5; |
| static const NSTimeInterval longDragLiftDelay = 0.65; |
| auto dragLiftDelay = _webView.configuration._dragLiftDelay; |
| if (dragLiftDelay == _WKDragLiftDelayMedium) |
| return mediumDragLiftDelay; |
| if (dragLiftDelay == _WKDragLiftDelayLong) |
| return longDragLiftDelay; |
| return _UIDragInteractionDefaultLiftDelay(); |
| } |
| |
| - (id <WKUIDelegatePrivate>)webViewUIDelegate |
| { |
| return (id <WKUIDelegatePrivate>)[_webView UIDelegate]; |
| } |
| |
| - (void)setupDataInteractionDelegates |
| { |
| _dragInteraction = adoptNS([[UIDragInteraction alloc] initWithDelegate:self]); |
| _dropInteraction = adoptNS([[UIDropInteraction alloc] initWithDelegate:self]); |
| [_dragInteraction _setLiftDelay:self.dragLiftDelay]; |
| [_dragInteraction setEnabled:shouldEnableDragInteractionForPolicy(_webView._dragInteractionPolicy)]; |
| |
| [self addInteraction:_dragInteraction.get()]; |
| [self addInteraction:_dropInteraction.get()]; |
| } |
| |
| - (void)teardownDataInteractionDelegates |
| { |
| if (_dragInteraction) |
| [self removeInteraction:_dragInteraction.get()]; |
| |
| if (_dropInteraction) |
| [self removeInteraction:_dropInteraction.get()]; |
| |
| _dragInteraction = nil; |
| _dropInteraction = nil; |
| |
| [self cleanUpDragSourceSessionState]; |
| } |
| |
| - (void)_startDrag:(RetainPtr<CGImageRef>)image item:(const DragItem&)item |
| { |
| ASSERT(item.sourceAction != DragSourceActionNone); |
| |
| if (item.promisedBlob) |
| [self _prepareToDragPromisedBlob:item.promisedBlob]; |
| |
| auto dragImage = adoptNS([[UIImage alloc] initWithCGImage:image.get() scale:_page->deviceScaleFactor() orientation:UIImageOrientationUp]); |
| _dragDropInteractionState.stageDragItem(item, dragImage.get()); |
| } |
| |
| - (void)_didHandleAdditionalDragItemsRequest:(BOOL)added |
| { |
| auto completion = _dragDropInteractionState.takeAddDragItemCompletionBlock(); |
| if (!completion) |
| return; |
| |
| WebItemProviderRegistrationInfoList *registrationList = [[WebItemProviderPasteboard sharedInstance] takeRegistrationList]; |
| if (!added || !registrationList || !_dragDropInteractionState.hasStagedDragSource()) { |
| _dragDropInteractionState.clearStagedDragSource(); |
| completion(@[ ]); |
| return; |
| } |
| |
| auto stagedDragSource = _dragDropInteractionState.stagedDragSource(); |
| NSArray *dragItemsToAdd = [self _itemsForBeginningOrAddingToSessionWithRegistrationList:registrationList stagedDragSource:stagedDragSource]; |
| |
| RELEASE_LOG(DragAndDrop, "Drag session: %p adding %tu items", _dragDropInteractionState.dragSession(), dragItemsToAdd.count); |
| _dragDropInteractionState.clearStagedDragSource(dragItemsToAdd.count ? DragDropInteractionState::DidBecomeActive::Yes : DragDropInteractionState::DidBecomeActive::No); |
| |
| completion(dragItemsToAdd); |
| |
| if (dragItemsToAdd.count) |
| _page->didStartDrag(); |
| } |
| |
| - (void)_didHandleStartDataInteractionRequest:(BOOL)started |
| { |
| BlockPtr<void()> savedCompletionBlock = _dragDropInteractionState.takeDragStartCompletionBlock(); |
| ASSERT(savedCompletionBlock); |
| |
| RELEASE_LOG(DragAndDrop, "Handling drag start request (started: %d, completion block: %p)", started, savedCompletionBlock.get()); |
| if (savedCompletionBlock) |
| savedCompletionBlock(); |
| |
| if (!_dragDropInteractionState.dragSession().items.count) { |
| auto positionForDragEnd = roundedIntPoint(_dragDropInteractionState.adjustedPositionForDragEnd()); |
| [self cleanUpDragSourceSessionState]; |
| if (started) { |
| // A client of the Objective C SPI or UIKit might have prevented the drag from beginning entirely in the UI process, in which case |
| // we need to balance the `dragstart` event with a `dragend`. |
| _page->dragEnded(positionForDragEnd, positionForDragEnd, DragOperationNone); |
| } |
| } |
| } |
| |
| - (void)computeClientAndGlobalPointsForDropSession:(id <UIDropSession>)session outClientPoint:(CGPoint *)outClientPoint outGlobalPoint:(CGPoint *)outGlobalPoint |
| { |
| // FIXME: This makes the behavior of drag events on iOS consistent with other synthetic mouse events on iOS (see WebPage::completeSyntheticClick). |
| // However, we should experiment with making the client position relative to the window and the global position in document coordinates. See |
| // https://bugs.webkit.org/show_bug.cgi?id=173855 for more details. |
| auto locationInContentView = [session locationInView:self]; |
| if (outClientPoint) |
| *outClientPoint = locationInContentView; |
| |
| if (outGlobalPoint) |
| *outGlobalPoint = locationInContentView; |
| } |
| |
| static UIDropOperation dropOperationForWebCoreDragOperation(DragOperation operation) |
| { |
| if (operation & DragOperationMove) |
| return UIDropOperationMove; |
| |
| if (operation & DragOperationCopy) |
| return UIDropOperationCopy; |
| |
| return UIDropOperationCancel; |
| } |
| |
| - (DragData)dragDataForDropSession:(id <UIDropSession>)session dragDestinationAction:(WKDragDestinationAction)dragDestinationAction |
| { |
| CGPoint global; |
| CGPoint client; |
| [self computeClientAndGlobalPointsForDropSession:session outClientPoint:&client outGlobalPoint:&global]; |
| |
| DragOperation dragOperationMask = static_cast<DragOperation>(session.allowsMoveOperation ? DragOperationEvery : (DragOperationEvery & ~DragOperationMove)); |
| return { session, roundedIntPoint(client), roundedIntPoint(global), dragOperationMask, DragApplicationNone, static_cast<DragDestinationAction>(dragDestinationAction) }; |
| } |
| |
| - (void)cleanUpDragSourceSessionState |
| { |
| RELEASE_LOG(DragAndDrop, "Cleaning up dragging state (has pending operation: %d)", [[WebItemProviderPasteboard sharedInstance] hasPendingOperation]); |
| if (![[WebItemProviderPasteboard sharedInstance] hasPendingOperation]) { |
| // If we're performing a drag operation, don't clear out the pasteboard yet, since another web view may still require access to it. |
| // The pasteboard will be cleared after the last client is finished performing a drag operation using the item providers. |
| [[WebItemProviderPasteboard sharedInstance] setItemProviders:nil]; |
| } |
| |
| [[WebItemProviderPasteboard sharedInstance] stageRegistrationList:nil]; |
| [self _restoreCalloutBarIfNeeded]; |
| |
| [_visibleContentViewSnapshot removeFromSuperview]; |
| _visibleContentViewSnapshot = nil; |
| [_editDropCaretView remove]; |
| _editDropCaretView = nil; |
| _isAnimatingConcludeEditDrag = NO; |
| _shouldRestoreCalloutBarAfterDrop = NO; |
| |
| _dragDropInteractionState.dragAndDropSessionsDidEnd(); |
| _dragDropInteractionState = { }; |
| } |
| |
| static NSArray<UIItemProvider *> *extractItemProvidersFromDragItems(NSArray<UIDragItem *> *dragItems) |
| { |
| NSMutableArray<UIItemProvider *> *providers = [NSMutableArray array]; |
| for (UIDragItem *item in dragItems) { |
| RetainPtr<UIItemProvider> provider = item.itemProvider; |
| if (provider) |
| [providers addObject:provider.get()]; |
| } |
| return providers; |
| } |
| |
| static NSArray<UIItemProvider *> *extractItemProvidersFromDropSession(id <UIDropSession> session) |
| { |
| return extractItemProvidersFromDragItems(session.items); |
| } |
| |
| - (void)_didConcludeEditDataInteraction:(std::optional<TextIndicatorData>)data |
| { |
| if (!data) |
| return; |
| |
| auto snapshotWithoutSelection = data->contentImageWithoutSelection; |
| if (!snapshotWithoutSelection) |
| return; |
| |
| auto unselectedSnapshotImage = snapshotWithoutSelection->nativeImage(); |
| if (!unselectedSnapshotImage) |
| return; |
| |
| auto dataInteractionUnselectedContentImage = adoptNS([[UIImage alloc] initWithCGImage:unselectedSnapshotImage.get() scale:_page->deviceScaleFactor() orientation:UIImageOrientationUp]); |
| RetainPtr<UIImageView> unselectedContentSnapshot = adoptNS([[UIImageView alloc] initWithImage:dataInteractionUnselectedContentImage.get()]); |
| [unselectedContentSnapshot setFrame:data->contentImageWithoutSelectionRectInRootViewCoordinates]; |
| |
| RetainPtr<WKContentView> protectedSelf = self; |
| RetainPtr<UIView> visibleContentViewSnapshot = adoptNS(_visibleContentViewSnapshot.leakRef()); |
| |
| _isAnimatingConcludeEditDrag = YES; |
| [self insertSubview:unselectedContentSnapshot.get() belowSubview:visibleContentViewSnapshot.get()]; |
| [UIView animateWithDuration:0.25 animations:^() { |
| [visibleContentViewSnapshot setAlpha:0]; |
| } completion:^(BOOL completed) { |
| [visibleContentViewSnapshot removeFromSuperview]; |
| [UIView animateWithDuration:0.25 animations:^() { |
| [protectedSelf setSuppressAssistantSelectionView:NO]; |
| [unselectedContentSnapshot setAlpha:0]; |
| } completion:^(BOOL completed) { |
| [unselectedContentSnapshot removeFromSuperview]; |
| }]; |
| }]; |
| } |
| |
| - (void)_didPerformDataInteractionControllerOperation:(BOOL)handled |
| { |
| RELEASE_LOG(DragAndDrop, "Finished performing drag controller operation (handled: %d)", handled); |
| [[WebItemProviderPasteboard sharedInstance] decrementPendingOperationCount]; |
| id <UIDropSession> dropSession = _dragDropInteractionState.dropSession(); |
| if ([self.webViewUIDelegate respondsToSelector:@selector(_webView:dataInteractionOperationWasHandled:forSession:itemProviders:)]) |
| [self.webViewUIDelegate _webView:_webView dataInteractionOperationWasHandled:handled forSession:dropSession itemProviders:[WebItemProviderPasteboard sharedInstance].itemProviders]; |
| |
| if (!_isAnimatingConcludeEditDrag) |
| self.suppressAssistantSelectionView = NO; |
| |
| CGPoint global; |
| CGPoint client; |
| [self computeClientAndGlobalPointsForDropSession:dropSession outClientPoint:&client outGlobalPoint:&global]; |
| [self cleanUpDragSourceSessionState]; |
| _page->dragEnded(roundedIntPoint(client), roundedIntPoint(global), _page->currentDragOperation()); |
| } |
| |
| - (void)_didChangeDataInteractionCaretRect:(CGRect)previousRect currentRect:(CGRect)rect |
| { |
| BOOL previousRectIsEmpty = CGRectIsEmpty(previousRect); |
| BOOL currentRectIsEmpty = CGRectIsEmpty(rect); |
| if (previousRectIsEmpty && currentRectIsEmpty) |
| return; |
| |
| if (previousRectIsEmpty) { |
| _editDropCaretView = adoptNS([[_UITextDragCaretView alloc] initWithTextInputView:self]); |
| [_editDropCaretView insertAtPosition:[WKTextPosition textPositionWithRect:rect]]; |
| return; |
| } |
| |
| if (currentRectIsEmpty) { |
| [_editDropCaretView remove]; |
| _editDropCaretView = nil; |
| return; |
| } |
| |
| [_editDropCaretView updateToPosition:[WKTextPosition textPositionWithRect:rect]]; |
| } |
| |
| - (void)_prepareToDragPromisedBlob:(const PromisedBlobInfo&)info |
| { |
| auto session = retainPtr(_dragDropInteractionState.dragSession()); |
| if (!session) { |
| ASSERT_NOT_REACHED(); |
| return; |
| } |
| |
| auto numberOfAdditionalTypes = info.additionalTypes.size(); |
| ASSERT(numberOfAdditionalTypes == info.additionalData.size()); |
| |
| RELEASE_LOG(DragAndDrop, "Drag session: %p preparing to drag blob: %s", session.get(), info.blobURL.string().utf8().data()); |
| |
| auto registrationList = adoptNS([[WebItemProviderRegistrationInfoList alloc] init]); |
| [registrationList setPreferredPresentationStyle:WebPreferredPresentationStyleAttachment]; |
| if (!info.filename.isEmpty()) |
| [registrationList setSuggestedName:info.filename]; |
| if (numberOfAdditionalTypes == info.additionalData.size() && numberOfAdditionalTypes) { |
| for (size_t index = 0; index < numberOfAdditionalTypes; ++index) { |
| auto nsData = info.additionalData[index]->createNSData(); |
| [registrationList addData:nsData.get() forType:info.additionalTypes[index]]; |
| } |
| } |
| |
| [registrationList addPromisedType:info.contentType fileCallback:[session = WTFMove(session), weakSelf = WeakObjCPtr<WKContentView>(self), url = info.blobURL] (WebItemProviderFileCallback callback) { |
| auto strongSelf = weakSelf.get(); |
| if (!strongSelf) { |
| callback(nil, [NSError errorWithDomain:WKErrorDomain code:WKErrorWebViewInvalidated userInfo:nil]); |
| return; |
| } |
| |
| NSString *temporaryBlobDirectory = FileSystem::createTemporaryDirectory(@"blobs"); |
| NSURL *destinationURL = [NSURL fileURLWithPath:[temporaryBlobDirectory stringByAppendingPathComponent:[NSUUID UUID].UUIDString]]; |
| |
| RELEASE_LOG(DragAndDrop, "Drag session: %p delivering promised blob at path: %@", session.get(), destinationURL.path); |
| strongSelf->_page->writeBlobToFilePath(url, destinationURL.path, [protectedURL = retainPtr(destinationURL), protectedCallback = makeBlockPtr(callback)] (bool success) { |
| if (success) |
| protectedCallback(protectedURL.get(), nil); |
| else |
| protectedCallback(nil, [NSError errorWithDomain:WKErrorDomain code:WKErrorUnknown userInfo:nil]); |
| }); |
| |
| [ensureLocalDragSessionContext(session.get()) addTemporaryDirectory:temporaryBlobDirectory]; |
| }]; |
| |
| WebItemProviderPasteboard *pasteboard = [WebItemProviderPasteboard sharedInstance]; |
| pasteboard.itemProviders = @[ [registrationList itemProvider] ]; |
| [pasteboard stageRegistrationList:registrationList.get()]; |
| } |
| |
| - (WKDragDestinationAction)_dragDestinationActionForDropSession:(id <UIDropSession>)session |
| { |
| id <WKUIDelegatePrivate> uiDelegate = self.webViewUIDelegate; |
| if ([uiDelegate respondsToSelector:@selector(_webView:dragDestinationActionMaskForDraggingInfo:)]) |
| return [uiDelegate _webView:_webView dragDestinationActionMaskForDraggingInfo:session]; |
| |
| return WKDragDestinationActionAny & ~WKDragDestinationActionLoad; |
| } |
| |
| - (id <UIDragDropSession>)currentDragOrDropSession |
| { |
| if (_dragDropInteractionState.dropSession()) |
| return _dragDropInteractionState.dropSession(); |
| return _dragDropInteractionState.dragSession(); |
| } |
| |
| - (void)_restoreCalloutBarIfNeeded |
| { |
| if (!_shouldRestoreCalloutBarAfterDrop) |
| return; |
| |
| // FIXME: This SPI should be renamed in UIKit to reflect a more general purpose of revealing hidden interaction assistant controls. |
| [_webSelectionAssistant didEndScrollingOverflow]; |
| [_textSelectionAssistant didEndScrollingOverflow]; |
| _shouldRestoreCalloutBarAfterDrop = NO; |
| } |
| |
| - (NSArray<UIDragItem *> *)_itemsForBeginningOrAddingToSessionWithRegistrationList:(WebItemProviderRegistrationInfoList *)registrationList stagedDragSource:(const DragSourceState&)stagedDragSource |
| { |
| UIItemProvider *defaultItemProvider = registrationList.itemProvider; |
| if (!defaultItemProvider) |
| return @[ ]; |
| |
| NSArray *adjustedItemProviders; |
| id <WKUIDelegatePrivate> uiDelegate = self.webViewUIDelegate; |
| if ([uiDelegate respondsToSelector:@selector(_webView:adjustedDataInteractionItemProvidersForItemProvider:representingObjects:additionalData:)]) { |
| auto representingObjects = adoptNS([[NSMutableArray alloc] init]); |
| auto additionalData = adoptNS([[NSMutableDictionary alloc] init]); |
| [registrationList enumerateItems:[representingObjects, additionalData] (id <WebItemProviderRegistrar> item, NSUInteger) { |
| if ([item respondsToSelector:@selector(representingObjectForClient)]) |
| [representingObjects addObject:item.representingObjectForClient]; |
| if ([item respondsToSelector:@selector(typeIdentifierForClient)] && [item respondsToSelector:@selector(dataForClient)]) |
| [additionalData setObject:item.dataForClient forKey:item.typeIdentifierForClient]; |
| }]; |
| adjustedItemProviders = [uiDelegate _webView:_webView adjustedDataInteractionItemProvidersForItemProvider:defaultItemProvider representingObjects:representingObjects.get() additionalData:additionalData.get()]; |
| } else |
| adjustedItemProviders = @[ defaultItemProvider ]; |
| |
| NSMutableArray *dragItems = [NSMutableArray arrayWithCapacity:adjustedItemProviders.count]; |
| for (UIItemProvider *itemProvider in adjustedItemProviders) { |
| auto item = adoptNS([[UIDragItem alloc] initWithItemProvider:itemProvider]); |
| [item _setPrivateLocalContext:@(stagedDragSource.itemIdentifier)]; |
| [dragItems addObject:item.autorelease()]; |
| } |
| |
| return dragItems; |
| } |
| |
| - (NSDictionary *)_autofillContext |
| { |
| BOOL provideStrongPasswordAssistance = _focusRequiresStrongPasswordAssistance && _assistedNodeInformation.elementType == InputType::Password; |
| if (!hasAssistedNode(_assistedNodeInformation) || (!_assistedNodeInformation.acceptsAutofilledLoginCredentials && !provideStrongPasswordAssistance)) |
| return nil; |
| |
| if (provideStrongPasswordAssistance) |
| return @{ @"_automaticPasswordKeyboard" : @YES }; |
| |
| NSURL *platformURL = _assistedNodeInformation.representingPageURL; |
| if (platformURL) |
| return @{ @"_WebViewURL" : platformURL }; |
| |
| return nil; |
| } |
| |
| #pragma mark - UIDragInteractionDelegate |
| |
| - (BOOL)_dragInteraction:(UIDragInteraction *)interaction shouldDelayCompetingGestureRecognizer:(UIGestureRecognizer *)competingGestureRecognizer |
| { |
| if (_highlightLongPressGestureRecognizer == competingGestureRecognizer) { |
| // Since 3D touch still recognizes alongside the drag lift, and also requires the highlight long press |
| // gesture to be active to support cancelling when `touchstart` is prevented, we should also allow the |
| // highlight long press to recognize simultaneously, and manually cancel it when the drag lift is |
| // recognized (see _dragInteraction:prepareForSession:completion:). |
| return NO; |
| } |
| return [competingGestureRecognizer isKindOfClass:[UILongPressGestureRecognizer class]]; |
| } |
| |
| - (NSInteger)_dragInteraction:(UIDragInteraction *)interaction dataOwnerForSession:(id <UIDragSession>)session |
| { |
| id <WKUIDelegatePrivate> uiDelegate = self.webViewUIDelegate; |
| NSInteger dataOwner = 0; |
| if ([uiDelegate respondsToSelector:@selector(_webView:dataOwnerForDragSession:)]) |
| dataOwner = [uiDelegate _webView:_webView dataOwnerForDragSession:session]; |
| return dataOwner; |
| } |
| |
| - (void)_dragInteraction:(UIDragInteraction *)interaction itemsForAddingToSession:(id <UIDragSession>)session withTouchAtPoint:(CGPoint)point completion:(void(^)(NSArray<UIDragItem *> *))completion |
| { |
| if (!_dragDropInteractionState.shouldRequestAdditionalItemForDragSession(session)) { |
| completion(@[ ]); |
| return; |
| } |
| |
| _dragDropInteractionState.dragSessionWillRequestAdditionalItem(completion); |
| _page->requestAdditionalItemsForDragSession(roundedIntPoint(point), roundedIntPoint(point)); |
| } |
| |
| - (void)_dragInteraction:(UIDragInteraction *)interaction prepareForSession:(id <UIDragSession>)session completion:(dispatch_block_t)completion |
| { |
| [self _cancelLongPressGestureRecognizer]; |
| |
| RELEASE_LOG(DragAndDrop, "Preparing for drag session: %p", session); |
| if (self.currentDragOrDropSession) { |
| // FIXME: Support multiple simultaneous drag sessions in the future. |
| RELEASE_LOG(DragAndDrop, "Drag session failed: %p (a current drag session already exists)", session); |
| completion(); |
| return; |
| } |
| |
| [self cleanUpDragSourceSessionState]; |
| |
| _dragDropInteractionState.prepareForDragSession(session, completion); |
| |
| auto dragOrigin = roundedIntPoint([session locationInView:self]); |
| _page->requestStartDataInteraction(dragOrigin, roundedIntPoint([self convertPoint:dragOrigin toView:self.window])); |
| |
| RELEASE_LOG(DragAndDrop, "Drag session requested: %p at origin: {%d, %d}", session, dragOrigin.x(), dragOrigin.y()); |
| } |
| |
| - (NSArray<UIDragItem *> *)dragInteraction:(UIDragInteraction *)interaction itemsForBeginningSession:(id <UIDragSession>)session |
| { |
| ASSERT(interaction == _dragInteraction); |
| RELEASE_LOG(DragAndDrop, "Drag items requested for session: %p", session); |
| if (_dragDropInteractionState.dragSession() != session) { |
| RELEASE_LOG(DragAndDrop, "Drag session failed: %p (delegate session does not match %p)", session, _dragDropInteractionState.dragSession()); |
| return @[ ]; |
| } |
| |
| if (!_dragDropInteractionState.hasStagedDragSource()) { |
| RELEASE_LOG(DragAndDrop, "Drag session failed: %p (missing staged drag source)", session); |
| return @[ ]; |
| } |
| |
| auto stagedDragSource = _dragDropInteractionState.stagedDragSource(); |
| WebItemProviderRegistrationInfoList *registrationList = [[WebItemProviderPasteboard sharedInstance] takeRegistrationList]; |
| NSArray *dragItems = [self _itemsForBeginningOrAddingToSessionWithRegistrationList:registrationList stagedDragSource:stagedDragSource]; |
| if (![dragItems count]) |
| _page->dragCancelled(); |
| |
| RELEASE_LOG(DragAndDrop, "Drag session: %p starting with %tu items", session, [dragItems count]); |
| _dragDropInteractionState.clearStagedDragSource([dragItems count] ? DragDropInteractionState::DidBecomeActive::Yes : DragDropInteractionState::DidBecomeActive::No); |
| |
| return dragItems; |
| } |
| |
| - (UITargetedDragPreview *)dragInteraction:(UIDragInteraction *)interaction previewForLiftingItem:(UIDragItem *)item session:(id <UIDragSession>)session |
| { |
| id <WKUIDelegatePrivate> uiDelegate = self.webViewUIDelegate; |
| if ([uiDelegate respondsToSelector:@selector(_webView:previewForLiftingItem:session:)]) { |
| UITargetedDragPreview *overridenPreview = [uiDelegate _webView:_webView previewForLiftingItem:item session:session]; |
| if (overridenPreview) |
| return overridenPreview; |
| } |
| return _dragDropInteractionState.previewForDragItem(item, self, self.unscaledView); |
| } |
| |
| - (void)dragInteraction:(UIDragInteraction *)interaction willAnimateLiftWithAnimator:(id <UIDragAnimating>)animator session:(id <UIDragSession>)session |
| { |
| if (!_shouldRestoreCalloutBarAfterDrop && _dragDropInteractionState.anyActiveDragSourceIs(DragSourceActionSelection)) { |
| // FIXME: This SPI should be renamed in UIKit to reflect a more general purpose of hiding interaction assistant controls. |
| [_webSelectionAssistant willStartScrollingOverflow]; |
| [_textSelectionAssistant willStartScrollingOverflow]; |
| _shouldRestoreCalloutBarAfterDrop = YES; |
| } |
| |
| auto positionForDragEnd = roundedIntPoint(_dragDropInteractionState.adjustedPositionForDragEnd()); |
| RetainPtr<WKContentView> protectedSelf(self); |
| [animator addCompletion:[session, positionForDragEnd, protectedSelf, page = _page] (UIViewAnimatingPosition finalPosition) { |
| #if RELEASE_LOG_DISABLED |
| UNUSED_PARAM(session); |
| #endif |
| if (finalPosition == UIViewAnimatingPositionStart) { |
| RELEASE_LOG(DragAndDrop, "Drag session ended at start: %p", session); |
| // The lift was canceled, so -dropInteraction:sessionDidEnd: will never be invoked. This is the last chance to clean up. |
| [protectedSelf cleanUpDragSourceSessionState]; |
| page->dragEnded(positionForDragEnd, positionForDragEnd, DragOperationNone); |
| } |
| }]; |
| } |
| |
| - (void)dragInteraction:(UIDragInteraction *)interaction sessionWillBegin:(id <UIDragSession>)session |
| { |
| RELEASE_LOG(DragAndDrop, "Drag session beginning: %p", session); |
| id <WKUIDelegatePrivate> uiDelegate = self.webViewUIDelegate; |
| if ([uiDelegate respondsToSelector:@selector(_webView:dataInteraction:sessionWillBegin:)]) |
| [uiDelegate _webView:_webView dataInteraction:interaction sessionWillBegin:session]; |
| |
| [_actionSheetAssistant cleanupSheet]; |
| _dragDropInteractionState.dragSessionWillBegin(); |
| _page->didStartDrag(); |
| } |
| |
| - (void)dragInteraction:(UIDragInteraction *)interaction session:(id <UIDragSession>)session didEndWithOperation:(UIDropOperation)operation |
| { |
| RELEASE_LOG(DragAndDrop, "Drag session ended: %p (with operation: %tu, performing operation: %d, began dragging: %d)", session, operation, _dragDropInteractionState.isPerformingDrop(), _dragDropInteractionState.didBeginDragging()); |
| |
| [self _restoreCalloutBarIfNeeded]; |
| |
| id <WKUIDelegatePrivate> uiDelegate = self.webViewUIDelegate; |
| if ([uiDelegate respondsToSelector:@selector(_webView:dataInteraction:session:didEndWithOperation:)]) |
| [uiDelegate _webView:_webView dataInteraction:interaction session:session didEndWithOperation:operation]; |
| |
| if (_dragDropInteractionState.isPerformingDrop()) |
| return; |
| |
| [self cleanUpDragSourceSessionState]; |
| _page->dragEnded(roundedIntPoint(_dragDropInteractionState.adjustedPositionForDragEnd()), roundedIntPoint(_dragDropInteractionState.adjustedPositionForDragEnd()), operation); |
| } |
| |
| - (UITargetedDragPreview *)dragInteraction:(UIDragInteraction *)interaction previewForCancellingItem:(UIDragItem *)item withDefault:(UITargetedDragPreview *)defaultPreview |
| { |
| id <WKUIDelegatePrivate> uiDelegate = self.webViewUIDelegate; |
| if ([uiDelegate respondsToSelector:@selector(_webView:previewForCancellingItem:withDefault:)]) { |
| UITargetedDragPreview *overridenPreview = [uiDelegate _webView:_webView previewForCancellingItem:item withDefault:defaultPreview]; |
| if (overridenPreview) |
| return overridenPreview; |
| } |
| return _dragDropInteractionState.previewForDragItem(item, self, self.unscaledView); |
| } |
| |
| - (BOOL)_dragInteraction:(UIDragInteraction *)interaction item:(UIDragItem *)item shouldDelaySetDownAnimationWithCompletion:(void(^)(void))completion |
| { |
| _dragDropInteractionState.dragSessionWillDelaySetDownAnimation(completion); |
| return YES; |
| } |
| |
| - (void)dragInteraction:(UIDragInteraction *)interaction item:(UIDragItem *)item willAnimateCancelWithAnimator:(id <UIDragAnimating>)animator |
| { |
| [animator addCompletion:[protectedSelf = retainPtr(self), page = _page] (UIViewAnimatingPosition finalPosition) { |
| page->dragCancelled(); |
| if (auto completion = protectedSelf->_dragDropInteractionState.takeDragCancelSetDownBlock()) { |
| page->callAfterNextPresentationUpdate([completion] (CallbackBase::Error) { |
| completion(); |
| }); |
| } |
| }]; |
| } |
| |
| - (void)dragInteraction:(UIDragInteraction *)interaction sessionDidTransferItems:(id <UIDragSession>)session |
| { |
| [existingLocalDragSessionContext(session) cleanUpTemporaryDirectories]; |
| } |
| |
| #pragma mark - UIDropInteractionDelegate |
| |
| - (NSInteger)_dropInteraction:(UIDropInteraction *)interaction dataOwnerForSession:(id <UIDropSession>)session |
| { |
| id <WKUIDelegatePrivate> uiDelegate = self.webViewUIDelegate; |
| NSInteger dataOwner = 0; |
| if ([uiDelegate respondsToSelector:@selector(_webView:dataOwnerForDropSession:)]) |
| dataOwner = [uiDelegate _webView:_webView dataOwnerForDropSession:session]; |
| return dataOwner; |
| } |
| |
| - (BOOL)dropInteraction:(UIDropInteraction *)interaction canHandleSession:(id<UIDropSession>)session |
| { |
| // FIXME: Support multiple simultaneous drop sessions in the future. |
| id <UIDragDropSession> dragOrDropSession = self.currentDragOrDropSession; |
| RELEASE_LOG(DragAndDrop, "Can handle drag session: %p with local session: %p existing session: %p?", session, session.localDragSession, dragOrDropSession); |
| |
| return !dragOrDropSession || session.localDragSession == dragOrDropSession; |
| } |
| |
| - (void)dropInteraction:(UIDropInteraction *)interaction sessionDidEnter:(id <UIDropSession>)session |
| { |
| RELEASE_LOG(DragAndDrop, "Drop session entered: %p with %tu items", session, session.items.count); |
| auto dragData = [self dragDataForDropSession:session dragDestinationAction:[self _dragDestinationActionForDropSession:session]]; |
| |
| _dragDropInteractionState.dropSessionDidEnterOrUpdate(session, dragData); |
| |
| [[WebItemProviderPasteboard sharedInstance] setItemProviders:extractItemProvidersFromDropSession(session)]; |
| _page->dragEntered(dragData, "data interaction pasteboard"); |
| } |
| |
| - (UIDropProposal *)dropInteraction:(UIDropInteraction *)interaction sessionDidUpdate:(id <UIDropSession>)session |
| { |
| [[WebItemProviderPasteboard sharedInstance] setItemProviders:extractItemProvidersFromDropSession(session)]; |
| |
| auto dragData = [self dragDataForDropSession:session dragDestinationAction:[self _dragDestinationActionForDropSession:session]]; |
| _page->dragUpdated(dragData, "data interaction pasteboard"); |
| _dragDropInteractionState.dropSessionDidEnterOrUpdate(session, dragData); |
| |
| NSUInteger operation = dropOperationForWebCoreDragOperation(_page->currentDragOperation()); |
| if ([self.webViewUIDelegate respondsToSelector:@selector(_webView:willUpdateDataInteractionOperationToOperation:forSession:)]) |
| operation = [self.webViewUIDelegate _webView:_webView willUpdateDataInteractionOperationToOperation:operation forSession:session]; |
| |
| return [[[UIDropProposal alloc] initWithDropOperation:static_cast<UIDropOperation>(operation)] autorelease]; |
| } |
| |
| - (void)dropInteraction:(UIDropInteraction *)interaction sessionDidExit:(id <UIDropSession>)session |
| { |
| RELEASE_LOG(DragAndDrop, "Drop session exited: %p with %tu items", session, session.items.count); |
| [[WebItemProviderPasteboard sharedInstance] setItemProviders:extractItemProvidersFromDropSession(session)]; |
| |
| auto dragData = [self dragDataForDropSession:session dragDestinationAction:WKDragDestinationActionAny]; |
| _page->dragExited(dragData, "data interaction pasteboard"); |
| _page->resetCurrentDragInformation(); |
| |
| _dragDropInteractionState.dropSessionDidExit(); |
| } |
| |
| - (void)dropInteraction:(UIDropInteraction *)interaction performDrop:(id <UIDropSession>)session |
| { |
| NSArray <UIItemProvider *> *itemProviders = extractItemProvidersFromDropSession(session); |
| id <WKUIDelegatePrivate> uiDelegate = self.webViewUIDelegate; |
| if ([uiDelegate respondsToSelector:@selector(_webView:performDataInteractionOperationWithItemProviders:)]) { |
| if ([uiDelegate _webView:_webView performDataInteractionOperationWithItemProviders:itemProviders]) |
| return; |
| } |
| |
| if ([uiDelegate respondsToSelector:@selector(_webView:willPerformDropWithSession:)]) { |
| itemProviders = extractItemProvidersFromDragItems([uiDelegate _webView:_webView willPerformDropWithSession:session]); |
| if (!itemProviders.count) |
| return; |
| } |
| |
| _dragDropInteractionState.dropSessionWillPerformDrop(); |
| |
| [[WebItemProviderPasteboard sharedInstance] setItemProviders:itemProviders]; |
| [[WebItemProviderPasteboard sharedInstance] incrementPendingOperationCount]; |
| auto dragData = [self dragDataForDropSession:session dragDestinationAction:WKDragDestinationActionAny]; |
| |
| RELEASE_LOG(DragAndDrop, "Loading data from %tu item providers for session: %p", itemProviders.count, session); |
| // Always loading content from the item provider ensures that the web process will be allowed to call back in to the UI |
| // process to access pasteboard contents at a later time. Ideally, we only need to do this work if we're over a file input |
| // or the page prevented default on `dragover`, but without this, dropping into a normal editable areas will fail due to |
| // item providers not loading any data. |
| RetainPtr<WKContentView> retainedSelf(self); |
| [[WebItemProviderPasteboard sharedInstance] doAfterLoadingProvidedContentIntoFileURLs:[retainedSelf, capturedDragData = WTFMove(dragData)] (NSArray *fileURLs) mutable { |
| RELEASE_LOG(DragAndDrop, "Loaded data into %tu files", fileURLs.count); |
| Vector<String> filenames; |
| for (NSURL *fileURL in fileURLs) |
| filenames.append([fileURL path]); |
| capturedDragData.setFileNames(filenames); |
| |
| SandboxExtension::Handle sandboxExtensionHandle; |
| SandboxExtension::HandleArray sandboxExtensionForUpload; |
| retainedSelf->_page->createSandboxExtensionsIfNeeded(filenames, sandboxExtensionHandle, sandboxExtensionForUpload); |
| retainedSelf->_page->performDragOperation(capturedDragData, "data interaction pasteboard", WTFMove(sandboxExtensionHandle), WTFMove(sandboxExtensionForUpload)); |
| |
| retainedSelf->_visibleContentViewSnapshot = [retainedSelf snapshotViewAfterScreenUpdates:NO]; |
| [retainedSelf setSuppressAssistantSelectionView:YES]; |
| [UIView performWithoutAnimation:[retainedSelf] { |
| [retainedSelf->_visibleContentViewSnapshot setFrame:[retainedSelf bounds]]; |
| [retainedSelf addSubview:retainedSelf->_visibleContentViewSnapshot.get()]; |
| }]; |
| }]; |
| } |
| |
| - (UITargetedDragPreview *)dropInteraction:(UIDropInteraction *)interaction previewForDroppingItem:(UIDragItem *)item withDefault:(UITargetedDragPreview *)defaultPreview |
| { |
| CGRect caretRect = _page->currentDragCaretRect(); |
| if (CGRectIsEmpty(caretRect)) |
| return nil; |
| |
| // FIXME: <rdar://problem/31074376> [WK2] Performing an edit drag should transition from the initial drag preview to the final drop preview |
| // This is blocked on UIKit support, since we aren't able to update the text clipping rects of a UITargetedDragPreview mid-flight. For now, |
| // just zoom to the center of the caret rect while shrinking the drop preview. |
| auto caretRectInWindowCoordinates = [self convertRect:caretRect toView:[UITextEffectsWindow sharedTextEffectsWindow]]; |
| auto caretCenterInWindowCoordinates = CGPointMake(CGRectGetMidX(caretRectInWindowCoordinates), CGRectGetMidY(caretRectInWindowCoordinates)); |
| auto target = adoptNS([[UIDragPreviewTarget alloc] initWithContainer:[UITextEffectsWindow sharedTextEffectsWindow] center:caretCenterInWindowCoordinates transform:CGAffineTransformMakeScale(0, 0)]); |
| return [defaultPreview retargetedPreviewWithTarget:target.get()]; |
| } |
| |
| - (void)dropInteraction:(UIDropInteraction *)interaction sessionDidEnd:(id <UIDropSession>)session |
| { |
| RELEASE_LOG(DragAndDrop, "Drop session ended: %p (performing operation: %d, began dragging: %d)", session, _dragDropInteractionState.isPerformingDrop(), _dragDropInteractionState.didBeginDragging()); |
| if (_dragDropInteractionState.isPerformingDrop()) { |
| // In the case where we are performing a drop, wait until after the drop is handled in the web process to reset drag and drop interaction state. |
| return; |
| } |
| |
| if (_dragDropInteractionState.didBeginDragging()) { |
| // In the case where the content view is a source of drag items, wait until -dragInteraction:session:didEndWithOperation: to reset drag and drop interaction state. |
| return; |
| } |
| |
| CGPoint global; |
| CGPoint client; |
| [self computeClientAndGlobalPointsForDropSession:session outClientPoint:&client outGlobalPoint:&global]; |
| [self cleanUpDragSourceSessionState]; |
| _page->dragEnded(roundedIntPoint(client), roundedIntPoint(global), DragOperationNone); |
| } |
| |
| #endif |
| |
| #if USE(APPLE_INTERNAL_SDK) |
| #import <WebKitAdditions/WKContentViewInteractionAdditions.mm> |
| #import <WebKitAdditions/WKContentViewInteractionAdditionsAfter.mm> |
| #endif |
| |
| @end |
| |
| @implementation WKContentView (WKTesting) |
| |
| - (void)_simulateTextEntered:(NSString *)text |
| { |
| #if ENABLE(EXTRA_ZOOM_MODE) |
| if ([_presentedFullScreenInputViewController isKindOfClass:[WKTextInputListViewController class]]) |
| [(WKTextInputListViewController *)_presentedFullScreenInputViewController.get() enterText:text]; |
| #else |
| [self insertText:text]; |
| #endif |
| } |
| |
| - (void)_simulateLongPressActionAtLocation:(CGPoint)location |
| { |
| RetainPtr<WKContentView> protectedSelf = self; |
| [self doAfterPositionInformationUpdate:[protectedSelf] (InteractionInformationAtPosition) { |
| if (SEL action = [protectedSelf _actionForLongPress]) |
| [protectedSelf performSelector:action]; |
| } forRequest:InteractionInformationRequest(roundedIntPoint(location))]; |
| } |
| |
| - (void)selectFormAccessoryPickerRow:(NSInteger)rowIndex |
| { |
| #if ENABLE(EXTRA_ZOOM_MODE) |
| if ([_presentedFullScreenInputViewController isKindOfClass:[WKSelectMenuListViewController class]]) |
| [(WKSelectMenuListViewController *)_presentedFullScreenInputViewController.get() selectItemAtIndex:rowIndex]; |
| #else |
| if ([_inputPeripheral isKindOfClass:[WKFormSelectControl self]]) |
| [(WKFormSelectControl *)_inputPeripheral selectRow:rowIndex inComponent:0 extendingSelection:NO]; |
| #endif |
| } |
| |
| - (NSString *)selectFormPopoverTitle |
| { |
| if (![_inputPeripheral isKindOfClass:[WKFormSelectControl self]]) |
| return nil; |
| |
| return [(WKFormSelectControl *)_inputPeripheral selectFormPopoverTitle]; |
| } |
| |
| - (NSString *)formInputLabel |
| { |
| #if ENABLE(EXTRA_ZOOM_MODE) |
| if (_presentedFullScreenInputViewController) |
| return [self inputLabelTextForViewController:(id)_presentedFullScreenInputViewController.get()]; |
| #endif |
| return nil; |
| } |
| |
| - (void)setTimePickerValueToHour:(NSInteger)hour minute:(NSInteger)minute |
| { |
| #if ENABLE(EXTRA_ZOOM_MODE) |
| if ([_presentedFullScreenInputViewController isKindOfClass:[WKTimePickerViewController class]]) |
| [(WKTimePickerViewController *)_presentedFullScreenInputViewController.get() setHour:hour minute:minute]; |
| #endif |
| } |
| |
| - (NSDictionary *)_contentsOfUserInterfaceItem:(NSString *)userInterfaceItem |
| { |
| if ([userInterfaceItem isEqualToString:@"actionSheet"]) |
| return @{ userInterfaceItem: [_actionSheetAssistant currentAvailableActionTitles] }; |
| |
| #if HAVE(LINK_PREVIEW) |
| if ([userInterfaceItem isEqualToString:@"linkPreviewPopoverContents"]) { |
| NSString *url = [_previewItemController previewData][UIPreviewDataLink]; |
| return @{ userInterfaceItem: @{ @"pageURL": url } }; |
| } |
| #endif |
| |
| return nil; |
| } |
| |
| @end |
| |
| #if HAVE(LINK_PREVIEW) |
| |
| @implementation WKContentView (WKInteractionPreview) |
| |
| - (void)_registerPreview |
| { |
| if (!_webView.allowsLinkPreview) |
| return; |
| |
| _previewItemController = adoptNS([[UIPreviewItemController alloc] initWithView:self]); |
| [_previewItemController setDelegate:self]; |
| _previewGestureRecognizer = _previewItemController.get().presentationGestureRecognizer; |
| if ([_previewItemController respondsToSelector:@selector(presentationSecondaryGestureRecognizer)]) |
| _previewSecondaryGestureRecognizer = _previewItemController.get().presentationSecondaryGestureRecognizer; |
| } |
| |
| - (void)_unregisterPreview |
| { |
| [_previewItemController setDelegate:nil]; |
| _previewGestureRecognizer = nil; |
| _previewSecondaryGestureRecognizer = nil; |
| _previewItemController = nil; |
| } |
| |
| - (BOOL)_interactionShouldBeginFromPreviewItemController:(UIPreviewItemController *)controller forPosition:(CGPoint)position |
| { |
| if (!_highlightLongPressCanClick) |
| return NO; |
| |
| InteractionInformationRequest request(roundedIntPoint(position)); |
| request.includeSnapshot = true; |
| request.includeLinkIndicator = true; |
| if (![self ensurePositionInformationIsUpToDate:request]) |
| return NO; |
| if (!_positionInformation.isLink && !_positionInformation.isImage && !_positionInformation.isAttachment) |
| return NO; |
| |
| const URL& linkURL = _positionInformation.url; |
| if (_positionInformation.isLink) { |
| id <WKUIDelegatePrivate> uiDelegate = static_cast<id <WKUIDelegatePrivate>>([_webView UIDelegate]); |
| if ([uiDelegate respondsToSelector:@selector(webView:shouldPreviewElement:)]) { |
| auto previewElementInfo = adoptNS([[WKPreviewElementInfo alloc] _initWithLinkURL:(NSURL *)linkURL]); |
| return [uiDelegate webView:_webView shouldPreviewElement:previewElementInfo.get()]; |
| } |
| if (linkURL.isEmpty()) |
| return NO; |
| if (linkURL.protocolIsInHTTPFamily()) |
| return YES; |
| #if ENABLE(DATA_DETECTION) |
| if (DataDetection::canBePresentedByDataDetectors(linkURL)) |
| return YES; |
| #endif |
| return NO; |
| } |
| return YES; |
| } |
| |
| - (NSDictionary *)_dataForPreviewItemController:(UIPreviewItemController *)controller atPosition:(CGPoint)position type:(UIPreviewItemType *)type |
| { |
| *type = UIPreviewItemTypeNone; |
| |
| id <WKUIDelegatePrivate> uiDelegate = static_cast<id <WKUIDelegatePrivate>>([_webView UIDelegate]); |
| BOOL supportsImagePreview = [uiDelegate respondsToSelector:@selector(_webView:commitPreviewedImageWithURL:)]; |
| BOOL canShowImagePreview = _positionInformation.isImage && supportsImagePreview; |
| BOOL canShowLinkPreview = _positionInformation.isLink || canShowImagePreview; |
| BOOL useImageURLForLink = NO; |
| BOOL respondsToAttachmentListForWebViewSourceIsManaged = [uiDelegate respondsToSelector:@selector(_attachmentListForWebView:sourceIsManaged:)]; |
| BOOL supportsAttachmentPreview = ([uiDelegate respondsToSelector:@selector(_attachmentListForWebView:)] || respondsToAttachmentListForWebViewSourceIsManaged) |
| && [uiDelegate respondsToSelector:@selector(_webView:indexIntoAttachmentListForElement:)]; |
| BOOL canShowAttachmentPreview = (_positionInformation.isAttachment || _positionInformation.isImage) && supportsAttachmentPreview; |
| BOOL isDataDetectorLink = NO; |
| #if ENABLE(DATA_DETECTION) |
| isDataDetectorLink = _positionInformation.isDataDetectorLink; |
| #endif |
| |
| if (canShowImagePreview && _positionInformation.isAnimatedImage) { |
| canShowImagePreview = NO; |
| canShowLinkPreview = YES; |
| useImageURLForLink = YES; |
| } |
| |
| if (!canShowLinkPreview && !canShowImagePreview && !canShowAttachmentPreview) |
| return nil; |
| |
| const URL& linkURL = _positionInformation.url; |
| if (!useImageURLForLink && (linkURL.isEmpty() || (!linkURL.protocolIsInHTTPFamily() && !isDataDetectorLink))) { |
| if (canShowLinkPreview && !canShowImagePreview) |
| return nil; |
| canShowLinkPreview = NO; |
| } |
| |
| NSMutableDictionary *dataForPreview = [[[NSMutableDictionary alloc] init] autorelease]; |
| if (canShowLinkPreview) { |
| *type = UIPreviewItemTypeLink; |
| if (useImageURLForLink) |
| dataForPreview[UIPreviewDataLink] = (NSURL *)_positionInformation.imageURL; |
| else |
| dataForPreview[UIPreviewDataLink] = (NSURL *)linkURL; |
| #if ENABLE(DATA_DETECTION) |
| if (isDataDetectorLink) { |
| NSDictionary *context = nil; |
| if ([uiDelegate respondsToSelector:@selector(_dataDetectionContextForWebView:)]) |
| context = [uiDelegate _dataDetectionContextForWebView:_webView]; |
| |
| DDDetectionController *controller = [getDDDetectionControllerClass() sharedController]; |
| NSDictionary *newContext = nil; |
| RetainPtr<NSMutableDictionary> extendedContext; |
| DDResultRef ddResult = [controller resultForURL:dataForPreview[UIPreviewDataLink] identifier:_positionInformation.dataDetectorIdentifier selectedText:[self selectedText] results:_positionInformation.dataDetectorResults.get() context:context extendedContext:&newContext]; |
| if (ddResult) |
| dataForPreview[UIPreviewDataDDResult] = (__bridge id)ddResult; |
| if (!_positionInformation.textBefore.isEmpty() || !_positionInformation.textAfter.isEmpty()) { |
| extendedContext = adoptNS([@{ |
| getkDataDetectorsLeadingText() : _positionInformation.textBefore, |
| getkDataDetectorsTrailingText() : _positionInformation.textAfter, |
| } mutableCopy]); |
| |
| if (newContext) |
| [extendedContext addEntriesFromDictionary:newContext]; |
| newContext = extendedContext.get(); |
| } |
| if (newContext) |
| dataForPreview[UIPreviewDataDDContext] = newContext; |
| } |
| #endif // ENABLE(DATA_DETECTION) |
| } else if (canShowImagePreview) { |
| *type = UIPreviewItemTypeImage; |
| dataForPreview[UIPreviewDataLink] = (NSURL *)_positionInformation.imageURL; |
| } else if (canShowAttachmentPreview) { |
| *type = UIPreviewItemTypeAttachment; |
| auto element = adoptNS([[_WKActivatedElementInfo alloc] _initWithType:_WKActivatedElementTypeAttachment URL:(NSURL *)linkURL location:_positionInformation.request.point title:_positionInformation.title ID:_positionInformation.idAttribute rect:_positionInformation.bounds image:nil]); |
| NSUInteger index = [uiDelegate _webView:_webView indexIntoAttachmentListForElement:element.get()]; |
| if (index != NSNotFound) { |
| BOOL sourceIsManaged = NO; |
| if (respondsToAttachmentListForWebViewSourceIsManaged) |
| dataForPreview[UIPreviewDataAttachmentList] = [uiDelegate _attachmentListForWebView:_webView sourceIsManaged:&sourceIsManaged]; |
| else |
| dataForPreview[UIPreviewDataAttachmentList] = [uiDelegate _attachmentListForWebView:_webView]; |
| dataForPreview[UIPreviewDataAttachmentIndex] = [NSNumber numberWithUnsignedInteger:index]; |
| |
| #if __IPHONE_OS_VERSION_MIN_REQUIRED >= 120000 |
| // FIXME: Replace the following NSString literal with a UIKit NSString constant. |
| dataForPreview[@"UIPreviewDataAttachmentListIsContentManaged"] = [NSNumber numberWithBool:sourceIsManaged]; |
| #else |
| dataForPreview[UIPreviewDataAttachmentListSourceIsManaged] = [NSNumber numberWithBool:sourceIsManaged]; |
| #endif |
| } |
| } |
| |
| return dataForPreview; |
| } |
| |
| - (CGRect)_presentationRectForPreviewItemController:(UIPreviewItemController *)controller |
| { |
| return _positionInformation.bounds; |
| } |
| |
| static NSString *previewIdentifierForElementAction(_WKElementAction *action) |
| { |
| switch (action.type) { |
| case _WKElementActionTypeOpen: |
| return WKPreviewActionItemIdentifierOpen; |
| case _WKElementActionTypeCopy: |
| return WKPreviewActionItemIdentifierCopy; |
| #if !defined(TARGET_OS_IOS) || TARGET_OS_IOS |
| case _WKElementActionTypeAddToReadingList: |
| return WKPreviewActionItemIdentifierAddToReadingList; |
| #endif |
| case _WKElementActionTypeShare: |
| return WKPreviewActionItemIdentifierShare; |
| default: |
| return nil; |
| } |
| ASSERT_NOT_REACHED(); |
| return nil; |
| } |
| |
| - (UIViewController *)_presentedViewControllerForPreviewItemController:(UIPreviewItemController *)controller |
| { |
| id <WKUIDelegatePrivate> uiDelegate = static_cast<id <WKUIDelegatePrivate>>([_webView UIDelegate]); |
| |
| [_webView _didShowForcePressPreview]; |
| |
| NSURL *targetURL = controller.previewData[UIPreviewDataLink]; |
| URL coreTargetURL = targetURL; |
| bool isValidURLForImagePreview = !coreTargetURL.isEmpty() && (WebCore::protocolIsInHTTPFamily(coreTargetURL) || WebCore::protocolIs(coreTargetURL, "data")); |
| |
| if ([_previewItemController type] == UIPreviewItemTypeLink) { |
| _highlightLongPressCanClick = NO; |
| _page->startInteractionWithElementAtPosition(_positionInformation.request.point); |
| |
| // Treat animated images like a link preview |
| if (isValidURLForImagePreview && _positionInformation.isAnimatedImage) { |
| RetainPtr<_WKActivatedElementInfo> animatedImageElementInfo = adoptNS([[_WKActivatedElementInfo alloc] _initWithType:_WKActivatedElementTypeImage URL:targetURL location:_positionInformation.request.point title:_positionInformation.title ID:_positionInformation.idAttribute rect:_positionInformation.bounds image:_positionInformation.image.get()]); |
| |
| if ([uiDelegate respondsToSelector:@selector(_webView:previewViewControllerForAnimatedImageAtURL:defaultActions:elementInfo:imageSize:)]) { |
| RetainPtr<NSArray> actions = [_actionSheetAssistant defaultActionsForImageSheet:animatedImageElementInfo.get()]; |
| return [uiDelegate _webView:_webView previewViewControllerForAnimatedImageAtURL:targetURL defaultActions:actions.get() elementInfo:animatedImageElementInfo.get() imageSize:_positionInformation.image->size()]; |
| } |
| } |
| |
| RetainPtr<_WKActivatedElementInfo> elementInfo = adoptNS([[_WKActivatedElementInfo alloc] _initWithType:_WKActivatedElementTypeLink URL:targetURL location:_positionInformation.request.point title:_positionInformation.title ID:_positionInformation.idAttribute rect:_positionInformation.bounds image:_positionInformation.image.get()]); |
| |
| auto actions = [_actionSheetAssistant defaultActionsForLinkSheet:elementInfo.get()]; |
| if ([uiDelegate respondsToSelector:@selector(webView:previewingViewControllerForElement:defaultActions:)]) { |
| auto previewActions = adoptNS([[NSMutableArray alloc] init]); |
| for (_WKElementAction *elementAction in actions.get()) { |
| WKPreviewAction *previewAction = [WKPreviewAction actionWithIdentifier:previewIdentifierForElementAction(elementAction) title:[elementAction title] style:UIPreviewActionStyleDefault handler:^(UIPreviewAction *, UIViewController *) { |
| [elementAction runActionWithElementInfo:elementInfo.get()]; |
| }]; |
| [previewActions addObject:previewAction]; |
| } |
| auto previewElementInfo = adoptNS([[WKPreviewElementInfo alloc] _initWithLinkURL:targetURL]); |
| if (UIViewController *controller = [uiDelegate webView:_webView previewingViewControllerForElement:previewElementInfo.get() defaultActions:previewActions.get()]) |
| return controller; |
| } |
| |
| if ([uiDelegate respondsToSelector:@selector(_webView:previewViewControllerForURL:defaultActions:elementInfo:)]) |
| return [uiDelegate _webView:_webView previewViewControllerForURL:targetURL defaultActions:actions.get() elementInfo:elementInfo.get()]; |
| |
| if ([uiDelegate respondsToSelector:@selector(_webView:previewViewControllerForURL:)]) |
| return [uiDelegate _webView:_webView previewViewControllerForURL:targetURL]; |
| return nil; |
| } |
| |
| if ([_previewItemController type] == UIPreviewItemTypeImage) { |
| if (!isValidURLForImagePreview) |
| return nil; |
| |
| RetainPtr<NSURL> alternateURL = targetURL; |
| RetainPtr<NSDictionary> imageInfo; |
| RetainPtr<CGImageRef> cgImage = _positionInformation.image->makeCGImageCopy(); |
| RetainPtr<UIImage> uiImage = adoptNS([[UIImage alloc] initWithCGImage:cgImage.get()]); |
| if ([uiDelegate respondsToSelector:@selector(_webView:alternateURLFromImage:userInfo:)]) { |
| NSDictionary *userInfo; |
| alternateURL = [uiDelegate _webView:_webView alternateURLFromImage:uiImage.get() userInfo:&userInfo]; |
| imageInfo = userInfo; |
| } |
| |
| RetainPtr<_WKActivatedElementInfo> elementInfo = adoptNS([[_WKActivatedElementInfo alloc] _initWithType:_WKActivatedElementTypeImage URL:alternateURL.get() location:_positionInformation.request.point title:_positionInformation.title ID:_positionInformation.idAttribute rect:_positionInformation.bounds image:_positionInformation.image.get() userInfo:imageInfo.get()]); |
| _page->startInteractionWithElementAtPosition(_positionInformation.request.point); |
| |
| if ([uiDelegate respondsToSelector:@selector(_webView:willPreviewImageWithURL:)]) |
| [uiDelegate _webView:_webView willPreviewImageWithURL:targetURL]; |
| |
| auto defaultActions = [_actionSheetAssistant defaultActionsForImageSheet:elementInfo.get()]; |
| if (imageInfo && [uiDelegate respondsToSelector:@selector(_webView:previewViewControllerForImage:alternateURL:defaultActions:elementInfo:)]) { |
| UIViewController *previewViewController = [uiDelegate _webView:_webView previewViewControllerForImage:uiImage.get() alternateURL:alternateURL.get() defaultActions:defaultActions.get() elementInfo:elementInfo.get()]; |
| if (previewViewController) |
| return previewViewController; |
| } |
| |
| return [[[WKImagePreviewViewController alloc] initWithCGImage:cgImage defaultActions:defaultActions elementInfo:elementInfo] autorelease]; |
| } |
| |
| return nil; |
| } |
| |
| - (void)_previewItemController:(UIPreviewItemController *)controller commitPreview:(UIViewController *)viewController |
| { |
| id <WKUIDelegatePrivate> uiDelegate = static_cast<id <WKUIDelegatePrivate>>([_webView UIDelegate]); |
| if ([_previewItemController type] == UIPreviewItemTypeImage) { |
| if ([uiDelegate respondsToSelector:@selector(_webView:commitPreviewedImageWithURL:)]) { |
| const URL& imageURL = _positionInformation.imageURL; |
| if (imageURL.isEmpty() || !(imageURL.protocolIsInHTTPFamily() || imageURL.protocolIs("data"))) |
| return; |
| [uiDelegate _webView:_webView commitPreviewedImageWithURL:(NSURL *)imageURL]; |
| return; |
| } |
| return; |
| } |
| |
| if ([uiDelegate respondsToSelector:@selector(webView:commitPreviewingViewController:)]) { |
| [uiDelegate webView:_webView commitPreviewingViewController:viewController]; |
| return; |
| } |
| |
| if ([uiDelegate respondsToSelector:@selector(_webView:commitPreviewedViewController:)]) { |
| [uiDelegate _webView:_webView commitPreviewedViewController:viewController]; |
| return; |
| } |
| |
| } |
| |
| - (void)_interactionStartedFromPreviewItemController:(UIPreviewItemController *)controller |
| { |
| [self _removeDefaultGestureRecognizers]; |
| |
| [self _cancelInteraction]; |
| } |
| |
| - (void)_interactionStoppedFromPreviewItemController:(UIPreviewItemController *)controller |
| { |
| [self _addDefaultGestureRecognizers]; |
| |
| if (![_actionSheetAssistant isShowingSheet]) |
| _page->stopInteraction(); |
| } |
| |
| - (void)_previewItemController:(UIPreviewItemController *)controller didDismissPreview:(UIViewController *)viewController committing:(BOOL)committing |
| { |
| id<WKUIDelegatePrivate> uiDelegate = static_cast<id <WKUIDelegatePrivate>>([_webView UIDelegate]); |
| if ([uiDelegate respondsToSelector:@selector(_webView:didDismissPreviewViewController:committing:)]) |
| [uiDelegate _webView:_webView didDismissPreviewViewController:viewController committing:committing]; |
| else if ([uiDelegate respondsToSelector:@selector(_webView:didDismissPreviewViewController:)]) |
| [uiDelegate _webView:_webView didDismissPreviewViewController:viewController]; |
| |
| [_webView _didDismissForcePressPreview]; |
| } |
| |
| - (UIImage *)_presentationSnapshotForPreviewItemController:(UIPreviewItemController *)controller |
| { |
| if (!_positionInformation.linkIndicator.contentImage) |
| return nullptr; |
| return [[[UIImage alloc] initWithCGImage:_positionInformation.linkIndicator.contentImage->nativeImage().get()] autorelease]; |
| } |
| |
| - (NSArray *)_presentationRectsForPreviewItemController:(UIPreviewItemController *)controller |
| { |
| RetainPtr<NSMutableArray> rectArray = adoptNS([[NSMutableArray alloc] init]); |
| |
| if (_positionInformation.linkIndicator.contentImage) { |
| FloatPoint origin = _positionInformation.linkIndicator.textBoundingRectInRootViewCoordinates.location(); |
| for (FloatRect& rect : _positionInformation.linkIndicator.textRectsInBoundingRectCoordinates) { |
| CGRect cgRect = rect; |
| cgRect.origin.x += origin.x(); |
| cgRect.origin.y += origin.y(); |
| [rectArray addObject:[NSValue valueWithCGRect:cgRect]]; |
| } |
| } else { |
| const float marginInPx = 4 * _page->deviceScaleFactor(); |
| CGRect cgRect = CGRectInset(_positionInformation.bounds, -marginInPx, -marginInPx); |
| [rectArray addObject:[NSValue valueWithCGRect:cgRect]]; |
| } |
| |
| return rectArray.autorelease(); |
| } |
| |
| - (void)_previewItemControllerDidCancelPreview:(UIPreviewItemController *)controller |
| { |
| _highlightLongPressCanClick = NO; |
| |
| [_webView _didDismissForcePressPreview]; |
| } |
| |
| @end |
| |
| #endif // HAVE(LINK_PREVIEW) |
| |
| // UITextRange, UITextPosition and UITextSelectionRect implementations for WK2 |
| |
| @implementation WKTextRange (UITextInputAdditions) |
| |
| - (BOOL)_isCaret |
| { |
| return self.empty; |
| } |
| |
| - (BOOL)_isRanged |
| { |
| return !self.empty; |
| } |
| |
| @end |
| |
| @implementation WKTextRange |
| |
| +(WKTextRange *)textRangeWithState:(BOOL)isNone isRange:(BOOL)isRange isEditable:(BOOL)isEditable startRect:(CGRect)startRect endRect:(CGRect)endRect selectionRects:(NSArray *)selectionRects selectedTextLength:(NSUInteger)selectedTextLength |
| { |
| WKTextRange *range = [[WKTextRange alloc] init]; |
| range.isNone = isNone; |
| range.isRange = isRange; |
| range.isEditable = isEditable; |
| range.startRect = startRect; |
| range.endRect = endRect; |
| range.selectedTextLength = selectedTextLength; |
| range.selectionRects = selectionRects; |
| return [range autorelease]; |
| } |
| |
| - (void)dealloc |
| { |
| [self.selectionRects release]; |
| [super dealloc]; |
| } |
| |
| - (NSString *)description |
| { |
| return [NSString stringWithFormat:@"%@(%p) - start:%@, end:%@", [self class], self, NSStringFromCGRect(self.startRect), NSStringFromCGRect(self.endRect)]; |
| } |
| |
| - (WKTextPosition *)start |
| { |
| WKTextPosition *pos = [WKTextPosition textPositionWithRect:self.startRect]; |
| return pos; |
| } |
| |
| - (UITextPosition *)end |
| { |
| WKTextPosition *pos = [WKTextPosition textPositionWithRect:self.endRect]; |
| return pos; |
| } |
| |
| - (BOOL)isEmpty |
| { |
| return !self.isRange; |
| } |
| |
| // FIXME: Overriding isEqual: without overriding hash will cause trouble if this ever goes into an NSSet or is the key in an NSDictionary, |
| // since two equal items could have different hashes. |
| - (BOOL)isEqual:(id)other |
| { |
| if (![other isKindOfClass:[WKTextRange class]]) |
| return NO; |
| |
| WKTextRange *otherRange = (WKTextRange *)other; |
| |
| if (self == other) |
| return YES; |
| |
| // FIXME: Probably incorrect for equality to ignore so much of the object state. |
| // It ignores isNone, isEditable, selectedTextLength, and selectionRects. |
| |
| if (self.isRange) { |
| if (!otherRange.isRange) |
| return NO; |
| return CGRectEqualToRect(self.startRect, otherRange.startRect) && CGRectEqualToRect(self.endRect, otherRange.endRect); |
| } else { |
| if (otherRange.isRange) |
| return NO; |
| // FIXME: Do we need to check isNone here? |
| return CGRectEqualToRect(self.startRect, otherRange.startRect); |
| } |
| } |
| |
| @end |
| |
| @implementation WKTextPosition |
| |
| @synthesize positionRect = _positionRect; |
| |
| + (WKTextPosition *)textPositionWithRect:(CGRect)positionRect |
| { |
| WKTextPosition *pos =[[WKTextPosition alloc] init]; |
| pos.positionRect = positionRect; |
| return [pos autorelease]; |
| } |
| |
| // FIXME: Overriding isEqual: without overriding hash will cause trouble if this ever goes into a NSSet or is the key in an NSDictionary, |
| // since two equal items could have different hashes. |
| - (BOOL)isEqual:(id)other |
| { |
| if (![other isKindOfClass:[WKTextPosition class]]) |
| return NO; |
| |
| return CGRectEqualToRect(self.positionRect, ((WKTextPosition *)other).positionRect); |
| } |
| |
| - (NSString *)description |
| { |
| return [NSString stringWithFormat:@"<WKTextPosition: %p, {%@}>", self, NSStringFromCGRect(self.positionRect)]; |
| } |
| |
| @end |
| |
| @implementation WKTextSelectionRect |
| |
| - (id)initWithWebRect:(WebSelectionRect *)wRect |
| { |
| self = [super init]; |
| if (self) |
| self.webRect = wRect; |
| |
| return self; |
| } |
| |
| - (void)dealloc |
| { |
| self.webRect = nil; |
| [super dealloc]; |
| } |
| |
| // FIXME: we are using this implementation for now |
| // that uses WebSelectionRect, but we want to provide our own |
| // based on WebCore::SelectionRect. |
| |
| + (NSArray *)textSelectionRectsWithWebRects:(NSArray *)webRects |
| { |
| NSMutableArray *array = [NSMutableArray arrayWithCapacity:webRects.count]; |
| for (WebSelectionRect *webRect in webRects) { |
| RetainPtr<WKTextSelectionRect> rect = adoptNS([[WKTextSelectionRect alloc] initWithWebRect:webRect]); |
| [array addObject:rect.get()]; |
| } |
| return array; |
| } |
| |
| - (CGRect)rect |
| { |
| return _webRect.rect; |
| } |
| |
| - (UITextWritingDirection)writingDirection |
| { |
| return (UITextWritingDirection)_webRect.writingDirection; |
| } |
| |
| - (UITextRange *)range |
| { |
| return nil; |
| } |
| |
| - (BOOL)containsStart |
| { |
| return _webRect.containsStart; |
| } |
| |
| - (BOOL)containsEnd |
| { |
| return _webRect.containsEnd; |
| } |
| |
| - (BOOL)isVertical |
| { |
| return !_webRect.isHorizontal; |
| } |
| |
| @end |
| |
| @implementation WKAutocorrectionRects |
| |
| + (WKAutocorrectionRects *)autocorrectionRectsWithRects:(CGRect)firstRect lastRect:(CGRect)lastRect |
| { |
| WKAutocorrectionRects *rects =[[WKAutocorrectionRects alloc] init]; |
| rects.firstRect = firstRect; |
| rects.lastRect = lastRect; |
| return [rects autorelease]; |
| } |
| |
| @end |
| |
| @implementation WKAutocorrectionContext |
| |
| + (WKAutocorrectionContext *)autocorrectionContextWithData:(NSString *)beforeText markedText:(NSString *)markedText selectedText:(NSString *)selectedText afterText:(NSString *)afterText selectedRangeInMarkedText:(NSRange)range |
| { |
| WKAutocorrectionContext *context = [[WKAutocorrectionContext alloc] init]; |
| |
| if ([beforeText length]) |
| context.contextBeforeSelection = beforeText; |
| if ([selectedText length]) |
| context.selectedText = selectedText; |
| if ([markedText length]) |
| context.markedText = markedText; |
| if ([afterText length]) |
| context.contextAfterSelection = afterText; |
| context.rangeInMarkedText = range; |
| return [context autorelease]; |
| } |
| |
| @end |
| |
| #endif // PLATFORM(IOS) |