summaryrefslogtreecommitdiffstats
path: root/chrome/browser/renderer_host
diff options
context:
space:
mode:
authorhbono@chromium.org <hbono@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2009-08-03 07:38:06 +0000
committerhbono@chromium.org <hbono@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2009-08-03 07:38:06 +0000
commitf17709bbedbdc5cf3bdc88dc93d264e0191b8f2b (patch)
tree04e899a7dc1edaf1152f9419671338505a65ca06 /chrome/browser/renderer_host
parentfb6ec999c0d049c78b16ca6106d5e45624f94ac8 (diff)
downloadchromium_src-f17709bbedbdc5cf3bdc88dc93d264e0191b8f2b.zip
chromium_src-f17709bbedbdc5cf3bdc88dc93d264e0191b8f2b.tar.gz
chromium_src-f17709bbedbdc5cf3bdc88dc93d264e0191b8f2b.tar.bz2
Implement the NSTextInput protocol.
This change implements the NSTextInput protocol to integrate dead-keys and IME support into Mac Chromium. Same as Linux, to improve compatibility with Windows Chrome, this change emulates IPC messages sent on Windows when we input characters to fix Issue 11952 and Issue 11981. Even though I notice we need more work for fixing edge cases (e.g. disabling IMEs on a password input) also on Mac, it is the good starting point. (Supporting edge-cases requires complicated code and it makes hard to review.) BUG=11952 "IME support is not implemented" BUG=11981 "Deadkeys do not work" BUG=16393 "Mac: Not able to insert any letter using "Special Characters" pallet" TEST=Open a web page which contains an <input> form (e.g. <http://www.google.com/>), type a '[{' key and an 'A' key on a Canadian-French keyboard, and see a Latin character "U+00E2" is displayed in the <input> form. TEST=Open a web page which contains an <input> form (e.g. <http://www.google.com/>), enable an Chinese Pinyin IME, type a 'W' key, type an 'O' key, and see a Chinese character is displayed in the <input> form. TEST=Open a web page which contains a <textarea> form, type a return key, and see a new line is inserted. Review URL: http://codereview.chromium.org/150206 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@22262 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'chrome/browser/renderer_host')
-rw-r--r--chrome/browser/renderer_host/render_widget_host_view_mac.h46
-rw-r--r--chrome/browser/renderer_host/render_widget_host_view_mac.mm259
2 files changed, 303 insertions, 2 deletions
diff --git a/chrome/browser/renderer_host/render_widget_host_view_mac.h b/chrome/browser/renderer_host/render_widget_host_view_mac.h
index a693377..7c6ece5 100644
--- a/chrome/browser/renderer_host/render_widget_host_view_mac.h
+++ b/chrome/browser/renderer_host/render_widget_host_view_mac.h
@@ -29,7 +29,8 @@ class RWHVMEditCommandHelper;
// but that means that the view needs to own the delegate and will dispose of it
// when it's removed from the view system.
-@interface RenderWidgetHostViewCocoa : BaseView <RenderWidgetHostViewMacOwner> {
+@interface RenderWidgetHostViewCocoa
+ : BaseView <RenderWidgetHostViewMacOwner, NSTextInput> {
@private
RenderWidgetHostViewMac* renderWidgetHostView_;
BOOL canBeKeyView_;
@@ -130,6 +131,49 @@ class RenderWidgetHostViewMac : public RenderWidgetHostView {
// value returns true for is_null() if we are not recording whiteout times.
base::TimeTicks whiteout_start_time_;
+ // Variables used by our implementaion of the NSTextInput protocol.
+ // An input method of Mac calls the methods of this protocol not only to
+ // notify an application of its status, but also to retrieve the status of
+ // the application. That is, an application cannot control an input method
+ // directly.
+ // This object keeps the status of a composition of the renderer and returns
+ // it when an input method asks for it.
+ // We need to implement Objective-C methods for the NSTextInput protocol. On
+ // the other hand, we need to implement a C++ method for an IPC-message
+ // handler which receives input-method events from the renderer.
+ // To avoid fragmentation of variables used by our input-method
+ // implementation, we define all variables as public member variables of
+ // this C++ class so both the C++ methods and the Objective-C methods can
+ // access them.
+
+ // Represents the input-method attributes supported by this object.
+ NSArray* im_attributes_;
+
+ // Represents whether or not an input method is composing a text.
+ bool im_composing_;
+
+ // Represents the range of the composition string (i.e. a text being
+ // composed by an input method), and the range of the selected text of the
+ // composition string.
+ // TODO(hbono): need to save the composition string itself for the
+ // attributedSubstringFromRange method?
+ NSRange im_marked_range_;
+ NSRange im_selected_range_;
+
+ // Represents the state of modifier keys.
+ // An input method doesn't notify the state of modifier keys. On the other
+ // hand, the state of modifier keys are required by Char events because they
+ // are dispatched to onkeypress() event handlers of JavaScript.
+ // To create a Char event in NSTextInput methods, we save the latest state
+ // of modifier keys when we receive it.
+ int im_modifiers_;
+
+ // Represents the cursor position in this view coordinate.
+ // The renderer sends the cursor position through an IPC message.
+ // We save the latest cursor position here and return it when an input
+ // methods needs it.
+ NSRect im_caret_rect_;
+
private:
// Updates the display cursor to the current cursor if the cursor is over this
// render view.
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 6a8c60b..ea3bf8f 100644
--- a/chrome/browser/renderer_host/render_widget_host_view_mac.mm
+++ b/chrome/browser/renderer_host/render_widget_host_view_mac.mm
@@ -5,6 +5,7 @@
#include "chrome/browser/renderer_host/render_widget_host_view_mac.h"
#include "base/histogram.h"
+#include "base/string_util.h"
#include "base/sys_string_conversions.h"
#include "chrome/browser/browser_trial.h"
#import "chrome/browser/cocoa/rwhvm_editcommand_helper.h"
@@ -47,6 +48,8 @@ RenderWidgetHostView* RenderWidgetHostView::CreateViewForWidget(
RenderWidgetHostViewMac::RenderWidgetHostViewMac(RenderWidgetHost* widget)
: render_widget_host_(widget),
about_to_validate_and_paint_(false),
+ im_attributes_(NULL),
+ im_composing_(false),
is_loading_(false),
is_hidden_(false),
shutdown_factory_(this),
@@ -208,7 +211,19 @@ void RenderWidgetHostViewMac::SetIsLoading(bool is_loading) {
void RenderWidgetHostViewMac::IMEUpdateStatus(int control,
const gfx::Rect& caret_rect) {
- NOTIMPLEMENTED();
+ // The renderer updates its IME status.
+ // We need to control the input method according to the given message.
+
+ // We need to convert the coordinate of the cursor rectangle sent from the
+ // renderer and save it. Our IME backend uses a coordinate system whose
+ // origin is the upper-left corner of this view. On the other hand, Cocoa
+ // uses a coordinate system whose origin is the lower-left corner of this
+ // view. So, we convert the cursor rectangle and save it.
+ NSRect view_rect = [cocoa_view_ bounds];
+ const int y_offset = static_cast<int>(view_rect.size.height);
+ im_caret_rect_ = NSMakeRect(caret_rect.x(),
+ y_offset - caret_rect.y() - caret_rect.height(),
+ caret_rect.width(), caret_rect.height());
}
void RenderWidgetHostViewMac::DidPaintRect(const gfx::Rect& rect) {
@@ -458,8 +473,28 @@ void RenderWidgetHostViewMac::SetActive(bool active) {
// the popup in the first place.
NativeWebKeyboardEvent event(theEvent);
+
+ // Save the modifier keys so the insertText method can use it when it sends
+ // a Char event, which is dispatched as an onkeypress() event of JavaScript.
+ renderWidgetHostView_->im_modifiers_ = event.modifiers;
+
+ // To emulate Windows, over-write |event.windowsKeyCode| to VK_PROCESSKEY
+ // while an input method is composing a text.
+ // Gmail checks this code in its onkeydown handler to stop auto-completing
+ // e-mail addresses while composing a CJK text.
+ if ([theEvent type] == NSKeyDown && renderWidgetHostView_->im_composing_)
+ event.windowsKeyCode = 0xE5;
+
+ // Dispatch this keyboard event to the renderer.
if (renderWidgetHostView_->render_widget_host_)
renderWidgetHostView_->render_widget_host_->ForwardKeyboardEvent(event);
+
+ // Dispatch a NSKeyDown event to an input method.
+ // To send an onkeydown() event before an onkeypress() event, we should
+ // dispatch this NSKeyDown event AFTER sending it to the renderer.
+ // (See <https://bugs.webkit.org/show_bug.cgi?id=25119>).
+ if ([theEvent type] == NSKeyDown)
+ [self interpretKeyEvents:[NSArray arrayWithObject:theEvent]];
}
- (void)scrollWheel:(NSEvent *)theEvent {
@@ -819,5 +854,227 @@ static const NSTrackingRectTag kTrackingRectTag = 0xBADFACE;
return [[toolTip_ copy] autorelease];
}
+// Below is our NSTextInput implementation.
+//
+// When WebHTMLView receives a NSKeyDown event, WebHTMLView calls the following
+// functions to process this event.
+//
+// [WebHTMLView keyDown] ->
+// EventHandler::keyEvent() ->
+// ...
+// [WebEditorClient handleKeyboardEvent] ->
+// [WebHTMLView _interceptEditingKeyEvent] ->
+// [NSResponder interpretKeyEvents] ->
+// [WebHTMLView insertText] ->
+// Editor::insertText()
+//
+// Unfortunately, it is hard for Chromium to use this implementation because
+// it causes key-typing jank.
+// RenderWidgetHostViewMac is running in a browser process. On the other
+// hand, Editor and EventHandler are running in a renderer process.
+// So, if we used this implementation, a NSKeyDown event is dispatched to
+// the following functions of Chromium.
+//
+// [RenderWidgetHostViewMac keyEvent] (browser) ->
+// |Sync IPC (KeyDown)| (*1) ->
+// EventHandler::keyEvent() (renderer) ->
+// ...
+// EditorClientImpl::handleKeyboardEvent() (renderer) ->
+// |Sync IPC| (*2) ->
+// [RenderWidgetHostViewMac _interceptEditingKeyEvent] (browser) ->
+// [self interpretKeyEvents] ->
+// [RenderWidgetHostViewMac insertText] (browser) ->
+// |Async IPC| ->
+// Editor::insertText() (renderer)
+//
+// (*1) we need to wait until this call finishes since WebHTMLView uses the
+// result of EventHandler::keyEvent().
+// (*2) we need to wait until this call finishes since WebEditorClient uses
+// the result of [WebHTMLView _interceptEditingKeyEvent].
+//
+// This needs many sync IPC messages sent between a browser and a renderer for
+// each key event, which would probably result in key-typing jank.
+// To avoid this problem, this implementation processes key events (and IME
+// events) totally in a browser process and sends asynchronous input events,
+// almost same as KeyboardEvents (and TextEvents) of DOM Level 3, to a
+// renderer process.
+//
+// [RenderWidgetHostViewMac keyEvent] (browser) ->
+// |Async IPC (RawKeyDown)| ->
+// [self interpretKeyEvents] ->
+// [RenderWidgetHostViewMac insertText] (browser) ->
+// |Async IPC (Char)| ->
+// Editor::insertText() (renderer)
+//
+// Since this implementation doesn't have to wait any IPC calls, this doesn't
+// make any key-typing jank. --hbono 7/23/09
+//
+extern "C" {
+extern NSString *NSTextInputReplacementRangeAttributeName;
+}
+
+- (NSArray *)validAttributesForMarkedText {
+ // This code is just copied from WebKit except renaming variables.
+ if (!renderWidgetHostView_->im_attributes_) {
+ renderWidgetHostView_->im_attributes_ = [[NSArray alloc] initWithObjects:
+ NSUnderlineStyleAttributeName,
+ NSUnderlineColorAttributeName,
+ NSMarkedClauseSegmentAttributeName,
+ NSTextInputReplacementRangeAttributeName,
+ nil];
+ }
+ return renderWidgetHostView_->im_attributes_;
+}
+
+- (NSUInteger)characterIndexForPoint:(NSPoint)thePoint {
+ NOTIMPLEMENTED();
+ return NSNotFound;
+}
+
+- (NSRect)firstRectForCharacterRange:(NSRange)theRange {
+ // An input method requests a cursor rectangle to display its candidate
+ // window.
+ // Calculate the screen coordinate of the cursor rectangle saved in
+ // RenderWidgetHostViewMac::IMEUpdateStatus() and send it to the IME.
+ // Since this window may be moved since we receive the cursor rectangle last
+ // time we sent the cursor rectangle to the IME, so we should map from the
+ // view coordinate to the screen coordinate every time when an IME need it.
+ NSRect resultRect = renderWidgetHostView_->im_caret_rect_;
+ resultRect = [self convertRect:resultRect toView:nil];
+ NSWindow* window = [self window];
+ if (window)
+ resultRect.origin = [window convertBaseToScreen:resultRect.origin];
+ return resultRect;
+}
+
+- (NSRange)selectedRange {
+ // Return the selected range saved in the setMarkedText method.
+ return renderWidgetHostView_->im_selected_range_;
+}
+
+- (NSRange)markedRange {
+ // An input method calls this method to check if an application really has
+ // a text being composed when hasMarkedText call returns true.
+ // Returns the range saved in the setMarkedText method so the input method
+ // calls the setMarkedText method and we can update the composition node
+ // there. (When this method returns an empty range, the input method doesn't
+ // call the setMarkedText method.)
+ return renderWidgetHostView_->im_marked_range_;
+}
+
+- (NSAttributedString *)attributedSubstringFromRange:(NSRange)nsRange {
+ // TODO(hbono): Even though many input method works without implementing
+ // this method, we need to save a copy of the string in the setMarkedText
+ // method and create a NSAttributedString with the given range.
+ NOTIMPLEMENTED();
+ return nil;
+}
+
+- (NSInteger)conversationIdentifier {
+ return reinterpret_cast<NSInteger>(self);
+}
+
+- (BOOL)hasMarkedText {
+ // An input method calls this function to figure out whether or not an
+ // application is really composing a text. If it is composing, it calls
+ // the markedRange method, and maybe calls the setMarkedTest method.
+ // It seems an input method usually calls this function when it is about to
+ // cancel an ongoing composition. If an application has a non-empty marked
+ // range, it calls the setMarkedText method to delete the range.
+ return renderWidgetHostView_->im_composing_ ? YES : NO;
+}
+
+- (void)unmarkText {
+ // Delete the composition node of the renderer and finish an ongoing
+ // composition.
+ // It seems an input method calls the setMarkedText method and set an empty
+ // text when it cancels an ongoing composition, i.e. I have never seen an
+ // input method calls this method.
+ renderWidgetHostView_->render_widget_host_->ImeCancelComposition();
+ renderWidgetHostView_->im_composing_ = false;
+}
+
+- (void)setMarkedText:(id)string selectedRange:(NSRange)newSelRange {
+ // An input method updates the composition string.
+ // We send the given text and range to the renderer so it can update the
+ // composition node of WebKit.
+ BOOL isAttributedString = [string isKindOfClass:[NSAttributedString class]];
+ NSString* im_text = isAttributedString ? [string string] : string;
+ int length = [im_text length];
+ int cursor;
+ int target_start;
+ int target_end;
+ if (!newSelRange.length) {
+ // The given text doesn't have any range to be highlighted.
+ // Put the cursor to the end of this text and clear the selection range.
+ cursor = length;
+ target_start = 0;
+ target_end = 0;
+ } else {
+ // The given text has a range to be highlighted.
+ // Set the selection range to the given one and put the cursor at the
+ // beginning of the selection range.
+ cursor = newSelRange.location;
+ target_start = newSelRange.location;
+ target_end = newSelRange.location + newSelRange.length;
+ }
+
+ // Dispatch this IME event to the renderer and update the IME state of this
+ // object.
+ // Input methods of Mac use setMarkedText calls with an empty text to cancel
+ // an ongoing composition. So, we should check whether or not the given text
+ // is empty to update the IME state. (Our IME backend can automatically
+ // cancels an ongoing composition when we send an empty text. So, it is OK
+ // to send an empty text to the renderer.)
+ renderWidgetHostView_->render_widget_host_->ImeSetComposition(
+ UTF8ToUTF16([im_text UTF8String]), cursor, target_start, target_end);
+ renderWidgetHostView_->GetRenderWidgetHost()->ImeSetInputMode(true);
+ renderWidgetHostView_->im_composing_ = length > 0;
+ renderWidgetHostView_->im_marked_range_.location = 0;
+ renderWidgetHostView_->im_marked_range_.length = length;
+ renderWidgetHostView_->im_selected_range_.location = newSelRange.location;
+ renderWidgetHostView_->im_selected_range_.length = newSelRange.length;
+}
+
+- (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:)) {
+ NativeWebKeyboardEvent event('\r', renderWidgetHostView_->im_modifiers_,
+ base::Time::Now().ToDoubleT());
+ renderWidgetHostView_->render_widget_host_->ForwardKeyboardEvent(event);
+ }
+}
+
+- (void)insertText:(id)string {
+ // An input method has characters to be inserted.
+ // Same as Linux, Mac calls this method not only:
+ // * when an input method finishs composing text, but also;
+ // * when we type an ASCII character (without using input methods).
+ // When we aren't using input methods, we should send the given character as
+ // a Char event so it is dispatched to an onkeypress() event handler of
+ // JavaScript.
+ // On the other hand, when we are using input methods, we should send the
+ // given characters as an IME event and prevent the characters from being
+ // dispatched to onkeypress() event handlers.
+ BOOL isAttributedString = [string isKindOfClass:[NSAttributedString class]];
+ NSString* im_text = isAttributedString ? [string string] : string;
+ if (!renderWidgetHostView_->im_composing_ && [im_text length] == 1) {
+ NativeWebKeyboardEvent event([im_text characterAtIndex:0],
+ renderWidgetHostView_->im_modifiers_,
+ base::Time::Now().ToDoubleT());
+ renderWidgetHostView_->render_widget_host_->ForwardKeyboardEvent(event);
+ } else {
+ renderWidgetHostView_->render_widget_host_->ImeConfirmComposition(
+ UTF8ToUTF16([im_text UTF8String]));
+ }
+ renderWidgetHostView_->im_composing_ = false;
+}
+
@end