Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(385)

Side by Side Diff: chrome/browser/renderer_host/render_widget_host_view_mac.mm

Issue 2805075: [Mac]Handle edit commands from input methods correctly. (Closed) Base URL: http://src.chromium.org/git/chromium.git
Patch Set: git-try -b mac Created 10 years, 5 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
« no previous file with comments | « chrome/browser/renderer_host/render_widget_host_view_mac.h ('k') | no next file » | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
1 // Copyright (c) 2010 The Chromium Authors. All rights reserved. 1 // Copyright (c) 2010 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be 2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file. 3 // found in the LICENSE file.
4 4
5 #include <QuartzCore/QuartzCore.h> 5 #include <QuartzCore/QuartzCore.h>
6 6
7 #include "chrome/browser/renderer_host/render_widget_host_view_mac.h" 7 #include "chrome/browser/renderer_host/render_widget_host_view_mac.h"
8 8
9 #include "app/surface/io_surface_support_mac.h" 9 #include "app/surface/io_surface_support_mac.h"
10 #import "base/chrome_application_mac.h" 10 #import "base/chrome_application_mac.h"
(...skipping 788 matching lines...) Expand 10 before | Expand all | Expand 10 after
799 if (text_input_type_ == WebKit::WebTextInputTypePassword) 799 if (text_input_type_ == WebKit::WebTextInputTypePassword)
800 EnablePasswordInput(); 800 EnablePasswordInput();
801 else 801 else
802 DisablePasswordInput(); 802 DisablePasswordInput();
803 } else { 803 } else {
804 if (text_input_type_ == WebKit::WebTextInputTypePassword) 804 if (text_input_type_ == WebKit::WebTextInputTypePassword)
805 DisablePasswordInput(); 805 DisablePasswordInput();
806 } 806 }
807 } 807 }
808 808
809 // EditCommandMatcher ---------------------------------------------------------
810
811 // This class is used to capture the shortcuts that a given key event maps to.
812 // We instantiate a vanilla NSResponder, call interpretKeyEvents on it, and
813 // record all of the selectors passed into doCommandBySelector while
814 // interpreting the key event. The selectors are converted into edit commands
815 // which can be passed to the render process.
816 //
817 // Caveats:
818 // - Shortcuts involving a sequence of key combinations (chords) don't work,
819 // because we instantiate a new responder for each event.
820 // - We ignore key combinations that don't include a modifier (ctrl, cmd, alt)
821 // because this was causing strange behavior (e.g. tab always inserted a tab
822 // rather than moving to the next field on the page).
823
824 @interface EditCommandMatcher : NSResponder {
825 EditCommands* edit_commands_;
826 }
827 @end
828
829 @implementation EditCommandMatcher
830
831 - (id)initWithEditCommands:(EditCommands*)edit_commands {
832 if ((self = [super init]) != nil) {
833 edit_commands_ = edit_commands;
834 }
835 return self;
836 }
837
838 - (void)doCommandBySelector:(SEL)selector {
839 NSString* editCommand =
840 RWHVMEditCommandHelper::CommandNameForSelector(selector);
841 edit_commands_->push_back(
842 EditCommand(base::SysNSStringToUTF8(editCommand), ""));
843 }
844
845 - (void)insertText:(id)string {
846 // If we don't ignore this, then sometimes we get a bell.
847 }
848
849 + (void)matchEditCommands:(EditCommands*)edit_commands
850 forEvent:(NSEvent*)theEvent {
851 if ([theEvent type] != NSKeyDown)
852 return;
853 // Don't interpret plain key presses. This screws up things like <Tab>.
854 NSUInteger flags = [theEvent modifierFlags];
855 flags &= (NSControlKeyMask | NSAlternateKeyMask | NSCommandKeyMask);
856 if (!flags)
857 return;
858 scoped_nsobject<EditCommandMatcher> matcher(
859 [[EditCommandMatcher alloc] initWithEditCommands:edit_commands]);
860 [matcher.get() interpretKeyEvents:[NSArray arrayWithObject:theEvent]];
861 }
862
863 @end
864
865 // RenderWidgetHostViewCocoa --------------------------------------------------- 809 // RenderWidgetHostViewCocoa ---------------------------------------------------
866 810
867 @implementation RenderWidgetHostViewCocoa 811 @implementation RenderWidgetHostViewCocoa
868 812
869 @synthesize caretRect = caretRect_; 813 @synthesize caretRect = caretRect_;
870 814
871 - (id)initWithRenderWidgetHostViewMac:(RenderWidgetHostViewMac*)r { 815 - (id)initWithRenderWidgetHostViewMac:(RenderWidgetHostViewMac*)r {
872 self = [super initWithFrame:NSZeroRect]; 816 self = [super initWithFrame:NSZeroRect];
873 if (self) { 817 if (self) {
874 editCommand_helper_.reset(new RWHVMEditCommandHelper); 818 editCommand_helper_.reset(new RWHVMEditCommandHelper);
(...skipping 133 matching lines...) Expand 10 before | Expand all | Expand 10 after
1008 // text was deleted or not after handling the key down event. 952 // text was deleted or not after handling the key down event.
1009 BOOL oldHasMarkedText = hasMarkedText_; 953 BOOL oldHasMarkedText = hasMarkedText_;
1010 954
1011 // This method should not be called recursively. 955 // This method should not be called recursively.
1012 DCHECK(!handlingKeyDown_); 956 DCHECK(!handlingKeyDown_);
1013 957
1014 // Tells insertText: and doCommandBySelector: that we are handling a key 958 // Tells insertText: and doCommandBySelector: that we are handling a key
1015 // down event. 959 // down event.
1016 handlingKeyDown_ = YES; 960 handlingKeyDown_ = YES;
1017 961
1018 // These two variables might be set when handling the keyboard event. 962 // These variables might be set when handling the keyboard event.
1019 // Clear them here so that we can know whether they have changed afterwards. 963 // Clear them here so that we can know whether they have changed afterwards.
1020 textToBeInserted_.clear(); 964 textToBeInserted_.clear();
1021 markedText_.clear(); 965 markedText_.clear();
1022 underlines_.clear(); 966 underlines_.clear();
1023 unmarkTextCalled_ = NO; 967 unmarkTextCalled_ = NO;
968 hasEditCommands_ = NO;
969 editCommands_.clear();
1024 970
1025 // Sends key down events to input method first, then we can decide what should 971 // Sends key down events to input method first, then we can decide what should
1026 // be done according to input method's feedback. 972 // be done according to input method's feedback.
1027 if (renderWidgetHostView_->text_input_type_ == WebKit::WebTextInputTypeText) 973 [self interpretKeyEvents:[NSArray arrayWithObject:theEvent]];
1028 [self interpretKeyEvents:[NSArray arrayWithObject:theEvent]];
1029 974
1030 handlingKeyDown_ = NO; 975 handlingKeyDown_ = NO;
1031 976
977 // Indicates if we should send the key event and corresponding editor commands
978 // after processing the input method result.
979 BOOL delayEventUntilAfterImeCompostion = NO;
980
1032 // To emulate Windows, over-write |event.windowsKeyCode| to VK_PROCESSKEY 981 // To emulate Windows, over-write |event.windowsKeyCode| to VK_PROCESSKEY
1033 // while an input method is composing or inserting a text. 982 // while an input method is composing or inserting a text.
1034 // Gmail checks this code in its onkeydown handler to stop auto-completing 983 // Gmail checks this code in its onkeydown handler to stop auto-completing
1035 // e-mail addresses while composing a CJK text. 984 // e-mail addresses while composing a CJK text.
1036 // If the text to be inserted has only one character, then we don't need this 985 // If the text to be inserted has only one character, then we don't need this
1037 // trick, because we'll send the text as a key press event instead. 986 // trick, because we'll send the text as a key press event instead.
1038 if (hasMarkedText_ || oldHasMarkedText || textToBeInserted_.length() > 1) { 987 if (hasMarkedText_ || oldHasMarkedText || textToBeInserted_.length() > 1) {
1039 event.windowsKeyCode = 0xE5; // VKEY_PROCESSKEY 988 NativeWebKeyboardEvent fakeEvent = event;
1040 event.setKeyIdentifierFromWindowsKeyCode(); 989 fakeEvent.windowsKeyCode = 0xE5; // VKEY_PROCESSKEY
1041 event.skip_in_browser = true; 990 fakeEvent.setKeyIdentifierFromWindowsKeyCode();
991 fakeEvent.skip_in_browser = true;
992 widgetHost->ForwardKeyboardEvent(fakeEvent);
993 // If this key event was handled by the input method, but
994 // -doCommandBySelector: (invoked by the call to -interpretKeyEvents: above)
995 // enqueued edit commands, then in order to let webkit handle them
996 // correctly, we need to send the real key event and corresponding edit
997 // commands after processing the input method result.
998 // We shouldn't do this if a new marked text was set by the input method,
999 // otherwise the new marked text might be cancelled by webkit.
1000 if (hasEditCommands_ && !hasMarkedText_)
1001 delayEventUntilAfterImeCompostion = YES;
1042 } else { 1002 } else {
1043 // Look up shortcut, if any, for this key combination. 1003 if (!editCommands_.empty())
1044 EditCommands editCommands; 1004 widgetHost->ForwardEditCommandsForNextKeyEvent(editCommands_);
1045 [EditCommandMatcher matchEditCommands:&editCommands forEvent:theEvent]; 1005 widgetHost->ForwardKeyboardEvent(event);
1046 if (!editCommands.empty())
1047 widgetHost->ForwardEditCommandsForNextKeyEvent(editCommands);
1048 } 1006 }
1049 1007
1050 // Forward the key down event first.
1051 widgetHost->ForwardKeyboardEvent(event);
1052
1053 // Calling ForwardKeyboardEvent() could have destroyed the widget. When the 1008 // Calling ForwardKeyboardEvent() could have destroyed the widget. When the
1054 // widget was destroyed, |renderWidgetHostView_->render_widget_host_| will 1009 // widget was destroyed, |renderWidgetHostView_->render_widget_host_| will
1055 // be set to NULL. So we check it here and return immediately if it's NULL. 1010 // be set to NULL. So we check it here and return immediately if it's NULL.
1056 if (!renderWidgetHostView_->render_widget_host_) 1011 if (!renderWidgetHostView_->render_widget_host_)
1057 return; 1012 return;
1058 1013
1059 // Then send keypress and/or composition related events. 1014 // Then send keypress and/or composition related events.
1060 // If there was a marked text or the text to be inserted is longer than 1 1015 // If there was a marked text or the text to be inserted is longer than 1
1061 // character, then we send the text by calling ConfirmComposition(). 1016 // character, then we send the text by calling ConfirmComposition().
1062 // Otherwise, if the text to be inserted only contains 1 character, then we 1017 // Otherwise, if the text to be inserted only contains 1 character, then we
1063 // can just send a keypress event which is fabricated by changing the type of 1018 // can just send a keypress event which is fabricated by changing the type of
1064 // the keydown event, so that we can retain all necessary informations, such 1019 // the keydown event, so that we can retain all necessary informations, such
1065 // as unmodifiedText, etc. And we need to set event.skip_in_browser to true to 1020 // as unmodifiedText, etc. And we need to set event.skip_in_browser to true to
1066 // prevent the browser from handling it again. 1021 // prevent the browser from handling it again.
1067 // Note that, |textToBeInserted_| is a UTF-16 string, but it's fine to only 1022 // Note that, |textToBeInserted_| is a UTF-16 string, but it's fine to only
1068 // handle BMP characters here, as we can always insert non-BMP characters as 1023 // handle BMP characters here, as we can always insert non-BMP characters as
1069 // text. 1024 // text.
1070 BOOL textInserted = NO; 1025 BOOL textInserted = NO;
1071 if (textToBeInserted_.length() > (oldHasMarkedText ? 0 : 1)) { 1026 if (textToBeInserted_.length() >
1027 ((hasMarkedText_ || oldHasMarkedText) ? 0u : 1u)) {
1072 widgetHost->ImeConfirmComposition(textToBeInserted_); 1028 widgetHost->ImeConfirmComposition(textToBeInserted_);
1073 textInserted = YES; 1029 textInserted = YES;
1074 } else if (textToBeInserted_.length() == 1) {
1075 event.type = WebKit::WebInputEvent::Char;
1076 event.text[0] = textToBeInserted_[0];
1077 event.text[1] = 0;
1078 event.skip_in_browser = true;
1079 widgetHost->ForwardKeyboardEvent(event);
1080 } else if (!hasMarkedText_ && !oldHasMarkedText &&
1081 [[theEvent characters] length] > 0) {
1082 // We don't get insertText: calls if ctrl is down and not even a keyDown:
1083 // call if cmd is down, or in password input mode, so synthesize a keypress
1084 // event for these cases.
1085 event.type = WebKit::WebInputEvent::Char;
1086 event.skip_in_browser = true;
1087 widgetHost->ForwardKeyboardEvent(event);
1088 } 1030 }
1089 1031
1090 // Updates or cancels the composition. If some text has been inserted, then 1032 // Updates or cancels the composition. If some text has been inserted, then
1091 // we don't need to cancel the composition explicitly. 1033 // we don't need to cancel the composition explicitly.
1092 if (hasMarkedText_ && markedText_.length()) { 1034 if (hasMarkedText_ && markedText_.length()) {
1093 // Sends the updated marked text to the renderer so it can update the 1035 // Sends the updated marked text to the renderer so it can update the
1094 // composition node in WebKit. 1036 // composition node in WebKit.
1095 // When marked text is available, |selectedRange_| will be the range being 1037 // When marked text is available, |selectedRange_| will be the range being
1096 // selected inside the marked text. 1038 // selected inside the marked text.
1097 widgetHost->ImeSetComposition(markedText_, underlines_, 1039 widgetHost->ImeSetComposition(markedText_, underlines_,
1098 selectedRange_.location, 1040 selectedRange_.location,
1099 NSMaxRange(selectedRange_)); 1041 NSMaxRange(selectedRange_));
1100 } else if (oldHasMarkedText && !hasMarkedText_ && !textInserted) { 1042 } else if (oldHasMarkedText && !hasMarkedText_ && !textInserted) {
1101 if (unmarkTextCalled_) 1043 if (unmarkTextCalled_)
1102 widgetHost->ImeConfirmComposition(); 1044 widgetHost->ImeConfirmComposition();
1103 else 1045 else
1104 widgetHost->ImeCancelComposition(); 1046 widgetHost->ImeCancelComposition();
1105 } 1047 }
1106 1048
1049 // If the key event was handled by the input method but it also generated some
1050 // edit commands, then we need to send the real key event and corresponding
1051 // edit commands here. This usually occurs when the input method wants to
1052 // finish current composition session but still wants the application to
1053 // handle the key event. See http://crbug.com/48161 for reference.
1054 if (delayEventUntilAfterImeCompostion) {
1055 // If |delayEventUntilAfterImeCompostion| is YES, then a fake key down event
1056 // with windowsKeyCode == 0xE5 has already been sent to webkit.
1057 // So before sending the real key down event, we need to send a fake key up
1058 // event to balance it.
1059 NativeWebKeyboardEvent fakeEvent = event;
1060 fakeEvent.type = WebKit::WebInputEvent::KeyUp;
1061 fakeEvent.skip_in_browser = true;
1062 widgetHost->ForwardKeyboardEvent(fakeEvent);
1063 // Not checking |renderWidgetHostView_->render_widget_host_| here because
1064 // a key event with |skip_in_browser| == true won't be handled by browser,
1065 // thus it won't destroy the widget.
1066
1067 if (!editCommands_.empty())
1068 widgetHost->ForwardEditCommandsForNextKeyEvent(editCommands_);
1069 widgetHost->ForwardKeyboardEvent(event);
1070
1071 // Calling ForwardKeyboardEvent() could have destroyed the widget. When the
1072 // widget was destroyed, |renderWidgetHostView_->render_widget_host_| will
1073 // be set to NULL. So we check it here and return immediately if it's NULL.
1074 if (!renderWidgetHostView_->render_widget_host_)
1075 return;
1076 }
1077
1078 // Only send a corresponding key press event if there is no marked text.
1079 if (!hasMarkedText_) {
1080 if (!textInserted && textToBeInserted_.length() == 1) {
1081 // If a single character was inserted, then we just send it as a keypress
1082 // event.
1083 event.type = WebKit::WebInputEvent::Char;
1084 event.text[0] = textToBeInserted_[0];
1085 event.text[1] = 0;
1086 event.skip_in_browser = true;
1087 widgetHost->ForwardKeyboardEvent(event);
1088 } else if ((!textInserted || delayEventUntilAfterImeCompostion) &&
1089 editCommands_.empty() && [[theEvent characters] length] > 0) {
1090 // We don't get insertText: calls if ctrl or cmd is down, or the key event
1091 // generates an insert command. So synthesize a keypress event for these
1092 // cases, unless the key event generated any other command.
1093 event.type = WebKit::WebInputEvent::Char;
1094 event.skip_in_browser = true;
1095 widgetHost->ForwardKeyboardEvent(event);
1096 }
1097 }
1098
1107 // Possibly autohide the cursor. 1099 // Possibly autohide the cursor.
1108 if ([RenderWidgetHostViewCocoa shouldAutohideCursorForEvent:theEvent]) 1100 if ([RenderWidgetHostViewCocoa shouldAutohideCursorForEvent:theEvent])
1109 [NSCursor setHiddenUntilMouseMoves:YES]; 1101 [NSCursor setHiddenUntilMouseMoves:YES];
1110 } 1102 }
1111 1103
1112 - (void)scrollWheel:(NSEvent *)theEvent { 1104 - (void)scrollWheel:(NSEvent *)theEvent {
1113 [self cancelChildPopups]; 1105 [self cancelChildPopups];
1114 1106
1115 const WebMouseWheelEvent& event = 1107 const WebMouseWheelEvent& event =
1116 WebInputEventFactory::mouseWheelEvent(theEvent, self); 1108 WebInputEventFactory::mouseWheelEvent(theEvent, self);
(...skipping 784 matching lines...) Expand 10 before | Expand all | Expand 10 after
1901 if (!handlingKeyDown_) { 1893 if (!handlingKeyDown_) {
1902 renderWidgetHostView_->render_widget_host_->ImeSetComposition( 1894 renderWidgetHostView_->render_widget_host_->ImeSetComposition(
1903 markedText_, underlines_, 1895 markedText_, underlines_,
1904 newSelRange.location, NSMaxRange(newSelRange)); 1896 newSelRange.location, NSMaxRange(newSelRange));
1905 } 1897 }
1906 } 1898 }
1907 1899
1908 - (void)doCommandBySelector:(SEL)selector { 1900 - (void)doCommandBySelector:(SEL)selector {
1909 // An input method calls this function to dispatch an editing command to be 1901 // An input method calls this function to dispatch an editing command to be
1910 // handled by this view. 1902 // handled by this view.
1911 // Even though most editing commands has been already handled by the 1903 if (selector == @selector(noop:))
1912 // RWHVMEditCommandHelper object, we need to handle an insertNewline: command 1904 return;
1913 // and send a '\r' character to WebKit so that WebKit dispatches this 1905
1914 // character to onkeypress() event handlers. 1906 std::string command(
1915 // TODO(hbono): need to handle more commands? 1907 [RWHVMEditCommandHelper::CommandNameForSelector(selector) UTF8String]);
1916 if (selector == @selector(insertNewline:)) { 1908
1917 if (handlingKeyDown_) { 1909 // If this method is called when handling a key down event, then we need to
1918 // If we are handling a key down event, then we just need to append a '\r' 1910 // handle the command in the key event handler. Otherwise we can just handle
1919 // character to |textToBeInserted_| which will then be handled by 1911 // it here.
1920 // keyEvent: method. 1912 if (handlingKeyDown_) {
1921 textToBeInserted_.push_back('\r'); 1913 hasEditCommands_ = YES;
1922 } else { 1914 // We ignore commands that insert characters, because this was causing
1923 // This call is not initiated by a key event, so just executed the 1915 // strange behavior (e.g. tab always inserted a tab rather than moving to
1924 // corresponding editor command. 1916 // the next field on the page).
1925 renderWidgetHostView_->render_widget_host_->ForwardEditCommand( 1917 if (!StartsWithASCII(command, "insert", false))
1926 "InsertNewline", ""); 1918 editCommands_.push_back(EditCommand(command, ""));
1927 } 1919 } else {
1920 renderWidgetHostView_->render_widget_host_->ForwardEditCommand(command, "");
1928 } 1921 }
1929 } 1922 }
1930 1923
1931 - (void)insertText:(id)string { 1924 - (void)insertText:(id)string {
1932 // An input method has characters to be inserted. 1925 // An input method has characters to be inserted.
1933 // Same as Linux, Mac calls this method not only: 1926 // Same as Linux, Mac calls this method not only:
1934 // * when an input method finishs composing text, but also; 1927 // * when an input method finishs composing text, but also;
1935 // * when we type an ASCII character (without using input methods). 1928 // * when we type an ASCII character (without using input methods).
1936 // When we aren't using input methods, we should send the given character as 1929 // When we aren't using input methods, we should send the given character as
1937 // a Char event so it is dispatched to an onkeypress() event handler of 1930 // a Char event so it is dispatched to an onkeypress() event handler of
(...skipping 160 matching lines...) Expand 10 before | Expand all | Expand 10 after
2098 if (!hasMarkedText_) 2091 if (!hasMarkedText_)
2099 return; 2092 return;
2100 2093
2101 if (renderWidgetHostView_->render_widget_host_) 2094 if (renderWidgetHostView_->render_widget_host_)
2102 renderWidgetHostView_->render_widget_host_->ImeConfirmComposition(); 2095 renderWidgetHostView_->render_widget_host_->ImeConfirmComposition();
2103 2096
2104 [self cancelComposition]; 2097 [self cancelComposition];
2105 } 2098 }
2106 2099
2107 @end 2100 @end
OLDNEW
« no previous file with comments | « chrome/browser/renderer_host/render_widget_host_view_mac.h ('k') | no next file » | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698