| Index: chrome/browser/renderer_host/render_widget_host_view_mac.mm
|
| ===================================================================
|
| --- chrome/browser/renderer_host/render_widget_host_view_mac.mm (revision 22136)
|
| +++ chrome/browser/renderer_host/render_widget_host_view_mac.mm (working copy)
|
| @@ -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 @@
|
| 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::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 @@
|
| // 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 @@
|
| 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
|
|
|
|
|