WKView being inside WKWebView leads to weird API issues
authortimothy_horton@apple.com <timothy_horton@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Fri, 30 Oct 2015 17:02:36 +0000 (17:02 +0000)
committertimothy_horton@apple.com <timothy_horton@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Fri, 30 Oct 2015 17:02:36 +0000 (17:02 +0000)
https://bugs.webkit.org/show_bug.cgi?id=150174

Reviewed by Darin Adler.

* UIProcess/API/mac/WKView.mm:
(-[WKView doCommandBySelector:]):
(-[WKView insertText:]):
(-[WKView insertText:replacementRange:]):
(-[WKView inputContext]):
(-[WKView performKeyEquivalent:]):
(-[WKView keyUp:]):
(-[WKView keyDown:]):
(-[WKView flagsChanged:]):
(-[WKView setMarkedText:selectedRange:replacementRange:]):
(-[WKView unmarkText]):
(-[WKView selectedRange]):
(-[WKView hasMarkedText]):
(-[WKView markedRange]):
(-[WKView attributedSubstringForProposedRange:actualRange:]):
(-[WKView characterIndexForPoint:]):
(-[WKView firstRectForCharacterRange:actualRange:]):
(-[WKView selectedRangeWithCompletionHandler:]):
(-[WKView markedRangeWithCompletionHandler:]):
(-[WKView hasMarkedTextWithCompletionHandler:]):
(-[WKView attributedSubstringForProposedRange:completionHandler:]):
(-[WKView firstRectForCharacterRange:completionHandler:]):
(-[WKView characterIndexForPoint:completionHandler:]):
(-[WKView _superPerformKeyEquivalent:]):
(-[WKView _superKeyDown:]):
(extractUnderlines): Deleted.
(-[WKView _collectKeyboardLayoutCommandsForEvent:to:]): Deleted.
(-[WKView _interpretKeyEvent:completionHandler:]): Deleted.
(-[WKView NO_RETURN_DUE_TO_ASSERT]): Deleted.
(-[WKView _interpretKeyEvent:savingCommandsTo:]): Deleted.
(-[WKView _executeSavedKeypressCommands]): Deleted.
(-[WKView _doneWithKeyEvent:eventWasHandled:]): Deleted.
* UIProcess/API/mac/WKViewInternal.h:
* UIProcess/Cocoa/WebViewImpl.h:
* UIProcess/Cocoa/WebViewImpl.mm:
(WebKit::WebViewImpl::doneWithKeyEvent):
(WebKit::extractUnderlines):
(WebKit::WebViewImpl::collectKeyboardLayoutCommandsForEvent):
(WebKit::WebViewImpl::interpretKeyEvent):
(WebKit::WebViewImpl::doCommandBySelector):
(WebKit::WebViewImpl::insertText):
(WebKit::WebViewImpl::selectedRangeWithCompletionHandler):
(WebKit::WebViewImpl::markedRangeWithCompletionHandler):
(WebKit::WebViewImpl::hasMarkedTextWithCompletionHandler):
(WebKit::WebViewImpl::attributedSubstringForProposedRange):
(WebKit::WebViewImpl::firstRectForCharacterRange):
(WebKit::WebViewImpl::characterIndexForPoint):
(WebKit::WebViewImpl::inputContext):
(WebKit::WebViewImpl::unmarkText):
(WebKit::WebViewImpl::setMarkedText):
(WebKit::WebViewImpl::selectedRange):
(WebKit::WebViewImpl::hasMarkedText):
(WebKit::WebViewImpl::markedRange):
(WebKit::WebViewImpl::performKeyEquivalent):
(WebKit::WebViewImpl::keyUp):
(WebKit::WebViewImpl::keyDown):
(WebKit::WebViewImpl::flagsChanged):
(WebKit::WebViewImpl::executeSavedKeypressCommands):
* UIProcess/mac/PageClientImpl.mm:
(WebKit::PageClientImpl::doneWithKeyEvent):
Move NSTextInputClient implementation.

git-svn-id: https://svn.webkit.org/repository/webkit/trunk@191791 268f45cc-cd09-0410-ab3c-d52691b4dbfc

Source/WebKit2/ChangeLog
Source/WebKit2/UIProcess/API/mac/WKView.mm
Source/WebKit2/UIProcess/API/mac/WKViewInternal.h
Source/WebKit2/UIProcess/Cocoa/WebViewImpl.h
Source/WebKit2/UIProcess/Cocoa/WebViewImpl.mm
Source/WebKit2/UIProcess/mac/PageClientImpl.mm

index 9e281dbc282300e9347201e6dc0659511521488c..9d75b875260f8aebafbbdc45c5f08dc2998eb837 100644 (file)
@@ -1,3 +1,72 @@
+2015-10-30  Tim Horton  <timothy_horton@apple.com>
+
+        WKView being inside WKWebView leads to weird API issues
+        https://bugs.webkit.org/show_bug.cgi?id=150174
+
+        Reviewed by Darin Adler.
+
+        * UIProcess/API/mac/WKView.mm:
+        (-[WKView doCommandBySelector:]):
+        (-[WKView insertText:]):
+        (-[WKView insertText:replacementRange:]):
+        (-[WKView inputContext]):
+        (-[WKView performKeyEquivalent:]):
+        (-[WKView keyUp:]):
+        (-[WKView keyDown:]):
+        (-[WKView flagsChanged:]):
+        (-[WKView setMarkedText:selectedRange:replacementRange:]):
+        (-[WKView unmarkText]):
+        (-[WKView selectedRange]):
+        (-[WKView hasMarkedText]):
+        (-[WKView markedRange]):
+        (-[WKView attributedSubstringForProposedRange:actualRange:]):
+        (-[WKView characterIndexForPoint:]):
+        (-[WKView firstRectForCharacterRange:actualRange:]):
+        (-[WKView selectedRangeWithCompletionHandler:]):
+        (-[WKView markedRangeWithCompletionHandler:]):
+        (-[WKView hasMarkedTextWithCompletionHandler:]):
+        (-[WKView attributedSubstringForProposedRange:completionHandler:]):
+        (-[WKView firstRectForCharacterRange:completionHandler:]):
+        (-[WKView characterIndexForPoint:completionHandler:]):
+        (-[WKView _superPerformKeyEquivalent:]):
+        (-[WKView _superKeyDown:]):
+        (extractUnderlines): Deleted.
+        (-[WKView _collectKeyboardLayoutCommandsForEvent:to:]): Deleted.
+        (-[WKView _interpretKeyEvent:completionHandler:]): Deleted.
+        (-[WKView NO_RETURN_DUE_TO_ASSERT]): Deleted.
+        (-[WKView _interpretKeyEvent:savingCommandsTo:]): Deleted.
+        (-[WKView _executeSavedKeypressCommands]): Deleted.
+        (-[WKView _doneWithKeyEvent:eventWasHandled:]): Deleted.
+        * UIProcess/API/mac/WKViewInternal.h:
+        * UIProcess/Cocoa/WebViewImpl.h:
+        * UIProcess/Cocoa/WebViewImpl.mm:
+        (WebKit::WebViewImpl::doneWithKeyEvent):
+        (WebKit::extractUnderlines):
+        (WebKit::WebViewImpl::collectKeyboardLayoutCommandsForEvent):
+        (WebKit::WebViewImpl::interpretKeyEvent):
+        (WebKit::WebViewImpl::doCommandBySelector):
+        (WebKit::WebViewImpl::insertText):
+        (WebKit::WebViewImpl::selectedRangeWithCompletionHandler):
+        (WebKit::WebViewImpl::markedRangeWithCompletionHandler):
+        (WebKit::WebViewImpl::hasMarkedTextWithCompletionHandler):
+        (WebKit::WebViewImpl::attributedSubstringForProposedRange):
+        (WebKit::WebViewImpl::firstRectForCharacterRange):
+        (WebKit::WebViewImpl::characterIndexForPoint):
+        (WebKit::WebViewImpl::inputContext):
+        (WebKit::WebViewImpl::unmarkText):
+        (WebKit::WebViewImpl::setMarkedText):
+        (WebKit::WebViewImpl::selectedRange):
+        (WebKit::WebViewImpl::hasMarkedText):
+        (WebKit::WebViewImpl::markedRange):
+        (WebKit::WebViewImpl::performKeyEquivalent):
+        (WebKit::WebViewImpl::keyUp):
+        (WebKit::WebViewImpl::keyDown):
+        (WebKit::WebViewImpl::flagsChanged):
+        (WebKit::WebViewImpl::executeSavedKeypressCommands):
+        * UIProcess/mac/PageClientImpl.mm:
+        (WebKit::PageClientImpl::doneWithKeyEvent):
+        Move NSTextInputClient implementation.
+
 2015-10-30  Carlos Garcia Campos  <cgarcia@igalia.com>
 
         [GTK] Move the socket polling off the WorkQueue
index 9361f18b63d2273d4fa570f1553986da0bb1648d..6785122eacc03f9a5aba035a118f3b5677c18e1f 100644 (file)
 #import "WKBrowsingContextGroupPrivate.h"
 #import "WKProcessGroupPrivate.h"
 
-#if USE(ASYNC_NSTEXTINPUTCLIENT)
-@interface NSTextInputContext (WKNSTextInputContextDetails)
-- (void)handleEvent:(NSEvent *)theEvent completionHandler:(void(^)(BOOL handled))completionHandler;
-- (void)handleEventByInputMethod:(NSEvent *)theEvent completionHandler:(void(^)(BOOL handled))completionHandler;
-- (BOOL)handleEventByKeyboardLayout:(NSEvent *)theEvent;
-@end
-#endif
-
 using namespace WebKit;
 using namespace WebCore;
 
-#if !USE(ASYNC_NSTEXTINPUTCLIENT)
-struct WKViewInterpretKeyEventsParameters {
-    bool eventInterpretationHadSideEffects;
-    bool consumedByIM;
-    bool executingSavedKeypressCommands;
-    Vector<KeypressCommand>* commands;
-};
-#endif
-
 @interface WKViewData : NSObject {
 @public
     std::unique_ptr<PageClientImpl> _pageClient;
     RefPtr<WebPageProxy> _page;
     std::unique_ptr<WebViewImpl> _impl;
-
-    // We keep here the event when resending it to
-    // the application to distinguish the case of a new event from one 
-    // that has been already sent to WebCore.
-    RetainPtr<NSEvent> _keyDownEventBeingResent;
-#if USE(ASYNC_NSTEXTINPUTCLIENT)
-    Vector<KeypressCommand>* _collectedKeypressCommands;
-#else
-    WKViewInterpretKeyEventsParameters* _interpretKeyEventsParameters;
-#endif
 }
 
 @end
@@ -758,932 +731,116 @@ NATIVE_MOUSE_EVENT_HANDLER_INTERNAL(mouseDraggedInternal)
     return result;
 }
 
-static void extractUnderlines(NSAttributedString *string, Vector<CompositionUnderline>& result)
-{
-    int length = [[string string] length];
-    
-    int i = 0;
-    while (i < length) {
-        NSRange range;
-        NSDictionary *attrs = [string attributesAtIndex:i longestEffectiveRange:&range inRange:NSMakeRange(i, length - i)];
-        
-        if (NSNumber *style = [attrs objectForKey:NSUnderlineStyleAttributeName]) {
-            Color color = Color::black;
-            if (NSColor *colorAttr = [attrs objectForKey:NSUnderlineColorAttributeName])
-                color = colorFromNSColor([colorAttr colorUsingColorSpaceName:NSDeviceRGBColorSpace]);
-            result.append(CompositionUnderline(range.location, NSMaxRange(range), color, [style intValue] > 1));
-        }
-        
-        i = range.location + range.length;
-    }
-}
-
-#if USE(ASYNC_NSTEXTINPUTCLIENT)
-
-- (void)_collectKeyboardLayoutCommandsForEvent:(NSEvent *)event to:(Vector<KeypressCommand>&)commands
-{
-    if ([event type] != NSKeyDown)
-        return;
-
-    ASSERT(!_data->_collectedKeypressCommands);
-    _data->_collectedKeypressCommands = &commands;
-
-    if (NSTextInputContext *context = [self inputContext])
-        [context handleEventByKeyboardLayout:event];
-    else
-        [self interpretKeyEvents:[NSArray arrayWithObject:event]];
-
-    _data->_collectedKeypressCommands = nullptr;
-}
-
-- (void)_interpretKeyEvent:(NSEvent *)event completionHandler:(void(^)(BOOL handled, const Vector<KeypressCommand>& commands))completionHandler
-{
-    // For regular Web content, input methods run before passing a keydown to DOM, but plug-ins get an opportunity to handle the event first.
-    // There is no need to collect commands, as the plug-in cannot execute them.
-    if (_data->_impl->pluginComplexTextInputIdentifier()) {
-        completionHandler(NO, Vector<KeypressCommand>());
-        return;
-    }
-
-    if (![self inputContext]) {
-        Vector<KeypressCommand> commands;
-        [self _collectKeyboardLayoutCommandsForEvent:event to:commands];
-        completionHandler(NO, commands);
-        return;
-    }
-
-    LOG(TextInput, "-> handleEventByInputMethod:%p %@", event, event);
-    [[self inputContext] handleEventByInputMethod:event completionHandler:^(BOOL handled) {
-        
-        LOG(TextInput, "... handleEventByInputMethod%s handled", handled ? "" : " not");
-        if (handled) {
-            completionHandler(YES, Vector<KeypressCommand>());
-            return;
-        }
-
-        Vector<KeypressCommand> commands;
-        [self _collectKeyboardLayoutCommandsForEvent:event to:commands];
-        completionHandler(NO, commands);
-    }];
-}
-
 - (void)doCommandBySelector:(SEL)selector
 {
-    LOG(TextInput, "doCommandBySelector:\"%s\"", sel_getName(selector));
-
-    Vector<KeypressCommand>* keypressCommands = _data->_collectedKeypressCommands;
-
-    if (keypressCommands) {
-        KeypressCommand command(NSStringFromSelector(selector));
-        keypressCommands->append(command);
-        LOG(TextInput, "...stored");
-        _data->_page->registerKeypressCommandName(command.commandName);
-    } else {
-        // FIXME: Send the command to Editor synchronously and only send it along the
-        // responder chain if it's a selector that does not correspond to an editing command.
-        [super doCommandBySelector:selector];
-    }
+    _data->_impl->doCommandBySelector(selector);
 }
 
 - (void)insertText:(id)string
 {
-    // Unlike an NSTextInputClient variant with replacementRange, this NSResponder method is called when there is no input context,
-    // so text input processing isn't performed. We are not going to actually insert any text in that case, but saving an insertText
-    // command ensures that a keypress event is dispatched as appropriate.
-    [self insertText:string replacementRange:NSMakeRange(NSNotFound, 0)];
+    _data->_impl->insertText(string);
 }
 
 - (void)insertText:(id)string replacementRange:(NSRange)replacementRange
 {
-    BOOL isAttributedString = [string isKindOfClass:[NSAttributedString class]];
-    ASSERT(isAttributedString || [string isKindOfClass:[NSString class]]);
-
-    if (replacementRange.location != NSNotFound)
-        LOG(TextInput, "insertText:\"%@\" replacementRange:(%u, %u)", isAttributedString ? [string string] : string, replacementRange.location, replacementRange.length);
-    else
-        LOG(TextInput, "insertText:\"%@\"", isAttributedString ? [string string] : string);
-
-    NSString *text;
-    Vector<TextAlternativeWithRange> dictationAlternatives;
-
-    bool registerUndoGroup = false;
-    if (isAttributedString) {
-#if USE(DICTATION_ALTERNATIVES)
-        collectDictationTextAlternatives(string, dictationAlternatives);
-#endif
-#if USE(INSERTION_UNDO_GROUPING)
-        registerUndoGroup = shouldRegisterInsertionUndoGroup(string);
-#endif
-        // FIXME: We ignore most attributes from the string, so for example inserting from Character Palette loses font and glyph variation data.
-        text = [string string];
-    } else
-        text = string;
-
-    // insertText can be called for several reasons:
-    // - If it's from normal key event processing (including key bindings), we save the action to perform it later.
-    // - If it's from an input method, then we should insert the text now.
-    // - If it's sent outside of keyboard event processing (e.g. from Character Viewer, or when confirming an inline input area with a mouse),
-    // then we also execute it immediately, as there will be no other chance.
-    Vector<KeypressCommand>* keypressCommands = _data->_collectedKeypressCommands;
-    if (keypressCommands) {
-        ASSERT(replacementRange.location == NSNotFound);
-        KeypressCommand command("insertText:", text);
-        keypressCommands->append(command);
-        LOG(TextInput, "...stored");
-        _data->_page->registerKeypressCommandName(command.commandName);
-        return;
-    }
-
-    String eventText = text;
-    eventText.replace(NSBackTabCharacter, NSTabCharacter); // same thing is done in KeyEventMac.mm in WebCore
-    if (!dictationAlternatives.isEmpty())
-        _data->_page->insertDictatedTextAsync(eventText, replacementRange, dictationAlternatives, registerUndoGroup);
-    else
-        _data->_page->insertTextAsync(eventText, replacementRange, registerUndoGroup);
-}
-
-- (void)selectedRangeWithCompletionHandler:(void(^)(NSRange selectedRange))completionHandlerPtr
-{
-    RetainPtr<id> completionHandler = adoptNS([completionHandlerPtr copy]);
-
-    LOG(TextInput, "selectedRange");
-    _data->_page->getSelectedRangeAsync([completionHandler](const EditingRange& editingRangeResult, WebKit::CallbackBase::Error error) {
-        void (^completionHandlerBlock)(NSRange) = (void (^)(NSRange))completionHandler.get();
-        if (error != WebKit::CallbackBase::Error::None) {
-            LOG(TextInput, "    ...selectedRange failed.");
-            completionHandlerBlock(NSMakeRange(NSNotFound, 0));
-            return;
-        }
-        NSRange result = editingRangeResult;
-        if (result.location == NSNotFound)
-            LOG(TextInput, "    -> selectedRange returned (NSNotFound, %llu)", result.length);
-        else
-            LOG(TextInput, "    -> selectedRange returned (%llu, %llu)", result.location, result.length);
-        completionHandlerBlock(result);
-    });
-}
-
-- (void)markedRangeWithCompletionHandler:(void(^)(NSRange markedRange))completionHandlerPtr
-{
-    RetainPtr<id> completionHandler = adoptNS([completionHandlerPtr copy]);
-
-    LOG(TextInput, "markedRange");
-    _data->_page->getMarkedRangeAsync([completionHandler](const EditingRange& editingRangeResult, WebKit::CallbackBase::Error error) {
-        void (^completionHandlerBlock)(NSRange) = (void (^)(NSRange))completionHandler.get();
-        if (error != WebKit::CallbackBase::Error::None) {
-            LOG(TextInput, "    ...markedRange failed.");
-            completionHandlerBlock(NSMakeRange(NSNotFound, 0));
-            return;
-        }
-        NSRange result = editingRangeResult;
-        if (result.location == NSNotFound)
-            LOG(TextInput, "    -> markedRange returned (NSNotFound, %llu)", result.length);
-        else
-            LOG(TextInput, "    -> markedRange returned (%llu, %llu)", result.location, result.length);
-        completionHandlerBlock(result);
-    });
-}
-
-- (void)hasMarkedTextWithCompletionHandler:(void(^)(BOOL hasMarkedText))completionHandlerPtr
-{
-    RetainPtr<id> completionHandler = adoptNS([completionHandlerPtr copy]);
-
-    LOG(TextInput, "hasMarkedText");
-    _data->_page->getMarkedRangeAsync([completionHandler](const EditingRange& editingRangeResult, WebKit::CallbackBase::Error error) {
-        void (^completionHandlerBlock)(BOOL) = (void (^)(BOOL))completionHandler.get();
-        if (error != WebKit::CallbackBase::Error::None) {
-            LOG(TextInput, "    ...hasMarkedText failed.");
-            completionHandlerBlock(NO);
-            return;
-        }
-        BOOL hasMarkedText = editingRangeResult.location != notFound;
-        LOG(TextInput, "    -> hasMarkedText returned %u", hasMarkedText);
-        completionHandlerBlock(hasMarkedText);
-    });
-}
-
-- (void)attributedSubstringForProposedRange:(NSRange)nsRange completionHandler:(void(^)(NSAttributedString *attrString, NSRange actualRange))completionHandlerPtr
-{
-    RetainPtr<id> completionHandler = adoptNS([completionHandlerPtr copy]);
-
-    LOG(TextInput, "attributedSubstringFromRange:(%u, %u)", nsRange.location, nsRange.length);
-    _data->_page->attributedSubstringForCharacterRangeAsync(nsRange, [completionHandler](const AttributedString& string, const EditingRange& actualRange, WebKit::CallbackBase::Error error) {
-        void (^completionHandlerBlock)(NSAttributedString *, NSRange) = (void (^)(NSAttributedString *, NSRange))completionHandler.get();
-        if (error != WebKit::CallbackBase::Error::None) {
-            LOG(TextInput, "    ...attributedSubstringFromRange failed.");
-            completionHandlerBlock(0, NSMakeRange(NSNotFound, 0));
-            return;
-        }
-        LOG(TextInput, "    -> attributedSubstringFromRange returned %@", [string.string.get() string]);
-        completionHandlerBlock([[string.string.get() retain] autorelease], actualRange);
-    });
-}
-
-- (void)firstRectForCharacterRange:(NSRange)theRange completionHandler:(void(^)(NSRect firstRect, NSRange actualRange))completionHandlerPtr
-{
-    RetainPtr<id> completionHandler = adoptNS([completionHandlerPtr copy]);
-
-    LOG(TextInput, "firstRectForCharacterRange:(%u, %u)", theRange.location, theRange.length);
-
-    // Just to match NSTextView's behavior. Regression tests cannot detect this;
-    // to reproduce, use a test application from http://bugs.webkit.org/show_bug.cgi?id=4682
-    // (type something; try ranges (1, -1) and (2, -1).
-    if ((theRange.location + theRange.length < theRange.location) && (theRange.location + theRange.length != 0))
-        theRange.length = 0;
-
-    if (theRange.location == NSNotFound) {
-        LOG(TextInput, "    -> NSZeroRect");
-        completionHandlerPtr(NSZeroRect, theRange);
-        return;
-    }
-
-    _data->_page->firstRectForCharacterRangeAsync(theRange, [self, completionHandler](const IntRect& rect, const EditingRange& actualRange, WebKit::CallbackBase::Error error) {
-        void (^completionHandlerBlock)(NSRect, NSRange) = (void (^)(NSRect, NSRange))completionHandler.get();
-        if (error != WebKit::CallbackBase::Error::None) {
-            LOG(TextInput, "    ...firstRectForCharacterRange failed.");
-            completionHandlerBlock(NSZeroRect, NSMakeRange(NSNotFound, 0));
-            return;
-        }
-
-        NSRect resultRect = [self convertRect:rect toView:nil];
-        resultRect = [self.window convertRectToScreen:resultRect];
-
-        LOG(TextInput, "    -> firstRectForCharacterRange returned (%f, %f, %f, %f)", resultRect.origin.x, resultRect.origin.y, resultRect.size.width, resultRect.size.height);
-        completionHandlerBlock(resultRect, actualRange);
-    });
-}
-
-- (void)characterIndexForPoint:(NSPoint)thePoint completionHandler:(void(^)(NSUInteger))completionHandlerPtr
-{
-    RetainPtr<id> completionHandler = adoptNS([completionHandlerPtr copy]);
-
-    LOG(TextInput, "characterIndexForPoint:(%f, %f)", thePoint.x, thePoint.y);
-
-    NSWindow *window = [self window];
-
-#pragma clang diagnostic push
-#pragma clang diagnostic ignored "-Wdeprecated-declarations"
-    if (window)
-        thePoint = [window convertScreenToBase:thePoint];
-#pragma clang diagnostic pop
-    thePoint = [self convertPoint:thePoint fromView:nil];  // the point is relative to the main frame
-
-    _data->_page->characterIndexForPointAsync(IntPoint(thePoint), [completionHandler](uint64_t result, WebKit::CallbackBase::Error error) {
-        void (^completionHandlerBlock)(NSUInteger) = (void (^)(NSUInteger))completionHandler.get();
-        if (error != WebKit::CallbackBase::Error::None) {
-            LOG(TextInput, "    ...characterIndexForPoint failed.");
-            completionHandlerBlock(0);
-            return;
-        }
-        if (result == notFound)
-            result = NSNotFound;
-        LOG(TextInput, "    -> characterIndexForPoint returned %lu", result);
-        completionHandlerBlock(result);
-    });
+    _data->_impl->insertText(string, replacementRange);
 }
 
 - (NSTextInputContext *)inputContext
 {
-    if (_data->_impl->pluginComplexTextInputIdentifier()) {
-        ASSERT(!_data->_collectedKeypressCommands); // Should not get here from -_interpretKeyEvent:completionHandler:, we only use WKTextInputWindowController after giving the plug-in a chance to handle keydown natively.
-        return [[WKTextInputWindowController sharedTextInputWindowController] inputContext];
-    }
-
-    // Disable text input machinery when in non-editable content. An invisible inline input area affects performance, and can prevent Expose from working.
-    if (!_data->_page->editorState().isContentEditable)
-        return nil;
-
-    return [super inputContext];
-}
-
-- (void)unmarkText
-{
-    LOG(TextInput, "unmarkText");
-
-    _data->_page->confirmCompositionAsync();
-}
-
-- (void)setMarkedText:(id)string selectedRange:(NSRange)selectedRange replacementRange:(NSRange)replacementRange
-{
-    BOOL isAttributedString = [string isKindOfClass:[NSAttributedString class]];
-    ASSERT(isAttributedString || [string isKindOfClass:[NSString class]]);
-
-    LOG(TextInput, "setMarkedText:\"%@\" selectedRange:(%u, %u) replacementRange:(%u, %u)", isAttributedString ? [string string] : string, selectedRange.location, selectedRange.length, replacementRange.location, replacementRange.length);
-
-    Vector<CompositionUnderline> underlines;
-    NSString *text;
-
-    if (isAttributedString) {
-        // FIXME: We ignore most attributes from the string, so an input method cannot specify e.g. a font or a glyph variation.
-        text = [string string];
-        extractUnderlines(string, underlines);
-    } else
-        text = string;
-
-    if (_data->_impl->inSecureInputState()) {
-        // In password fields, we only allow ASCII dead keys, and don't allow inline input, matching NSSecureTextInputField.
-        // Allowing ASCII dead keys is necessary to enable full Roman input when using a Vietnamese keyboard.
-        ASSERT(!_data->_page->editorState().hasComposition);
-        _data->_impl->notifyInputContextAboutDiscardedComposition();
-        // FIXME: We should store the command to handle it after DOM event processing, as it's regular keyboard input now, not a composition.
-        if ([text length] == 1 && isASCII([text characterAtIndex:0]))
-            _data->_page->insertTextAsync(text, replacementRange);
-        else
-            NSBeep();
-        return;
-    }
-
-    _data->_page->setCompositionAsync(text, underlines, selectedRange, replacementRange);
-}
-
-// Synchronous NSTextInputClient is still implemented to catch spurious sync calls. Remove when that is no longer needed.
-
-- (NSRange)selectedRange NO_RETURN_DUE_TO_ASSERT
-{
-    ASSERT_NOT_REACHED();
-    return NSMakeRange(NSNotFound, 0);
-}
-
-- (BOOL)hasMarkedText NO_RETURN_DUE_TO_ASSERT
-{
-    ASSERT_NOT_REACHED();
-    return NO;
-}
-
-- (NSRange)markedRange NO_RETURN_DUE_TO_ASSERT
-{
-    ASSERT_NOT_REACHED();
-    return NSMakeRange(NSNotFound, 0);
-}
-
-- (NSAttributedString *)attributedSubstringForProposedRange:(NSRange)nsRange actualRange:(NSRangePointer)actualRange NO_RETURN_DUE_TO_ASSERT
-{
-    ASSERT_NOT_REACHED();
-    return nil;
-}
-
-- (NSUInteger)characterIndexForPoint:(NSPoint)thePoint NO_RETURN_DUE_TO_ASSERT
-{
-    ASSERT_NOT_REACHED();
-    return 0;
-}
-
-- (NSRect)firstRectForCharacterRange:(NSRange)theRange actualRange:(NSRangePointer)actualRange NO_RETURN_DUE_TO_ASSERT
-{ 
-    ASSERT_NOT_REACHED();
-    return NSMakeRect(0, 0, 0, 0);
+    return _data->_impl->inputContext();
 }
 
 - (BOOL)performKeyEquivalent:(NSEvent *)event
 {
-    if (_data->_impl->ignoresNonWheelEvents())
-        return NO;
-
-    // There's a chance that responding to this event will run a nested event loop, and
-    // fetching a new event might release the old one. Retaining and then autoreleasing
-    // the current event prevents that from causing a problem inside WebKit or AppKit code.
-    [[event retain] autorelease];
-
-    // We get Esc key here after processing either Esc or Cmd+period. The former starts as a keyDown, and the latter starts as a key equivalent,
-    // but both get transformed to a cancelOperation: command, executing which passes an Esc key event to -performKeyEquivalent:.
-    // Don't interpret this event again, avoiding re-entrancy and infinite loops.
-    if ([[event charactersIgnoringModifiers] isEqualToString:@"\e"] && !([event modifierFlags] & NSDeviceIndependentModifierFlagsMask))
-        return [super performKeyEquivalent:event];
-
-    if (_data->_keyDownEventBeingResent) {
-        // WebCore has already seen the event, no need for custom processing.
-        // Note that we can get multiple events for each event being re-sent. For example, for Cmd+'=' AppKit
-        // first performs the original key equivalent, and if that isn't handled, it dispatches a synthetic Cmd+'+'.
-        return [super performKeyEquivalent:event];
-    }
-
-    ASSERT(event == [NSApp currentEvent]);
-
-    _data->_impl->disableComplexTextInputIfNecessary();
-
-    // Pass key combos through WebCore if there is a key binding available for
-    // this event. This lets webpages have a crack at intercepting key-modified keypresses.
-    // FIXME: Why is the firstResponder check needed?
-    if (self == [[self window] firstResponder]) {
-        [self _interpretKeyEvent:event completionHandler:^(BOOL handledByInputMethod, const Vector<KeypressCommand>& commands) {
-            _data->_page->handleKeyboardEvent(NativeWebKeyboardEvent(event, handledByInputMethod, commands));
-        }];
-        return YES;
-    }
-    
-    return [super performKeyEquivalent:event];
+    return _data->_impl->performKeyEquivalent(event);
 }
 
 - (void)keyUp:(NSEvent *)theEvent
 {
-    if (_data->_impl->ignoresNonWheelEvents())
-        return;
-
-    LOG(TextInput, "keyUp:%p %@", theEvent, theEvent);
-
-    [self _interpretKeyEvent:theEvent completionHandler:^(BOOL handledByInputMethod, const Vector<KeypressCommand>& commands) {
-        ASSERT(!handledByInputMethod || commands.isEmpty());
-        _data->_page->handleKeyboardEvent(NativeWebKeyboardEvent(theEvent, handledByInputMethod, commands));
-    }];
+    _data->_impl->keyUp(theEvent);
 }
 
 - (void)keyDown:(NSEvent *)theEvent
 {
-    if (_data->_impl->ignoresNonWheelEvents())
-        return;
-
-    LOG(TextInput, "keyDown:%p %@%s", theEvent, theEvent, (theEvent == _data->_keyDownEventBeingResent) ? " (re-sent)" : "");
-
-    if (_data->_impl->tryHandlePluginComplexTextInputKeyDown(theEvent)) {
-        LOG(TextInput, "...handled by plug-in");
-        return;
-    }
-
-    // We could be receiving a key down from AppKit if we have re-sent an event
-    // that maps to an action that is currently unavailable (for example a copy when
-    // there is no range selection).
-    // If this is the case we should ignore the key down.
-    if (_data->_keyDownEventBeingResent == theEvent) {
-        [super keyDown:theEvent];
-        return;
-    }
-
-    [self _interpretKeyEvent:theEvent completionHandler:^(BOOL handledByInputMethod, const Vector<KeypressCommand>& commands) {
-        ASSERT(!handledByInputMethod || commands.isEmpty());
-        _data->_page->handleKeyboardEvent(NativeWebKeyboardEvent(theEvent, handledByInputMethod, commands));
-    }];
+    _data->_impl->keyDown(theEvent);
 }
 
 - (void)flagsChanged:(NSEvent *)theEvent
 {
-    if (_data->_impl->ignoresNonWheelEvents())
-        return;
-
-    LOG(TextInput, "flagsChanged:%p %@", theEvent, theEvent);
-
-    unsigned short keyCode = [theEvent keyCode];
-
-    // Don't make an event from the num lock and function keys
-    if (!keyCode || keyCode == 10 || keyCode == 63)
-        return;
-
-    [self _interpretKeyEvent:theEvent completionHandler:^(BOOL handledByInputMethod, const Vector<KeypressCommand>& commands) {
-        _data->_page->handleKeyboardEvent(NativeWebKeyboardEvent(theEvent, handledByInputMethod, commands));
-    }];
-}
-
-#else // USE(ASYNC_NSTEXTINPUTCLIENT)
-
-- (BOOL)_interpretKeyEvent:(NSEvent *)event savingCommandsTo:(Vector<WebCore::KeypressCommand>&)commands
-{
-    ASSERT(!_data->_interpretKeyEventsParameters);
-    ASSERT(commands.isEmpty());
-
-    if ([event type] == NSFlagsChanged)
-        return NO;
-
-    WKViewInterpretKeyEventsParameters parameters;
-    parameters.eventInterpretationHadSideEffects = false;
-    parameters.executingSavedKeypressCommands = false;
-    // We assume that an input method has consumed the event, and only change this assumption if one of the NSTextInput methods is called.
-    // We assume the IM will *not* consume hotkey sequences.
-    parameters.consumedByIM = !([event modifierFlags] & NSCommandKeyMask);
-    parameters.commands = &commands;
-    _data->_interpretKeyEventsParameters = &parameters;
-
-    LOG(TextInput, "-> interpretKeyEvents:%p %@", event, event);
-    [self interpretKeyEvents:[NSArray arrayWithObject:event]];
-
-    _data->_interpretKeyEventsParameters = nullptr;
-
-    // An input method may consume an event and not tell us (e.g. when displaying a candidate window),
-    // in which case we should not bubble the event up the DOM.
-    if (parameters.consumedByIM) {
-        ASSERT(commands.isEmpty());
-        LOG(TextInput, "...event %p was consumed by an input method", event);
-        return YES;
-    }
-
-    LOG(TextInput, "...interpretKeyEvents for event %p done, returns %d", event, parameters.eventInterpretationHadSideEffects);
-
-    // If we have already executed all or some of the commands, the event is "handled". Note that there are additional checks on web process side.
-    return parameters.eventInterpretationHadSideEffects;
-}
-
-- (void)_executeSavedKeypressCommands
-{
-    WKViewInterpretKeyEventsParameters* parameters = _data->_interpretKeyEventsParameters;
-    if (!parameters || parameters->commands->isEmpty())
-        return;
-
-    // We could be called again if the execution of one command triggers a call to selectedRange.
-    // In this case, the state is up to date, and we don't need to execute any more saved commands to return a result.
-    if (parameters->executingSavedKeypressCommands)
-        return;
-
-    LOG(TextInput, "Executing %u saved keypress commands...", parameters->commands->size());
-
-    parameters->executingSavedKeypressCommands = true;
-    parameters->eventInterpretationHadSideEffects |= _data->_page->executeKeypressCommands(*parameters->commands);
-    parameters->commands->clear();
-    parameters->executingSavedKeypressCommands = false;
-
-    LOG(TextInput, "...done executing saved keypress commands.");
-}
-
-- (void)doCommandBySelector:(SEL)selector
-{
-    LOG(TextInput, "doCommandBySelector:\"%s\"", sel_getName(selector));
-
-    WKViewInterpretKeyEventsParameters* parameters = _data->_interpretKeyEventsParameters;
-    if (parameters)
-        parameters->consumedByIM = false;
-
-    // As in insertText:replacementRange:, we assume that the call comes from an input method if there is marked text.
-    bool isFromInputMethod = _data->_page->editorState().hasComposition;
-
-    if (parameters && !isFromInputMethod) {
-        KeypressCommand command(NSStringFromSelector(selector));
-        parameters->commands->append(command);
-        LOG(TextInput, "...stored");
-        _data->_page->registerKeypressCommandName(command.commandName);
-    } else {
-        // FIXME: Send the command to Editor synchronously and only send it along the
-        // responder chain if it's a selector that does not correspond to an editing command.
-        [super doCommandBySelector:selector];
-    }
-}
-
-- (void)insertText:(id)string
-{
-    // Unlike an NSTextInputClient variant with replacementRange, this NSResponder method is called when there is no input context,
-    // so text input processing isn't performed. We are not going to actually insert any text in that case, but saving an insertText
-    // command ensures that a keypress event is dispatched as appropriate.
-    [self insertText:string replacementRange:NSMakeRange(NSNotFound, 0)];
+    _data->_impl->flagsChanged(theEvent);
 }
 
-- (void)insertText:(id)string replacementRange:(NSRange)replacementRange
+- (void)setMarkedText:(id)string selectedRange:(NSRange)newSelectedRange replacementRange:(NSRange)replacementRange
 {
-    BOOL isAttributedString = [string isKindOfClass:[NSAttributedString class]];
-    ASSERT(isAttributedString || [string isKindOfClass:[NSString class]]);
-
-    if (replacementRange.location != NSNotFound)
-        LOG(TextInput, "insertText:\"%@\" replacementRange:(%u, %u)", isAttributedString ? [string string] : string, replacementRange.location, replacementRange.length);
-    else
-        LOG(TextInput, "insertText:\"%@\"", isAttributedString ? [string string] : string);
-    WKViewInterpretKeyEventsParameters* parameters = _data->_interpretKeyEventsParameters;
-    if (parameters)
-        parameters->consumedByIM = false;
-
-    NSString *text;
-    bool isFromInputMethod = _data->_page->editorState().hasComposition;
-
-    Vector<TextAlternativeWithRange> dictationAlternatives;
-
-    if (isAttributedString) {
-#if USE(DICTATION_ALTERNATIVES)
-        collectDictationTextAlternatives(string, dictationAlternatives);
-#endif
-        // FIXME: We ignore most attributes from the string, so for example inserting from Character Palette loses font and glyph variation data.
-        text = [string string];
-    } else
-        text = string;
-
-    // insertText can be called for several reasons:
-    // - If it's from normal key event processing (including key bindings), we may need to save the action to perform it later.
-    // - If it's from an input method, then we should insert the text now. We assume it's from the input method if we have marked text.
-    // FIXME: In theory, this could be wrong for some input methods, so we should try to find another way to determine if the call is from the input method.
-    // - If it's sent outside of keyboard event processing (e.g. from Character Viewer, or when confirming an inline input area with a mouse),
-    // then we also execute it immediately, as there will be no other chance.
-    if (parameters && !isFromInputMethod) {
-        // FIXME: Handle replacementRange in this case, too. It's known to occur in practice when canceling Press and Hold (see <rdar://11940670>).
-        ASSERT(replacementRange.location == NSNotFound);
-        KeypressCommand command("insertText:", text);
-        parameters->commands->append(command);
-        _data->_page->registerKeypressCommandName(command.commandName);
-        return;
-    }
-
-    String eventText = text;
-    eventText.replace(NSBackTabCharacter, NSTabCharacter); // same thing is done in KeyEventMac.mm in WebCore
-    bool eventHandled;
-    if (!dictationAlternatives.isEmpty())
-        eventHandled = _data->_page->insertDictatedText(eventText, replacementRange, dictationAlternatives);
-    else
-        eventHandled = _data->_page->insertText(eventText, replacementRange);
-
-    if (parameters)
-        parameters->eventInterpretationHadSideEffects |= eventHandled;
+    _data->_impl->setMarkedText(string, newSelectedRange, replacementRange);
 }
 
-- (NSTextInputContext *)inputContext
+- (void)unmarkText
 {
-    WKViewInterpretKeyEventsParameters* parameters = _data->_interpretKeyEventsParameters;
-
-    if (_data->_impl->pluginComplexTextInputIdentifier() && !parameters)
-        return [[WKTextInputWindowController sharedTextInputWindowController] inputContext];
-
-    // Disable text input machinery when in non-editable content. An invisible inline input area affects performance, and can prevent Expose from working.
-    if (!_data->_page->editorState().isContentEditable)
-        return nil;
-
-    return [super inputContext];
+    _data->_impl->unmarkText();
 }
 
 - (NSRange)selectedRange
 {
-    [self _executeSavedKeypressCommands];
-
-    EditingRange selectedRange;
-    _data->_page->getSelectedRange(selectedRange);
-
-    NSRange result = selectedRange;
-    if (result.location == NSNotFound)
-        LOG(TextInput, "selectedRange -> (NSNotFound, %u)", result.length);
-    else
-        LOG(TextInput, "selectedRange -> (%u, %u)", result.location, result.length);
-
-    return result;
+    return _data->_impl->selectedRange();
 }
 
 - (BOOL)hasMarkedText
 {
-    WKViewInterpretKeyEventsParameters* parameters = _data->_interpretKeyEventsParameters;
-
-    BOOL result;
-    if (parameters) {
-        result = _data->_page->editorState().hasComposition;
-        if (result) {
-            // A saved command can confirm a composition, but it cannot start a new one.
-            [self _executeSavedKeypressCommands];
-            result = _data->_page->editorState().hasComposition;
-        }
-    } else {
-        EditingRange markedRange;
-        _data->_page->getMarkedRange(markedRange);
-        result = markedRange.location != notFound;
-    }
-
-    LOG(TextInput, "hasMarkedText -> %u", result);
-    return result;
-}
-
-- (void)unmarkText
-{
-    [self _executeSavedKeypressCommands];
-
-    LOG(TextInput, "unmarkText");
-
-    // Use pointer to get parameters passed to us by the caller of interpretKeyEvents.
-    WKViewInterpretKeyEventsParameters* parameters = _data->_interpretKeyEventsParameters;
-
-    if (parameters) {
-        parameters->eventInterpretationHadSideEffects = true;
-        parameters->consumedByIM = false;
-    }
-
-    _data->_page->confirmComposition();
-}
-
-- (void)setMarkedText:(id)string selectedRange:(NSRange)newSelectedRange replacementRange:(NSRange)replacementRange
-{
-    [self _executeSavedKeypressCommands];
-
-    BOOL isAttributedString = [string isKindOfClass:[NSAttributedString class]];
-    ASSERT(isAttributedString || [string isKindOfClass:[NSString class]]);
-
-    LOG(TextInput, "setMarkedText:\"%@\" selectedRange:(%u, %u)", isAttributedString ? [string string] : string, newSelectedRange.location, newSelectedRange.length);
-
-    // Use pointer to get parameters passed to us by the caller of interpretKeyEvents.
-    WKViewInterpretKeyEventsParameters* parameters = _data->_interpretKeyEventsParameters;
-
-    if (parameters) {
-        parameters->eventInterpretationHadSideEffects = true;
-        parameters->consumedByIM = false;
-    }
-    
-    Vector<CompositionUnderline> underlines;
-    NSString *text;
-
-    if (isAttributedString) {
-        // FIXME: We ignore most attributes from the string, so an input method cannot specify e.g. a font or a glyph variation.
-        text = [string string];
-        extractUnderlines(string, underlines);
-    } else
-        text = string;
-
-    if (_data->_page->editorState().isInPasswordField) {
-        // In password fields, we only allow ASCII dead keys, and don't allow inline input, matching NSSecureTextInputField.
-        // Allowing ASCII dead keys is necessary to enable full Roman input when using a Vietnamese keyboard.
-        ASSERT(!_data->_page->editorState().hasComposition);
-        _data->_impl->notifyInputContextAboutDiscardedComposition();
-        if ([text length] == 1 && [[text decomposedStringWithCanonicalMapping] characterAtIndex:0] < 0x80) {
-            _data->_page->insertText(text, replacementRange);
-        } else
-            NSBeep();
-        return;
-    }
-
-    _data->_page->setComposition(text, underlines, newSelectedRange, replacementRange);
+    return _data->_impl->hasMarkedText();
 }
 
 - (NSRange)markedRange
 {
-    [self _executeSavedKeypressCommands];
-
-    EditingRange markedRange;
-    _data->_page->getMarkedRange(markedRange);
-
-    NSRange result = markedRange;
-    if (result.location == NSNotFound)
-        LOG(TextInput, "markedRange -> (NSNotFound, %u)", result.length);
-    else
-        LOG(TextInput, "markedRange -> (%u, %u)", result.location, result.length);
-
-    return result;
+    return _data->_impl->markedRange();
 }
 
 - (NSAttributedString *)attributedSubstringForProposedRange:(NSRange)nsRange actualRange:(NSRangePointer)actualRange
 {
-    [self _executeSavedKeypressCommands];
-
-    if (!_data->_page->editorState().isContentEditable) {
-        LOG(TextInput, "attributedSubstringFromRange:(%u, %u) -> nil", nsRange.location, nsRange.length);
-        return nil;
-    }
-
-    if (_data->_page->editorState().isInPasswordField)
-        return nil;
-
-    AttributedString result;
-    _data->_page->getAttributedSubstringFromRange(nsRange, result);
-
-    if (actualRange) {
-        *actualRange = nsRange;
-        actualRange->length = [result.string length];
-    }
-
-    LOG(TextInput, "attributedSubstringFromRange:(%u, %u) -> \"%@\"", nsRange.location, nsRange.length, [result.string string]);
-    return [[result.string retain] autorelease];
+    return _data->_impl->attributedSubstringForProposedRange(nsRange, actualRange);
 }
 
 - (NSUInteger)characterIndexForPoint:(NSPoint)thePoint
 {
-    [self _executeSavedKeypressCommands];
-
-    NSWindow *window = [self window];
-    
-#pragma clang diagnostic push
-#pragma clang diagnostic ignored "-Wdeprecated-declarations"
-    if (window)
-        thePoint = [window convertScreenToBase:thePoint];
-#pragma clang diagnostic pop
-    thePoint = [self convertPoint:thePoint fromView:nil];  // the point is relative to the main frame
-    
-    uint64_t result = _data->_page->characterIndexForPoint(IntPoint(thePoint));
-    if (result == notFound)
-        result = NSNotFound;
-    LOG(TextInput, "characterIndexForPoint:(%f, %f) -> %u", thePoint.x, thePoint.y, result);
-    return result;
+    return _data->_impl->characterIndexForPoint(thePoint);
 }
 
 - (NSRect)firstRectForCharacterRange:(NSRange)theRange actualRange:(NSRangePointer)actualRange
-{ 
-    [self _executeSavedKeypressCommands];
-
-    // Just to match NSTextView's behavior. Regression tests cannot detect this;
-    // to reproduce, use a test application from http://bugs.webkit.org/show_bug.cgi?id=4682
-    // (type something; try ranges (1, -1) and (2, -1).
-    if ((theRange.location + theRange.length < theRange.location) && (theRange.location + theRange.length != 0))
-        theRange.length = 0;
-
-    if (theRange.location == NSNotFound) {
-        if (actualRange)
-            *actualRange = theRange;
-        LOG(TextInput, "firstRectForCharacterRange:(NSNotFound, %u) -> NSZeroRect", theRange.length);
-        return NSZeroRect;
-    }
-
-    NSRect resultRect = _data->_page->firstRectForCharacterRange(theRange);
-    resultRect = [self convertRect:resultRect toView:nil];
-    resultRect = [self.window convertRectToScreen:resultRect];
+{
+    return _data->_impl->firstRectForCharacterRange(theRange, actualRange);
+}
 
-    if (actualRange) {
-        // FIXME: Update actualRange to match the range of first rect.
-        *actualRange = theRange;
-    }
+#if USE(ASYNC_NSTEXTINPUTCLIENT)
 
-    LOG(TextInput, "firstRectForCharacterRange:(%u, %u) -> (%f, %f, %f, %f)", theRange.location, theRange.length, resultRect.origin.x, resultRect.origin.y, resultRect.size.width, resultRect.size.height);
-    return resultRect;
+- (void)selectedRangeWithCompletionHandler:(void(^)(NSRange selectedRange))completionHandlerPtr
+{
+    _data->_impl->selectedRangeWithCompletionHandler(completionHandlerPtr);
 }
 
-- (BOOL)performKeyEquivalent:(NSEvent *)event
+- (void)markedRangeWithCompletionHandler:(void(^)(NSRange markedRange))completionHandlerPtr
 {
-    if (_data->_impl->ignoresNonWheelEvents())
-        return NO;
-
-    // There's a chance that responding to this event will run a nested event loop, and
-    // fetching a new event might release the old one. Retaining and then autoreleasing
-    // the current event prevents that from causing a problem inside WebKit or AppKit code.
-    [[event retain] autorelease];
-
-    // We get Esc key here after processing either Esc or Cmd+period. The former starts as a keyDown, and the latter starts as a key equivalent,
-    // but both get transformed to a cancelOperation: command, executing which passes an Esc key event to -performKeyEquivalent:.
-    // Don't interpret this event again, avoiding re-entrancy and infinite loops.
-    if ([[event charactersIgnoringModifiers] isEqualToString:@"\e"] && !([event modifierFlags] & NSDeviceIndependentModifierFlagsMask))
-        return [super performKeyEquivalent:event];
-
-    if (_data->_keyDownEventBeingResent) {
-        // WebCore has already seen the event, no need for custom processing.
-        // Note that we can get multiple events for each event being re-sent. For example, for Cmd+'=' AppKit
-        // first performs the original key equivalent, and if that isn't handled, it dispatches a synthetic Cmd+'+'.
-        return [super performKeyEquivalent:event];
-    }
-
-    ASSERT(event == [NSApp currentEvent]);
-
-    _data->_impl->disableComplexTextInputIfNecessary();
-
-    // Pass key combos through WebCore if there is a key binding available for
-    // this event. This lets webpages have a crack at intercepting key-modified keypresses.
-    // FIXME: Why is the firstResponder check needed?
-    if (self == [[self window] firstResponder]) {
-        Vector<KeypressCommand> commands;
-        BOOL handledByInputMethod = [self _interpretKeyEvent:event savingCommandsTo:commands];
-        _data->_page->handleKeyboardEvent(NativeWebKeyboardEvent(event, handledByInputMethod, commands));
-        return YES;
-    }
-    
-    return [super performKeyEquivalent:event];
+    _data->_impl->markedRangeWithCompletionHandler(completionHandlerPtr);
 }
 
-- (void)keyUp:(NSEvent *)theEvent
+- (void)hasMarkedTextWithCompletionHandler:(void(^)(BOOL hasMarkedText))completionHandlerPtr
 {
-    if (_data->_impl->ignoresNonWheelEvents())
-        return;
-
-    LOG(TextInput, "keyUp:%p %@", theEvent, theEvent);
-    // We don't interpret the keyUp event, as this breaks key bindings (see <https://bugs.webkit.org/show_bug.cgi?id=130100>).
-    _data->_page->handleKeyboardEvent(NativeWebKeyboardEvent(theEvent, false, Vector<KeypressCommand>()));
+    _data->_impl->hasMarkedTextWithCompletionHandler(completionHandlerPtr);
 }
 
-- (void)keyDown:(NSEvent *)theEvent
+- (void)attributedSubstringForProposedRange:(NSRange)nsRange completionHandler:(void(^)(NSAttributedString *attrString, NSRange actualRange))completionHandlerPtr
 {
-    if (_data->_impl->ignoresNonWheelEvents())
-        return;
-
-    LOG(TextInput, "keyDown:%p %@%s", theEvent, theEvent, (theEvent == _data->_keyDownEventBeingResent) ? " (re-sent)" : "");
-
-    // There's a chance that responding to this event will run a nested event loop, and
-    // fetching a new event might release the old one. Retaining and then autoreleasing
-    // the current event prevents that from causing a problem inside WebKit or AppKit code.
-    [[theEvent retain] autorelease];
-
-    if (_data->_impl->tryHandlePluginComplexTextInputKeyDown(theEvent)) {
-        LOG(TextInput, "...handled by plug-in");
-        return;
-    }
-
-    // We could be receiving a key down from AppKit if we have re-sent an event
-    // that maps to an action that is currently unavailable (for example a copy when
-    // there is no range selection).
-    // If this is the case we should ignore the key down.
-    if (_data->_keyDownEventBeingResent == theEvent) {
-        [super keyDown:theEvent];
-        return;
-    }
-
-    Vector<KeypressCommand> commands;
-    BOOL handledByInputMethod = [self _interpretKeyEvent:theEvent savingCommandsTo:commands];
-    if (!commands.isEmpty()) {
-        // An input method may make several actions per keypress. For example, pressing Return with Korean IM both confirms it and sends a newline.
-        // IM-like actions are handled immediately (so the return value from UI process is true), but there are saved commands that
-        // should be handled like normal text input after DOM event dispatch.
-        handledByInputMethod = NO;
-    }
-
-    _data->_page->handleKeyboardEvent(NativeWebKeyboardEvent(theEvent, handledByInputMethod, commands));
+    _data->_impl->attributedSubstringForProposedRange(nsRange, completionHandlerPtr);
 }
 
-- (void)flagsChanged:(NSEvent *)theEvent
+- (void)firstRectForCharacterRange:(NSRange)theRange completionHandler:(void(^)(NSRect firstRect, NSRange actualRange))completionHandlerPtr
 {
-    if (_data->_impl->ignoresNonWheelEvents())
-        return;
-
-    LOG(TextInput, "flagsChanged:%p %@", theEvent, theEvent);
-
-    // There's a chance that responding to this event will run a nested event loop, and
-    // fetching a new event might release the old one. Retaining and then autoreleasing
-    // the current event prevents that from causing a problem inside WebKit or AppKit code.
-    [[theEvent retain] autorelease];
-
-    unsigned short keyCode = [theEvent keyCode];
-
-    // Don't make an event from the num lock and function keys
-    if (!keyCode || keyCode == 10 || keyCode == 63)
-        return;
+    _data->_impl->firstRectForCharacterRange(theRange, completionHandlerPtr);
+}
 
-    _data->_page->handleKeyboardEvent(NativeWebKeyboardEvent(theEvent, false, Vector<KeypressCommand>()));
+- (void)characterIndexForPoint:(NSPoint)thePoint completionHandler:(void(^)(NSUInteger))completionHandlerPtr
+{
+    _data->_impl->characterIndexForPoint(thePoint, completionHandlerPtr);
 }
 
 #endif // USE(ASYNC_NSTEXTINPUTCLIENT)
@@ -1731,6 +888,16 @@ static void extractUnderlines(NSAttributedString *string, Vector<CompositionUnde
     [super doCommandBySelector:selector];
 }
 
+- (BOOL)_superPerformKeyEquivalent:(NSEvent *)event
+{
+    return [super performKeyEquivalent:event];
+}
+
+- (void)_superKeyDown:(NSEvent *)event
+{
+    [super keyDown:event];
+}
+
 - (NSArray *)validAttributesForMarkedText
 {
     static NSArray *validAttributes;
@@ -1892,30 +1059,6 @@ static void extractUnderlines(NSAttributedString *string, Vector<CompositionUnde
     _data->_impl->quickLookWithEvent(event);
 }
 
-- (void)_doneWithKeyEvent:(NSEvent *)event eventWasHandled:(BOOL)eventWasHandled
-{
-    if ([event type] != NSKeyDown)
-        return;
-
-    if (_data->_impl->tryPostProcessPluginComplexTextInputKeyDown(event))
-        return;
-    
-    if (eventWasHandled) {
-        [NSCursor setHiddenUntilMouseMoves:YES];
-        return;
-    }
-
-    // resending the event may destroy this WKView
-    RetainPtr<WKView> protector(self);
-
-    ASSERT(!_data->_keyDownEventBeingResent);
-    _data->_keyDownEventBeingResent = event;
-    [NSApp _setCurrentEvent:event];
-    [NSApp sendEvent:event];
-
-    _data->_keyDownEventBeingResent = nullptr;
-}
-
 - (NSTrackingRectTag)addTrackingRect:(NSRect)rect owner:(id)owner userData:(void *)data assumeInside:(BOOL)assumeInside
 {
     return _data->_impl->addTrackingRect(NSRectToCGRect(rect), owner, data, assumeInside);
index 3da825b670e1aef3e26ad1a5df17558ef17570d3..d4a15dae31cf2db5bd33cfcff96b48ef4d6ce94f 100644 (file)
@@ -50,7 +50,6 @@ class WebProcessPool;
 @property (nonatomic, setter=_setThumbnailView:) _WKThumbnailView *_thumbnailView;
 #endif
 
-- (void)_doneWithKeyEvent:(NSEvent *)event eventWasHandled:(BOOL)eventWasHandled;
 - (void)_addFontPanelObserver;
 
 @end
index 4104226e6135c3452754b5a2dcf2dee6d246985b..cb44c0aa8770507f8baaddcf39764b99c0122750 100644 (file)
@@ -59,6 +59,8 @@ OBJC_CLASS _WKThumbnailView;
 - (void)_superSmartMagnifyWithEvent:(NSEvent *)event;
 - (id)_superAccessibilityAttributeValue:(NSString *)attribute;
 - (void)_superDoCommandBySelector:(SEL)selector;
+- (BOOL)_superPerformKeyEquivalent:(NSEvent *)event;
+- (void)_superKeyDown:(NSEvent *)event;
 
 // This is a hack; these things live can live on a category (e.g. WKView (Private)) but WKView itself conforms to this protocol.
 // They're not actually optional.
@@ -74,6 +76,10 @@ OBJC_CLASS _WKThumbnailView;
 
 @end
 
+namespace WebCore {
+struct KeyPressCommand;
+}
+
 namespace WebKit {
 
 class DrawingAreaProxy;
@@ -86,6 +92,15 @@ typedef id <NSValidatedUserInterfaceItem> ValidationItem;
 typedef Vector<RetainPtr<ValidationItem>> ValidationVector;
 typedef HashMap<String, ValidationVector> ValidationMap;
 
+#if !USE(ASYNC_NSTEXTINPUTCLIENT)
+struct WKViewInterpretKeyEventsParameters {
+    bool eventInterpretationHadSideEffects;
+    bool consumedByIM;
+    bool executingSavedKeypressCommands;
+    Vector<WebCore::KeypressCommand>* commands;
+};
+#endif
+
 class WebViewImpl {
     WTF_MAKE_FAST_ALLOCATED;
     WTF_MAKE_NONCOPYABLE(WebViewImpl);
@@ -392,6 +407,33 @@ public:
     void setTotalHeightOfBanners(CGFloat totalHeightOfBanners) { m_totalHeightOfBanners = totalHeightOfBanners; }
     CGFloat totalHeightOfBanners() const { return m_totalHeightOfBanners; }
 
+    void doneWithKeyEvent(NSEvent *, bool eventWasHandled);
+    void doCommandBySelector(SEL);
+    void insertText(id string);
+    void insertText(id string, NSRange replacementRange);
+    NSTextInputContext *inputContext();
+    void unmarkText();
+    void setMarkedText(id string, NSRange selectedRange, NSRange replacementRange);
+    NSRange selectedRange();
+    bool hasMarkedText();
+    NSRange markedRange();
+    NSAttributedString *attributedSubstringForProposedRange(NSRange, NSRangePointer actualRange);
+    NSUInteger characterIndexForPoint(NSPoint);
+    NSRect firstRectForCharacterRange(NSRange, NSRangePointer actualRange);
+    bool performKeyEquivalent(NSEvent *);
+    void keyUp(NSEvent *);
+    void keyDown(NSEvent *);
+    void flagsChanged(NSEvent *);
+
+#if USE(ASYNC_NSTEXTINPUTCLIENT)
+    void selectedRangeWithCompletionHandler(void(^)(NSRange));
+    void hasMarkedTextWithCompletionHandler(void(^)(BOOL hasMarkedText));
+    void markedRangeWithCompletionHandler(void(^)(NSRange));
+    void attributedSubstringForProposedRange(NSRange, void(^)(NSAttributedString *attrString, NSRange actualRange));
+    void firstRectForCharacterRange(NSRange, void(^)(NSRect firstRect, NSRange actualRange));
+    void characterIndexForPoint(NSPoint, void(^)(NSUInteger));
+#endif // USE(ASYNC_NSTEXTINPUTCLIENT)
+
 private:
     WeakPtr<WebViewImpl> createWeakPtr() { return m_weakPtrFactory.createWeakPtr(); }
 
@@ -411,6 +453,14 @@ private:
 
     void setUserInterfaceItemState(NSString *commandName, bool enabled, int state);
 
+#if USE(ASYNC_NSTEXTINPUTCLIENT)
+    void collectKeyboardLayoutCommandsForEvent(NSEvent *, Vector<WebCore::KeypressCommand>&);
+    void interpretKeyEvent(NSEvent *, void(^completionHandler)(BOOL handled, const Vector<WebCore::KeypressCommand>&));
+#else
+    void executeSavedKeypressCommands();
+    bool interpretKeyEvent(NSEvent *, Vector<WebCore::KeypressCommand>&);
+#endif
+
     NSView <WebViewImplDelegate> *m_view;
     WebPageProxy& m_page;
     PageClient& m_pageClient;
@@ -516,6 +566,16 @@ private:
     CGFloat m_totalHeightOfBanners { 0 };
 
     RetainPtr<NSView> m_inspectorAttachmentView;
+
+    // We keep here the event when resending it to
+    // the application to distinguish the case of a new event from one
+    // that has been already sent to WebCore.
+    RetainPtr<NSEvent> m_keyDownEventBeingResent;
+#if USE(ASYNC_NSTEXTINPUTCLIENT)
+    Vector<WebCore::KeypressCommand>* m_collectedKeypressCommands;
+#else
+    WKViewInterpretKeyEventsParameters* m_interpretKeyEventsParameters;
+#endif
 };
     
 } // namespace WebKit
index 9ffbb341b0ecb44a52d7a539f982a0d819eaea42..9e68cef9b86b975f064472e9136be5e629c4754f 100644 (file)
@@ -74,6 +74,8 @@
 #import <WebCore/NSWindowSPI.h>
 #import <WebCore/PlatformEventFactoryMac.h>
 #import <WebCore/SoftLinking.h>
+#import <WebCore/TextAlternativeWithRange.h>
+#import <WebCore/TextUndoInsertionMarkupMac.h>
 #import <WebCore/ViewState.h>
 #import <WebCore/WebActionDisablingCALayerDelegate.h>
 #import <WebCore/WebCoreCALayerExtras.h>
 
 SOFT_LINK_CONSTANT_MAY_FAIL(Lookup, LUNotificationPopoverWillClose, NSString *)
 
+// FIXME: Move to an SPI header.
+#if USE(ASYNC_NSTEXTINPUTCLIENT)
+@interface NSTextInputContext (WKNSTextInputContextDetails)
+- (void)handleEvent:(NSEvent *)event completionHandler:(void(^)(BOOL handled))completionHandler;
+- (void)handleEventByInputMethod:(NSEvent *)event completionHandler:(void(^)(BOOL handled))completionHandler;
+- (BOOL)handleEventByKeyboardLayout:(NSEvent *)event;
+@end
+#endif
+
 @interface WKWindowVisibilityObserver : NSObject {
     NSView *_view;
     WebKit::WebViewImpl *_impl;
@@ -532,7 +543,7 @@ bool WebViewImpl::becomeFirstResponder()
         NSEvent *keyboardEvent = nil;
         if ([event type] == NSKeyDown || [event type] == NSKeyUp)
             keyboardEvent = event;
-        m_page.setInitialFocus(direction == NSSelectingNext, keyboardEvent != nil, NativeWebKeyboardEvent(keyboardEvent, false, Vector<WebCore::KeypressCommand>()), [](WebKit::CallbackBase::Error) { });
+        m_page.setInitialFocus(direction == NSSelectingNext, keyboardEvent != nil, NativeWebKeyboardEvent(keyboardEvent, false, { }), [](WebKit::CallbackBase::Error) { });
     }
     return true;
 }
@@ -3068,6 +3079,966 @@ void WebViewImpl::gestureEventWasNotHandledByWebCoreFromViewOnly(NSEvent *event)
 #endif
 }
 
+void WebViewImpl::doneWithKeyEvent(NSEvent *event, bool eventWasHandled)
+{
+    if ([event type] != NSKeyDown)
+        return;
+
+    if (tryPostProcessPluginComplexTextInputKeyDown(event))
+        return;
+
+    if (eventWasHandled) {
+        [NSCursor setHiddenUntilMouseMoves:YES];
+        return;
+    }
+
+    // resending the event may destroy this WKView
+    RetainPtr<NSView> protector(m_view);
+
+    ASSERT(!m_keyDownEventBeingResent);
+    m_keyDownEventBeingResent = event;
+    [NSApp _setCurrentEvent:event];
+    [NSApp sendEvent:event];
+    
+    m_keyDownEventBeingResent = nullptr;
+}
+
+static Vector<WebCore::CompositionUnderline> extractUnderlines(NSAttributedString *string)
+{
+    Vector<WebCore::CompositionUnderline> result;
+    int length = string.string.length;
+
+    for (int i = 0; i < length;) {
+        NSRange range;
+        NSDictionary *attrs = [string attributesAtIndex:i longestEffectiveRange:&range inRange:NSMakeRange(i, length - i)];
+
+        if (NSNumber *style = [attrs objectForKey:NSUnderlineStyleAttributeName]) {
+            WebCore::Color color = WebCore::Color::black;
+            if (NSColor *colorAttr = [attrs objectForKey:NSUnderlineColorAttributeName])
+                color = WebCore::colorFromNSColor(colorAttr);
+            result.append(WebCore::CompositionUnderline(range.location, NSMaxRange(range), color, style.intValue > 1));
+        }
+        
+        i = range.location + range.length;
+    }
+
+    return result;
+}
+
+static bool eventKeyCodeIsZeroOrNumLockOrFn(NSEvent *event)
+{
+    unsigned short keyCode = [event keyCode];
+    return !keyCode || keyCode == 10 || keyCode == 63;
+}
+
+#if USE(ASYNC_NSTEXTINPUTCLIENT)
+
+Vector<WebCore::KeypressCommand> WebViewImpl::collectKeyboardLayoutCommandsForEvent(NSEvent *event)
+{
+    Vector<WebCore::KeypressCommand>& commands;
+
+    if ([event type] != NSKeyDown)
+        return;
+
+    ASSERT(!m_collectedKeypressCommands);
+    m_collectedKeypressCommands = &commands;
+
+    if (NSTextInputContext *context = inputContext())
+        [context handleEventByKeyboardLayout:event];
+    else
+        [m_view interpretKeyEvents:[NSArray arrayWithObject:event]];
+
+    m_collectedKeypressCommands = nullptr;
+
+    return commands;
+}
+
+void WebViewImpl::interpretKeyEvent(NSEvent *event, void(^completionHandler)(BOOL handled, const Vector<WebCore::KeypressCommand>& commands))
+{
+    // For regular Web content, input methods run before passing a keydown to DOM, but plug-ins get an opportunity to handle the event first.
+    // There is no need to collect commands, as the plug-in cannot execute them.
+    if (pluginComplexTextInputIdentifier()) {
+        completionHandler(NO, { });
+        return;
+    }
+
+    if (!inputContext()) {
+        auto commands = collectKeyboardLayoutCommandsForEvent(event);
+        completionHandler(NO, commands);
+        return;
+    }
+
+    LOG(TextInput, "-> handleEventByInputMethod:%p %@", event, event);
+    [inputContext() handleEventByInputMethod:event completionHandler:^(BOOL handled) {
+        
+        LOG(TextInput, "... handleEventByInputMethod%s handled", handled ? "" : " not");
+        if (handled) {
+            completionHandler(YES, { });
+            return;
+        }
+
+        auto commands = collectKeyboardLayoutCommandsForEvent(event);
+        completionHandler(NO, commands);
+    }];
+}
+
+void WebViewImpl::doCommandBySelector(SEL selector)
+{
+    LOG(TextInput, "doCommandBySelector:\"%s\"", sel_getName(selector));
+
+    if (auto* keypressCommands = m_collectedKeypressCommands) {
+        WebCore::KeypressCommand command(NSStringFromSelector(selector));
+        keypressCommands->append(command);
+        LOG(TextInput, "...stored");
+        m_page.registerKeypressCommandName(command.commandName);
+    } else {
+        // FIXME: Send the command to Editor synchronously and only send it along the
+        // responder chain if it's a selector that does not correspond to an editing command.
+        [m_view _superDoCommandBySelector:selector];
+    }
+}
+
+void WebViewImpl::insertText(id string)
+{
+    // Unlike an NSTextInputClient variant with replacementRange, this NSResponder method is called when there is no input context,
+    // so text input processing isn't performed. We are not going to actually insert any text in that case, but saving an insertText
+    // command ensures that a keypress event is dispatched as appropriate.
+    insertText(string, NSMakeRange(NSNotFound, 0));
+}
+
+void WebViewImpl::insertText(id string, NSRange replacementRange)
+{
+    BOOL isAttributedString = [string isKindOfClass:[NSAttributedString class]];
+    ASSERT(isAttributedString || [string isKindOfClass:[NSString class]]);
+
+    if (replacementRange.location != NSNotFound)
+        LOG(TextInput, "insertText:\"%@\" replacementRange:(%u, %u)", isAttributedString ? [string string] : string, replacementRange.location, replacementRange.length);
+    else
+        LOG(TextInput, "insertText:\"%@\"", isAttributedString ? [string string] : string);
+
+    NSString *text;
+    Vector<WebCore::TextAlternativeWithRange> dictationAlternatives;
+
+    bool registerUndoGroup = false;
+    if (isAttributedString) {
+#if USE(DICTATION_ALTERNATIVES)
+        WebCore::collectDictationTextAlternatives(string, dictationAlternatives);
+#endif
+#if USE(INSERTION_UNDO_GROUPING)
+        registerUndoGroup = WebCore::shouldRegisterInsertionUndoGroup(string);
+#endif
+        // FIXME: We ignore most attributes from the string, so for example inserting from Character Palette loses font and glyph variation data.
+        text = [string string];
+    } else
+        text = string;
+
+    // insertText can be called for several reasons:
+    // - If it's from normal key event processing (including key bindings), we save the action to perform it later.
+    // - If it's from an input method, then we should insert the text now.
+    // - If it's sent outside of keyboard event processing (e.g. from Character Viewer, or when confirming an inline input area with a mouse),
+    // then we also execute it immediately, as there will be no other chance.
+    Vector<WebCore::KeypressCommand>* keypressCommands = m_collectedKeypressCommands;
+    if (keypressCommands) {
+        ASSERT(replacementRange.location == NSNotFound);
+        WebCore::KeypressCommand command("insertText:", text);
+        keypressCommands->append(command);
+        LOG(TextInput, "...stored");
+        m_page.registerKeypressCommandName(command.commandName);
+        return;
+    }
+
+    String eventText = text;
+    eventText.replace(NSBackTabCharacter, NSTabCharacter); // same thing is done in KeyEventMac.mm in WebCore
+    if (!dictationAlternatives.isEmpty())
+        m_page.insertDictatedTextAsync(eventText, replacementRange, dictationAlternatives, registerUndoGroup);
+    else
+        m_page.insertTextAsync(eventText, replacementRange, registerUndoGroup);
+}
+
+void WebViewImpl::selectedRangeWithCompletionHandler(void(^completionHandlerPtr)(NSRange selectedRange))
+{
+    auto completionHandler = adoptNS([completionHandlerPtr copy]);
+
+    LOG(TextInput, "selectedRange");
+    m_page.getSelectedRangeAsync([completionHandler](const EditingRange& editingRangeResult, WebKit::CallbackBase::Error error) {
+        void (^completionHandlerBlock)(NSRange) = (void (^)(NSRange))completionHandler.get();
+        if (error != WebKit::CallbackBase::Error::None) {
+            LOG(TextInput, "    ...selectedRange failed.");
+            completionHandlerBlock(NSMakeRange(NSNotFound, 0));
+            return;
+        }
+        NSRange result = editingRangeResult;
+        if (result.location == NSNotFound)
+            LOG(TextInput, "    -> selectedRange returned (NSNotFound, %llu)", result.length);
+        else
+            LOG(TextInput, "    -> selectedRange returned (%llu, %llu)", result.location, result.length);
+        completionHandlerBlock(result);
+    });
+}
+
+void WebViewImpl::markedRangeWithCompletionHandler(void(^completionHandlerPtr)(NSRange markedRange))
+{
+    auto completionHandler = adoptNS([completionHandlerPtr copy]);
+
+    LOG(TextInput, "markedRange");
+    m_page.getMarkedRangeAsync([completionHandler](const EditingRange& editingRangeResult, WebKit::CallbackBase::Error error) {
+        void (^completionHandlerBlock)(NSRange) = (void (^)(NSRange))completionHandler.get();
+        if (error != WebKit::CallbackBase::Error::None) {
+            LOG(TextInput, "    ...markedRange failed.");
+            completionHandlerBlock(NSMakeRange(NSNotFound, 0));
+            return;
+        }
+        NSRange result = editingRangeResult;
+        if (result.location == NSNotFound)
+            LOG(TextInput, "    -> markedRange returned (NSNotFound, %llu)", result.length);
+        else
+            LOG(TextInput, "    -> markedRange returned (%llu, %llu)", result.location, result.length);
+        completionHandlerBlock(result);
+    });
+}
+
+void WebViewImpl::hasMarkedTextWithCompletionHandler(void(^completionHandlerPtr)(BOOL hasMarkedText))
+{
+    auto completionHandler = adoptNS([completionHandlerPtr copy]);
+
+    LOG(TextInput, "hasMarkedText");
+    m_page.getMarkedRangeAsync([completionHandler](const EditingRange& editingRangeResult, WebKit::CallbackBase::Error error) {
+        void (^completionHandlerBlock)(BOOL) = (void (^)(BOOL))completionHandler.get();
+        if (error != WebKit::CallbackBase::Error::None) {
+            LOG(TextInput, "    ...hasMarkedText failed.");
+            completionHandlerBlock(NO);
+            return;
+        }
+        BOOL hasMarkedText = editingRangeResult.location != notFound;
+        LOG(TextInput, "    -> hasMarkedText returned %u", hasMarkedText);
+        completionHandlerBlock(hasMarkedText);
+    });
+}
+
+void WebViewImpl::attributedSubstringForProposedRange(NSRange proposedRange, void(^completionHandlerPtr)(NSAttributedString *attrString, NSRange actualRange))
+{
+    auto completionHandler = adoptNS([completionHandlerPtr copy]);
+
+    LOG(TextInput, "attributedSubstringFromRange:(%u, %u)", proposedRange.location, proposedRange.length);
+    m_page.attributedSubstringForCharacterRangeAsync(proposedRange, [completionHandler](const AttributedString& string, const EditingRange& actualRange, WebKit::CallbackBase::Error error) {
+        void (^completionHandlerBlock)(NSAttributedString *, NSRange) = (void (^)(NSAttributedString *, NSRange))completionHandler.get();
+        if (error != WebKit::CallbackBase::Error::None) {
+            LOG(TextInput, "    ...attributedSubstringFromRange failed.");
+            completionHandlerBlock(0, NSMakeRange(NSNotFound, 0));
+            return;
+        }
+        LOG(TextInput, "    -> attributedSubstringFromRange returned %@", [string.string.get() string]);
+        completionHandlerBlock([[string.string.get() retain] autorelease], actualRange);
+    });
+}
+
+void WebViewImpl::firstRectForCharacterRange(NSRange range, void(^completionHandlerPtr)(NSRect firstRect, NSRange actualRange))
+{
+    auto completionHandler = adoptNS([completionHandlerPtr copy]);
+
+    LOG(TextInput, "firstRectForCharacterRange:(%u, %u)", range.location, range.length);
+
+    // Just to match NSTextView's behavior. Regression tests cannot detect this;
+    // to reproduce, use a test application from http://bugs.webkit.org/show_bug.cgi?id=4682
+    // (type something; try ranges (1, -1) and (2, -1).
+    if ((range.location + range.length < range.location) && (range.location + range.length != 0))
+        range.length = 0;
+
+    if (range.location == NSNotFound) {
+        LOG(TextInput, "    -> NSZeroRect");
+        completionHandlerPtr(NSZeroRect, range);
+        return;
+    }
+
+    auto weakThis = createWeakPtr();
+    m_page.firstRectForCharacterRangeAsync(range, [weakThis, completionHandler](const WebCore::IntRect& rect, const EditingRange& actualRange, WebKit::CallbackBase::Error error) {
+        if (!weakThis)
+            return;
+
+        void (^completionHandlerBlock)(NSRect, NSRange) = (void (^)(NSRect, NSRange))completionHandler.get();
+        if (error != WebKit::CallbackBase::Error::None) {
+            LOG(TextInput, "    ...firstRectForCharacterRange failed.");
+            completionHandlerBlock(NSZeroRect, NSMakeRange(NSNotFound, 0));
+            return;
+        }
+
+        NSRect resultRect = [weakThis->m_view convertRect:rect toView:nil];
+        resultRect = [weakThis->m_view.window convertRectToScreen:resultRect];
+
+        LOG(TextInput, "    -> firstRectForCharacterRange returned (%f, %f, %f, %f)", resultRect.origin.x, resultRect.origin.y, resultRect.size.width, resultRect.size.height);
+        completionHandlerBlock(resultRect, actualRange);
+    });
+}
+
+void WebViewImpl::characterIndexForPoint(NSPoint point, void(^completionHandlerPtr)(NSUInteger))
+{
+    auto completionHandler = adoptNS([completionHandlerPtr copy]);
+
+    LOG(TextInput, "characterIndexForPoint:(%f, %f)", point.x, point.y);
+
+    NSWindow *window = m_view.window;
+
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wdeprecated-declarations"
+    if (window)
+        point = [window convertScreenToBase:point];
+#pragma clang diagnostic pop
+    point = [m_view convertPoint:point fromView:nil];  // the point is relative to the main frame
+
+    m_page.characterIndexForPointAsync(WebCore::IntPoint(point), [completionHandler](uint64_t result, WebKit::CallbackBase::Error error) {
+        void (^completionHandlerBlock)(NSUInteger) = (void (^)(NSUInteger))completionHandler.get();
+        if (error != WebKit::CallbackBase::Error::None) {
+            LOG(TextInput, "    ...characterIndexForPoint failed.");
+            completionHandlerBlock(0);
+            return;
+        }
+        if (result == notFound)
+            result = NSNotFound;
+        LOG(TextInput, "    -> characterIndexForPoint returned %lu", result);
+        completionHandlerBlock(result);
+    });
+}
+
+NSTextInputContext *WebViewImpl::inputContext()
+{
+    if (pluginComplexTextInputIdentifier()) {
+        ASSERT(!m_collectedKeypressCommands); // Should not get here from -_interpretKeyEvent:completionHandler:, we only use WKTextInputWindowController after giving the plug-in a chance to handle keydown natively.
+        return [[WKTextInputWindowController sharedTextInputWindowController] inputContext];
+    }
+
+    // Disable text input machinery when in non-editable content. An invisible inline input area affects performance, and can prevent Expose from working.
+    if (!m_page.editorState().isContentEditable)
+        return nil;
+
+    return [m_view _superInputContext];
+}
+
+void WebViewImpl::unmarkText()
+{
+    LOG(TextInput, "unmarkText");
+
+    m_page.confirmCompositionAsync();
+}
+
+void WebViewImpl::setMarkedText(id string, NSRange selectedRange, NSRange replacementRange)
+{
+    BOOL isAttributedString = [string isKindOfClass:[NSAttributedString class]];
+    ASSERT(isAttributedString || [string isKindOfClass:[NSString class]]);
+
+    LOG(TextInput, "setMarkedText:\"%@\" selectedRange:(%u, %u) replacementRange:(%u, %u)", isAttributedString ? [string string] : string, selectedRange.location, selectedRange.length, replacementRange.location, replacementRange.length);
+
+    Vector<WebCore::CompositionUnderline> underlines;
+    NSString *text;
+
+    if (isAttributedString) {
+        // FIXME: We ignore most attributes from the string, so an input method cannot specify e.g. a font or a glyph variation.
+        text = [string string];
+        underlines = extractUnderlines(string);
+    } else
+        text = string;
+
+    if (inSecureInputState()) {
+        // In password fields, we only allow ASCII dead keys, and don't allow inline input, matching NSSecureTextInputField.
+        // Allowing ASCII dead keys is necessary to enable full Roman input when using a Vietnamese keyboard.
+        ASSERT(!m_page.editorState().hasComposition);
+        notifyInputContextAboutDiscardedComposition();
+        // FIXME: We should store the command to handle it after DOM event processing, as it's regular keyboard input now, not a composition.
+        if ([text length] == 1 && isASCII([text characterAtIndex:0]))
+            m_page.insertTextAsync(text, replacementRange);
+        else
+            NSBeep();
+        return;
+    }
+
+    m_page.setCompositionAsync(text, underlines, selectedRange, replacementRange);
+}
+
+// Synchronous NSTextInputClient is still implemented to catch spurious sync calls. Remove when that is no longer needed.
+
+NSRange WebViewImpl::selectedRange()
+{
+    ASSERT_NOT_REACHED();
+    return NSMakeRange(NSNotFound, 0);
+}
+
+bool WebViewImpl::hasMarkedText()
+{
+    ASSERT_NOT_REACHED();
+    return NO;
+}
+
+NSRange WebViewImpl::markedRange()
+{
+    ASSERT_NOT_REACHED();
+    return NSMakeRange(NSNotFound, 0);
+}
+
+NSAttributedString *WebViewImpl::attributedSubstringForProposedRange(NSRange nsRange, NSRangePointer actualRange)
+{
+    ASSERT_NOT_REACHED();
+    return nil;
+}
+
+NSUInteger WebViewImpl::characterIndexForPoint(NSPoint point)
+{
+    ASSERT_NOT_REACHED();
+    return 0;
+}
+
+NSRect WebViewImpl::firstRectForCharacterRange(NSRange range, NSRangePointer actualRange)
+{
+    ASSERT_NOT_REACHED();
+    return NSZeroRect;
+}
+
+bool WebViewImpl::performKeyEquivalent(NSEvent *event)
+{
+    if (ignoresNonWheelEvents())
+        return NO;
+
+    // There's a chance that responding to this event will run a nested event loop, and
+    // fetching a new event might release the old one. Retaining and then autoreleasing
+    // the current event prevents that from causing a problem inside WebKit or AppKit code.
+    [[event retain] autorelease];
+
+    // We get Esc key here after processing either Esc or Cmd+period. The former starts as a keyDown, and the latter starts as a key equivalent,
+    // but both get transformed to a cancelOperation: command, executing which passes an Esc key event to -performKeyEquivalent:.
+    // Don't interpret this event again, avoiding re-entrancy and infinite loops.
+    if ([[event charactersIgnoringModifiers] isEqualToString:@"\e"] && !([event modifierFlags] & NSDeviceIndependentModifierFlagsMask))
+        return [m_view _superPerformKeyEquivalent:event];
+
+    if (m_keyDownEventBeingResent) {
+        // WebCore has already seen the event, no need for custom processing.
+        // Note that we can get multiple events for each event being re-sent. For example, for Cmd+'=' AppKit
+        // first performs the original key equivalent, and if that isn't handled, it dispatches a synthetic Cmd+'+'.
+        return [m_view _superPerformKeyEquivalent:event];
+    }
+
+    ASSERT(event == [NSApp currentEvent]);
+
+    disableComplexTextInputIfNecessary();
+
+    // Pass key combos through WebCore if there is a key binding available for
+    // this event. This lets webpages have a crack at intercepting key-modified keypresses.
+    // FIXME: Why is the firstResponder check needed?
+    if (m_view == m_view.window.firstResponder) {
+        interpretKeyEvent(event, ^(BOOL handledByInputMethod, const Vector<WebCore::KeypressCommand>& commands) {
+            m_page.handleKeyboardEvent(NativeWebKeyboardEvent(event, handledByInputMethod, commands));
+        });
+        return YES;
+    }
+    
+    return [m_view _superPerformKeyEquivalent:event];
+}
+
+void WebViewImpl::keyUp(NSEvent *event)
+{
+    if (ignoresNonWheelEvents())
+        return;
+
+    LOG(TextInput, "keyUp:%p %@", event, event);
+
+    interpretKeyEvent(event, ^(BOOL handledByInputMethod, const Vector<WebCore::KeypressCommand>& commands) {
+        ASSERT(!handledByInputMethod || commands.isEmpty());
+        m_page.handleKeyboardEvent(NativeWebKeyboardEvent(event, handledByInputMethod, commands));
+    });
+}
+
+void WebViewImpl::keyDown(NSEvent *event)
+{
+    if (ignoresNonWheelEvents())
+        return;
+
+    LOG(TextInput, "keyDown:%p %@%s", event, event, (event == m_keyDownEventBeingResent) ? " (re-sent)" : "");
+
+    if (tryHandlePluginComplexTextInputKeyDown(event)) {
+        LOG(TextInput, "...handled by plug-in");
+        return;
+    }
+
+    // We could be receiving a key down from AppKit if we have re-sent an event
+    // that maps to an action that is currently unavailable (for example a copy when
+    // there is no range selection).
+    // If this is the case we should ignore the key down.
+    if (m_keyDownEventBeingResent == event) {
+        [m_view _superKeyDown:event];
+        return;
+    }
+
+    interpretKeyEvent(event, ^(BOOL handledByInputMethod, const Vector<WebCore::KeypressCommand>& commands) {
+        ASSERT(!handledByInputMethod || commands.isEmpty());
+        m_page.handleKeyboardEvent(NativeWebKeyboardEvent(event, handledByInputMethod, commands));
+    });
+}
+
+void WebViewImpl::flagsChanged(NSEvent *event)
+{
+    if (ignoresNonWheelEvents())
+        return;
+
+    LOG(TextInput, "flagsChanged:%p %@", event, event);
+
+    // Don't make an event from the num lock and function keys
+    if (eventKeyCodeIsZeroOrNumLockOrFn(event))
+        return;
+
+    interpretKeyEvent(event, ^(BOOL handledByInputMethod, const Vector<WebCore::KeypressCommand>& commands) {
+        m_page.handleKeyboardEvent(NativeWebKeyboardEvent(event, handledByInputMethod, commands));
+    });
+}
+
+#else // USE(ASYNC_NSTEXTINPUTCLIENT)
+
+bool WebViewImpl::interpretKeyEvent(NSEvent *event, Vector<WebCore::KeypressCommand>& commands)
+{
+    ASSERT(!m_interpretKeyEventsParameters);
+    ASSERT(commands.isEmpty());
+
+    if ([event type] == NSFlagsChanged)
+        return NO;
+
+    WKViewInterpretKeyEventsParameters parameters;
+    parameters.eventInterpretationHadSideEffects = false;
+    parameters.executingSavedKeypressCommands = false;
+    // We assume that an input method has consumed the event, and only change this assumption if one of the NSTextInput methods is called.
+    // We assume the IM will *not* consume hotkey sequences.
+    parameters.consumedByIM = !([event modifierFlags] & NSCommandKeyMask);
+    parameters.commands = &commands;
+    m_interpretKeyEventsParameters = &parameters;
+
+    LOG(TextInput, "-> interpretKeyEvents:%p %@", event, event);
+    [m_view interpretKeyEvents:[NSArray arrayWithObject:event]];
+
+    m_interpretKeyEventsParameters = nullptr;
+
+    // An input method may consume an event and not tell us (e.g. when displaying a candidate window),
+    // in which case we should not bubble the event up the DOM.
+    if (parameters.consumedByIM) {
+        ASSERT(commands.isEmpty());
+        LOG(TextInput, "...event %p was consumed by an input method", event);
+        return YES;
+    }
+
+    LOG(TextInput, "...interpretKeyEvents for event %p done, returns %d", event, parameters.eventInterpretationHadSideEffects);
+
+    // If we have already executed all or some of the commands, the event is "handled". Note that there are additional checks on web process side.
+    return parameters.eventInterpretationHadSideEffects;
+}
+
+void WebViewImpl::executeSavedKeypressCommands()
+{
+    auto* parameters = m_interpretKeyEventsParameters;
+    if (!parameters || parameters->commands->isEmpty())
+        return;
+
+    // We could be called again if the execution of one command triggers a call to selectedRange.
+    // In this case, the state is up to date, and we don't need to execute any more saved commands to return a result.
+    if (parameters->executingSavedKeypressCommands)
+        return;
+
+    LOG(TextInput, "Executing %u saved keypress commands...", parameters->commands->size());
+
+    parameters->executingSavedKeypressCommands = true;
+    parameters->eventInterpretationHadSideEffects |= m_page.executeKeypressCommands(*parameters->commands);
+    parameters->commands->clear();
+    parameters->executingSavedKeypressCommands = false;
+
+    LOG(TextInput, "...done executing saved keypress commands.");
+}
+
+void WebViewImpl::doCommandBySelector(SEL selector)
+{
+    LOG(TextInput, "doCommandBySelector:\"%s\"", sel_getName(selector));
+
+    auto* parameters = m_interpretKeyEventsParameters;
+    if (parameters)
+        parameters->consumedByIM = false;
+
+    // As in insertText:replacementRange:, we assume that the call comes from an input method if there is marked text.
+    bool isFromInputMethod = m_page.editorState().hasComposition;
+
+    if (parameters && !isFromInputMethod) {
+        WebCore::KeypressCommand command(NSStringFromSelector(selector));
+        parameters->commands->append(command);
+        LOG(TextInput, "...stored");
+        m_page.registerKeypressCommandName(command.commandName);
+    } else {
+        // FIXME: Send the command to Editor synchronously and only send it along the
+        // responder chain if it's a selector that does not correspond to an editing command.
+        [m_view _superDoCommandBySelector:selector];
+    }
+}
+
+void WebViewImpl::insertText(id string)
+{
+    // Unlike an NSTextInputClient variant with replacementRange, this NSResponder method is called when there is no input context,
+    // so text input processing isn't performed. We are not going to actually insert any text in that case, but saving an insertText
+    // command ensures that a keypress event is dispatched as appropriate.
+    insertText(string, NSMakeRange(NSNotFound, 0));
+}
+
+void WebViewImpl::insertText(id string, NSRange replacementRange)
+{
+    bool isAttributedString = [string isKindOfClass:[NSAttributedString class]];
+    ASSERT(isAttributedString || [string isKindOfClass:[NSString class]]);
+
+    if (replacementRange.location != NSNotFound)
+        LOG(TextInput, "insertText:\"%@\" replacementRange:(%u, %u)", isAttributedString ? [string string] : string, replacementRange.location, replacementRange.length);
+    else
+        LOG(TextInput, "insertText:\"%@\"", isAttributedString ? [string string] : string);
+    auto* parameters = m_interpretKeyEventsParameters;
+    if (parameters)
+        parameters->consumedByIM = false;
+
+    NSString *text;
+    bool isFromInputMethod = m_page.editorState().hasComposition;
+
+    Vector<WebCore::TextAlternativeWithRange> dictationAlternatives;
+
+    if (isAttributedString) {
+#if USE(DICTATION_ALTERNATIVES)
+        WebCore::collectDictationTextAlternatives(string, dictationAlternatives);
+#endif
+        // FIXME: We ignore most attributes from the string, so for example inserting from Character Palette loses font and glyph variation data.
+        text = [string string];
+    } else
+        text = string;
+
+    // insertText can be called for several reasons:
+    // - If it's from normal key event processing (including key bindings), we may need to save the action to perform it later.
+    // - If it's from an input method, then we should insert the text now. We assume it's from the input method if we have marked text.
+    // FIXME: In theory, this could be wrong for some input methods, so we should try to find another way to determine if the call is from the input method.
+    // - If it's sent outside of keyboard event processing (e.g. from Character Viewer, or when confirming an inline input area with a mouse),
+    // then we also execute it immediately, as there will be no other chance.
+    if (parameters && !isFromInputMethod) {
+        // FIXME: Handle replacementRange in this case, too. It's known to occur in practice when canceling Press and Hold (see <rdar://11940670>).
+        ASSERT(replacementRange.location == NSNotFound);
+        WebCore::KeypressCommand command("insertText:", text);
+        parameters->commands->append(command);
+        m_page.registerKeypressCommandName(command.commandName);
+        return;
+    }
+
+    String eventText = text;
+    eventText.replace(NSBackTabCharacter, NSTabCharacter); // same thing is done in KeyEventMac.mm in WebCore
+    bool eventHandled;
+    if (!dictationAlternatives.isEmpty())
+        eventHandled = m_page.insertDictatedText(eventText, replacementRange, dictationAlternatives);
+    else
+        eventHandled = m_page.insertText(eventText, replacementRange);
+
+    if (parameters)
+        parameters->eventInterpretationHadSideEffects |= eventHandled;
+}
+
+NSTextInputContext *WebViewImpl::inputContext()
+{
+    auto* parameters = m_interpretKeyEventsParameters;
+
+    if (pluginComplexTextInputIdentifier() && !parameters)
+        return [[WKTextInputWindowController sharedTextInputWindowController] inputContext];
+
+    // Disable text input machinery when in non-editable content. An invisible inline input area affects performance, and can prevent Expose from working.
+    if (!m_page.editorState().isContentEditable)
+        return nil;
+
+    return [m_view _superInputContext];
+}
+
+NSRange WebViewImpl::selectedRange()
+{
+    executeSavedKeypressCommands();
+
+    EditingRange selectedRange;
+    m_page.getSelectedRange(selectedRange);
+
+    NSRange result = selectedRange;
+    if (result.location == NSNotFound)
+        LOG(TextInput, "selectedRange -> (NSNotFound, %u)", result.length);
+    else
+        LOG(TextInput, "selectedRange -> (%u, %u)", result.location, result.length);
+
+    return result;
+}
+
+bool WebViewImpl::hasMarkedText()
+{
+    auto* parameters = m_interpretKeyEventsParameters;
+
+    BOOL result;
+    if (parameters) {
+        result = m_page.editorState().hasComposition;
+        if (result) {
+            // A saved command can confirm a composition, but it cannot start a new one.
+            executeSavedKeypressCommands();
+            result = m_page.editorState().hasComposition;
+        }
+    } else {
+        EditingRange markedRange;
+        m_page.getMarkedRange(markedRange);
+        result = markedRange.location != notFound;
+    }
+
+    LOG(TextInput, "hasMarkedText -> %u", result);
+    return result;
+}
+
+void WebViewImpl::unmarkText()
+{
+    executeSavedKeypressCommands();
+
+    LOG(TextInput, "unmarkText");
+
+    auto* parameters = m_interpretKeyEventsParameters;
+
+    if (parameters) {
+        parameters->eventInterpretationHadSideEffects = true;
+        parameters->consumedByIM = false;
+    }
+
+    m_page.confirmComposition();
+}
+
+void WebViewImpl::setMarkedText(id string, NSRange newSelectedRange, NSRange replacementRange)
+{
+    executeSavedKeypressCommands();
+
+    BOOL isAttributedString = [string isKindOfClass:[NSAttributedString class]];
+    ASSERT(isAttributedString || [string isKindOfClass:[NSString class]]);
+
+    LOG(TextInput, "setMarkedText:\"%@\" selectedRange:(%u, %u)", isAttributedString ? [string string] : string, newSelectedRange.location, newSelectedRange.length);
+
+    auto* parameters = m_interpretKeyEventsParameters;
+
+    if (parameters) {
+        parameters->eventInterpretationHadSideEffects = true;
+        parameters->consumedByIM = false;
+    }
+    
+    Vector<WebCore::CompositionUnderline> underlines;
+    NSString *text;
+
+    if (isAttributedString) {
+        // FIXME: We ignore most attributes from the string, so an input method cannot specify e.g. a font or a glyph variation.
+        text = [string string];
+        underlines = extractUnderlines(string);
+    } else
+        text = string;
+
+    if (m_page.editorState().isInPasswordField) {
+        // In password fields, we only allow ASCII dead keys, and don't allow inline input, matching NSSecureTextInputField.
+        // Allowing ASCII dead keys is necessary to enable full Roman input when using a Vietnamese keyboard.
+        ASSERT(!m_page.editorState().hasComposition);
+        notifyInputContextAboutDiscardedComposition();
+        if ([text length] == 1 && [[text decomposedStringWithCanonicalMapping] characterAtIndex:0] < 0x80) {
+            m_page.insertText(text, replacementRange);
+        } else
+            NSBeep();
+        return;
+    }
+
+    m_page.setComposition(text, underlines, newSelectedRange, replacementRange);
+}
+
+NSRange WebViewImpl::markedRange()
+{
+    executeSavedKeypressCommands();
+
+    EditingRange markedRange;
+    m_page.getMarkedRange(markedRange);
+
+    NSRange result = markedRange;
+    if (result.location == NSNotFound)
+        LOG(TextInput, "markedRange -> (NSNotFound, %u)", result.length);
+    else
+        LOG(TextInput, "markedRange -> (%u, %u)", result.location, result.length);
+
+    return result;
+}
+
+NSAttributedString *WebViewImpl::attributedSubstringForProposedRange(NSRange proposedRange, NSRangePointer actualRange)
+{
+    executeSavedKeypressCommands();
+
+    if (!m_page.editorState().isContentEditable) {
+        LOG(TextInput, "attributedSubstringFromRange:(%u, %u) -> nil", proposedRange.location, proposedRange.length);
+        return nil;
+    }
+
+    if (m_page.editorState().isInPasswordField)
+        return nil;
+
+    AttributedString result;
+    m_page.getAttributedSubstringFromRange(proposedRange, result);
+
+    if (actualRange) {
+        *actualRange = proposedRange;
+        actualRange->length = [result.string length];
+    }
+
+    LOG(TextInput, "attributedSubstringFromRange:(%u, %u) -> \"%@\"", proposedRange.location, proposedRange.length, [result.string string]);
+    return [[result.string retain] autorelease];
+}
+
+NSUInteger WebViewImpl::characterIndexForPoint(NSPoint point)
+{
+    executeSavedKeypressCommands();
+
+    NSWindow *window = m_view.window;
+    
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wdeprecated-declarations"
+    if (window)
+        point = [window convertScreenToBase:point];
+#pragma clang diagnostic pop
+    point = [m_view convertPoint:point fromView:nil];  // the point is relative to the main frame
+    
+    uint64_t result = m_page.characterIndexForPoint(WebCore::IntPoint(point));
+    if (result == notFound)
+        result = NSNotFound;
+    LOG(TextInput, "characterIndexForPoint:(%f, %f) -> %u", point.x, point.y, result);
+    return result;
+}
+
+NSRect WebViewImpl::firstRectForCharacterRange(NSRange range, NSRangePointer actualRange)
+{
+    executeSavedKeypressCommands();
+
+    // Just to match NSTextView's behavior. Regression tests cannot detect this;
+    // to reproduce, use a test application from http://bugs.webkit.org/show_bug.cgi?id=4682
+    // (type something; try ranges (1, -1) and (2, -1).
+    if ((range.location + range.length < range.location) && (range.location + range.length != 0))
+        range.length = 0;
+
+    if (range.location == NSNotFound) {
+        if (actualRange)
+            *actualRange = range;
+        LOG(TextInput, "firstRectForCharacterRange:(NSNotFound, %u) -> NSZeroRect", range.length);
+        return NSZeroRect;
+    }
+
+    NSRect resultRect = m_page.firstRectForCharacterRange(range);
+    resultRect = [m_view convertRect:resultRect toView:nil];
+    resultRect = [m_view.window convertRectToScreen:resultRect];
+
+    if (actualRange) {
+        // FIXME: Update actualRange to match the range of first rect.
+        *actualRange = range;
+    }
+
+    LOG(TextInput, "firstRectForCharacterRange:(%u, %u) -> (%f, %f, %f, %f)", range.location, range.length, resultRect.origin.x, resultRect.origin.y, resultRect.size.width, resultRect.size.height);
+    return resultRect;
+}
+
+bool WebViewImpl::performKeyEquivalent(NSEvent *event)
+{
+    if (ignoresNonWheelEvents())
+        return false;
+
+    // There's a chance that responding to this event will run a nested event loop, and
+    // fetching a new event might release the old one. Retaining and then autoreleasing
+    // the current event prevents that from causing a problem inside WebKit or AppKit code.
+    [[event retain] autorelease];
+
+    // We get Esc key here after processing either Esc or Cmd+period. The former starts as a keyDown, and the latter starts as a key equivalent,
+    // but both get transformed to a cancelOperation: command, executing which passes an Esc key event to -performKeyEquivalent:.
+    // Don't interpret this event again, avoiding re-entrancy and infinite loops.
+    if ([[event charactersIgnoringModifiers] isEqualToString:@"\e"] && !([event modifierFlags] & NSDeviceIndependentModifierFlagsMask))
+        return [m_view _superPerformKeyEquivalent:event];
+
+    if (m_keyDownEventBeingResent) {
+        // WebCore has already seen the event, no need for custom processing.
+        // Note that we can get multiple events for each event being re-sent. For example, for Cmd+'=' AppKit
+        // first performs the original key equivalent, and if that isn't handled, it dispatches a synthetic Cmd+'+'.
+        return [m_view _superPerformKeyEquivalent:event];
+    }
+
+    ASSERT(event == [NSApp currentEvent]);
+
+    disableComplexTextInputIfNecessary();
+
+    // Pass key combos through WebCore if there is a key binding available for
+    // this event. This lets webpages have a crack at intercepting key-modified keypresses.
+    // FIXME: Why is the firstResponder check needed?
+    if (m_view == m_view.window.firstResponder) {
+        Vector<WebCore::KeypressCommand> commands;
+        bool handledByInputMethod = interpretKeyEvent(event, commands);
+        m_page.handleKeyboardEvent(NativeWebKeyboardEvent(event, handledByInputMethod, commands));
+        return true;
+    }
+    
+    return [m_view _superPerformKeyEquivalent:event];
+}
+
+void WebViewImpl::keyUp(NSEvent *event)
+{
+    if (ignoresNonWheelEvents())
+        return;
+
+    LOG(TextInput, "keyUp:%p %@", event, event);
+    // We don't interpret the keyUp event, as this breaks key bindings (see <https://bugs.webkit.org/show_bug.cgi?id=130100>).
+    m_page.handleKeyboardEvent(NativeWebKeyboardEvent(event, false, { }));
+}
+
+void WebViewImpl::keyDown(NSEvent *event)
+{
+    if (ignoresNonWheelEvents())
+        return;
+
+    LOG(TextInput, "keyDown:%p %@%s", event, event, (event == m_keyDownEventBeingResent) ? " (re-sent)" : "");
+
+    // There's a chance that responding to this event will run a nested event loop, and
+    // fetching a new event might release the old one. Retaining and then autoreleasing
+    // the current event prevents that from causing a problem inside WebKit or AppKit code.
+    [[event retain] autorelease];
+
+    if (tryHandlePluginComplexTextInputKeyDown(event)) {
+        LOG(TextInput, "...handled by plug-in");
+        return;
+    }
+
+    // We could be receiving a key down from AppKit if we have re-sent an event
+    // that maps to an action that is currently unavailable (for example a copy when
+    // there is no range selection).
+    // If this is the case we should ignore the key down.
+    if (m_keyDownEventBeingResent == event) {
+        [m_view _superKeyDown:event];
+        return;
+    }
+
+    Vector<WebCore::KeypressCommand> commands;
+    bool handledByInputMethod = interpretKeyEvent(event, commands);
+    if (!commands.isEmpty()) {
+        // An input method may make several actions per keypress. For example, pressing Return with Korean IM both confirms it and sends a newline.
+        // IM-like actions are handled immediately (so the return value from UI process is true), but there are saved commands that
+        // should be handled like normal text input after DOM event dispatch.
+        handledByInputMethod = false;
+    }
+
+    m_page.handleKeyboardEvent(NativeWebKeyboardEvent(event, handledByInputMethod, commands));
+}
+
+void WebViewImpl::flagsChanged(NSEvent *event)
+{
+    if (ignoresNonWheelEvents())
+        return;
+
+    LOG(TextInput, "flagsChanged:%p %@", event, event);
+
+    // There's a chance that responding to this event will run a nested event loop, and
+    // fetching a new event might release the old one. Retaining and then autoreleasing
+    // the current event prevents that from causing a problem inside WebKit or AppKit code.
+    [[event retain] autorelease];
+
+    // Don't make an event from the num lock and function keys
+    if (eventKeyCodeIsZeroOrNumLockOrFn(event))
+        return;
+
+    m_page.handleKeyboardEvent(NativeWebKeyboardEvent(event, false, { }));
+}
+
+#endif // USE(ASYNC_NSTEXTINPUTCLIENT)
+
 } // namespace WebKit
 
 #endif // PLATFORM(MAC)
index 4000cf9e98ee9cf4e465fbaf30dfead49b645f55..d4f0ab33b96790a8fbcabbcab640785aea2755c7 100644 (file)
@@ -435,7 +435,7 @@ IntRect PageClientImpl::rootViewToScreen(const IntRect& rect)
 
 void PageClientImpl::doneWithKeyEvent(const NativeWebKeyboardEvent& event, bool eventWasHandled)
 {
-    [m_wkView _doneWithKeyEvent:event.nativeEvent() eventWasHandled:eventWasHandled];
+    m_impl->doneWithKeyEvent(event.nativeEvent(), eventWasHandled);
 }
 
 RefPtr<WebPopupMenuProxy> PageClientImpl::createPopupMenuProxy(WebPageProxy& page)