Index: chrome/browser/renderer_host/render_widget_host_view_mac.mm |
diff --git a/chrome/browser/renderer_host/render_widget_host_view_mac.mm b/chrome/browser/renderer_host/render_widget_host_view_mac.mm |
index 7dd47d4ee5a83c3143be6f4c1986263fa8ed02a9..b0f5bd3ba8611d9cc7e154c15619a45ada80c3da 100644 |
--- a/chrome/browser/renderer_host/render_widget_host_view_mac.mm |
+++ b/chrome/browser/renderer_host/render_widget_host_view_mac.mm |
@@ -806,62 +806,6 @@ void RenderWidgetHostViewMac::SetTextInputActive(bool active) { |
} |
} |
-// EditCommandMatcher --------------------------------------------------------- |
- |
-// This class is used to capture the shortcuts that a given key event maps to. |
-// We instantiate a vanilla NSResponder, call interpretKeyEvents on it, and |
-// record all of the selectors passed into doCommandBySelector while |
-// interpreting the key event. The selectors are converted into edit commands |
-// which can be passed to the render process. |
-// |
-// Caveats: |
-// - Shortcuts involving a sequence of key combinations (chords) don't work, |
-// because we instantiate a new responder for each event. |
-// - We ignore key combinations that don't include a modifier (ctrl, cmd, alt) |
-// because this was causing strange behavior (e.g. tab always inserted a tab |
-// rather than moving to the next field on the page). |
- |
-@interface EditCommandMatcher : NSResponder { |
- EditCommands* edit_commands_; |
-} |
-@end |
- |
-@implementation EditCommandMatcher |
- |
-- (id)initWithEditCommands:(EditCommands*)edit_commands { |
- if ((self = [super init]) != nil) { |
- edit_commands_ = edit_commands; |
- } |
- return self; |
-} |
- |
-- (void)doCommandBySelector:(SEL)selector { |
- NSString* editCommand = |
- RWHVMEditCommandHelper::CommandNameForSelector(selector); |
- edit_commands_->push_back( |
- EditCommand(base::SysNSStringToUTF8(editCommand), "")); |
-} |
- |
-- (void)insertText:(id)string { |
- // If we don't ignore this, then sometimes we get a bell. |
-} |
- |
-+ (void)matchEditCommands:(EditCommands*)edit_commands |
- forEvent:(NSEvent*)theEvent { |
- if ([theEvent type] != NSKeyDown) |
- return; |
- // Don't interpret plain key presses. This screws up things like <Tab>. |
- NSUInteger flags = [theEvent modifierFlags]; |
- flags &= (NSControlKeyMask | NSAlternateKeyMask | NSCommandKeyMask); |
- if (!flags) |
- return; |
- scoped_nsobject<EditCommandMatcher> matcher( |
- [[EditCommandMatcher alloc] initWithEditCommands:edit_commands]); |
- [matcher.get() interpretKeyEvents:[NSArray arrayWithObject:theEvent]]; |
-} |
- |
-@end |
- |
// RenderWidgetHostViewCocoa --------------------------------------------------- |
@implementation RenderWidgetHostViewCocoa |
@@ -1015,20 +959,25 @@ void RenderWidgetHostViewMac::SetTextInputActive(bool active) { |
// down event. |
handlingKeyDown_ = YES; |
- // These two variables might be set when handling the keyboard event. |
+ // These variables might be set when handling the keyboard event. |
// Clear them here so that we can know whether they have changed afterwards. |
textToBeInserted_.clear(); |
markedText_.clear(); |
underlines_.clear(); |
unmarkTextCalled_ = NO; |
+ hasEditCommands_ = NO; |
+ editCommands_.clear(); |
// Sends key down events to input method first, then we can decide what should |
// be done according to input method's feedback. |
- if (renderWidgetHostView_->text_input_type_ == WebKit::WebTextInputTypeText) |
- [self interpretKeyEvents:[NSArray arrayWithObject:theEvent]]; |
+ [self interpretKeyEvents:[NSArray arrayWithObject:theEvent]]; |
handlingKeyDown_ = NO; |
+ // Indicates if we should send the key event and corresponding editor commands |
+ // after processing the input method result. |
+ BOOL delayEventUntilAfterImeCompostion = NO; |
+ |
// To emulate Windows, over-write |event.windowsKeyCode| to VK_PROCESSKEY |
// while an input method is composing or inserting a text. |
// Gmail checks this code in its onkeydown handler to stop auto-completing |
@@ -1036,20 +985,26 @@ void RenderWidgetHostViewMac::SetTextInputActive(bool active) { |
// If the text to be inserted has only one character, then we don't need this |
// trick, because we'll send the text as a key press event instead. |
if (hasMarkedText_ || oldHasMarkedText || textToBeInserted_.length() > 1) { |
- event.windowsKeyCode = 0xE5; // VKEY_PROCESSKEY |
- event.setKeyIdentifierFromWindowsKeyCode(); |
- event.skip_in_browser = true; |
+ NativeWebKeyboardEvent fakeEvent = event; |
+ fakeEvent.windowsKeyCode = 0xE5; // VKEY_PROCESSKEY |
+ fakeEvent.setKeyIdentifierFromWindowsKeyCode(); |
+ fakeEvent.skip_in_browser = true; |
+ widgetHost->ForwardKeyboardEvent(fakeEvent); |
+ // If this key event was handled by the input method, but |
+ // -doCommandBySelector: (invoked by the call to -interpretKeyEvents: above) |
+ // enqueued edit commands, then in order to let webkit handle them |
+ // correctly, we need to send the real key event and corresponding edit |
+ // commands after processing the input method result. |
+ // We shouldn't do this if a new marked text was set by the input method, |
+ // otherwise the new marked text might be cancelled by webkit. |
+ if (hasEditCommands_ && !hasMarkedText_) |
+ delayEventUntilAfterImeCompostion = YES; |
} else { |
- // Look up shortcut, if any, for this key combination. |
- EditCommands editCommands; |
- [EditCommandMatcher matchEditCommands:&editCommands forEvent:theEvent]; |
- if (!editCommands.empty()) |
- widgetHost->ForwardEditCommandsForNextKeyEvent(editCommands); |
+ if (!editCommands_.empty()) |
+ widgetHost->ForwardEditCommandsForNextKeyEvent(editCommands_); |
+ widgetHost->ForwardKeyboardEvent(event); |
} |
- // Forward the key down event first. |
- widgetHost->ForwardKeyboardEvent(event); |
- |
// Calling ForwardKeyboardEvent() could have destroyed the widget. When the |
// widget was destroyed, |renderWidgetHostView_->render_widget_host_| will |
// be set to NULL. So we check it here and return immediately if it's NULL. |
@@ -1068,23 +1023,10 @@ void RenderWidgetHostViewMac::SetTextInputActive(bool active) { |
// handle BMP characters here, as we can always insert non-BMP characters as |
// text. |
BOOL textInserted = NO; |
- if (textToBeInserted_.length() > (oldHasMarkedText ? 0 : 1)) { |
+ if (textToBeInserted_.length() > |
+ ((hasMarkedText_ || oldHasMarkedText) ? 0u : 1u)) { |
widgetHost->ImeConfirmComposition(textToBeInserted_); |
textInserted = YES; |
- } else if (textToBeInserted_.length() == 1) { |
- event.type = WebKit::WebInputEvent::Char; |
- event.text[0] = textToBeInserted_[0]; |
- event.text[1] = 0; |
- event.skip_in_browser = true; |
- widgetHost->ForwardKeyboardEvent(event); |
- } else if (!hasMarkedText_ && !oldHasMarkedText && |
- [[theEvent characters] length] > 0) { |
- // We don't get insertText: calls if ctrl is down and not even a keyDown: |
- // call if cmd is down, or in password input mode, so synthesize a keypress |
- // event for these cases. |
- event.type = WebKit::WebInputEvent::Char; |
- event.skip_in_browser = true; |
- widgetHost->ForwardKeyboardEvent(event); |
} |
// Updates or cancels the composition. If some text has been inserted, then |
@@ -1095,8 +1037,8 @@ void RenderWidgetHostViewMac::SetTextInputActive(bool active) { |
// When marked text is available, |selectedRange_| will be the range being |
// selected inside the marked text. |
widgetHost->ImeSetComposition(markedText_, underlines_, |
- selectedRange_.location, |
- NSMaxRange(selectedRange_)); |
+ selectedRange_.location, |
+ NSMaxRange(selectedRange_)); |
} else if (oldHasMarkedText && !hasMarkedText_ && !textInserted) { |
if (unmarkTextCalled_) |
widgetHost->ImeConfirmComposition(); |
@@ -1104,6 +1046,56 @@ void RenderWidgetHostViewMac::SetTextInputActive(bool active) { |
widgetHost->ImeCancelComposition(); |
} |
+ // If the key event was handled by the input method but it also generated some |
+ // edit commands, then we need to send the real key event and corresponding |
+ // edit commands here. This usually occurs when the input method wants to |
+ // finish current composition session but still wants the application to |
+ // handle the key event. See http://crbug.com/48161 for reference. |
+ if (delayEventUntilAfterImeCompostion) { |
+ // If |delayEventUntilAfterImeCompostion| is YES, then a fake key down event |
+ // with windowsKeyCode == 0xE5 has already been sent to webkit. |
+ // So before sending the real key down event, we need to send a fake key up |
+ // event to balance it. |
+ NativeWebKeyboardEvent fakeEvent = event; |
+ fakeEvent.type = WebKit::WebInputEvent::KeyUp; |
+ fakeEvent.skip_in_browser = true; |
+ widgetHost->ForwardKeyboardEvent(fakeEvent); |
+ // Not checking |renderWidgetHostView_->render_widget_host_| here because |
+ // a key event with |skip_in_browser| == true won't be handled by browser, |
+ // thus it won't destroy the widget. |
+ |
+ if (!editCommands_.empty()) |
+ widgetHost->ForwardEditCommandsForNextKeyEvent(editCommands_); |
+ widgetHost->ForwardKeyboardEvent(event); |
+ |
+ // Calling ForwardKeyboardEvent() could have destroyed the widget. When the |
+ // widget was destroyed, |renderWidgetHostView_->render_widget_host_| will |
+ // be set to NULL. So we check it here and return immediately if it's NULL. |
+ if (!renderWidgetHostView_->render_widget_host_) |
+ return; |
+ } |
+ |
+ // Only send a corresponding key press event if there is no marked text. |
+ if (!hasMarkedText_) { |
+ if (!textInserted && textToBeInserted_.length() == 1) { |
+ // If a single character was inserted, then we just send it as a keypress |
+ // event. |
+ event.type = WebKit::WebInputEvent::Char; |
+ event.text[0] = textToBeInserted_[0]; |
+ event.text[1] = 0; |
+ event.skip_in_browser = true; |
+ widgetHost->ForwardKeyboardEvent(event); |
+ } else if ((!textInserted || delayEventUntilAfterImeCompostion) && |
+ editCommands_.empty() && [[theEvent characters] length] > 0) { |
+ // We don't get insertText: calls if ctrl or cmd is down, or the key event |
+ // generates an insert command. So synthesize a keypress event for these |
+ // cases, unless the key event generated any other command. |
+ event.type = WebKit::WebInputEvent::Char; |
+ event.skip_in_browser = true; |
+ widgetHost->ForwardKeyboardEvent(event); |
+ } |
+ } |
+ |
// Possibly autohide the cursor. |
if ([RenderWidgetHostViewCocoa shouldAutohideCursorForEvent:theEvent]) |
[NSCursor setHiddenUntilMouseMoves:YES]; |
@@ -1908,23 +1900,24 @@ extern NSString *NSTextInputReplacementRangeAttributeName; |
- (void)doCommandBySelector:(SEL)selector { |
// An input method calls this function to dispatch an editing command to be |
// handled by this view. |
- // Even though most editing commands has been already handled by the |
- // RWHVMEditCommandHelper object, we need to handle an insertNewline: command |
- // and send a '\r' character to WebKit so that WebKit dispatches this |
- // character to onkeypress() event handlers. |
- // TODO(hbono): need to handle more commands? |
- if (selector == @selector(insertNewline:)) { |
- if (handlingKeyDown_) { |
- // If we are handling a key down event, then we just need to append a '\r' |
- // character to |textToBeInserted_| which will then be handled by |
- // keyEvent: method. |
- textToBeInserted_.push_back('\r'); |
- } else { |
- // This call is not initiated by a key event, so just executed the |
- // corresponding editor command. |
- renderWidgetHostView_->render_widget_host_->ForwardEditCommand( |
- "InsertNewline", ""); |
- } |
+ if (selector == @selector(noop:)) |
+ return; |
+ |
+ std::string command( |
+ [RWHVMEditCommandHelper::CommandNameForSelector(selector) UTF8String]); |
+ |
+ // If this method is called when handling a key down event, then we need to |
+ // handle the command in the key event handler. Otherwise we can just handle |
+ // it here. |
+ if (handlingKeyDown_) { |
+ hasEditCommands_ = YES; |
+ // We ignore commands that insert characters, because this was causing |
+ // strange behavior (e.g. tab always inserted a tab rather than moving to |
+ // the next field on the page). |
+ if (!StartsWithASCII(command, "insert", false)) |
+ editCommands_.push_back(EditCommand(command, "")); |
+ } else { |
+ renderWidgetHostView_->render_widget_host_->ForwardEditCommand(command, ""); |
} |
} |