| /* |
| * Copyright (C) 2012-2014 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 "EditingRange.h" |
| #import "NativeWebKeyboardEvent.h" |
| #import "NativeWebTouchEvent.h" |
| #import "SmartMagnificationController.h" |
| #import "WKActionSheetAssistant.h" |
| #import "WKFormInputControl.h" |
| #import "WKFormSelectControl.h" |
| #import "WKInspectorNodeSearchGestureRecognizer.h" |
| #import "WKWebViewConfiguration.h" |
| #import "WKWebViewInternal.h" |
| #import "WKWebViewPrivate.h" |
| #import "WebEvent.h" |
| #import "WebIOSEventFactory.h" |
| #import "WebPageMessages.h" |
| #import "WebProcessProxy.h" |
| #import "_WKFormDelegate.h" |
| #import "_WKFormInputSession.h" |
| #import <CoreText/CTFontDescriptor.h> |
| #import <DataDetectorsUI/DDDetectionController.h> |
| #import <TextInput/TI_NSStringExtras.h> |
| #import <UIKit/UIApplication_Private.h> |
| #import <UIKit/UIFont_Private.h> |
| #import <UIKit/UIGestureRecognizer_Private.h> |
| #import <UIKit/UIKeyboardImpl.h> |
| #import <UIKit/UIKeyboardIntl.h> |
| #import <UIKit/UILongPressGestureRecognizer_Private.h> |
| #import <UIKit/UITapGestureRecognizer_Private.h> |
| #import <UIKit/UITextInteractionAssistant_Private.h> |
| #import <UIKit/UIWebDocumentView.h> // FIXME: should not include this header. |
| #import <UIKit/_UIHighlightView.h> |
| #import <UIKit/_UIWebHighlightLongPressGestureRecognizer.h> |
| #import <WebCore/Color.h> |
| #import <WebCore/FloatQuad.h> |
| #import <WebCore/SoftLinking.h> |
| #import <WebCore/WebEvent.h> |
| #import <WebKit/WebSelectionRect.h> // FIXME: WK2 should not include WebKit headers! |
| #import <wtf/RetainPtr.h> |
| |
| SOFT_LINK_PRIVATE_FRAMEWORK(DataDetectorsUI) |
| SOFT_LINK_CLASS(DataDetectorsUI, DDDetectionController) |
| |
| using namespace WebCore; |
| using 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 UITextInteractionAssistant (StagingToRemove) |
| - (void)scheduleReplacementsForText:(NSString *)text; |
| - (void)showTextServiceFor:(NSString *)selectedTerm fromRect:(CGRect)presentationRect; |
| - (void)scheduleChineseTransliterationForText:(NSString *)text; |
| @end |
| |
| @interface UIWKSelectionAssistant (StagingToRemove) |
| - (void)showTextServiceFor:(NSString *)selectedTerm fromRect:(CGRect)presentationRect; |
| @end |
| |
| @interface UIKeyboardImpl (StagingToRemove) |
| - (void)didHandleWebKeyEvent; |
| @end |
| |
| @interface UIView (UIViewInternalHack) |
| + (BOOL)_addCompletion:(void(^)(BOOL))completion; |
| @end |
| |
| @interface WKFormInputSession : NSObject <_WKFormInputSession> |
| |
| - (instancetype)initWithContentView:(WKContentView *)view userObject:(NSObject <NSSecureCoding> *)userObject; |
| - (void)invalidate; |
| |
| @end |
| |
| @implementation WKFormInputSession { |
| WKContentView *_contentView; |
| RetainPtr<NSObject <NSSecureCoding>> _userObject; |
| } |
| |
| - (instancetype)initWithContentView:(WKContentView *)view userObject:(NSObject <NSSecureCoding> *)userObject |
| { |
| if (!(self = [super init])) |
| return nil; |
| |
| _contentView = view; |
| _userObject = userObject; |
| |
| return self; |
| } |
| |
| - (NSObject <NSSecureCoding> *)userObject |
| { |
| return _userObject.get(); |
| } |
| |
| - (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]; |
| } |
| |
| - (void)invalidate |
| { |
| _contentView = nil; |
| } |
| |
| @end |
| |
| @interface WKContentView (WKInteractionPrivate) |
| - (void)accessibilitySpeakSelectionSetContent:(NSString *)string; |
| @end |
| |
| @implementation WKContentView (WKInteraction) |
| |
| static UIWebSelectionMode toUIWebSelectionMode(WKSelectionGranularity granularity) |
| { |
| switch (granularity) { |
| case WKSelectionGranularityDynamic: |
| return UIWebSelectionModeWeb; |
| case WKSelectionGranularityCharacter: |
| return UIWebSelectionModeTextOnly; |
| } |
| |
| ASSERT_NOT_REACHED(); |
| return UIWebSelectionModeWeb; |
| } |
| |
| - (void)setupInteraction |
| { |
| if (!_interactionViewsContainerView) { |
| _interactionViewsContainerView = adoptNS([[UIView alloc] init]); |
| [_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()]; |
| |
| _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()]; |
| |
| _doubleTapGestureRecognizer = adoptNS([[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(_doubleTapRecognized:)]); |
| [_doubleTapGestureRecognizer setNumberOfTapsRequired:2]; |
| [_doubleTapGestureRecognizer setDelegate:self]; |
| [self addGestureRecognizer:_doubleTapGestureRecognizer.get()]; |
| [_singleTapGestureRecognizer requireOtherGestureToFail:_doubleTapGestureRecognizer.get()]; |
| |
| _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()]; |
| |
| _longPressGestureRecognizer = adoptNS([[UILongPressGestureRecognizer alloc] initWithTarget:self action:@selector(_longPressRecognized:)]); |
| [_longPressGestureRecognizer setDelay:tapAndHoldDelay]; |
| [_longPressGestureRecognizer setDelegate:self]; |
| [self addGestureRecognizer:_longPressGestureRecognizer.get()]; |
| |
| [[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 useSelectionAssistantWithMode:toUIWebSelectionMode([[_webView configuration] selectionGranularity])]; |
| |
| _actionSheetAssistant = adoptNS([[WKActionSheetAssistant alloc] initWithView:self]); |
| _smartMagnificationController = std::make_unique<SmartMagnificationController>(self); |
| } |
| |
| - (void)cleanupInteraction |
| { |
| _webSelectionAssistant = nil; |
| _textSelectionAssistant = nil; |
| _actionSheetAssistant = nil; |
| _smartMagnificationController = nil; |
| _didAccessoryTabInitiateFocus = NO; |
| [_formInputSession invalidate]; |
| _formInputSession = nil; |
| [_highlightView removeFromSuperview]; |
| |
| if (_interactionViewsContainerView) { |
| [self.layer removeObserver:self forKeyPath:@"transform"]; |
| [_interactionViewsContainerView removeFromSuperview]; |
| _interactionViewsContainerView = nil; |
| } |
| |
| [_touchEventGestureRecognizer setDelegate:nil]; |
| [self removeGestureRecognizer:_touchEventGestureRecognizer.get()]; |
| |
| [_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()]; |
| |
| [_twoFingerDoubleTapGestureRecognizer setDelegate:nil]; |
| [self removeGestureRecognizer:_twoFingerDoubleTapGestureRecognizer.get()]; |
| |
| _inspectorNodeSearchEnabled = NO; |
| if (_inspectorNodeSearchGestureRecognizer) { |
| [_inspectorNodeSearchGestureRecognizer setDelegate:nil]; |
| [self removeGestureRecognizer:_inspectorNodeSearchGestureRecognizer.get()]; |
| _inspectorNodeSearchGestureRecognizer = nil; |
| } |
| |
| if (_fileUploadPanel) { |
| [_fileUploadPanel setDelegate:nil]; |
| [_fileUploadPanel dismiss]; |
| _fileUploadPanel = nil; |
| } |
| } |
| |
| - (void)_removeDefaultGestureRecognizers |
| { |
| [self removeGestureRecognizer:_touchEventGestureRecognizer.get()]; |
| [self removeGestureRecognizer:_singleTapGestureRecognizer.get()]; |
| [self removeGestureRecognizer:_highlightLongPressGestureRecognizer.get()]; |
| [self removeGestureRecognizer:_longPressGestureRecognizer.get()]; |
| [self removeGestureRecognizer:_doubleTapGestureRecognizer.get()]; |
| [self removeGestureRecognizer:_twoFingerDoubleTapGestureRecognizer.get()]; |
| } |
| |
| - (void)_addDefaultGestureRecognizers |
| { |
| [self addGestureRecognizer:_touchEventGestureRecognizer.get()]; |
| [self addGestureRecognizer:_singleTapGestureRecognizer.get()]; |
| [self addGestureRecognizer:_highlightLongPressGestureRecognizer.get()]; |
| [self addGestureRecognizer:_longPressGestureRecognizer.get()]; |
| [self addGestureRecognizer:_doubleTapGestureRecognizer.get()]; |
| [self addGestureRecognizer:_twoFingerDoubleTapGestureRecognizer.get()]; |
| } |
| |
| - (UIView*)unscaledView |
| { |
| return _interactionViewsContainerView.get(); |
| } |
| |
| - (CGFloat)inverseScale |
| { |
| return 1 / [[self layer] transform].m11; |
| } |
| |
| - (UIScrollView *)_scroller |
| { |
| return [_webView scrollView]; |
| } |
| |
| - (CGRect)unobscuredContentRect |
| { |
| return _page->unobscuredContentRect(); |
| } |
| |
| - (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]; |
| [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)isEditable |
| { |
| return _isEditable; |
| } |
| |
| - (BOOL)canBecomeFirstResponder |
| { |
| // We might want to return something else |
| // if we decide to enable/disable interaction programmatically. |
| return YES; |
| } |
| |
| - (BOOL)resignFirstResponder |
| { |
| // FIXME: Maybe we should call resignFirstResponder on the superclass |
| // and do nothing if the return value is NO. |
| // We need to complete the editing operation before we blur the element. |
| [_inputPeripheral endEditing]; |
| _page->blurAssistedNode(); |
| [self _cancelInteraction]; |
| [_webSelectionAssistant resignedFirstResponder]; |
| |
| return [super resignFirstResponder]; |
| } |
| |
| - (void)_webTouchEventsRecognized:(UIWebTouchEventsGestureRecognizer *)gestureRecognizer |
| { |
| const _UIWebTouchEvent* lastTouchEvent = gestureRecognizer.lastTouchEvent; |
| NativeWebTouchEvent nativeWebTouchEvent(lastTouchEvent); |
| |
| _lastInteractionLocation = lastTouchEvent->locationInDocumentCoordinates; |
| nativeWebTouchEvent.setCanPreventNativeGestures(!_canSendTouchEventsAsynchronously || [gestureRecognizer isDefaultPrevented]); |
| |
| if (_canSendTouchEventsAsynchronously) |
| _page->handleTouchEventAsynchronously(nativeWebTouchEvent); |
| else |
| _page->handleTouchEventSynchronously(nativeWebTouchEvent); |
| |
| if (nativeWebTouchEvent.allTouchPointsAreReleased()) |
| _canSendTouchEventsAsynchronously = NO; |
| } |
| |
| - (void)_inspectorNodeSearchRecognized:(UIGestureRecognizer *)gestureRecognizer |
| { |
| ASSERT(_inspectorNodeSearchEnabled); |
| |
| 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]); |
| } |
| |
| - (void)_webTouchEvent:(const WebKit::NativeWebTouchEvent&)touchEvent preventsNativeGestures:(BOOL)preventsNativeGesture |
| { |
| if (preventsNativeGesture) { |
| _canSendTouchEventsAsynchronously = YES; |
| [_touchEventGestureRecognizer setDefaultPrevented:YES]; |
| } |
| } |
| |
| 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) |
| { |
| return [NSValue valueWithCGSize:CGSizeMake(borderRadius.width() + minimumTapHighlightRadius, borderRadius.height() + minimumTapHighlightRadius)]; |
| } |
| |
| - (void)_updateTapHighlight |
| { |
| if (![_highlightView superview]) |
| return; |
| |
| { |
| RetainPtr<UIColor> highlightUIKitColor = adoptNS([[UIColor alloc] initWithCGColor:cachedCGColor(_tapHighlightInformation.color, WebCore::ColorSpaceDeviceRGB)]); |
| [_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>(enclosingRectExtendedToDevicePixels(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, 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)]; |
| [borderRadii addObject:nsSizeForTapHighlightBorderRadius(_tapHighlightInformation.topRightRadius)]; |
| [borderRadii addObject:nsSizeForTapHighlightBorderRadius(_tapHighlightInformation.bottomLeftRadius)]; |
| [borderRadii addObject:nsSizeForTapHighlightBorderRadius(_tapHighlightInformation.bottomRightRadius)]; |
| [_highlightView setCornerRadii:borderRadii.get()]; |
| } |
| |
| - (void)_showTapHighlight |
| { |
| if (!highlightedQuadsAreSmallerThanRect(_tapHighlightInformation.quads, _page->unobscuredContentRect())) |
| return; |
| |
| if (!_highlightView) { |
| _highlightView = adoptNS([[_UIHighlightView alloc] initWithFrame:CGRectZero]); |
| [_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 || _latestTapHighlightID != requestID) |
| return; |
| |
| _isTapHighlightIDValid = NO; |
| |
| _tapHighlightInformation.color = color; |
| _tapHighlightInformation.quads = highlightedQuads; |
| _tapHighlightInformation.topLeftRadius = topLeftRadius; |
| _tapHighlightInformation.topRightRadius = topRightRadius; |
| _tapHighlightInformation.bottomLeftRadius = bottomLeftRadius; |
| _tapHighlightInformation.bottomRightRadius = bottomRightRadius; |
| |
| if (_potentialTapInProgress) { |
| _hasTapHighlightForPotentialTap = YES; |
| return; |
| } |
| |
| [self _showTapHighlight]; |
| } |
| |
| - (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 !UICurrentUserInterfaceIdiomIsPad(); |
| case InputType::Date: |
| case InputType::Month: |
| case InputType::DateTimeLocal: |
| case InputType::Time: |
| return !UICurrentUserInterfaceIdiomIsPad(); |
| default: |
| return !_assistedNodeInformation.isReadOnly; |
| } |
| return NO; |
| } |
| |
| - (BOOL)_requiresKeyboardResetOnReload |
| { |
| return YES; |
| } |
| |
| - (void)_displayFormNodeInputView |
| { |
| [self _zoomToFocusRect:_assistedNodeInformation.elementRect |
| selectionRect: _didAccessoryTabInitiateFocus ? IntRect() : _assistedNodeInformation.selectionRect |
| fontSize:_assistedNodeInformation.nodeFontSize |
| minimumScale:_assistedNodeInformation.minimumScaleFactor |
| maximumScale:_assistedNodeInformation.maximumScaleFactor |
| allowScaling:(_assistedNodeInformation.allowsUserScaling && !UICurrentUserInterfaceIdiomIsPad()) |
| forceScroll:[self requiresAccessoryView]]; |
| _didAccessoryTabInitiateFocus = NO; |
| [self _updateAccessory]; |
| } |
| |
| - (UIView *)inputView |
| { |
| if (_assistedNodeInformation.elementType == InputType::None) |
| return nil; |
| |
| if (!_inputPeripheral) |
| _inputPeripheral = adoptNS(_assistedNodeInformation.elementType == InputType::Select ? [WKFormSelectControl createPeripheralWithView:self] : [WKFormInputControl createPeripheralWithView:self]); |
| else |
| [self _displayFormNodeInputView]; |
| |
| return [_inputPeripheral assistantView]; |
| } |
| |
| - (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. |
| if ((preventingGestureRecognizer == _textSelectionAssistant.get().loupeGesture || [_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 (isSamePair(gestureRecognizer, otherGestureRecognizer, _highlightLongPressGestureRecognizer.get(), _longPressGestureRecognizer.get())) |
| return YES; |
| |
| if (isSamePair(gestureRecognizer, otherGestureRecognizer, _highlightLongPressGestureRecognizer.get(), _webSelectionAssistant.get().selectionLongPressRecognizer)) |
| return YES; |
| |
| if (isSamePair(gestureRecognizer, otherGestureRecognizer, _singleTapGestureRecognizer.get(), _textSelectionAssistant.get().singleTapGesture)) |
| return YES; |
| |
| return NO; |
| } |
| |
| - (void)_showImageSheet |
| { |
| [_actionSheetAssistant showImageSheet]; |
| } |
| |
| - (void)_showLinkSheet |
| { |
| [_actionSheetAssistant showLinkSheet]; |
| } |
| |
| - (void)_showDataDetectorsSheet |
| { |
| [_actionSheetAssistant showDataDetectorsSheet]; |
| } |
| |
| - (SEL)_actionForLongPress |
| { |
| if (_positionInformation.clickableElementName == "IMG") |
| return @selector(_showImageSheet); |
| else if (_positionInformation.clickableElementName == "A") { |
| NSURL *targetURL = [NSURL URLWithString:_positionInformation.url]; |
| if ([[getDDDetectionControllerClass() tapAndHoldSchemes] containsObject:[targetURL scheme]]) |
| return @selector(_showDataDetectorsSheet); |
| return @selector(_showLinkSheet); |
| } |
| return nil; |
| } |
| |
| - (void)ensurePositionInformationIsUpToDate:(CGPoint)point |
| { |
| if (!_hasValidPositionInformation || roundedIntPoint(point) != _positionInformation.point) { |
| _page->getPositionInformation(roundedIntPoint(point), _positionInformation); |
| _hasValidPositionInformation = YES; |
| } |
| } |
| |
| - (void)_updatePositionInformation |
| { |
| _hasValidPositionInformation = NO; |
| _page->requestPositionInformation(_positionInformation.point); |
| } |
| |
| - (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer |
| { |
| CGPoint point = [gestureRecognizer locationInView:self]; |
| |
| if (gestureRecognizer == _highlightLongPressGestureRecognizer |
| || gestureRecognizer == _doubleTapGestureRecognizer |
| || gestureRecognizer == _twoFingerDoubleTapGestureRecognizer |
| || gestureRecognizer == _singleTapGestureRecognizer) { |
| |
| if (_textSelectionAssistant) { |
| // Request information about the position with sync message. |
| // If the assisted node is the same, prevent the gesture. |
| _page->getPositionInformation(roundedIntPoint(point), _positionInformation); |
| _hasValidPositionInformation = YES; |
| if (_positionInformation.nodeAtPositionIsAssistedNode) |
| return NO; |
| } |
| } |
| |
| if (gestureRecognizer == _highlightLongPressGestureRecognizer) { |
| if (_textSelectionAssistant) { |
| // 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.clickableElementName.isNull()) |
| return NO; |
| return [self _actionForLongPress] != nil; |
| } else { |
| // We still have no idea about what is at the location. |
| // Send and async message to find out. |
| _hasValidPositionInformation = NO; |
| _page->requestPositionInformation(roundedIntPoint(point)); |
| 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. |
| [self ensurePositionInformationIsUpToDate:point]; |
| |
| if (_textSelectionAssistant) { |
| // 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; |
| [UIView animateWithDuration:0.1 |
| animations:^{ |
| [_highlightView layer].opacity = 0; |
| } |
| completion:^(BOOL finished){ |
| if (finished) |
| [_highlightView removeFromSuperview]; |
| }]; |
| } |
| |
| - (BOOL)hasSelectablePositionAtPoint:(CGPoint)point |
| { |
| if (_inspectorNodeSearchEnabled) |
| return NO; |
| |
| [self ensurePositionInformationIsUpToDate:point]; |
| return _positionInformation.isSelectable; |
| } |
| |
| - (BOOL)pointIsNearMarkedText:(CGPoint)point |
| { |
| [self ensurePositionInformationIsUpToDate:point]; |
| return _positionInformation.isNearMarkedText; |
| } |
| |
| - (BOOL)pointIsInAssistedNode:(CGPoint)point |
| { |
| [self ensurePositionInformationIsUpToDate:point]; |
| return _positionInformation.nodeAtPositionIsAssistedNode; |
| } |
| |
| - (NSArray *)webSelectionRects |
| { |
| unsigned size = _page->editorState().selectionRects.size(); |
| if (!size) |
| return nil; |
| |
| NSMutableArray *webRects = [NSMutableArray arrayWithCapacity:size]; |
| for (unsigned i = 0; i < size; i++) { |
| const WebCore::SelectionRect& coreRect = _page->editorState().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; |
| } |
| |
| - (void)_highlightLongPressRecognized:(UILongPressGestureRecognizer *)gestureRecognizer |
| { |
| ASSERT(gestureRecognizer == _highlightLongPressGestureRecognizer); |
| |
| _lastInteractionLocation = gestureRecognizer.startPoint; |
| |
| switch ([gestureRecognizer state]) { |
| case UIGestureRecognizerStateBegan: |
| cancelPotentialTapIfNecessary(self); |
| _page->tapHighlightAtPosition([gestureRecognizer startPoint], ++_latestTapHighlightID); |
| _isTapHighlightIDValid = YES; |
| break; |
| case UIGestureRecognizerStateEnded: |
| if (!_positionInformation.clickableElementName.isEmpty()) { |
| [self _attemptClickAtLocation:[gestureRecognizer startPoint]]; |
| [self _finishInteraction]; |
| } else |
| [self _cancelInteraction]; |
| break; |
| case UIGestureRecognizerStateCancelled: |
| [self _cancelInteraction]; |
| break; |
| default: |
| break; |
| } |
| } |
| |
| - (void)_longPressRecognized:(UILongPressGestureRecognizer *)gestureRecognizer |
| { |
| ASSERT(gestureRecognizer == _longPressGestureRecognizer); |
| |
| _lastInteractionLocation = gestureRecognizer.startPoint; |
| |
| if ([gestureRecognizer state] == UIGestureRecognizerStateBegan) { |
| SEL action = [self _actionForLongPress]; |
| if (action) { |
| [self performSelector:action]; |
| [self _cancelLongPressGestureRecognizer]; |
| [UIApp _cancelAllTouches]; |
| } |
| } |
| } |
| |
| - (void)_singleTapRecognized:(UITapGestureRecognizer *)gestureRecognizer |
| { |
| ASSERT(gestureRecognizer == _singleTapGestureRecognizer); |
| ASSERT(!_potentialTapInProgress); |
| |
| _page->potentialTapAtPosition(gestureRecognizer.location, ++_latestTapHighlightID); |
| _potentialTapInProgress = YES; |
| _isTapHighlightIDValid = YES; |
| } |
| |
| static void cancelPotentialTapIfNecessary(WKContentView* contentView) |
| { |
| if (contentView->_potentialTapInProgress) { |
| contentView->_potentialTapInProgress = NO; |
| [contentView _cancelInteraction]; |
| contentView->_page->cancelPotentialTap(); |
| } |
| } |
| |
| - (void)_singleTapDidReset:(UITapGestureRecognizer *)gestureRecognizer |
| { |
| ASSERT(gestureRecognizer == _singleTapGestureRecognizer); |
| cancelPotentialTapIfNecessary(self); |
| } |
| |
| - (void)_commitPotentialTapFailed |
| { |
| [self _cancelInteraction]; |
| } |
| |
| - (void)_singleTapCommited:(UITapGestureRecognizer *)gestureRecognizer |
| { |
| ASSERT(gestureRecognizer == _singleTapGestureRecognizer); |
| |
| if (![self isFirstResponder]) |
| [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; |
| |
| _potentialTapInProgress = NO; |
| |
| if (_hasTapHighlightForPotentialTap) { |
| [self _showTapHighlight]; |
| _hasTapHighlightForPotentialTap = NO; |
| } |
| |
| _page->commitPotentialTap(); |
| |
| [self _finishInteraction]; |
| } |
| |
| - (void)_doubleTapRecognized:(UITapGestureRecognizer *)gestureRecognizer |
| { |
| _lastInteractionLocation = gestureRecognizer.location; |
| |
| _smartMagnificationController->handleSmartMagnificationGesture(gestureRecognizer.location); |
| } |
| |
| - (void)_twoFingerDoubleTapRecognized:(UITapGestureRecognizer *)gestureRecognizer |
| { |
| _lastInteractionLocation = gestureRecognizer.location; |
| |
| _smartMagnificationController->handleResetMagnificationGesture(gestureRecognizer.location); |
| } |
| |
| - (void)_attemptClickAtLocation:(CGPoint)location |
| { |
| if (![self isFirstResponder]) |
| [self becomeFirstResponder]; |
| |
| _page->process().send(Messages::WebPage::HandleTap(IntPoint(location)), _page->pageID()); |
| } |
| |
| - (void)useSelectionAssistantWithMode:(UIWebSelectionMode)selectionMode |
| { |
| if (selectionMode == UIWebSelectionModeWeb) { |
| if (_textSelectionAssistant) { |
| [_textSelectionAssistant deactivateSelection]; |
| _textSelectionAssistant = nil; |
| } |
| if (!_webSelectionAssistant) |
| _webSelectionAssistant = adoptNS([[UIWKSelectionAssistant alloc] initWithView:self]); |
| } else if (selectionMode == UIWebSelectionModeTextOnly) { |
| if (_webSelectionAssistant) |
| _webSelectionAssistant = nil; |
| |
| if (!_textSelectionAssistant) |
| _textSelectionAssistant = adoptNS([[UIWKTextInteractionAssistant alloc] initWithView:self]); |
| else { |
| // Reset the gesture recognizers in case editibility has changed. |
| [_textSelectionAssistant setGestureRecognizers]; |
| } |
| |
| [_textSelectionAssistant activateSelection]; |
| } |
| } |
| |
| - (void)clearSelection |
| { |
| _page->clearSelection(); |
| } |
| |
| - (void)_positionInformationDidChange:(const InteractionInformationAtPosition&)info |
| { |
| _positionInformation = info; |
| _hasValidPositionInformation = YES; |
| if (_actionSheetAssistant) |
| [_actionSheetAssistant updateSheetPosition]; |
| } |
| |
| - (void)_willStartScrollingOrZooming |
| { |
| [_webSelectionAssistant willStartScrollingOrZoomingPage]; |
| [_textSelectionAssistant willStartScrollingOverflow]; |
| } |
| |
| - (void)scrollViewWillStartPanOrPinchGesture |
| { |
| _canSendTouchEventsAsynchronously = YES; |
| } |
| |
| - (void)_didEndScrollingOrZooming |
| { |
| [_webSelectionAssistant didEndScrollingOrZoomingPage]; |
| [_textSelectionAssistant didEndScrollingOverflow]; |
| } |
| |
| - (BOOL)requiresAccessoryView |
| { |
| 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: |
| return YES; |
| case InputType::ContentEditable: |
| case InputType::TextArea: |
| return YES; |
| case InputType::Select: |
| case InputType::Date: |
| case InputType::DateTime: |
| case InputType::DateTimeLocal: |
| case InputType::Month: |
| case InputType::Week: |
| case InputType::Time: |
| return !UICurrentUserInterfaceIdiomIsPad(); |
| } |
| } |
| |
| - (UIView *)inputAccessoryView |
| { |
| if (![self requiresAccessoryView]) |
| return nil; |
| |
| if (!_formAccessoryView) { |
| _formAccessoryView = adoptNS([[UIWebFormAccessory alloc] init]); |
| [_formAccessoryView setDelegate:self]; |
| } |
| |
| return _formAccessoryView.get(); |
| } |
| |
| - (NSArray *)supportedPasteboardTypesForCurrentSelection |
| { |
| if (_page->editorState().selectionIsNone) |
| return nil; |
| |
| static NSMutableArray *richTypes = nil; |
| static NSMutableArray *plainTextTypes = nil; |
| if (!plainTextTypes) { |
| plainTextTypes = [[NSMutableArray alloc] init]; |
| // FIXME: should add [plainTextTypes addObject:(id)kUTTypeURL]; |
| // when we figure out how to share this type between WebCore and WebKit2 |
| [plainTextTypes addObjectsFromArray:UIPasteboardTypeListString]; |
| |
| richTypes = [[NSMutableArray alloc] init]; |
| // FIXME: should add [richTypes addObject:(PasteboardTypes::WebArchivePboardType)]; |
| // when we figure out how to share this type between WebCore and WebKit2 |
| [richTypes addObjectsFromArray:UIPasteboardTypeListImage]; |
| [richTypes addObjectsFromArray:plainTextTypes]; |
| } |
| |
| return (_page->editorState().isContentRichlyEditable) ? richTypes : plainTextTypes; |
| } |
| |
| - (void)_addShortcut:(id)sender |
| { |
| if (_textSelectionAssistant && [_textSelectionAssistant respondsToSelector:@selector(showTextServiceFor:fromRect:)]) |
| [_textSelectionAssistant showTextServiceFor:[self selectedText] fromRect:_page->editorState().selectionRects[0].rect()]; |
| else if (_webSelectionAssistant && [_webSelectionAssistant respondsToSelector:@selector(showTextServiceFor:fromRect:)]) |
| [_webSelectionAssistant showTextServiceFor:[self selectedText] fromRect:_page->editorState().selectionRects[0].rect()]; |
| } |
| |
| - (NSString *)selectedText |
| { |
| return (NSString *)_page->editorState().wordAtSelection; |
| } |
| |
| - (BOOL)isReplaceAllowed |
| { |
| return _page->editorState().isReplaceAllowed; |
| } |
| |
| - (void)replaceText:(NSString *)text withText:(NSString *)word |
| { |
| _page->replaceSelectedText(text, word); |
| } |
| |
| - (void)selectWordBackward |
| { |
| _page->selectWordBackward(); |
| } |
| |
| - (void)_promptForReplace:(id)sender |
| { |
| if (_page->editorState().wordAtSelection.isEmpty()) |
| return; |
| |
| if ([_textSelectionAssistant respondsToSelector:@selector(scheduleReplacementsForText:)]) |
| [_textSelectionAssistant scheduleReplacementsForText:_page->editorState().wordAtSelection]; |
| } |
| |
| - (void)_transliterateChinese:(id)sender |
| { |
| if ([_textSelectionAssistant respondsToSelector:@selector(scheduleChineseTransliterationForText:)]) |
| [_textSelectionAssistant scheduleChineseTransliterationForText:_page->editorState().wordAtSelection]; |
| } |
| |
| - (void)_reanalyze:(id)sender |
| { |
| [_textSelectionAssistant scheduleReanalysis]; |
| } |
| |
| - (void)replace:(id)sender |
| { |
| [[UIKeyboardImpl sharedInstance] replaceText:sender]; |
| } |
| |
| - (NSDictionary *)textStylingAtPosition:(UITextPosition *)position inDirection:(UITextStorageDirection)direction |
| { |
| if (!position || !_page->editorState().isContentRichlyEditable) |
| return nil; |
| |
| NSMutableDictionary* result = [NSMutableDictionary dictionary]; |
| |
| CTFontSymbolicTraits symbolicTraits = 0; |
| if (_page->editorState().typingAttributes & AttributeBold) |
| symbolicTraits |= kCTFontBoldTrait; |
| if (_page->editorState().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 (_page->editorState().typingAttributes & AttributeUnderline) |
| [result setObject:[NSNumber numberWithInt:NSUnderlineStyleSingle] forKey:NSUnderlineStyleAttributeName]; |
| |
| return result; |
| } |
| |
| - (BOOL)canPerformAction:(SEL)action withSender:(id)sender |
| { |
| BOOL hasWebSelection = _webSelectionAssistant && !CGRectIsEmpty(_webSelectionAssistant.get().selectionFrame); |
| |
| 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().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; |
| |
| return YES; |
| } |
| |
| 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().isReplaceAllowed || ![[UIKeyboardImpl activeInstance] autocorrectSpellingEnabled]) |
| return NO; |
| if ([[self selectedText] _containsCJScriptsOnly]) |
| return NO; |
| return YES; |
| } |
| |
| if (action == @selector(_transliterateChinese:)) { |
| if (!_page->editorState().selectionIsRange || !_page->editorState().isReplaceAllowed || ![[UIKeyboardImpl activeInstance] autocorrectSpellingEnabled]) |
| return NO; |
| return UIKeyboardEnabledInputModesAllowChineseTransliterationForText([self selectedText]); |
| } |
| |
| if (action == @selector(_reanalyze:)) { |
| if (!_page->editorState().selectionIsRange || !_page->editorState().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]; |
| } |
| |
| - (void)_resetShowingTextStyle:(NSNotification *)notification |
| { |
| _showingTextStyleOptions = NO; |
| [_textSelectionAssistant hideTextStyleOptions]; |
| } |
| |
| - (void)_performAction:(SheetAction)action |
| { |
| _page->performActionOnElement((uint32_t)action); |
| } |
| |
| - (void)copy:(id)sender |
| { |
| _page->executeEditCommand(ASCIILiteral("copy")); |
| } |
| |
| - (void)cut:(id)sender |
| { |
| _page->executeEditCommand(ASCIILiteral("cut")); |
| } |
| |
| - (void)paste:(id)sender |
| { |
| _page->executeEditCommand(ASCIILiteral("paste")); |
| } |
| |
| - (void)select:(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)selectAll:(id)sender |
| { |
| [_textSelectionAssistant selectAll:sender]; |
| _page->executeEditCommand(ASCIILiteral("selectAll")); |
| } |
| |
| - (void)toggleBoldface:(id)sender |
| { |
| if (!_page->editorState().isContentRichlyEditable) |
| return; |
| |
| [self executeEditCommandWithCallback:@"toggleBold"]; |
| } |
| |
| - (void)toggleItalics:(id)sender |
| { |
| if (!_page->editorState().isContentRichlyEditable) |
| return; |
| |
| [self executeEditCommandWithCallback:@"toggleItalic"]; |
| } |
| |
| - (void)toggleUnderline:(id)sender |
| { |
| if (!_page->editorState().isContentRichlyEditable) |
| return; |
| |
| [self executeEditCommandWithCallback:@"toggleUnderline"]; |
| } |
| |
| - (void)_showTextStyleOptions:(id)sender |
| { |
| _showingTextStyleOptions = YES; |
| [_textSelectionAssistant showTextStyleOptions]; |
| } |
| |
| - (void)_showDictionary:(NSString *)text |
| { |
| CGRect presentationRect = _page->editorState().selectionRects[0].rect(); |
| if (_textSelectionAssistant) |
| [_textSelectionAssistant showDictionaryFor:text fromRect:presentationRect]; |
| else |
| [_webSelectionAssistant showDictionaryFor:text fromRect:presentationRect]; |
| } |
| |
| - (void)_define:(id)sender |
| { |
| _page->getSelectionOrContentsAsString([self](const String& string, CallbackBase::Error error) { |
| if (error != CallbackBase::Error::None) |
| return; |
| if (!string) |
| return; |
| |
| [self _showDictionary:string]; |
| }); |
| } |
| |
| - (void)accessibilityRetrieveSpeakSelectionContent |
| { |
| _page->getSelectionOrContentsAsString([self](const String& string, CallbackBase::Error error) { |
| if (error != CallbackBase::Error::None) |
| return; |
| if ([self respondsToSelector:@selector(accessibilitySpeakSelectionSetContent:)]) |
| [self accessibilitySpeakSelectionSetContent:string]; |
| }); |
| } |
| |
| // 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 & IsBlockSelection) |
| uiFlags |= UIWKIsBlockSelection; |
| if (flags & PhraseBoundaryChanged) |
| uiFlags |= UIWKPhraseBoundaryChanged; |
| |
| return static_cast<UIWKSelectionFlags>(uiFlags); |
| } |
| |
| static inline SelectionHandlePosition toSelectionHandlePosition(UIWKHandlePosition position) |
| { |
| switch (position) { |
| case UIWKHandleTop: |
| return SelectionHandlePosition::Top; |
| case UIWKHandleRight: |
| return SelectionHandlePosition::Right; |
| case UIWKHandleBottom: |
| return SelectionHandlePosition::Bottom; |
| case UIWKHandleLeft: |
| return SelectionHandlePosition::Left; |
| } |
| } |
| |
| static void selectionChangedWithGesture(WKContentView *view, const WebCore::IntPoint& point, uint32_t gestureType, uint32_t gestureState, uint32_t flags, CallbackBase::Error error) |
| { |
| if (error != 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, CallbackBase::Error error) |
| { |
| if (error != CallbackBase::Error::None) { |
| ASSERT_NOT_REACHED(); |
| return; |
| } |
| if ([view webSelectionAssistant]) |
| [(UIWKSelectionAssistant *)[view webSelectionAssistant] selectionChangedWithTouchAt:(CGPoint)point withSelectionTouch:toUIWKSelectionTouch((SelectionTouch)touch)]; |
| else |
| [(UIWKTextInteractionAssistant *)[view interactionAssistant] selectionChangedWithTouchAt:(CGPoint)point withSelectionTouch:toUIWKSelectionTouch((SelectionTouch)touch)]; |
| } |
| |
| - (void)_didUpdateBlockSelectionWithTouch:(SelectionTouch)touch withFlags:(SelectionFlags)flags growThreshold:(CGFloat)growThreshold shrinkThreshold:(CGFloat)shrinkThreshold |
| { |
| [_webSelectionAssistant blockSelectionChangedWithTouch:toUIWKSelectionTouch(touch) withFlags:toUIWKSelectionFlags(flags) growThreshold:growThreshold shrinkThreshold:shrinkThreshold]; |
| if (touch != SelectionTouch::Started && touch != SelectionTouch::Moved) |
| _usingGestureForSelection = NO; |
| } |
| |
| - (void)changeSelectionWithGestureAt:(CGPoint)point withGesture:(UIWKGestureType)gestureType withState:(UIGestureRecognizerState)state |
| { |
| _usingGestureForSelection = YES; |
| _page->selectWithGesture(WebCore::IntPoint(point), CharacterGranularity, static_cast<uint32_t>(toGestureType(gestureType)), static_cast<uint32_t>(toGestureRecognizerState(state)), [self, state](const WebCore::IntPoint& point, uint32_t gestureType, uint32_t gestureState, uint32_t flags, CallbackBase::Error error) { |
| selectionChangedWithGesture(self, point, gestureType, gestureState, flags, error); |
| if (state == UIGestureRecognizerStateEnded || state == UIGestureRecognizerStateCancelled) |
| _usingGestureForSelection = NO; |
| }); |
| } |
| |
| - (void)changeSelectionWithTouchAt:(CGPoint)point withSelectionTouch:(UIWKSelectionTouch)touch baseIsStart:(BOOL)baseIsStart |
| { |
| _usingGestureForSelection = YES; |
| _page->updateSelectionWithTouches(WebCore::IntPoint(point), static_cast<uint32_t>(toSelectionTouch(touch)), baseIsStart, [self, touch](const WebCore::IntPoint& point, uint32_t touch, CallbackBase::Error error) { |
| selectionChangedWithTouch(self, point, touch, 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, gestureState](const WebCore::IntPoint& point, uint32_t gestureType, uint32_t gestureState, uint32_t flags, CallbackBase::Error error) { |
| selectionChangedWithGesture(self, point, gestureType, gestureState, flags, error); |
| if (gestureState == UIGestureRecognizerStateEnded || gestureState == UIGestureRecognizerStateCancelled) |
| _usingGestureForSelection = NO; |
| }); |
| } |
| |
| - (void)changeBlockSelectionWithTouchAt:(CGPoint)point withSelectionTouch:(UIWKSelectionTouch)touch forHandle:(UIWKHandlePosition)handle |
| { |
| _usingGestureForSelection = YES; |
| _page->updateBlockSelectionWithTouch(WebCore::IntPoint(point), static_cast<uint32_t>(toSelectionTouch(touch)), static_cast<uint32_t>(toSelectionHandlePosition(handle))); |
| } |
| |
| - (void)moveByOffset:(NSInteger)offset |
| { |
| if (!offset) |
| return; |
| |
| [self beginSelectionChange]; |
| _page->moveSelectionByOffset(offset, [self](CallbackBase::Error) { |
| [self 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; |
| } |
| |
| _autocorrectionData.autocorrectionHandler = [completionHandler copy]; |
| _page->requestAutocorrectionData(input, [self](const Vector<FloatRect>& rects, const String& fontName, double fontSize, uint64_t traits, CallbackBase::Error) { |
| CGRect firstRect = CGRectZero; |
| CGRect lastRect = CGRectZero; |
| if (rects.size()) { |
| firstRect = rects[0]; |
| lastRect = rects[rects.size() - 1]; |
| } |
| |
| _autocorrectionData.fontName = fontName; |
| _autocorrectionData.fontSize = fontSize; |
| _autocorrectionData.fontTraits = traits; |
| _autocorrectionData.textFirstRect = firstRect; |
| _autocorrectionData.textLastRect = lastRect; |
| |
| _autocorrectionData.autocorrectionHandler(rects.size() ? [WKAutocorrectionRects autocorrectionRectsWithRects:firstRect lastRect:lastRect] : nil); |
| [_autocorrectionData.autocorrectionHandler release]; |
| _autocorrectionData.autocorrectionHandler = nil; |
| }); |
| } |
| |
| - (UTF32Char)_characterBeforeCaretSelection |
| { |
| return _page->editorState().characterBeforeSelection; |
| } |
| |
| - (UTF32Char)_characterInRelationToCaretSelection:(int)amount |
| { |
| switch (amount) { |
| case 0: |
| return _page->editorState().characterAfterSelection; |
| case -1: |
| return _page->editorState().characterBeforeSelection; |
| case -2: |
| return _page->editorState().twoCharacterBeforeSelection; |
| default: |
| return 0; |
| } |
| } |
| |
| - (BOOL)_selectionAtDocumentStart |
| { |
| return !_page->editorState().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, 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]; |
| _page->applyAutocorrection(correction, input, [self](const String& string, CallbackBase::Error error) { |
| _autocorrectionData.autocorrectionHandler(!string.isNull() ? [WKAutocorrectionRects autocorrectionRectsWithRects:_autocorrectionData.textFirstRect lastRect:_autocorrectionData.textLastRect] : nil); |
| [_autocorrectionData.autocorrectionHandler release]; |
| _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]; |
| _page->requestAutocorrectionContext([self](const String& beforeText, const String& markedText, const String& selectedText, const String& afterText, uint64_t location, uint64_t length, CallbackBase::Error) { |
| _autocorrectionData.autocorrectionContextHandler([WKAutocorrectionContext autocorrectionContextWithData:beforeText markedText:markedText selectedText:selectedText afterText:afterText selectedRangeInMarkedText:NSMakeRange(location, length)]); |
| }); |
| } |
| } |
| |
| // UIWebFormAccessoryDelegate |
| - (void)accessoryDone |
| { |
| [self resignFirstResponder]; |
| } |
| |
| - (NSArray *)keyCommands |
| { |
| return @[[UIKeyCommand keyCommandWithInput:@"\t" modifierFlags:0 action:@selector(_nextAccessoryTab:)], |
| [UIKeyCommand keyCommandWithInput:@"\t" modifierFlags:UIKeyModifierShift action:@selector(_prevAccessoryTab:)]]; |
| } |
| |
| - (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. |
| _page->focusNextAssistedNode(isNext); |
| } |
| |
| - (void)accessoryAutoFill |
| { |
| id <_WKFormDelegate> formDelegate = [_webView _formDelegate]; |
| if ([formDelegate respondsToSelector:@selector(_webView:accessoryViewCustomButtonTappedInFormInputSession:)]) |
| [formDelegate _webView:_webView accessoryViewCustomButtonTappedInFormInputSession:_formInputSession.get()]; |
| } |
| |
| - (void)accessoryClear |
| { |
| _page->setAssistedNodeValue(String()); |
| } |
| |
| - (void)_updateAccessory |
| { |
| [_formAccessoryView setNextEnabled:_assistedNodeInformation.hasNextNode]; |
| [_formAccessoryView setPreviousEnabled:_assistedNodeInformation.hasPreviousNode]; |
| |
| if (UICurrentUserInterfaceIdiomIsPad()) |
| [_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 |
| |
| - (void)beginSelectionChange |
| { |
| [self.inputDelegate selectionWillChange:self]; |
| } |
| |
| - (void)endSelectionChange |
| { |
| [self.inputDelegate selectionDidChange:self]; |
| } |
| |
| - (NSString *)textInRange:(UITextRange *)range |
| { |
| return nil; |
| } |
| |
| - (void)replaceRange:(UITextRange *)range withText:(NSString *)text |
| { |
| } |
| |
| - (UITextRange *)selectedTextRange |
| { |
| FloatRect startRect = _page->editorState().caretRectAtStart; |
| FloatRect endRect = _page->editorState().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:_page->editorState().selectedTextLength]; |
| } |
| |
| - (CGRect)caretRectForPosition:(UITextPosition *)position |
| { |
| return ((WKTextPosition *)position).positionRect; |
| } |
| |
| - (NSArray *)selectionRectsForRange:(UITextRange *)range |
| { |
| return [WKTextSelectionRect textSelectionRectsWithWebRects:((WKTextRange *)range).selectionRects]; |
| } |
| |
| - (void)setSelectedTextRange:(UITextRange *)range |
| { |
| } |
| |
| - (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 |
| { |
| return YES; |
| } |
| |
| // end of UITextInput protocol implementation |
| |
| static UITextAutocapitalizationType toUITextAutocapitalize(WebAutocapitalizeType webkitType) |
| { |
| switch (webkitType) { |
| case WebAutocapitalizeTypeDefault: |
| return UITextAutocapitalizationTypeSentences; |
| case WebAutocapitalizeTypeNone: |
| return UITextAutocapitalizationTypeNone; |
| case WebAutocapitalizeTypeWords: |
| return UITextAutocapitalizationTypeWords; |
| case WebAutocapitalizeTypeSentences: |
| return UITextAutocapitalizationTypeSentences; |
| case WebAutocapitalizeTypeAllCharacters: |
| return UITextAutocapitalizationTypeAllCharacters; |
| } |
| |
| return UITextAutocapitalizationTypeSentences; |
| } |
| |
| // UITextInputPrivate protocol |
| // Direct access to the (private) UITextInputTraits object. |
| - (UITextInputTraits *)textInputTraits |
| { |
| if (!_traits) |
| _traits = adoptNS([[UITextInputTraits alloc] init]); |
| |
| [_traits setSecureTextEntry:_assistedNodeInformation.elementType == InputType::Password]; |
| [_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]; |
| } |
| |
| return _traits.get(); |
| } |
| |
| - (UITextInteractionAssistant *)interactionAssistant |
| { |
| return _textSelectionAssistant.get(); |
| } |
| |
| - (UIWebSelectionAssistant *)webSelectionAssistant |
| { |
| return _webSelectionAssistant.get(); |
| } |
| |
| |
| // 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)handleKeyWebEvent:(WebIOSEvent *)theEvent |
| { |
| _page->handleKeyboardEvent(NativeWebKeyboardEvent(theEvent)); |
| } |
| |
| - (void)_didHandleKeyEvent:(WebIOSEvent *)event |
| { |
| if (event.type == WebEventKeyDown) { |
| // FIXME: This is only for staging purposes. |
| if ([[UIKeyboardImpl sharedInstance] respondsToSelector:@selector(didHandleWebKeyEvent)]) |
| [[UIKeyboardImpl sharedInstance] didHandleWebKeyEvent]; |
| } |
| } |
| |
| - (BOOL)_interpretKeyEvent:(WebIOSEvent *)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 kWebLeftArrowKey = 0x00AC; |
| static const unsigned kWebUpArrowKey = 0x00AD; |
| static const unsigned kWebRightArrowKey = 0x00AE; |
| static const unsigned kWebDownArrowKey = 0x00AF; |
| static const unsigned kWebDeleteForwardKey = 0xF728; |
| |
| if (!_page->editorState().isContentEditable && event.isTabKey) |
| return NO; |
| |
| BOOL shift = event.modifierFlags & WebEventFlagMaskShift; |
| |
| switch (event.characterSet) { |
| case WebEventCharacterSetSymbol: { |
| String command; |
| NSString *characters = [event charactersIgnoringModifiers]; |
| if ([characters length] == 0) |
| break; |
| switch ([characters characterAtIndex:0]) { |
| case kWebLeftArrowKey: |
| command = shift ? ASCIILiteral("moveLeftAndModifySelection") : ASCIILiteral("moveLeft"); |
| break; |
| |
| case kWebUpArrowKey: |
| command = shift ? ASCIILiteral("moveUpAndModifySelection") : ASCIILiteral("moveUp"); |
| break; |
| |
| case kWebRightArrowKey: |
| command = shift ? ASCIILiteral("moveRightAndModifySelection") : ASCIILiteral("moveRight"); |
| break; |
| |
| case kWebDownArrowKey: |
| command = shift ? ASCIILiteral("moveDownAndModifySelection") : ASCIILiteral("moveDown"); |
| break; |
| } |
| if (!command.isEmpty()) { |
| _page->executeEditCommand(command); |
| return YES; |
| } |
| break; |
| } |
| case WebEventCharacterSetASCII: |
| case WebEventCharacterSetUnicode: { |
| NSString *characters = [event characters]; |
| if ([characters length] == 0) |
| break; |
| switch ([characters characterAtIndex:0]) { |
| case kWebBackspaceKey: |
| case kWebDeleteKey: |
| [[UIKeyboardImpl sharedInstance] deleteFromInput]; |
| return YES; |
| |
| case kWebEnterKey: |
| case kWebReturnKey: |
| if (isCharEvent) { |
| // Map \r from HW keyboard to \n to match the behavior of the soft keyboard. |
| [[UIKeyboardImpl sharedInstance] addInputString:@"\n" withFlags:0]; |
| return YES; |
| } |
| return NO; |
| |
| case kWebDeleteForwardKey: |
| _page->executeEditCommand(ASCIILiteral("deleteForward")); |
| return YES; |
| |
| default: { |
| if (isCharEvent) { |
| [[UIKeyboardImpl sharedInstance] addInputString:event.characters withFlags:event.keyboardFlags]; |
| return YES; |
| } |
| return NO; |
| } |
| } |
| break; |
| } |
| default: |
| return NO; |
| } |
| |
| return NO; |
| } |
| |
| - (void)executeEditCommandWithCallback:(NSString *)commandName |
| { |
| [self beginSelectionChange]; |
| _page->executeEditCommand(commandName, [self](CallbackBase::Error) { |
| [self endSelectionChange]; |
| }); |
| } |
| |
| - (UITextInputArrowKeyHistory *)_moveUp:(BOOL)extending withHistory:(UITextInputArrowKeyHistory *)history |
| { |
| [self executeEditCommandWithCallback:@"moveUp"]; |
| return nil; |
| } |
| |
| - (UITextInputArrowKeyHistory *)_moveDown:(BOOL)extending withHistory:(UITextInputArrowKeyHistory *)history |
| { |
| [self executeEditCommandWithCallback:@"moveDown"]; |
| return nil; |
| } |
| |
| - (UITextInputArrowKeyHistory *)_moveLeft:(BOOL) extending withHistory:(UITextInputArrowKeyHistory *)history |
| { |
| [self executeEditCommandWithCallback:@"moveLeft"]; |
| return nil; |
| } |
| |
| - (UITextInputArrowKeyHistory *)_moveRight:(BOOL) extending withHistory:(UITextInputArrowKeyHistory *)history |
| { |
| [self executeEditCommandWithCallback:@"moveRight"]; |
| return nil; |
| } |
| |
| - (UITextInputArrowKeyHistory *)_moveToStartOfWord:(BOOL)extending withHistory:(UITextInputArrowKeyHistory *)history |
| { |
| [self executeEditCommandWithCallback:@"moveWordBackward"]; |
| return nil; |
| } |
| |
| - (UITextInputArrowKeyHistory *)_moveToStartOfParagraph:(BOOL) extending withHistory:(UITextInputArrowKeyHistory *)history |
| { |
| [self executeEditCommandWithCallback:@"moveToStartOfParagraph"]; |
| return nil; |
| } |
| |
| - (UITextInputArrowKeyHistory *)_moveToStartOfLine:(BOOL) extending withHistory:(UITextInputArrowKeyHistory *)history |
| { |
| [self executeEditCommandWithCallback:@"moveToStartOfLine"]; |
| return nil; |
| } |
| |
| - (UITextInputArrowKeyHistory *)_moveToStartOfDocument:(BOOL) extending withHistory:(UITextInputArrowKeyHistory *)history |
| { |
| [self executeEditCommandWithCallback:@"moveToBeginningOfDocument"]; |
| return nil; |
| } |
| |
| - (UITextInputArrowKeyHistory *)_moveToEndOfWord:(BOOL)extending withHistory:(UITextInputArrowKeyHistory *)history |
| { |
| [self executeEditCommandWithCallback:@"moveWordForward"]; |
| return nil; |
| } |
| |
| - (UITextInputArrowKeyHistory *)_moveToEndOfParagraph:(BOOL) extending withHistory:(UITextInputArrowKeyHistory *)history |
| { |
| [self executeEditCommandWithCallback:@"moveToEndOfParagraph"]; |
| return nil; |
| } |
| |
| - (UITextInputArrowKeyHistory *)_moveToEndOfLine:(BOOL) extending withHistory:(UITextInputArrowKeyHistory *)history |
| { |
| [self executeEditCommandWithCallback:@"moveToEndOfLine"]; |
| return nil; |
| } |
| |
| - (UITextInputArrowKeyHistory *)_moveToEndOfDocument:(BOOL) extending withHistory:(UITextInputArrowKeyHistory *)history |
| { |
| [self executeEditCommandWithCallback:@"moveToEndOfDocument"]; |
| return nil; |
| } |
| |
| // Sets a buffer to make room for autocorrection views |
| - (void)setBottomBufferHeight:(CGFloat)bottomBuffer |
| { |
| } |
| |
| - (UIView *)automaticallySelectedOverlay |
| { |
| return self; |
| } |
| |
| - (UITextGranularity)selectionGranularity |
| { |
| return UITextGranularityCharacter; |
| } |
| |
| - (void)insertDictationResult:(NSArray *)dictationResult withCorrectionIdentifier:(id)correctionIdentifier |
| { |
| } |
| |
| // 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().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 useSelectionAssistantWithMode:UIWebSelectionModeTextOnly]; |
| } |
| |
| - (void)_stopAssistingKeyboard |
| { |
| [self useSelectionAssistantWithMode:toUIWebSelectionMode([[_webView configuration] selectionGranularity])]; |
| } |
| |
| - (const AssistedNodeInformation&)assistedNodeInformation |
| { |
| return _assistedNodeInformation; |
| } |
| |
| - (Vector<OptionItem>&)assistedNodeSelectOptions |
| { |
| return _assistedNodeInformation.selectOptions; |
| } |
| |
| - (UIWebFormAccessory *)formAccessoryView |
| { |
| return _formAccessoryView.get(); |
| } |
| |
| - (void)_startAssistingNode:(const AssistedNodeInformation&)information userIsInteracting:(BOOL)userIsInteracting blurPreviousNode:(BOOL)blurPreviousNode userObject:(NSObject <NSSecureCoding> *)userObject |
| { |
| if (!userIsInteracting && !_textSelectionAssistant) |
| return; |
| |
| if (blurPreviousNode) |
| [self _stopAssistingNode]; |
| |
| // 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; |
| |
| _isEditable = YES; |
| _assistedNodeInformation = information; |
| _inputPeripheral = nil; |
| _traits = nil; |
| if (![self isFirstResponder]) |
| [self becomeFirstResponder]; |
| |
| switch (information.elementType) { |
| case InputType::Select: |
| case InputType::DateTimeLocal: |
| case InputType::Time: |
| case InputType::Month: |
| case InputType::Date: |
| break; |
| default: |
| [self _startAssistingKeyboard]; |
| break; |
| } |
| |
| if (information.insideFixedPosition) |
| [_webView _updateVisibleContentRects]; |
| |
| [self reloadInputViews]; |
| [self _displayFormNodeInputView]; |
| |
| // _inputPeripheral has been initialized in inputView called by reloadInputViews. |
| [_inputPeripheral beginEditing]; |
| |
| id <_WKFormDelegate> formDelegate = [_webView _formDelegate]; |
| if ([formDelegate respondsToSelector:@selector(_webView:didStartInputSession:)]) { |
| _formInputSession = adoptNS([[WKFormInputSession alloc] initWithContentView:self userObject:userObject]); |
| [formDelegate _webView:_webView didStartInputSession:_formInputSession.get()]; |
| } |
| } |
| |
| - (void)_stopAssistingNode |
| { |
| [_formInputSession invalidate]; |
| _formInputSession = nil; |
| _isEditable = NO; |
| _assistedNodeInformation.elementType = InputType::None; |
| [_inputPeripheral endEditing]; |
| _inputPeripheral = nil; |
| |
| [self _stopAssistingKeyboard]; |
| [self reloadInputViews]; |
| [self _updateAccessory]; |
| // The name is misleading, but this actually clears the selection views and removes any selection. |
| [_webSelectionAssistant resignedFirstResponder]; |
| } |
| |
| - (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]; |
| } |
| |
| - (void)selectWordForReplacement |
| { |
| _page->extendSelection(WordGranularity); |
| } |
| |
| - (void)_updateChangedSelection |
| { |
| if (!_selectionNeedsUpdate) |
| return; |
| |
| // 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; |
| } |
| } |
| |
| - (void)_showPlaybackTargetPicker:(BOOL)hasVideo fromRect:(const IntRect&)elementRect |
| { |
| if (!_airPlayRoutePicker) |
| _airPlayRoutePicker = adoptNS([[WKAirPlayRoutePicker alloc] initWithView:self]); |
| [_airPlayRoutePicker show:hasVideo fromRect:elementRect]; |
| } |
| |
| - (void)_showRunOpenPanel:(WebOpenPanelParameters*)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 - Implementation of UIWebTouchEventsGestureRecognizerDelegate. |
| |
| - (BOOL)shouldIgnoreWebTouch |
| { |
| return NO; |
| } |
| |
| - (BOOL)isAnyTouchOverActiveArea:(NSSet *)touches |
| { |
| return YES; |
| } |
| |
| @end |
| |
| // 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 copy]; |
| if ([selectedText length]) |
| context.selectedText = [selectedText copy]; |
| if ([markedText length]) |
| context.markedText = [markedText copy]; |
| if ([afterText length]) |
| context.contextAfterSelection = [afterText copy]; |
| context.rangeInMarkedText = range; |
| return [context autorelease]; |
| } |
| |
| - (void)dealloc |
| { |
| [self.contextBeforeSelection release]; |
| [self.markedText release]; |
| [self.selectedText release]; |
| [self.contextAfterSelection release]; |
| |
| [super dealloc]; |
| } |
| |
| @end |
| |
| #endif // PLATFORM(IOS) |