Chromium Code Reviews

Unified 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.
Jump to:
View side-by-side diff with in-line comments
« no previous file with comments | « chrome/browser/renderer_host/render_widget_host_view_mac.h ('k') | no next file » | no next file with comments »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
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, "");
}
}
« 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