Title: [186073] trunk/Source/WebKit2
Revision
186073
Author
[email protected]
Date
2015-06-29 10:43:46 -0700 (Mon, 29 Jun 2015)

Log Message

[iOS] Hardware Keyboard: All combinations of arrow keys and space key do not scroll the view.
https://bugs.webkit.org/show_bug.cgi?id=146290
rdar://problem/18466015

We don't normally get called by the keyboard to handle the event if we are not
interacting with editable content. In order to receive all the hardware keyboard events
we need to implement _handleKeyUIEvent which is called for every key event
when the view is first responder. This gives us the opportunity to send each keystroke
to the WebProcess to let any _javascript_ handler intercept it and then perform the default
action for the key combination.
Unfortunately this mechanism does not provide key repeat, which is implemented in the
keyboard layer. In order to have it at least for the arrow keys, we use the keyCommands
mechanism only for those.

Reviewed by Darin Adler.

* Platform/spi/ios/UIKitSPI.h:
* UIProcess/API/Cocoa/WKWebView.mm:
(-[WKWebView _scrollByOffset:]):
* UIProcess/API/Cocoa/WKWebViewInternal.h:
* UIProcess/ios/WKContentViewInteraction.mm:
(-[WKContentView canPerformAction:withSender:]):
(-[WKContentView keyCommands]):
(-[WKContentView _arrowKey:]):
(-[WKContentView _handleKeyUIEvent:]):
(-[WKContentView handleKeyEvent:]):
(-[WKContentView handleKeyWebEvent:]):
(-[WKContentView _interpretKeyEvent:isCharEvent:]):

Modified Paths

Diff

Modified: trunk/Source/WebKit2/ChangeLog (186072 => 186073)


--- trunk/Source/WebKit2/ChangeLog	2015-06-29 17:33:59 UTC (rev 186072)
+++ trunk/Source/WebKit2/ChangeLog	2015-06-29 17:43:46 UTC (rev 186073)
@@ -1,3 +1,34 @@
+2015-06-29  Enrica Casucci  <[email protected]>
+
+        [iOS] Hardware Keyboard: All combinations of arrow keys and space key do not scroll the view.
+        https://bugs.webkit.org/show_bug.cgi?id=146290
+        rdar://problem/18466015
+
+        We don't normally get called by the keyboard to handle the event if we are not
+        interacting with editable content. In order to receive all the hardware keyboard events
+        we need to implement _handleKeyUIEvent which is called for every key event
+        when the view is first responder. This gives us the opportunity to send each keystroke
+        to the WebProcess to let any _javascript_ handler intercept it and then perform the default
+        action for the key combination.
+        Unfortunately this mechanism does not provide key repeat, which is implemented in the
+        keyboard layer. In order to have it at least for the arrow keys, we use the keyCommands
+        mechanism only for those.
+
+        Reviewed by Darin Adler.
+
+        * Platform/spi/ios/UIKitSPI.h:
+        * UIProcess/API/Cocoa/WKWebView.mm:
+        (-[WKWebView _scrollByOffset:]):
+        * UIProcess/API/Cocoa/WKWebViewInternal.h:
+        * UIProcess/ios/WKContentViewInteraction.mm:
+        (-[WKContentView canPerformAction:withSender:]):
+        (-[WKContentView keyCommands]):
+        (-[WKContentView _arrowKey:]):
+        (-[WKContentView _handleKeyUIEvent:]):
+        (-[WKContentView handleKeyEvent:]):
+        (-[WKContentView handleKeyWebEvent:]):
+        (-[WKContentView _interpretKeyEvent:isCharEvent:]):
+
 2015-06-28  Dan Bernstein  <[email protected]>
 
         Fixed the iOS 8.x build after r186066.

Modified: trunk/Source/WebKit2/Platform/spi/ios/UIKitSPI.h (186072 => 186073)


--- trunk/Source/WebKit2/Platform/spi/ios/UIKitSPI.h	2015-06-29 17:33:59 UTC (rev 186072)
+++ trunk/Source/WebKit2/Platform/spi/ios/UIKitSPI.h	2015-06-29 17:43:46 UTC (rev 186073)
@@ -48,6 +48,7 @@
 #import <UIKit/UIPickerContentView_Private.h>
 #import <UIKit/UIPickerView_Private.h>
 #import <UIKit/UIPresentationController_Private.h>
+#import <UIKit/UIResponder_Private.h>
 #import <UIKit/UIScrollView_Private.h>
 #import <UIKit/UIStringDrawing_Private.h>
 #import <UIKit/UITableViewCell_Private.h>
@@ -110,6 +111,26 @@
 @end
 
 typedef enum {
+    kUIKeyboardInputRepeat                 = 1 << 0,
+    kUIKeyboardInputPopupVariant           = 1 << 1,
+    kUIKeyboardInputMultitap               = 1 << 2,
+    kUIKeyboardInputSkipCandidateSelection = 1 << 3,
+    kUIKeyboardInputDeadKey                = 1 << 4,
+    kUIKeyboardInputModifierFlagsChanged   = 1 << 5,
+    kUIKeyboardInputFlick                  = 1 << 6,
+    kUIKeyboardInputPreProcessed           = 1 << 7,
+} UIKeyboardInputFlags;
+
+@interface UIEvent (Details)
+@property (nonatomic, readonly) UIKeyboardInputFlags _inputFlags;
+- (void *)_hidEvent;
+- (NSString *)_unmodifiedInput;
+- (NSString *)_modifiedInput;
+- (NSInteger)_modifierFlags;
+- (BOOL)_isKeyDown;
+@end
+
+typedef enum {
     UIFontTraitPlain = 0x00000000,
 } UIFontTrait;
 
@@ -129,6 +150,10 @@
 - (id)initWithCGImage:(CGImageRef)CGImage imageOrientation:(UIImageOrientation)imageOrientation;
 @end
 
+@interface UIKeyCommand (Details)
+@property (nonatomic, readonly) UIEvent *_triggeringEvent;
+@end
+
 @protocol UIKeyboardImplGeometryDelegate
 @property (nonatomic, readwrite, getter=isMinimized) BOOL minimized;
 - (void)prepareForImplBoundsHeightChange:(CGFloat)endDelta suppressNotification:(BOOL)suppressNotification;
@@ -216,12 +241,17 @@
 @property (nonatomic, setter=_setMagnifierEnabled:) BOOL _magnifierEnabled;
 @end
 
+@interface UIResponder (Details)
+- (void)_handleKeyUIEvent:(UIEvent *)event;
+@end
+
 @interface UIScrollView (Details)
 - (void)_stopScrollingAndZoomingAnimations;
 - (void)_zoomToCenter:(CGPoint)center scale:(CGFloat)scale duration:(CFTimeInterval)duration force:(BOOL)force;
 - (void)_zoomToCenter:(CGPoint)center scale:(CGFloat)scale duration:(CFTimeInterval)duration;
 @property (nonatomic, getter=isZoomEnabled) BOOL zoomEnabled;
 @property (nonatomic, readonly, getter=_isAnimatingZoom) BOOL isAnimatingZoom;
+@property (nonatomic, readonly, getter=_isAnimatingScroll) BOOL isAnimatingScroll;
 @property (nonatomic) CGFloat horizontalScrollDecelerationFactor;
 @property (nonatomic) CGFloat verticalScrollDecelerationFactor;
 @end
@@ -743,4 +773,7 @@
 extern const float UIWebViewScalesToFitScale;
 extern const float UIWebViewStandardViewportWidth;
 
+extern NSString *const UIKeyInputPageUp;
+extern NSString *const UIKeyInputPageDown;
+
 WTF_EXTERN_C_END

Modified: trunk/Source/WebKit2/UIProcess/API/Cocoa/WKWebView.mm (186072 => 186073)


--- trunk/Source/WebKit2/UIProcess/API/Cocoa/WKWebView.mm	2015-06-29 17:33:59 UTC (rev 186072)
+++ trunk/Source/WebKit2/UIProcess/API/Cocoa/WKWebView.mm	2015-06-29 17:43:46 UTC (rev 186073)
@@ -110,6 +110,7 @@
 - (void)_adjustForAutomaticKeyboardInfo:(NSDictionary*)info animated:(BOOL)animated lastAdjustment:(CGFloat*)lastAdjustment;
 - (BOOL)_isScrollingToTop;
 - (BOOL)_isInterruptingDeceleration;
+- (CGPoint)_animatedTargetOffset;
 @end
 
 @interface UIPeripheralHost(UIKitInternal)
@@ -1197,6 +1198,18 @@
     return true;
 }
 
+- (void)_scrollByOffset:(WebCore::FloatPoint)offset
+{
+    CGPoint currentOffset = ([_scrollView _isAnimatingScroll]) ? [_scrollView _animatedTargetOffset] : [_scrollView contentOffset];
+
+    CGPoint boundedOffset = contentOffsetBoundedInValidRange(_scrollView.get(), currentOffset + offset);
+    
+    if (CGPointEqualToPoint(boundedOffset, currentOffset))
+        return;
+    [_contentView willStartZoomOrScroll];
+    [_scrollView setContentOffset:boundedOffset animated:YES];
+}
+
 - (void)_zoomOutWithOrigin:(WebCore::FloatPoint)origin animated:(BOOL)animated
 {
     [self _zoomToPoint:origin atScale:[_scrollView minimumZoomScale] animated:animated];

Modified: trunk/Source/WebKit2/UIProcess/API/Cocoa/WKWebViewInternal.h (186072 => 186073)


--- trunk/Source/WebKit2/UIProcess/API/Cocoa/WKWebViewInternal.h	2015-06-29 17:33:59 UTC (rev 186072)
+++ trunk/Source/WebKit2/UIProcess/API/Cocoa/WKWebViewInternal.h	2015-06-29 17:43:46 UTC (rev 186073)
@@ -82,6 +82,7 @@
 
 - (void)_scrollToContentOffset:(WebCore::FloatPoint)contentOffset;
 - (BOOL)_scrollToRect:(WebCore::FloatRect)targetRect origin:(WebCore::FloatPoint)origin minimumScrollDistance:(float)minimumScrollDistance;
+- (void)_scrollByOffset:(WebCore::FloatPoint)offset;
 - (void)_zoomToFocusRect:(WebCore::FloatRect)focusedElementRect selectionRect:(WebCore::FloatRect)selectionRectInDocumentCoordinates fontSize:(float)fontSize minimumScale:(double)minimumScale maximumScale:(double)maximumScale allowScaling:(BOOL)allowScaling forceScroll:(BOOL)forceScroll;
 - (BOOL)_zoomToRect:(WebCore::FloatRect)targetRect withOrigin:(WebCore::FloatPoint)origin fitEntireRect:(BOOL)fitEntireRect minimumScale:(double)minimumScale maximumScale:(double)maximumScale minimumScrollDistance:(float)minimumScrollDistance;
 - (void)_zoomOutWithOrigin:(WebCore::FloatPoint)origin animated:(BOOL)animated;

Modified: trunk/Source/WebKit2/UIProcess/ios/WKContentViewInteraction.mm (186072 => 186073)


--- trunk/Source/WebKit2/UIProcess/ios/WKContentViewInteraction.mm	2015-06-29 17:33:59 UTC (rev 186072)
+++ trunk/Source/WebKit2/UIProcess/ios/WKContentViewInteraction.mm	2015-06-29 17:43:46 UTC (rev 186073)
@@ -61,12 +61,17 @@
 #import <WebCore/CoreGraphicsSPI.h>
 #import <WebCore/FloatQuad.h>
 #import <WebCore/Pasteboard.h>
+#import <WebCore/Scrollbar.h>
 #import <WebCore/SoftLinking.h>
 #import <WebCore/WebEvent.h>
 #import <WebKit/WebSelectionRect.h> // FIXME: WK2 should not include WebKit headers!
 #import <WebKitSystemInterfaceIOS.h>
 #import <wtf/RetainPtr.h>
 
+@interface UIEvent(UIEventInternal)
+@property (nonatomic, assign) UIKeyboardInputFlags _inputFlags;
+@end
+
 using namespace WebCore;
 using namespace WebKit;
 
@@ -1423,6 +1428,9 @@
 {
     BOOL hasWebSelection = _webSelectionAssistant && !CGRectIsEmpty(_webSelectionAssistant.get().selectionFrame);
 
+    if (action == @selector(_arrowKey:))
+        return [self isFirstResponder];
+        
     if (action == @selector(_showTextStyleOptions:))
         return _page->editorState().isContentRichlyEditable && _page->editorState().selectionIsRange && !_showingTextStyleOptions;
     if (_showingTextStyleOptions)
@@ -2144,10 +2152,46 @@
 
 - (NSArray *)keyCommands
 {
-    return @[[UIKeyCommand keyCommandWithInput:@"\t" modifierFlags:0 action:@selector(_nextAccessoryTab:)],
-             [UIKeyCommand keyCommandWithInput:@"\t" modifierFlags:UIKeyModifierShift action:@selector(_prevAccessoryTab:)]];
+    static NSArray* nonEditableKeyCommands = [@[
+       [UIKeyCommand keyCommandWithInput:UIKeyInputUpArrow modifierFlags:0 action:@selector(_arrowKey:)],
+       [UIKeyCommand keyCommandWithInput:UIKeyInputDownArrow modifierFlags:0 action:@selector(_arrowKey:)],
+       [UIKeyCommand keyCommandWithInput:UIKeyInputLeftArrow modifierFlags:0 action:@selector(_arrowKey:)],
+       [UIKeyCommand keyCommandWithInput:UIKeyInputRightArrow modifierFlags:0 action:@selector(_arrowKey:)],
+       
+       [UIKeyCommand keyCommandWithInput:UIKeyInputUpArrow modifierFlags:UIKeyModifierCommand action:@selector(_arrowKey:)],
+       [UIKeyCommand keyCommandWithInput:UIKeyInputDownArrow modifierFlags:UIKeyModifierCommand action:@selector(_arrowKey:)],
+       
+       [UIKeyCommand keyCommandWithInput:UIKeyInputUpArrow modifierFlags:UIKeyModifierShift action:@selector(_arrowKey:)],
+       [UIKeyCommand keyCommandWithInput:UIKeyInputDownArrow modifierFlags:UIKeyModifierShift action:@selector(_arrowKey:)],
+       [UIKeyCommand keyCommandWithInput:UIKeyInputLeftArrow modifierFlags:UIKeyModifierShift action:@selector(_arrowKey:)],
+       [UIKeyCommand keyCommandWithInput:UIKeyInputRightArrow modifierFlags:UIKeyModifierShift action:@selector(_arrowKey:)],
+       
+       [UIKeyCommand keyCommandWithInput:UIKeyInputUpArrow modifierFlags:UIKeyModifierAlternate action:@selector(_arrowKey:)],
+       [UIKeyCommand keyCommandWithInput:UIKeyInputDownArrow modifierFlags:UIKeyModifierAlternate action:@selector(_arrowKey:)],
+       [UIKeyCommand keyCommandWithInput:UIKeyInputLeftArrow modifierFlags:UIKeyModifierAlternate action:@selector(_arrowKey:)],
+       [UIKeyCommand keyCommandWithInput:UIKeyInputRightArrow modifierFlags:UIKeyModifierAlternate action:@selector(_arrowKey:)],
+       
+       [UIKeyCommand keyCommandWithInput:@" " modifierFlags:0 action:@selector(_arrowKey:)],
+       [UIKeyCommand keyCommandWithInput:@" " modifierFlags:UIKeyModifierShift action:@selector(_arrowKey:)],
+       
+       [UIKeyCommand keyCommandWithInput:UIKeyInputPageDown modifierFlags:0 action:@selector(_arrowKey:)],
+       [UIKeyCommand keyCommandWithInput:UIKeyInputPageDown modifierFlags:0 action:@selector(_arrowKey:)],
+    ] retain];
+
+    static NSArray* editableKeyCommands = [@[
+       [UIKeyCommand keyCommandWithInput:@"\t" modifierFlags:0 action:@selector(_nextAccessoryTab:)],
+       [UIKeyCommand keyCommandWithInput:@"\t" modifierFlags:UIKeyModifierShift action:@selector(_prevAccessoryTab:)]
+    ] retain];
+    
+    return (_page->editorState().isContentEditable) ? editableKeyCommands : nonEditableKeyCommands;
 }
 
+- (void)_arrowKey:(id)sender
+{
+    UIKeyCommand* command = sender;
+    [self handleKeyEvent:command._triggeringEvent];
+}
+
 - (void)_nextAccessoryTab:(id)sender
 {
     [self accessoryTab:YES];
@@ -2543,6 +2587,32 @@
     return YES;
 }
 
+- (void)_handleKeyUIEvent:(::UIEvent *)event
+{
+    // We only want to handle key event from the hardware keyboard when we are
+    // first responder and we are not interacting with editable content.
+    if ([self isFirstResponder] && event._hidEvent && !_page->editorState().isContentEditable)
+        [self handleKeyEvent:event];
+
+    [super _handleKeyUIEvent:event];
+}
+
+- (void)handleKeyEvent:(::UIEvent *)event
+{
+    ::WebEvent *webEvent = [[[::WebEvent alloc] initWithKeyEventType:(event._isKeyDown) ? WebEventKeyDown : WebEventKeyUp
+                                                           timeStamp:event.timestamp
+                                                          characters:event._modifiedInput
+                                         charactersIgnoringModifiers:event._unmodifiedInput
+                                                           modifiers:event._modifierFlags
+                                                         isRepeating:(event._inputFlags & kUIKeyboardInputRepeat)
+                                                           withFlags:event._inputFlags
+                                                             keyCode:0
+                                                            isTabKey:[event._modifiedInput isEqualToString:@"\t"]
+                                                        characterSet:WebEventCharacterSetUnicode] autorelease];
+    
+    [self handleKeyWebEvent:webEvent];    
+}
+
 - (void)handleKeyWebEvent:(WebIOSEvent *)theEvent
 {
     _page->handleKeyboardEvent(NativeWebKeyboardEvent(theEvent));
@@ -2565,88 +2635,96 @@
     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;
+    static const unsigned kWebSpaceKey = 0x20;
 
     if (!_page->editorState().isContentEditable && event.isTabKey)
         return NO;
 
     BOOL shift = event.modifierFlags & WebEventFlagMaskShift;
+    BOOL command = event.modifierFlags & WebEventFlagMaskCommand;
+    BOOL option = event.modifierFlags & WebEventFlagMaskAlternate;
+    NSString *charactersIgnoringModifiers = [event charactersIgnoringModifiers];
+    BOOL shouldScroll = YES;
+    FloatPoint scrollOffset;
 
-    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;
+    if ([charactersIgnoringModifiers isEqualToString:UIKeyInputLeftArrow])
+        scrollOffset.setX(-Scrollbar::pixelsPerLineStep());
+    else if ([charactersIgnoringModifiers isEqualToString:UIKeyInputUpArrow]) {
+        if (option)
+            scrollOffset.setY(-_page->unobscuredContentRect().height());
+        else if (command)
+            scrollOffset.setY(-[self bounds].size.height);
+        else
+            scrollOffset.setY(-Scrollbar::pixelsPerLineStep());
+    } else if ([charactersIgnoringModifiers isEqualToString:UIKeyInputRightArrow])
+            scrollOffset.setX(Scrollbar::pixelsPerLineStep());
+    else if ([charactersIgnoringModifiers isEqualToString:UIKeyInputDownArrow]) {
+        if (option)
+            scrollOffset.setY(_page->unobscuredContentRect().height());
+        else if (command)
+            scrollOffset.setY([self bounds].size.height);
+        else
+            scrollOffset.setY(Scrollbar::pixelsPerLineStep());
+    } else if ([charactersIgnoringModifiers isEqualToString:UIKeyInputPageDown])
+        scrollOffset.setY(_page->unobscuredContentRect().height());
+    else if ([charactersIgnoringModifiers isEqualToString:UIKeyInputPageUp])
+        scrollOffset.setY(-_page->unobscuredContentRect().height());
+    else
+        shouldScroll = NO;
 
-        case kWebUpArrowKey:
-            command = shift ? ASCIILiteral("moveUpAndModifySelection") :  ASCIILiteral("moveUp");
-            break;
+    if (shouldScroll) {
+        [_webView _scrollByOffset:scrollOffset];
+        return YES;
+    }
 
-        case kWebRightArrowKey:
-            command = shift ? ASCIILiteral("moveRightAndModifySelection") :  ASCIILiteral("moveRight");
-            break;
+    UIKeyboardImpl *keyboard = [UIKeyboardImpl sharedInstance];
+    NSString *characters = [event characters];
+    
+    if (![characters length])
+        return NO;
 
-        case kWebDownArrowKey:
-            command = shift ? ASCIILiteral("moveDownAndModifySelection") :  ASCIILiteral("moveDown");
-            break;
+    switch ([characters characterAtIndex:0]) {
+    case kWebBackspaceKey:
+    case kWebDeleteKey:
+        // FIXME: remove deleteFromInput once UIKit adopts deleteFromInputWithFlags
+        if ([keyboard respondsToSelector:@selector(deleteFromInputWithFlags:)])
+            [keyboard deleteFromInputWithFlags:event.keyboardFlags];
+        else
+            [keyboard deleteFromInput];
+        return YES;
+
+    case kWebSpaceKey:
+        if (!_page->editorState().isContentEditable) {
+            [_webView _scrollByOffset:FloatPoint(0, shift ? -_page->unobscuredContentRect().height() : _page->unobscuredContentRect().height())];
+            return YES;
         }
-        if (!command.isEmpty()) {
-            _page->executeEditCommand(command);
+        if (isCharEvent) {
+            [keyboard addInputString:event.characters withFlags:event.keyboardFlags];
             return YES;
         }
         break;
-    }
-    case WebEventCharacterSetASCII:
-    case WebEventCharacterSetUnicode: {
-        NSString *characters = [event characters];
-        if ([characters length] == 0)
-            break;
-        UIKeyboardImpl *keyboard = [UIKeyboardImpl sharedInstance];
-        switch ([characters characterAtIndex:0]) {
-        case kWebBackspaceKey:
-        case kWebDeleteKey:
-            // FIXME: remove deleteFromInput once UIKit adopts deleteFromInputWithFlags
-            if ([keyboard respondsToSelector:@selector(deleteFromInputWithFlags:)])
-                [keyboard deleteFromInputWithFlags:event.keyboardFlags];
-            else
-                [keyboard deleteFromInput];
+
+    case kWebEnterKey:
+    case kWebReturnKey:
+        if (isCharEvent) {
+            // Map \r from HW keyboard to \n to match the behavior of the soft keyboard.
+            [keyboard addInputString:@"\n" withFlags:0];
             return YES;
+        }
+        break;
 
-        case kWebEnterKey:
-        case kWebReturnKey:
-            if (isCharEvent) {
-                // Map \r from HW keyboard to \n to match the behavior of the soft keyboard.
-                [keyboard addInputString:@"\n" withFlags:0];
-                return YES;
-            }
-            return NO;
+    case kWebDeleteForwardKey:
+        _page->executeEditCommand(ASCIILiteral("deleteForward"));
+        return YES;
 
-        case kWebDeleteForwardKey:
-            _page->executeEditCommand(ASCIILiteral("deleteForward"));
+    default:
+        if (isCharEvent) {
+            [keyboard addInputString:event.characters withFlags:event.keyboardFlags];
             return YES;
-
-        default: {
-            if (isCharEvent) {
-                [keyboard addInputString:event.characters withFlags:event.keyboardFlags];
-                return YES;
-            }
-            return NO;
         }
-    }
         break;
     }
-    default:
-        return NO;
-    }
 
     return NO;
 }
_______________________________________________
webkit-changes mailing list
[email protected]
https://lists.webkit.org/mailman/listinfo/webkit-changes

Reply via email to