Reviewed by Darin Adler.
authorap@apple.com <ap@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Fri, 1 Apr 2011 21:11:44 +0000 (21:11 +0000)
committerap@apple.com <ap@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Fri, 1 Apr 2011 21:11:44 +0000 (21:11 +0000)
        Make WebKit2 text input handling more like WebKit1
        https://bugs.webkit.org/show_bug.cgi?id=57649

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

24 files changed:
Source/WebCore/ChangeLog
Source/WebCore/dom/KeyboardEvent.h
Source/WebKit/mac/ChangeLog
Source/WebKit/mac/WebView/WebFrameInternal.h
Source/WebKit/mac/WebView/WebHTMLView.mm
Source/WebKit2/ChangeLog
Source/WebKit2/Platform/CoreIPC/HandleMessage.h
Source/WebKit2/Scripts/webkit2/messages.py
Source/WebKit2/Shared/WebCoreArgumentCoders.h
Source/WebKit2/Shared/mac/TextInputState.h [new file with mode: 0644]
Source/WebKit2/UIProcess/API/mac/PageClientImpl.h
Source/WebKit2/UIProcess/API/mac/PageClientImpl.mm
Source/WebKit2/UIProcess/API/mac/WKView.mm
Source/WebKit2/UIProcess/API/mac/WKViewInternal.h
Source/WebKit2/UIProcess/PageClient.h
Source/WebKit2/UIProcess/WebPageProxy.h
Source/WebKit2/UIProcess/WebPageProxy.messages.in
Source/WebKit2/UIProcess/mac/WebPageProxyMac.mm
Source/WebKit2/WebKit2.xcodeproj/project.pbxproj
Source/WebKit2/WebProcess/WebCoreSupport/mac/WebEditorClientMac.mm
Source/WebKit2/WebProcess/WebPage/WebPage.cpp
Source/WebKit2/WebProcess/WebPage/WebPage.h
Source/WebKit2/WebProcess/WebPage/WebPage.messages.in
Source/WebKit2/WebProcess/WebPage/mac/WebPageMac.mm

index 85217f4..9408887 100644 (file)
@@ -1,3 +1,14 @@
+2011-04-01  Alexey Proskuryakov  <ap@apple.com>
+
+        Reviewed by Darin Adler.
+
+        Make WebKit2 text input handling more like WebKit1
+        https://bugs.webkit.org/show_bug.cgi?id=57649
+
+        * dom/KeyboardEvent.h: (WebCore::KeypressCommand::KeypressCommand): Put back the assertions
+        we used to have. It is dangerous to confuse editor commands and selector names - besides the
+        presence of a semicolon, they sometimes have different names, and WebKit2 failed to map those.
+
 2011-04-01  Sheriff Bot  <webkit.review.bot@gmail.com>
 
         Unreviewed, rolling out r82711.
index ebdb9c8..bb4346f 100644 (file)
@@ -36,10 +36,10 @@ namespace WebCore {
 #if PLATFORM(MAC)
     struct KeypressCommand {
         KeypressCommand() { }
-        KeypressCommand(const String& commandName) : commandName(commandName) { }
-        KeypressCommand(const String& commandName, const String& text) : commandName(commandName), text(text) { }
+        KeypressCommand(const String& commandName) : commandName(commandName) { ASSERT(isASCIILower(commandName[0])); }
+        KeypressCommand(const String& commandName, const String& text) : commandName(commandName), text(text) { ASSERT(commandName == "insertText:"); }
 
-        String commandName;
+        String commandName; // Actually, a selector name - it may have a trailing colon, and a name that can be different from an editor command name.
         String text;
     };
 #endif
@@ -108,7 +108,8 @@ namespace WebCore {
         unsigned m_keyLocation;
         bool m_altGraphKey : 1;
 
-#if PLATFORM(MAC)        
+#if PLATFORM(MAC)
+        // Commands that were sent by AppKit when interpreting the event. Doesn't include input method commands.
         Vector<KeypressCommand> m_keypressCommands;
 #endif
     };
index 675b581..f50b2a6 100644 (file)
@@ -1,3 +1,27 @@
+2011-04-01  Alexey Proskuryakov  <ap@apple.com>
+
+        Reviewed by Darin Adler.
+
+        Make WebKit2 text input handling more like WebKit1
+        https://bugs.webkit.org/show_bug.cgi?id=57649
+
+        * WebView/WebFrameInternal.h: Expose _convertToDOMRange for use in WebHTMLView.
+        * WebView/WebHTMLView.mm:
+        (-[WebHTMLView _executeSavedKeypressCommands]): Renamed, since these are not editor commands.
+        (-[WebHTMLView _interpretKeyEvent:savingCommands:]): Updated for the renamed _executeSavedKeypressCommands.
+        (-[WebHTMLView characterIndexForPoint:]): Ditto.
+        (-[WebHTMLView firstRectForCharacterRange:]): Ditto.
+        (-[WebHTMLView selectedRange]): Ditto.
+        (-[WebHTMLView markedRange]): Ditto.
+        (-[WebHTMLView attributedSubstringFromRange:]): Ditto.
+        (-[WebHTMLView hasMarkedText]): Ditto.
+        (-[WebHTMLView unmarkText]): Ditto.
+        (-[WebHTMLView setMarkedText:selectedRange:]): Ditto. Changed the comment about the argument
+        type into an assertion. Removed resetting interpretKeyEventsParameters to 0, as we shouldn't
+        be able to call text input protocol methods from here (and we didn't even reset it back on
+        return). Changed to retrieve all data before starting to apply actions to better match WK2 code.
+        (-[WebHTMLView insertText:]): Ditto.
+
 2011-04-01  Timothy Hatcher  <timothy@apple.com>
 
         Make momentum scroll event latching work in WebKit2 on Mac.
index c13b721..ff382d4 100644 (file)
@@ -142,6 +142,7 @@ WebView *getWebView(WebFrame *webFrame);
 - (NSRange)_convertToNSRange:(WebCore::Range*)range;
 - (DOMRange *)_convertNSRangeToDOMRange:(NSRange)range;
 - (NSRange)_convertDOMRangeToNSRange:(DOMRange *)range;
+- (PassRefPtr<WebCore::Range>)_convertToDOMRange:(NSRange)nsrange;
 
 - (DOMDocumentFragment *)_documentFragmentWithMarkupString:(NSString *)markupString baseURLString:(NSString *)baseURLString;
 - (DOMDocumentFragment *)_documentFragmentWithNodesAsParagraphs:(NSArray *)nodes;
index f63baec..f810191 100644 (file)
@@ -5431,7 +5431,7 @@ static CGPoint coreGraphicsScreenPointForAppKitScreenPoint(NSPoint point)
     [self _updateMouseoverWithFakeEvent];
 }
 
-- (void)_executeSavedEditingCommands
+- (void)_executeSavedKeypressCommands
 {
     WebHTMLViewInterpretKeyEventsParameters* parameters = _private->interpretKeyEventsParameters;
     if (!parameters || parameters->event->keypressCommands().isEmpty())
@@ -5506,7 +5506,7 @@ static CGPoint coreGraphicsScreenPointForAppKitScreenPoint(NSPoint point)
         // If there are no text insertion commands, default keydown handler is the right time to execute the commands.
         // Keypress (Char event) handler is the latest opportunity to execute.
         if (!haveTextInsertionCommands || platformEvent->type() == PlatformKeyboardEvent::Char)
-            [self _executeSavedEditingCommands];
+            [self _executeSavedKeypressCommands];
     }
     _private->interpretKeyEventsParameters = 0;
 
@@ -5751,7 +5751,7 @@ static CGPoint coreGraphicsScreenPointForAppKitScreenPoint(NSPoint point)
 
 - (NSUInteger)characterIndexForPoint:(NSPoint)thePoint
 {
-    [self _executeSavedEditingCommands];
+    [self _executeSavedKeypressCommands];
 
     NSWindow *window = [self window];
     WebFrame *frame = [self _frame];
@@ -5773,7 +5773,7 @@ static CGPoint coreGraphicsScreenPointForAppKitScreenPoint(NSPoint point)
 
 - (NSRect)firstRectForCharacterRange:(NSRange)theRange
 {
-    [self _executeSavedEditingCommands];
+    [self _executeSavedKeypressCommands];
 
     WebFrame *frame = [self _frame];
     
@@ -5805,7 +5805,7 @@ static CGPoint coreGraphicsScreenPointForAppKitScreenPoint(NSPoint point)
 
 - (NSRange)selectedRange
 {
-    [self _executeSavedEditingCommands];
+    [self _executeSavedKeypressCommands];
 
     if (!isTextInput(core([self _frame]))) {
         LOG(TextInput, "selectedRange -> (NSNotFound, 0)");
@@ -5819,7 +5819,7 @@ static CGPoint coreGraphicsScreenPointForAppKitScreenPoint(NSPoint point)
 
 - (NSRange)markedRange
 {
-    [self _executeSavedEditingCommands];
+    [self _executeSavedKeypressCommands];
 
     WebFrame *webFrame = [self _frame];
     Frame* coreFrame = core(webFrame);
@@ -5833,7 +5833,7 @@ static CGPoint coreGraphicsScreenPointForAppKitScreenPoint(NSPoint point)
 
 - (NSAttributedString *)attributedSubstringFromRange:(NSRange)nsRange
 {
-    [self _executeSavedEditingCommands];
+    [self _executeSavedKeypressCommands];
 
     WebFrame *frame = [self _frame];
     Frame* coreFrame = core(frame);
@@ -5876,7 +5876,7 @@ static CGPoint coreGraphicsScreenPointForAppKitScreenPoint(NSPoint point)
 
 - (BOOL)hasMarkedText
 {
-    [self _executeSavedEditingCommands];
+    [self _executeSavedKeypressCommands];
 
     Frame* coreFrame = core([self _frame]);
     BOOL result = coreFrame && coreFrame->editor()->hasComposition();
@@ -5886,13 +5886,12 @@ static CGPoint coreGraphicsScreenPointForAppKitScreenPoint(NSPoint point)
 
 - (void)unmarkText
 {
-    [self _executeSavedEditingCommands];
+    [self _executeSavedKeypressCommands];
 
     LOG(TextInput, "unmarkText");
 
     // Use pointer to get parameters passed to us by the caller of interpretKeyEvents.
     WebHTMLViewInterpretKeyEventsParameters* parameters = _private->interpretKeyEventsParameters;
-    _private->interpretKeyEventsParameters = 0;
 
     if (parameters) {
         parameters->eventInterpretationHadSideEffects = true;
@@ -5925,15 +5924,15 @@ static void extractUnderlines(NSAttributedString *string, Vector<CompositionUnde
 
 - (void)setMarkedText:(id)string selectedRange:(NSRange)newSelRange
 {
-    [self _executeSavedEditingCommands];
+    [self _executeSavedKeypressCommands];
 
-    BOOL isAttributedString = [string isKindOfClass:[NSAttributedString class]]; // Otherwise, NSString
+    BOOL isAttributedString = [string isKindOfClass:[NSAttributedString class]];
+    ASSERT(isAttributedString || [string isKindOfClass:[NSString class]]);
 
     LOG(TextInput, "setMarkedText:\"%@\" selectedRange:(%u, %u)", isAttributedString ? [string string] : string, newSelRange.location, newSelRange.length);
 
     // Use pointer to get parameters passed to us by the caller of interpretKeyEvents.
     WebHTMLViewInterpretKeyEventsParameters* parameters = _private->interpretKeyEventsParameters;
-    _private->interpretKeyEventsParameters = 0;
 
     if (parameters) {
         parameters->eventInterpretationHadSideEffects = true;
@@ -5948,20 +5947,25 @@ static void extractUnderlines(NSAttributedString *string, Vector<CompositionUnde
         return;
 
     Vector<CompositionUnderline> underlines;
-    NSString *text = string;
+    NSString *text;
+    NSRange replacementRange = { NSNotFound, 0 };
 
     if (isAttributedString) {
-        unsigned markedTextLength = [(NSString *)string length];
-        NSString *rangeString = [string attribute:NSTextInputReplacementRangeAttributeName atIndex:0 longestEffectiveRange:0 inRange:NSMakeRange(0, markedTextLength)];
+        // 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];
+        NSString *rangeString = [string attribute:NSTextInputReplacementRangeAttributeName atIndex:0 longestEffectiveRange:0 inRange:NSMakeRange(0, [text length])];
         LOG(TextInput, "    ReplacementRange: %@", rangeString);
         // The AppKit adds a 'secret' property to the string that contains the replacement range.
         // The replacement range is the range of the the text that should be replaced with the new string.
         if (rangeString)
-            [[self _frame] _selectNSRange:NSRangeFromString(rangeString)];
+            replacementRange = NSRangeFromString(rangeString);
 
-        text = [string string];
         extractUnderlines(string, underlines);
-    }
+    } else
+        text = string;
+
+    if (replacementRange.location != NSNotFound)
+        [[self _frame] _selectNSRange:replacementRange];
 
     coreFrame->editor()->setComposition(text, underlines, newSelRange.location, NSMaxRange(newSelRange));
 }
@@ -6023,7 +6027,8 @@ static void extractUnderlines(NSAttributedString *string, Vector<CompositionUnde
 
 - (void)insertText:(id)string
 {
-    BOOL isAttributedString = [string isKindOfClass:[NSAttributedString class]]; // Otherwise, NSString
+    BOOL isAttributedString = [string isKindOfClass:[NSAttributedString class]];
+    ASSERT(isAttributedString || [string isKindOfClass:[NSString class]]);
 
     LOG(TextInput, "insertText:\"%@\"", isAttributedString ? [string string] : string);
 
@@ -6031,20 +6036,19 @@ static void extractUnderlines(NSAttributedString *string, Vector<CompositionUnde
     if (parameters)
         parameters->consumedByIM = false;
 
-    // We don't support inserting an attributed string but input methods don't appear to require this.
     RefPtr<Frame> coreFrame = core([self _frame]);
     NSString *text;
+    NSRange replacementRange = { NSNotFound, 0 };
     bool isFromInputMethod = coreFrame && coreFrame->editor()->hasComposition();
+
     if (isAttributedString) {
+        // FIXME: We ignore most attributes from the string, so for example inserting from Character Palette loses font and glyph variation data.
+        // It does not look like any input methods ever use insertText: with attributes other than NSTextInputReplacementRangeAttributeName.
         text = [string string];
-        // We deal with the NSTextInputReplacementRangeAttributeName attribute from NSAttributedString here
-        // simply because it is used by at least one Input Method -- it corresonds to the kEventParamTextInputSendReplaceRange
-        // event in TSM. This behavior matches that of -[WebHTMLView setMarkedText:selectedRange:] when it receives an
-        // NSAttributedString
         NSString *rangeString = [string attribute:NSTextInputReplacementRangeAttributeName atIndex:0 longestEffectiveRange:0 inRange:NSMakeRange(0, [text length])];
         LOG(TextInput, "    ReplacementRange: %@", rangeString);
         if (rangeString) {
-            [[self _frame] _selectNSRange:NSRangeFromString(rangeString)];
+            replacementRange = NSRangeFromString(rangeString);
             isFromInputMethod = true;
         }
     } else
@@ -6067,6 +6071,9 @@ static void extractUnderlines(NSAttributedString *string, Vector<CompositionUnde
     if (!coreFrame || !coreFrame->editor()->canEdit())
         return;
 
+    if (replacementRange.location != NSNotFound)
+        [[self _frame] _selectNSRange:replacementRange];
+
     bool eventHandled = false;
     String eventText = text;
     eventText.replace(NSBackTabCharacter, NSTabCharacter); // same thing is done in KeyEventMac.mm in WebCore
index 56a6cba..1814fe7 100644 (file)
@@ -1,3 +1,84 @@
+2011-04-01  Alexey Proskuryakov  <ap@apple.com>
+
+        Reviewed by Darin Adler.
+
+        Make WebKit2 text input handling more like WebKit1
+        https://bugs.webkit.org/show_bug.cgi?id=57649
+
+        We now send more sync messages from UI process to Web process for NSTextInput protocol
+        methods. Some of the exchanges are avoided by pre-calculating the responses before asking
+        UI process to interpret key events, and also with each sync message sent during
+        interpretation, and which could change the state.
+
+        * Platform/CoreIPC/HandleMessage.h: (CoreIPC::callMemberFunction): Added a missing
+        specialization for a function with 6 arguments and 2 returned values.
+
+        * Scripts/webkit2/messages.py: Added TextInputState to struct list.
+
+        * Shared/WebCoreArgumentCoders.h: Fixed a bug in CompositionUnderline encoding, which only
+        encoded a part of Color field, and didn't match decoder.
+
+        * Shared/mac/TextInputState.h: Added. This is state that's needed for IM machinery and that
+        we don't want to send sync messages for. This is similar to SelectionState, but the latter
+        is only updated asynchronously, and is thus less reliable.
+
+        * UIProcess/API/mac/PageClientImpl.h: We don't "intercept", we "interpret".
+
+        * UIProcess/API/mac/PageClientImpl.mm: (WebKit::PageClientImpl::interpretKeyEvent): Pass
+        current text input state, and don't expect anything input method related in return, as input
+        methods are now sync.
+
+        * UIProcess/API/mac/WKView.mm:
+        (-[WKView doCommandBySelector:]):
+        (-[WKView insertText:]):
+        (-[WKView keyDown:]):
+        (-[WKView _executeSavedKeypressCommands]):
+        (-[WKView inputContext]):
+        (-[WKView selectedRange]):
+        (-[WKView hasMarkedText]):
+        (-[WKView unmarkText]):
+        (-[WKView setMarkedText:selectedRange:]):
+        (-[WKView markedRange]):
+        (-[WKView attributedSubstringFromRange:]):
+        (-[WKView characterIndexForPoint:]):
+        (-[WKView firstRectForCharacterRange:]):
+        (-[WKView _interpretKeyEvent:withCachedTextInputState:savingCommandsTo:WebCore::]):
+        * UIProcess/API/mac/WKViewInternal.h:
+        * UIProcess/PageClient.h:
+        * UIProcess/WebPageProxy.h:
+        * UIProcess/WebPageProxy.messages.in:
+        * UIProcess/mac/WebPageProxyMac.mm:
+        (WebKit::WebPageProxy::setComposition):
+        (WebKit::WebPageProxy::confirmComposition):
+        (WebKit::WebPageProxy::insertText):
+        (WebKit::WebPageProxy::getSelectedRange):
+        (WebKit::WebPageProxy::executeKeypressCommands):
+        (WebKit::WebPageProxy::interpretQueuedKeyEvent):
+        * WebKit2.xcodeproj/project.pbxproj:
+        * WebProcess/WebCoreSupport/mac/WebEditorClientMac.mm:
+        (WebKit::WebEditorClient::handleKeyboardEvent):
+        (WebKit::WebEditorClient::handleInputMethodKeydown):
+        * WebProcess/WebPage/WebPage.cpp:
+        (WebKit::WebPage::WebPage):
+        * WebProcess/WebPage/WebPage.h:
+        (WebKit::WebPage::viewFrameInWindowCoordinates):
+        * WebProcess/WebPage/WebPage.messages.in:
+        * WebProcess/WebPage/mac/WebPageMac.mm:
+        (WebKit::createSelectorExceptionMap):
+        (WebKit::commandNameForSelectorName):
+        (WebKit::currentTextInputState):
+        (WebKit::frameForEvent):
+        (WebKit::WebPage::executeKeypressCommandsInternal):
+        (WebKit::WebPage::handleEditingKeyboardEvent):
+        (WebKit::WebPage::setComposition):
+        (WebKit::WebPage::confirmComposition):
+        (WebKit::WebPage::insertText):
+        (WebKit::WebPage::getSelectedRange):
+        (WebKit::convertToRange):
+        (WebKit::WebPage::executeKeypressCommands):
+        (WebKit::WebPage::performDefaultBehaviorForKeyEvent):
+        Rewrote to be more like WebKit1.
+
 2011-04-01  Chang Shu  <cshu@webkit.org>
 
         Reviewed by Darin Adler.
index a99c76e..8c8c3bc 100644 (file)
@@ -141,6 +141,12 @@ void callMemberFunction(const Arguments4<P1, P2, P3, P4>& args, Arguments1<R1>&
     (object->*function)(args.argument1, args.argument2, args.argument3, args.argument4, replyArgs.argument1);
 }
 
+template<typename C, typename MF, typename P1, typename P2, typename P3, typename P4, typename P5, typename P6, typename R1>
+void callMemberFunction(const Arguments6<P1, P2, P3, P4, P5, P6>& args, Arguments1<R1>& replyArgs, C* object, MF function)
+{
+    (object->*function)(args.argument1, args.argument2, args.argument3, args.argument4, args.argument5, args.argument6, replyArgs.argument1);
+}
+
 template<typename C, typename MF, typename P1, typename P2, typename P3, typename P4, typename P5, typename P6, typename P7, typename R1>
 void callMemberFunction(const Arguments7<P1, P2, P3, P4, P5, P6, P7>& args, Arguments1<R1>& replyArgs, C* object, MF function)
 {
index 159ca32..9bdd760 100644 (file)
@@ -270,6 +270,7 @@ def struct_or_class(namespace, type):
         'WebKit::SecurityOriginData',
         'WebKit::SelectionState',
         'WebKit::TextCheckerState',
+        'WebKit::TextInputState',
         'WebKit::WebNavigationDataStore',
         'WebKit::WebOpenPanelParameters::Data',
         'WebKit::WebPageCreationParameters',
index e12b9be..5394c25 100644 (file)
@@ -401,7 +401,7 @@ template<> struct ArgumentCoder<WebCore::KeypressCommand> {
 template<> struct ArgumentCoder<WebCore::CompositionUnderline> {
     static void encode(ArgumentEncoder* encoder, const WebCore::CompositionUnderline& underline)
     {
-        encoder->encode(CoreIPC::In(underline.startOffset, underline.endOffset, underline.thick, underline.color.rgb()));
+        encoder->encode(CoreIPC::In(underline.startOffset, underline.endOffset, underline.thick, underline.color));
     }
     
     static bool decode(ArgumentDecoder* decoder, WebCore::CompositionUnderline& underline)
diff --git a/Source/WebKit2/Shared/mac/TextInputState.h b/Source/WebKit2/Shared/mac/TextInputState.h
new file mode 100644 (file)
index 0000000..05ba912
--- /dev/null
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2011 Apple Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef TextInputState_h
+#define TextInputState_h
+
+#include "ArgumentCoders.h"
+
+namespace WebKit {
+
+struct TextInputState {
+    bool hasMarkedText;
+    bool selectionIsEditable;
+};
+
+}
+
+namespace CoreIPC {
+template<> struct ArgumentCoder<WebKit::TextInputState> : SimpleArgumentCoder<WebKit::TextInputState> { };
+}
+
+#endif // TextInputState_h
index e217fc5..636f12e 100644 (file)
@@ -71,7 +71,7 @@ private:
 
     virtual void registerEditCommand(PassRefPtr<WebEditCommandProxy>, WebPageProxy::UndoOrRedo);
     virtual void clearAllEditCommands();
-    virtual void interceptKeyEvent(const NativeWebKeyboardEvent& event, Vector<WebCore::KeypressCommand>& commandName, uint32_t selectionStart, uint32_t selectionEnd, Vector<WebCore::CompositionUnderline>& underlines);
+    virtual bool interpretKeyEvent(const NativeWebKeyboardEvent&, const TextInputState&, Vector<WebCore::KeypressCommand>&);
     virtual void setDragImage(const WebCore::IntPoint& clientPosition, PassRefPtr<ShareableBitmap> dragImage, bool isLinkDrag);
 
     virtual WebCore::FloatRect convertToDeviceSpace(const WebCore::FloatRect&);
index 7a0d62d..c5264d2 100644 (file)
@@ -291,10 +291,9 @@ void PageClientImpl::clearAllEditCommands()
     [[m_wkView undoManager] removeAllActionsWithTarget:m_undoTarget.get()];
 }
 
-void PageClientImpl::interceptKeyEvent(const NativeWebKeyboardEvent& event, Vector<WebCore::KeypressCommand>& commandsList, uint32_t selectionStart, uint32_t selectionEnd, Vector<WebCore::CompositionUnderline>& underlines)
+bool PageClientImpl::interpretKeyEvent(const NativeWebKeyboardEvent& event, const TextInputState& state, Vector<WebCore::KeypressCommand>& commands)
 {
-    commandsList = [m_wkView _interceptKeyEvent:event.nativeEvent()];
-    [m_wkView _getTextInputState:selectionStart selectionEnd:selectionEnd underlines:underlines];
+    return [m_wkView _interpretKeyEvent:event.nativeEvent() withCachedTextInputState:state savingCommandsTo:commands];
 }
 
 void PageClientImpl::setDragImage(const IntPoint& clientPosition, PassRefPtr<ShareableBitmap> dragImage, bool isLinkDrag)
index 05693ef..e31c415 100644 (file)
@@ -41,6 +41,7 @@
 #import "RunLoop.h"
 #import "TextChecker.h"
 #import "TextCheckerState.h"
+#import "TextInputState.h"
 #import "WKAPICast.h"
 #import "WKFullScreenWindowController.h"
 #import "WKPrintingView.h"
@@ -83,7 +84,7 @@
 
 extern "C" {
     // Need to declare this attribute name because AppKit exports it but does not make it available in API or SPI headers.
-    // <rdar://problem/8631468> tracks the request to make it available. This code should be removed when the bug is closed.
+    // FIXME: We wouldn't need this if we implemented NSTextInputClient protocol instead of deprecated NSTextInput.
     extern NSString *NSTextInputReplacementRangeAttributeName;
 }
 
@@ -98,6 +99,13 @@ typedef HashMap<String, ValidationVector> ValidationMap;
 
 }
 
+struct WKViewInterpretKeyEventsParameters {
+    TextInputState cachedTextInputState;
+    bool eventInterpretationHadSideEffects;
+    bool consumedByIM;
+    Vector<KeypressCommand>* commands;
+};
+
 @interface WKViewData : NSObject {
 @public
     OwnPtr<PageClientImpl> _pageClient;
@@ -122,18 +130,13 @@ typedef HashMap<String, ValidationVector> ValidationMap;
     // the application to distinguish the case of a new event from one 
     // that has been already sent to WebCore.
     RetainPtr<NSEvent> _keyDownEventBeingResent;
-    bool _isInInterpretKeyEvents;
-    Vector<KeypressCommand> _commandsList;
+    WKViewInterpretKeyEventsParameters* _interpretKeyEventsParameters;
 
     NSSize _resizeScrollOffset;
 
     // The identifier of the plug-in we want to send complex text input to, or 0 if there is none.
     uint64_t _pluginComplexTextInputIdentifier;
 
-    Vector<CompositionUnderline> _underlines;
-    unsigned _selectionStart;
-    unsigned _selectionEnd;
-
     bool _inBecomeFirstResponder;
     bool _inResignFirstResponder;
     NSEvent *_mouseDownEvent;
@@ -1018,46 +1021,67 @@ static const short kIOHIDEventTypeScroll = 6;
 {
     LOG(TextInput, "doCommandBySelector:\"%s\"", sel_getName(selector));
 
-    if (!_data->_isInInterpretKeyEvents) {
+    WKViewInterpretKeyEventsParameters* parameters = _data->_interpretKeyEventsParameters;
+    if (parameters)
+        parameters->consumedByIM = false;
+
+    // As in insertText:, we assume that the call comes from an input method if there is marked text.
+    bool isFromInputMethod = parameters && parameters->cachedTextInputState.hasMarkedText;
+
+    if (parameters && !isFromInputMethod)
+        parameters->commands->append(KeypressCommand(NSStringFromSelector(selector)));
+    else {
+        // FIXME: Send the command to Editor synchronously.
         [super doCommandBySelector:selector];
-        return;
     }
-    if (selector != @selector(noop:))
-        _data->_commandsList.append(KeypressCommand(commandNameForSelector(selector)));
 }
 
 - (void)insertText:(id)string
 {
-    BOOL isAttributedString = [string isKindOfClass:[NSAttributedString class]]; // Otherwise, NSString
-    
+    BOOL isAttributedString = [string isKindOfClass:[NSAttributedString class]];
+    ASSERT(isAttributedString || [string isKindOfClass:[NSString class]]);
+
     LOG(TextInput, "insertText:\"%@\"", isAttributedString ? [string string] : string);
+    WKViewInterpretKeyEventsParameters* parameters = _data->_interpretKeyEventsParameters;
+    if (parameters)
+        parameters->consumedByIM = false;
+
     NSString *text;
-    bool isFromInputMethod = _data->_page->selectionState().hasComposition;
+    NSRange replacementRange = { NSNotFound, 0 };
+    bool isFromInputMethod = parameters && parameters->cachedTextInputState.hasMarkedText;
 
     if (isAttributedString) {
+        // FIXME: We ignore most attributes from the string, so for example inserting from Character Palette loses font and glyph variation data.
+        // It does not look like any input methods ever use insertText: with attributes other than NSTextInputReplacementRangeAttributeName.
         text = [string string];
-        // We deal with the NSTextInputReplacementRangeAttributeName attribute from NSAttributedString here
-        // simply because it is used by at least one Input Method -- it corresonds to the kEventParamTextInputSendReplaceRange
-        // event in TSM.  This behaviour matches that of -[WebHTMLView setMarkedText:selectedRange:] when it receives an
-        // NSAttributedString
-        NSString *rangeString = [string attribute:NSTextInputReplacementRangeAttributeName atIndex:0 longestEffectiveRange:NULL inRange:NSMakeRange(0, [text length])];
+        NSString *rangeString = [string attribute:NSTextInputReplacementRangeAttributeName atIndex:0 longestEffectiveRange:0 inRange:NSMakeRange(0, [text length])];
         LOG(TextInput, "ReplacementRange: %@", rangeString);
-        if (rangeString)
-            isFromInputMethod = YES;
+        if (rangeString) {
+            replacementRange = NSRangeFromString(rangeString);
+            isFromInputMethod = true;
+        }
     } 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 go ahead and 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) {
+        parameters->commands->append(KeypressCommand("insertText:", text));
+        return;
+    }
+
+    TextInputState newTextInputState;
     String eventText = text;
-    
-    // We'd need a different code path here if we wanted to be able to handle this
-    // outside of interpretKeyEvents.
-    ASSERT(_data->_isInInterpretKeyEvents);
+    eventText.replace(NSBackTabCharacter, NSTabCharacter); // same thing is done in KeyEventMac.mm in WebCore
+    bool eventHandled = _data->_page->insertText(eventText, replacementRange.location, NSMaxRange(replacementRange), newTextInputState);
 
-    if (!isFromInputMethod)
-        _data->_commandsList.append(KeypressCommand("insertText", text));
-    else {
-        eventText.replace(NSBackTabCharacter, NSTabCharacter); // same thing is done in KeyEventMac.mm in WebCore
-        _data->_commandsList.append(KeypressCommand("insertText", eventText));
+    if (parameters) {
+        parameters->eventInterpretationHadSideEffects |= eventHandled;
+        parameters->cachedTextInputState = newTextInputState;
     }
 }
 
@@ -1130,9 +1154,6 @@ static const short kIOHIDEventTypeScroll = 6;
         }
     }
 
-    _data->_underlines.clear();
-    _data->_selectionStart = 0;
-    _data->_selectionEnd = 0;
     // 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).
@@ -1144,8 +1165,22 @@ static const short kIOHIDEventTypeScroll = 6;
     _data->_page->handleKeyboardEvent(NativeWebKeyboardEvent(theEvent, self));
 }
 
-- (NSTextInputContext *)inputContext {
-    if (_data->_pluginComplexTextInputIdentifier && !_data->_isInInterpretKeyEvents)
+- (void)_executeSavedKeypressCommands
+{
+    WKViewInterpretKeyEventsParameters* parameters = _data->_interpretKeyEventsParameters;
+    if (!parameters || parameters->commands->isEmpty())
+        return;
+
+    TextInputState newTextInputState;
+    parameters->eventInterpretationHadSideEffects |= _data->_page->executeKeypressCommands(*parameters->commands, newTextInputState);
+    parameters->cachedTextInputState = newTextInputState;
+    parameters->commands->clear();
+}
+
+
+- (NSTextInputContext *)inputContext
+{
+    if (_data->_pluginComplexTextInputIdentifier && !_data->_interpretKeyEventsParameters)
         return [[WKTextInputWindowController sharedTextInputWindowController] inputContext];
 
     return [super inputContext];
@@ -1153,28 +1188,59 @@ static const short kIOHIDEventTypeScroll = 6;
 
 - (NSRange)selectedRange
 {
-    if (_data->_page->selectionState().isNone || !_data->_page->selectionState().isContentEditable)
-        return NSMakeRange(NSNotFound, 0);
-    
-    LOG(TextInput, "selectedRange -> (%u, %u)", _data->_page->selectionState().selectedRangeStart, _data->_page->selectionState().selectedRangeLength);
-    return NSMakeRange(_data->_page->selectionState().selectedRangeStart, _data->_page->selectionState().selectedRangeLength);
+    [self _executeSavedKeypressCommands];
+
+    uint64_t selectionStart;
+    uint64_t selectionLength;
+    _data->_page->getSelectedRange(selectionStart, selectionLength);
+
+    NSRange result = NSMakeRange(selectionStart, selectionLength);
+    if (result.location == NSNotFound)
+        LOG(TextInput, "selectedRange -> (NSNotFound, %u)", result.length);
+    else
+        LOG(TextInput, "selectedRange -> (%u, %u)", result.location, result.length);
+
+    return result;
 }
 
 - (BOOL)hasMarkedText
 {
-    LOG(TextInput, "hasMarkedText -> %u", _data->_page->selectionState().hasComposition);
-    return _data->_page->selectionState().hasComposition;
+    [self _executeSavedKeypressCommands];
+
+    WKViewInterpretKeyEventsParameters* parameters = _data->_interpretKeyEventsParameters;
+
+    BOOL result;
+    if (parameters)
+        result = parameters->cachedTextInputState.hasMarkedText;
+    else {
+        uint64_t location;
+        uint64_t length;
+        _data->_page->getMarkedRange(location, length);
+        result = location != NSNotFound;
+    }
+
+    LOG(TextInput, "hasMarkedText -> %u", result);
+    return result;
 }
 
 - (void)unmarkText
 {
+    [self _executeSavedKeypressCommands];
+
     LOG(TextInput, "unmarkText");
 
-    // We'd need a different code path here if we wanted to be able to handle this
-    // outside of interpretKeyEvents.
-    ASSERT(_data->_isInInterpretKeyEvents);
+    // 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->_commandsList.append(KeypressCommand("unmarkText"));
+    TextInputState newTextInputState;
+    _data->_page->confirmComposition(newTextInputState);
+    if (parameters)
+        parameters->cachedTextInputState = newTextInputState;
 }
 
 - (NSArray *)validAttributesForMarkedText
@@ -1217,38 +1283,61 @@ static void extractUnderlines(NSAttributedString *string, Vector<CompositionUnde
 
 - (void)setMarkedText:(id)string selectedRange:(NSRange)newSelRange
 {
-    BOOL isAttributedString = [string isKindOfClass:[NSAttributedString class]]; // Otherwise, NSString
-    
+    [self _executeSavedKeypressCommands];
+
+    BOOL isAttributedString = [string isKindOfClass:[NSAttributedString class]];
+    ASSERT(isAttributedString || [string isKindOfClass:[NSString class]]);
+
     LOG(TextInput, "setMarkedText:\"%@\" selectedRange:(%u, %u)", isAttributedString ? [string string] : string, newSelRange.location, newSelRange.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;
+    }
     
-    NSString *text = string;
-    
+    Vector<CompositionUnderline> underlines;
+    NSString *text;
+    NSRange replacementRange = { NSNotFound, 0 };
+
     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, _data->_underlines);
-    }
-    
-    // We'd need a different code path here if we wanted to be able to handle this
-    // outside of interpretKeyEvents.
-    ASSERT(_data->_isInInterpretKeyEvents);
+        NSString *rangeString = [string attribute:NSTextInputReplacementRangeAttributeName atIndex:0 longestEffectiveRange:0 inRange:NSMakeRange(0, [text length])];
+        LOG(TextInput, "    ReplacementRange: %@", rangeString);
+        // The AppKit adds a 'secret' property to the string that contains the replacement range.
+        // The replacement range is the range of the the text that should be replaced with the new string.
+        if (rangeString)
+            replacementRange = NSRangeFromString(rangeString);
+
+        extractUnderlines(string, underlines);
+    } else
+        text = string;
 
-    _data->_commandsList.append(KeypressCommand("setMarkedText", text));
-    _data->_selectionStart = newSelRange.location;
-    _data->_selectionEnd = NSMaxRange(newSelRange);
+    TextInputState newTextInputState;
+    _data->_page->setComposition(text, underlines, newSelRange.location, NSMaxRange(newSelRange), replacementRange.location, NSMaxRange(replacementRange), newTextInputState);
+    if (parameters)
+        parameters->cachedTextInputState = newTextInputState;
 }
 
 - (NSRange)markedRange
 {
+    [self _executeSavedKeypressCommands];
+
     uint64_t location;
     uint64_t length;
-
     _data->_page->getMarkedRange(location, length);
+
     LOG(TextInput, "markedRange -> (%u, %u)", location, length);
     return NSMakeRange(location, length);
 }
 
 - (NSAttributedString *)attributedSubstringFromRange:(NSRange)nsRange
 {
+    [self _executeSavedKeypressCommands];
+
     // This is not implemented for now. Need to figure out how to serialize the attributed string across processes.
     LOG(TextInput, "attributedSubstringFromRange");
     return nil;
@@ -1256,6 +1345,8 @@ static void extractUnderlines(NSAttributedString *string, Vector<CompositionUnde
 
 - (NSUInteger)characterIndexForPoint:(NSPoint)thePoint
 {
+    [self _executeSavedKeypressCommands];
+
     NSWindow *window = [self window];
     
     if (window)
@@ -1269,6 +1360,8 @@ static void extractUnderlines(NSAttributedString *string, Vector<CompositionUnde
 
 - (NSRect)firstRectForCharacterRange:(NSRange)theRange
 { 
+    [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).
@@ -1744,27 +1837,31 @@ static void drawPageBackground(CGContextRef context, WebPageProxy* page, const I
     _data->_keyDownEventBeingResent = nullptr;
 }
 
-- (Vector<KeypressCommand>&)_interceptKeyEvent:(NSEvent *)theEvent 
+- (BOOL)_interpretKeyEvent:(NSEvent *)event withCachedTextInputState:(const TextInputState&)cachedTextInputState savingCommandsTo:(Vector<WebCore::KeypressCommand>&)commands
 {
-    ASSERT(!_data->_isInInterpretKeyEvents);
+    ASSERT(!_data->_interpretKeyEventsParameters);
+    ASSERT(commands.isEmpty());
 
-    _data->_isInInterpretKeyEvents = true;
-    _data->_commandsList.clear();
+    WKViewInterpretKeyEventsParameters parameters;
+    parameters.cachedTextInputState = cachedTextInputState;
+    parameters.eventInterpretationHadSideEffects = 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;
 
-    // Calling interpretKeyEvents will trigger one or more calls to doCommandBySelector and insertText
-    // that will populate the commandsList vector.
-    [self interpretKeyEvents:[NSArray arrayWithObject:theEvent]];
+    [self interpretKeyEvents:[NSArray arrayWithObject:event]];
 
-    _data->_isInInterpretKeyEvents = false;
+    _data->_interpretKeyEventsParameters = 0;
 
-    return _data->_commandsList;
-}
+    // 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)
+        return YES;
 
-- (void)_getTextInputState:(unsigned)start selectionEnd:(unsigned)end underlines:(Vector<CompositionUnderline>&)lines
-{
-    start = _data->_selectionStart;
-    end = _data->_selectionEnd;
-    lines = _data->_underlines;
+    // 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;
 }
 
 - (NSRect)_convertToDeviceSpace:(NSRect)rect
index e4a40f7..a626ec2 100644 (file)
@@ -35,6 +35,7 @@ namespace WebKit {
     class DrawingAreaProxy;
     class FindIndicator;
     class LayerTreeContext;
+    struct TextInputState;
 }
 
 #if ENABLE(FULLSCREEN_API)
@@ -51,8 +52,7 @@ namespace WebKit {
 - (void)_toolTipChangedFrom:(NSString *)oldToolTip to:(NSString *)newToolTip;
 - (void)_setCursor:(NSCursor *)cursor;
 - (void)_setUserInterfaceItemState:(NSString *)commandName enabled:(BOOL)isEnabled state:(int)newState;
-- (Vector<WebCore::KeypressCommand>&)_interceptKeyEvent:(NSEvent *)theEvent;
-- (void)_getTextInputState:(unsigned)start selectionEnd:(unsigned)end underlines:(Vector<WebCore::CompositionUnderline>&)lines;
+- (BOOL)_interpretKeyEvent:(NSEvent *)theEvent withCachedTextInputState:(const WebKit::TextInputState&)cachedTextInputState savingCommandsTo:(Vector<WebCore::KeypressCommand>&)commands;
 - (void)_resendKeyDownEvent:(NSEvent *)event;
 - (NSRect)_convertToDeviceSpace:(NSRect)rect;
 - (NSRect)_convertToUserSpace:(NSRect)rect;
index 83ce502..9c6e2a4 100644 (file)
@@ -100,7 +100,7 @@ public:
     virtual void clearAllEditCommands() = 0;
 #if PLATFORM(MAC)
     virtual void accessibilityWebProcessTokenReceived(const CoreIPC::DataReference&) = 0;
-    virtual void interceptKeyEvent(const NativeWebKeyboardEvent&, Vector<WebCore::KeypressCommand>& commandName, uint32_t selectionStart, uint32_t selectionEnd, Vector<WebCore::CompositionUnderline>& underlines) = 0;
+    virtual bool interpretKeyEvent(const NativeWebKeyboardEvent&, const TextInputState&, Vector<WebCore::KeypressCommand>&) = 0;
     virtual void setDragImage(const WebCore::IntPoint& clientPosition, PassRefPtr<ShareableBitmap> dragImage, bool isLinkDrag) = 0;
 #endif
 #if PLATFORM(WIN)
index e3e62d4..012ef8a 100644 (file)
@@ -97,6 +97,7 @@ class WebWheelEvent;
 struct DictionaryPopupInfo;
 struct PlatformPopupMenuData;
 struct PrintInfo;
+struct TextInputState;
 struct WebPageCreationParameters;
 struct WebPopupItem;
 
@@ -251,9 +252,16 @@ public:
 #if PLATFORM(MAC)
     void updateWindowIsVisible(bool windowIsVisible);
     void windowAndViewFramesChanged(const WebCore::IntRect& windowFrameInScreenCoordinates, const WebCore::IntRect& viewFrameInWindowCoordinates, const WebCore::IntPoint& accessibilityViewCoordinates);
+
+    void setComposition(const String& text, Vector<WebCore::CompositionUnderline> underlines, uint64_t selectionStart, uint64_t selectionEnd, uint64_t replacementRangeStart, uint64_t replacementRangeEnd, TextInputState& newState);
+    void confirmComposition(TextInputState& newState);
+    bool insertText(const String& text, uint64_t replacementRangeStart, uint64_t replacementRangeEnd, TextInputState& newState);
     void getMarkedRange(uint64_t& location, uint64_t& length);
+    void getSelectedRange(uint64_t& location, uint64_t& length);
     uint64_t characterIndexForPoint(const WebCore::IntPoint);
     WebCore::IntRect firstRectForCharacterRange(uint64_t, uint64_t);
+    bool executeKeypressCommands(const Vector<WebCore::KeypressCommand>&, TextInputState& newState);
+
     void sendComplexTextInputToPlugin(uint64_t pluginComplexTextInputIdentifier, const String& textInput);
     CGContextRef containingWindowGraphicsContext();
 #endif
@@ -581,7 +589,7 @@ private:
 
     // Keyboard handling
 #if PLATFORM(MAC)
-    void interpretKeyEvent(uint32_t eventType, Vector<WebCore::KeypressCommand>&, uint32_t selectionStart, uint32_t selectionEnd, Vector<WebCore::CompositionUnderline>& underlines);
+    void interpretQueuedKeyEvent(const TextInputState&, bool& handled, Vector<WebCore::KeypressCommand>&);
 #endif
     
     // Find.
index e145d82..d2cf969 100644 (file)
@@ -130,8 +130,8 @@ messages -> WebPageProxy {
     # Dictionary support.
     DidPerformDictionaryLookup(WTF::String text, WebKit::DictionaryPopupInfo dictionaryPopupInfo)
 
-    # Keyboard support messages
-    InterpretKeyEvent(uint32_t type) -> (Vector<WebCore::KeypressCommand> commandName, uint32_t selectionStart, uint32_t selectionEnd, Vector<WebCore::CompositionUnderline> underlines)
+    # Keyboard input support messages
+    InterpretQueuedKeyEvent(WebKit::TextInputState state) -> (bool handled, Vector<WebCore::KeypressCommand> savedCommands)
     
     # Remote accessibility messages
     RegisterWebProcessAccessibilityToken(CoreIPC::DataReference data)
index 90df81e..fc67720 100644 (file)
@@ -31,6 +31,7 @@
 #import "NativeWebKeyboardEvent.h"
 #import "PageClient.h"
 #import "TextChecker.h"
+#import "TextInputState.h"
 #import "WebPageMessages.h"
 #import "WebProcessProxy.h"
 #import <wtf/text/StringConcatenate.h>
@@ -134,11 +135,33 @@ void WebPageProxy::windowAndViewFramesChanged(const IntRect& windowFrameInScreen
     process()->send(Messages::WebPage::WindowAndViewFramesChanged(windowFrameInScreenCoordinates, viewFrameInWindowCoordinates, accessibilityViewCoordinates), m_pageID);
 }
 
+void WebPageProxy::setComposition(const String& text, Vector<CompositionUnderline> underlines, uint64_t selectionStart, uint64_t selectionEnd, uint64_t replacementRangeStart, uint64_t replacementRangeEnd, TextInputState& newState)
+{
+    process()->sendSync(Messages::WebPage::SetComposition(text, underlines, selectionStart, selectionEnd, replacementRangeStart, replacementRangeEnd), Messages::WebPage::SetComposition::Reply(newState), m_pageID);
+}
+
+void WebPageProxy::confirmComposition(TextInputState& newState)
+{
+    process()->sendSync(Messages::WebPage::ConfirmComposition(), Messages::WebPage::ConfirmComposition::Reply(newState), m_pageID);
+}
+
+bool WebPageProxy::insertText(const String& text, uint64_t replacementRangeStart, uint64_t replacementRangeEnd, TextInputState& newState)
+{
+    bool handled;
+    process()->sendSync(Messages::WebPage::InsertText(text, replacementRangeStart, replacementRangeEnd), Messages::WebPage::InsertText::Reply(handled, newState), m_pageID);
+    return handled;
+}
+
 void WebPageProxy::getMarkedRange(uint64_t& location, uint64_t& length)
 {
     process()->sendSync(Messages::WebPage::GetMarkedRange(), Messages::WebPage::GetMarkedRange::Reply(location, length), m_pageID);
 }
-    
+
+void WebPageProxy::getSelectedRange(uint64_t& location, uint64_t& length)
+{
+    process()->sendSync(Messages::WebPage::GetSelectedRange(), Messages::WebPage::GetSelectedRange::Reply(location, length), m_pageID);
+}
+
 uint64_t WebPageProxy::characterIndexForPoint(const IntPoint point)
 {
     uint64_t result;
@@ -152,7 +175,14 @@ WebCore::IntRect WebPageProxy::firstRectForCharacterRange(uint64_t location, uin
     process()->sendSync(Messages::WebPage::FirstRectForCharacterRange(location, length), Messages::WebPage::FirstRectForCharacterRange::Reply(resultRect), m_pageID);
     return resultRect;
 }
-    
+
+bool WebPageProxy::executeKeypressCommands(const Vector<WebCore::KeypressCommand>& commands, TextInputState& newState)
+{
+    bool result;
+    process()->sendSync(Messages::WebPage::ExecuteKeypressCommands(commands), Messages::WebPage::ExecuteKeypressCommands::Reply(result, newState), m_pageID);
+    return result;
+}
+
 bool WebPageProxy::writeSelectionToPasteboard(const String& pasteboardName, const Vector<String>& pasteboardTypes)
 {
     bool result;
@@ -186,9 +216,9 @@ void WebPageProxy::performDictionaryLookupAtLocation(const WebCore::FloatPoint&
     process()->send(Messages::WebPage::PerformDictionaryLookupAtLocation(point), m_pageID); 
 }
 
-void WebPageProxy::interpretKeyEvent(uint32_t type, Vector<KeypressCommand>& commandsList, uint32_t selectionStart, uint32_t selectionEnd, Vector<CompositionUnderline>& underlines)
+void WebPageProxy::interpretQueuedKeyEvent(const TextInputState& state, bool& handled, Vector<WebCore::KeypressCommand>& commands)
 {
-    m_pageClient->interceptKeyEvent(m_keyEventQueue.first(), commandsList, selectionStart, selectionEnd, underlines);
+    handled = m_pageClient->interpretKeyEvent(m_keyEventQueue.first(), state, commands);
 }
 
 // Complex text input support for plug-ins.
index 1eac205..4216fc0 100644 (file)
                D3B9484911FF4B6500032B39 /* WebSearchPopupMenu.h in Headers */ = {isa = PBXBuildFile; fileRef = D3B9484511FF4B6500032B39 /* WebSearchPopupMenu.h */; };
                E134F01712EA5D33004EC58D /* WKPrintingView.h in Headers */ = {isa = PBXBuildFile; fileRef = E134F01512EA5D11004EC58D /* WKPrintingView.h */; };
                E134F01A12EA5D99004EC58D /* WKPrintingView.mm in Sources */ = {isa = PBXBuildFile; fileRef = E134F01912EA5D99004EC58D /* WKPrintingView.mm */; };
+               E1839349134297A5001842EC /* TextInputState.h in Headers */ = {isa = PBXBuildFile; fileRef = E1839348134297A5001842EC /* TextInputState.h */; };
                E18C92F412DB9E7100CF2AEB /* PrintInfo.cpp in Sources */ = {isa = PBXBuildFile; fileRef = E18C92F312DB9E7100CF2AEB /* PrintInfo.cpp */; };
                E1BB16A413201B9B00F49431 /* FullKeyboardAccessWatcher.h in Headers */ = {isa = PBXBuildFile; fileRef = E1BB1688132018BA00F49431 /* FullKeyboardAccessWatcher.h */; };
                E1BB16A513201B9B00F49431 /* FullKeyboardAccessWatcher.mm in Sources */ = {isa = PBXBuildFile; fileRef = E1BB1689132018BA00F49431 /* FullKeyboardAccessWatcher.mm */; };
                D3B9484511FF4B6500032B39 /* WebSearchPopupMenu.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = WebSearchPopupMenu.h; sourceTree = "<group>"; };
                E134F01512EA5D11004EC58D /* WKPrintingView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = WKPrintingView.h; sourceTree = "<group>"; };
                E134F01912EA5D99004EC58D /* WKPrintingView.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = WKPrintingView.mm; sourceTree = "<group>"; };
+               E1839348134297A5001842EC /* TextInputState.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TextInputState.h; sourceTree = "<group>"; };
                E18C92F312DB9E7100CF2AEB /* PrintInfo.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = PrintInfo.cpp; sourceTree = "<group>"; };
                E1BB1688132018BA00F49431 /* FullKeyboardAccessWatcher.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FullKeyboardAccessWatcher.h; sourceTree = "<group>"; };
                E1BB1689132018BA00F49431 /* FullKeyboardAccessWatcher.mm */ = {isa = PBXFileReference; explicitFileType = sourcecode.cpp.objcpp; fileEncoding = 4; path = FullKeyboardAccessWatcher.mm; sourceTree = "<group>"; };
                                1AAB4AA91296F1540023952F /* SandboxExtensionMac.mm */,
                                1A9636BA12F348490078A062 /* ShareableSurface.cpp */,
                                1A9636BB12F348490078A062 /* ShareableSurface.h */,
+                               E1839348134297A5001842EC /* TextInputState.h */,
                                BC9E95D211449B0300870E71 /* UpdateChunk.cpp */,
                                BC9E95D111449B0300870E71 /* UpdateChunk.h */,
                                BCE23262122C6CF300D5C35A /* WebCoreArgumentCodersMac.mm */,
                                C064504A133BE709003470E2 /* LayerTreeHostCA.h in Headers */,
                                5110AE0D133C16CB0072717A /* WKIconDatabase.h in Headers */,
                                5123CF1C133D260A0056F800 /* WKIconDatabaseCG.h in Headers */,
+                               E1839349134297A5001842EC /* TextInputState.h in Headers */,
                                C0FA52431345694A0028E8C2 /* LayerTreeHostCAMac.h in Headers */,
                        );
                        runOnlyForDeploymentPostprocessing = 0;
index 7a95a72..282b232 100644 (file)
@@ -64,13 +64,13 @@ namespace WebKit {
     
 void WebEditorClient::handleKeyboardEvent(KeyboardEvent* event)
 {
-    if (m_page->interceptEditingKeyboardEvent(event, false))
+    if (m_page->handleEditingKeyboardEvent(event, false))
         event->setDefaultHandled();
 }
 
 void WebEditorClient::handleInputMethodKeydown(KeyboardEvent* event)
 {
-    if (m_page->interceptEditingKeyboardEvent(event, true))
+    if (m_page->handleEditingKeyboardEvent(event, true))
         event->setDefaultHandled();
 }
     
index 2ba8af5..e9aa29d 100644 (file)
@@ -151,6 +151,7 @@ WebPage::WebPage(uint64_t pageID, const WebPageCreationParameters& parameters)
 #if PLATFORM(MAC)
     , m_windowIsVisible(false)
     , m_isSmartInsertDeleteEnabled(parameters.isSmartInsertDeleteEnabled)
+    , m_keyboardEventBeingInterpreted(0)
 #elif PLATFORM(WIN)
     , m_nativeWindow(parameters.nativeWindow)
 #endif
index 64f4d32..24b110b 100644 (file)
@@ -81,6 +81,7 @@ namespace WebCore {
     class ResourceRequest;
     class SharedBuffer;
     class VisibleSelection;
+    struct KeypressCommand;
 }
 
 namespace WebKit {
@@ -104,6 +105,7 @@ class WebPageGroupProxy;
 class WebPopupMenu;
 class WebWheelEvent;
 struct PrintInfo;
+struct TextInputState;
 struct WebPageCreationParameters;
 struct WebPreferencesStore;
 
@@ -159,7 +161,9 @@ public:
     void layoutIfNeeded();
 
     // -- Called from WebCore clients.
-#if !PLATFORM(MAC)
+#if PLATFORM(MAC)
+    bool handleEditingKeyboardEvent(WebCore::KeyboardEvent*, bool saveCommands);
+#else
     bool handleEditingKeyboardEvent(WebCore::KeyboardEvent*);
 #endif
     void show();
@@ -240,7 +244,6 @@ public:
     bool windowIsVisible() const { return m_windowIsVisible; }
     const WebCore::IntRect& windowFrameInScreenCoordinates() const { return m_windowFrameInScreenCoordinates; }
     const WebCore::IntRect& viewFrameInWindowCoordinates() const { return m_viewFrameInWindowCoordinates; }
-    bool interceptEditingKeyboardEvent(WebCore::KeyboardEvent*, bool);
 #elif PLATFORM(WIN)
     HWND nativeWindow() const { return m_nativeWindow; }
 #endif
@@ -305,9 +308,14 @@ public:
     
     void sendComplexTextInputToPlugin(uint64_t pluginComplexTextInputIdentifier, const String& textInput);
 
+    void setComposition(const String& text, Vector<WebCore::CompositionUnderline> underlines, uint64_t selectionStart, uint64_t selectionEnd, uint64_t replacementRangeStart, uint64_t replacementRangeEnd, TextInputState& newState);
+    void confirmComposition(TextInputState& newState);
+    void insertText(const String& text, uint64_t replacementRangeStart, uint64_t replacementRangeEnd, bool& handled, TextInputState& newState);
     void getMarkedRange(uint64_t& location, uint64_t& length);
+    void getSelectedRange(uint64_t& location, uint64_t& length);
     void characterIndexForPoint(const WebCore::IntPoint point, uint64_t& result);
     void firstRectForCharacterRange(uint64_t location, uint64_t length, WebCore::IntRect& resultRect);
+    void executeKeypressCommands(const Vector<WebCore::KeypressCommand>&, bool& handled, TextInputState& newState);
     void writeSelectionToPasteboard(const WTF::String& pasteboardName, const WTF::Vector<WTF::String>& pasteboardTypes, bool& result);
     void readSelectionFromPasteboard(const WTF::String& pasteboardName, bool& result);
 #elif PLATFORM(WIN)
@@ -387,9 +395,15 @@ private:
     void didReceiveWebPageMessage(CoreIPC::Connection*, CoreIPC::MessageID, CoreIPC::ArgumentDecoder*);
     CoreIPC::SyncReplyMode didReceiveSyncWebPageMessage(CoreIPC::Connection*, CoreIPC::MessageID, CoreIPC::ArgumentDecoder*, CoreIPC::ArgumentEncoder*);
 
+#if !PLATFORM(MAC)
     static const char* interpretKeyEvent(const WebCore::KeyboardEvent*);
+#endif
     bool performDefaultBehaviorForKeyEvent(const WebKeyboardEvent&);
 
+#if PLATFORM(MAC)
+    bool executeKeypressCommandsInternal(const Vector<WebCore::KeypressCommand>&, WebCore::KeyboardEvent*);
+#endif
+
     String sourceForFrame(WebFrame*);
 
     void loadData(PassRefPtr<WebCore::SharedBuffer>, const String& MIMEType, const String& encodingName, const WebCore::KURL& baseURL, const WebCore::KURL& failingURL);
@@ -549,6 +563,9 @@ private:
     RetainPtr<AccessibilityWebPageObject> m_mockAccessibilityElement;
 
     RetainPtr<NSObject> m_dragSource;
+
+    WebCore::KeyboardEvent* m_keyboardEventBeingInterpreted;
+
 #elif PLATFORM(WIN)
     // Our view's window (in the UI process).
     HWND m_nativeWindow;
index 69bd54d..8ab292b 100644 (file)
@@ -179,14 +179,23 @@ messages -> WebPage {
 
     SetWindowIsVisible(bool windowIsVisible)
     WindowAndViewFramesChanged(WebCore::IntRect windowFrameInScreenCoordinates, WebCore::IntRect viewFrameInWindowCoordinates, WebCore::IntPoint accessibilityViewCoordinates)
-    GetMarkedRange() -> (uint64_t location, uint64_t length)
-    CharacterIndexForPoint(WebCore::IntPoint point) -> (uint64_t result)
-    FirstRectForCharacterRange(uint64_t location, uint64_t length) -> (WebCore::IntRect resultRect)
     RegisterUIProcessAccessibilityTokens(CoreIPC::DataReference elemenToken, CoreIPC::DataReference windowToken)
     WriteSelectionToPasteboard(WTF::String pasteboardName, WTF::Vector<WTF::String> pasteboardTypes) -> (bool result)
     ReadSelectionFromPasteboard(WTF::String pasteboardName) -> (bool result)
+
+    # Text input.
+    SetComposition(WTF::String text, WTF::Vector<WebCore::CompositionUnderline> underlines, uint64_t selectionStart, uint64_t selectionEnd, uint64_t replacementRangeStart, uint64_t replacementRangeEnd) -> (WebKit::TextInputState newState)
+    ConfirmComposition() -> (WebKit::TextInputState newState)
+    InsertText(WTF::String text, uint64_t replacementRangeStart, uint64_t replacementRangeEnd) -> (bool handled, WebKit::TextInputState newState)
+    GetMarkedRange() -> (uint64_t location, uint64_t length)
+    GetSelectedRange() -> (uint64_t location, uint64_t length)
+    CharacterIndexForPoint(WebCore::IntPoint point) -> (uint64_t result)
+    FirstRectForCharacterRange(uint64_t location, uint64_t length) -> (WebCore::IntRect resultRect)
+    ExecuteKeypressCommands(Vector<WebCore::KeypressCommand> savedCommands) -> (bool handled, WebKit::TextInputState newState)
+
 #endif
 #if PLATFORM(WIN)
+    // FIXME: Unify with Mac counterparts.
     ConfirmComposition(WTF::String compositionString)
     SetComposition(WTF::String compositionString, WTF::Vector<WebCore::CompositionUnderline> underlines, uint64_t cursorPosition)
     FirstRectForCharacterInSelectedRange(uint64_t characterPosition) -> (WebCore::IntRect resultRect)
index 8d81889..383cfa2 100644 (file)
@@ -29,6 +29,7 @@
 #import "AccessibilityWebPageObject.h"
 #import "DataReference.h"
 #import "PluginView.h"
+#import "TextInputState.h"
 #import "WebCoreArgumentCoders.h"
 #import "WebEvent.h"
 #import "WebFrame.h"
@@ -53,6 +54,8 @@ using namespace WebCore;
 
 namespace WebKit {
 
+static PassRefPtr<Range> convertToRange(Frame*, NSRange);
+
 void WebPage::platformInitialize()
 {
     m_page->addSchedulePair(SchedulePair::create([NSRunLoop currentRunLoop], kCFRunLoopCommonModes));
@@ -77,68 +80,129 @@ void WebPage::platformPreferencesDidChange(const WebPreferencesStore&)
 {
 }
 
-bool WebPage::interceptEditingKeyboardEvent(KeyboardEvent* evt, bool shouldSaveCommand)
+typedef HashMap<String, String> SelectorNameMap;
+
+// Map selectors into Editor command names.
+// This is not needed for any selectors that have the same name as the Editor command.
+static const SelectorNameMap* createSelectorExceptionMap()
+{
+    SelectorNameMap* map = new HashMap<String, String>;
+
+    map->add("insertNewlineIgnoringFieldEditor:", "InsertNewline");
+    map->add("insertParagraphSeparator:", "InsertNewline");
+    map->add("insertTabIgnoringFieldEditor:", "InsertTab");
+    map->add("pageDown:", "MovePageDown");
+    map->add("pageDownAndModifySelection:", "MovePageDownAndModifySelection");
+    map->add("pageUp:", "MovePageUp");
+    map->add("pageUpAndModifySelection:", "MovePageUpAndModifySelection");
+
+    return map;
+}
+
+static String commandNameForSelectorName(const String& selectorName)
+{
+    // Check the exception map first.
+    static const SelectorNameMap* exceptionMap = createSelectorExceptionMap();
+    SelectorNameMap::const_iterator it = exceptionMap->find(selectorName);
+    if (it != exceptionMap->end())
+        return it->second;
+
+    // Remove the trailing colon.
+    // No need to capitalize the command name since Editor command names are not case sensitive.
+    size_t selectorNameLength = selectorName.length();
+    if (selectorNameLength < 2 || selectorName[selectorNameLength - 1] != ':')
+        return String();
+    return selectorName.left(selectorNameLength - 1);
+}
+
+static TextInputState currentTextInputState(Frame* frame)
 {
-    Node* node = evt->target()->toNode();
+    TextInputState state;
+    state.hasMarkedText = frame->editor()->hasComposition();
+    state.selectionIsEditable = !frame->selection()->isNone() && frame->selection()->isContentEditable();
+    return state;
+}
+
+static Frame* frameForEvent(KeyboardEvent* event)
+{
+    Node* node = event->target()->toNode();
     ASSERT(node);
     Frame* frame = node->document()->frame();
     ASSERT(frame);
+    return frame;
+}
+
+bool WebPage::executeKeypressCommandsInternal(const Vector<WebCore::KeypressCommand>& commands, KeyboardEvent* event)
+{
+    Frame* frame = frameForEvent(event);
+    ASSERT(frame->page() == corePage());
+
+    bool eventWasHandled = false;
+    for (size_t i = 0; i < commands.size(); ++i) {
+        if (commands[i].commandName == "insertText:") {
+            ASSERT(!frame->editor()->hasComposition());
+
+            if (!frame->editor()->canEdit())
+                continue;
+
+            // An insertText: might be handled by other responders in the chain if we don't handle it.
+            // One example is space bar that results in scrolling down the page.
+            eventWasHandled |= frame->editor()->insertText(commands[i].text, event);
+        } else {
+            Editor::Command command = frame->editor()->command(commandNameForSelectorName(commands[i].commandName));
+            if (command.isSupported())
+                eventWasHandled |= command.execute(event);
+            // FIXME: WebHTMLView sends the event up the responder chain with WebResponderChainSink if it's not supported by the editor. Should we do the same?
+        }
+    }
+    return eventWasHandled;
+}
+
+bool WebPage::handleEditingKeyboardEvent(KeyboardEvent* event, bool saveCommands)
+{
+    ASSERT(!saveCommands || event->keypressCommands().isEmpty()); // Save commands once for each event.
+
+    Frame* frame = frameForEvent(event);
     
-    const PlatformKeyboardEvent* keyEvent = evt->keyEvent();
-    if (!keyEvent)
+    const PlatformKeyboardEvent* platformEvent = event->keyEvent();
+    if (!platformEvent)
         return false;
-    const Vector<KeypressCommand>& commands = evt->keypressCommands();
-    bool hasKeypressCommand = !commands.isEmpty();
-    
+    Vector<KeypressCommand>& commands = event->keypressCommands();
+
+    if ([platformEvent->macEvent() type] == NSFlagsChanged)
+        return false;
+
     bool eventWasHandled = false;
     
-    if (shouldSaveCommand || !hasKeypressCommand) {
-        Vector<KeypressCommand> commandsList;  
-        Vector<CompositionUnderline> underlines;
-        unsigned start;
-        unsigned end;
-        if (!WebProcess::shared().connection()->sendSync(Messages::WebPageProxy::InterpretKeyEvent(keyEvent->type()), 
-                                                         Messages::WebPageProxy::InterpretKeyEvent::Reply(commandsList, start, end, underlines),
-                                                         m_pageID, CoreIPC::Connection::NoTimeout))
+    if (saveCommands) {
+        KeyboardEvent* oldEvent = m_keyboardEventBeingInterpreted;
+        m_keyboardEventBeingInterpreted = event;
+        bool sendResult = WebProcess::shared().connection()->sendSync(Messages::WebPageProxy::InterpretQueuedKeyEvent(currentTextInputState(frame)), 
+            Messages::WebPageProxy::InterpretQueuedKeyEvent::Reply(eventWasHandled, commands), m_pageID);
+        m_keyboardEventBeingInterpreted = oldEvent;
+        if (!sendResult)
+            return false;
+
+        // 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.
+        if (!event->keypressCommands().isEmpty())
             return false;
-        if (commandsList.isEmpty())
-            return eventWasHandled;
-        
-        if (commandsList[0].commandName == "setMarkedText") {
-            frame->editor()->setComposition(commandsList[0].text, underlines, start, end);
-            eventWasHandled = true;
-        } else if (commandsList[0].commandName == "insertText" && frame->editor()->hasComposition()) {
-            frame->editor()->confirmComposition(commandsList[0].text);
-            eventWasHandled = true;
-        } else if (commandsList[0].commandName == "unmarkText") {
-            frame->editor()->confirmComposition();
-            eventWasHandled = true;
-        } else {
-            for (size_t i = 0; i < commandsList.size(); i++)
-                evt->keypressCommands().append(commandsList[i]);
-        }
     } else {
-        size_t size = commands.size();
-        // Are there commands that would just cause text insertion if executed via Editor?
+        // Are there commands that could just cause text insertion if executed via Editor?
         // WebKit doesn't have enough information about mode to decide how they should be treated, so we leave it upon WebCore
         // to either handle them immediately (e.g. Tab that changes focus) or let a keypress event be generated
         // (e.g. Tab that inserts a Tab character, or Enter).
         bool haveTextInsertionCommands = false;
-        for (size_t i = 0; i < size; ++i) {
-            if (frame->editor()->command(commands[i].commandName).isTextInsertion())
+        for (size_t i = 0; i < commands.size(); ++i) {
+            if (frame->editor()->command(commandNameForSelectorName(commands[i].commandName)).isTextInsertion())
                 haveTextInsertionCommands = true;
         }
-        if (!haveTextInsertionCommands || keyEvent->type() == PlatformKeyboardEvent::Char) {
-            for (size_t i = 0; i < size; ++i) {
-                if (commands[i].commandName == "insertText") {
-                    // Don't insert null or control characters as they can result in unexpected behaviour
-                    if (evt->charCode() < ' ')
-                        return false;
-                    eventWasHandled = frame->editor()->insertText(commands[i].text, evt);
-                } else
-                    if (frame->editor()->command(commands[i].commandName).isSupported())
-                        eventWasHandled = frame->editor()->command(commands[i].commandName).execute(evt);
-            }
+        // If there are no text insertion commands, default keydown handler is the right time to execute the commands.
+        // Keypress (Char event) handler is the latest opportunity to execute.
+        if (!haveTextInsertionCommands || platformEvent->type() == PlatformKeyboardEvent::Char) {
+            eventWasHandled = executeKeypressCommandsInternal(event->keypressCommands(), event);
+            event->keypressCommands().clear();
         }
     }
     return eventWasHandled;
@@ -152,6 +216,54 @@ void WebPage::sendComplexTextInputToPlugin(uint64_t pluginComplexTextInputIdenti
     }
 }
 
+void WebPage::setComposition(const String& text, Vector<CompositionUnderline> underlines, uint64_t selectionStart, uint64_t selectionEnd, uint64_t replacementRangeStart, uint64_t replacementRangeEnd, TextInputState& newState)
+{
+    Frame* frame = m_page->focusController()->focusedOrMainFrame();
+
+    if (frame->selection()->isContentEditable()) {
+        RefPtr<Range> replacementRange;
+        if (replacementRangeStart != NSNotFound) {
+            replacementRange = convertToRange(frame, NSMakeRange(replacementRangeStart, replacementRangeEnd - replacementRangeStart));
+            frame->selection()->setSelection(VisibleSelection(replacementRange.get(), SEL_DEFAULT_AFFINITY));
+        }
+
+        frame->editor()->setComposition(text, underlines, selectionStart, selectionEnd);
+    }
+
+    newState = currentTextInputState(frame);
+}
+
+void WebPage::confirmComposition(TextInputState& newState)
+{
+    Frame* frame = m_page->focusController()->focusedOrMainFrame();
+
+    frame->editor()->confirmComposition();
+
+    newState = currentTextInputState(frame);
+}
+
+void WebPage::insertText(const String& text, uint64_t replacementRangeStart, uint64_t replacementRangeEnd, bool& handled, TextInputState& newState)
+{
+    Frame* frame = m_page->focusController()->focusedOrMainFrame();
+
+    RefPtr<Range> replacementRange;
+    if (replacementRangeStart != NSNotFound) {
+        replacementRange = convertToRange(frame, NSMakeRange(replacementRangeStart, replacementRangeEnd - replacementRangeStart));
+        frame->selection()->setSelection(VisibleSelection(replacementRange.get(), SEL_DEFAULT_AFFINITY));
+    }
+
+    if (!frame->editor()->hasComposition()) {
+        // An insertText: might be handled by other responders in the chain if we don't handle it.
+        // One example is space bar that results in scrolling down the page.
+        handled = frame->editor()->insertText(text, m_keyboardEventBeingInterpreted);
+    } else {
+        handled = true;
+        frame->editor()->confirmComposition(text);
+    }
+
+    newState = currentTextInputState(frame);
+}
+
 void WebPage::getMarkedRange(uint64_t& location, uint64_t& length)
 {
     location = NSNotFound;
@@ -163,6 +275,16 @@ void WebPage::getMarkedRange(uint64_t& location, uint64_t& length)
     getLocationAndLengthFromRange(frame->editor()->compositionRange().get(), location, length);
 }
 
+void WebPage::getSelectedRange(uint64_t& location, uint64_t& length)
+{
+    location = NSNotFound;
+    length = 0;
+    Frame* frame = m_page->focusController()->focusedOrMainFrame();
+    if (!frame)
+        return;
+    
+    getLocationAndLengthFromRange(frame->selection()->toNormalizedRange().get(), location, length);
+}
 
 static PassRefPtr<Range> characterRangeAtPositionForPoint(Frame* frame, const VisiblePosition& position, const IntPoint& point)
 {
@@ -211,7 +333,7 @@ void WebPage::characterIndexForPoint(IntPoint point, uint64_t& index)
     getLocationAndLengthFromRange(range.get(), index, length);
 }
 
-static PassRefPtr<Range> convertToRange(Frame* frame, NSRange nsrange)
+PassRefPtr<Range> convertToRange(Frame* frame, NSRange nsrange)
 {
     if (nsrange.location > INT_MAX)
         return 0;
@@ -246,6 +368,15 @@ void WebPage::firstRectForCharacterRange(uint64_t location, uint64_t length, Web
     resultRect = frame->view()->contentsToWindow(rect);
 }
 
+void WebPage::executeKeypressCommands(const Vector<WebCore::KeypressCommand>& commands, bool& handled, TextInputState& newState)
+{
+    Frame* frame = m_page->focusController()->focusedOrMainFrame();
+
+    handled = executeKeypressCommandsInternal(commands, m_keyboardEventBeingInterpreted);
+
+    newState = currentTextInputState(frame);
+}
+
 static bool isPositionInRange(const VisiblePosition& position, Range* range)
 {
     RefPtr<Range> positionRange = makeRange(position, position);
@@ -388,7 +519,8 @@ bool WebPage::performDefaultBehaviorForKeyEvent(const WebKeyboardEvent& keyboard
     if (keyboardEvent.type() != WebEvent::KeyDown)
         return false;
 
-    // FIXME: This should be in WebCore.
+    // FIXME: Most of these are already handled by system key bindings, why are we hardcoding them here?
+    // FIXME: Common behaviors like scrolling down on Space should probably be implemented in WebCore.
 
     switch (keyboardEvent.windowsVirtualKeyCode()) {
     case VK_BACK: