diff options
-rw-r--r-- | chrome/browser/renderer_host/render_widget_host_view_mac.h | 46 | ||||
-rw-r--r-- | chrome/browser/renderer_host/render_widget_host_view_mac.mm | 259 | ||||
-rw-r--r-- | chrome/common/native_web_keyboard_event.h | 3 | ||||
-rw-r--r-- | chrome/common/native_web_keyboard_event_mac.mm | 9 | ||||
-rw-r--r-- | webkit/api/public/WebInputEvent.h | 35 | ||||
-rw-r--r-- | webkit/api/public/mac/WebInputEventFactory.h | 7 | ||||
-rw-r--r-- | webkit/api/src/mac/WebInputEventFactory.mm | 21 |
7 files changed, 362 insertions, 18 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 diff --git a/chrome/common/native_web_keyboard_event.h b/chrome/common/native_web_keyboard_event.h index 021d061..87f5a90 100644 --- a/chrome/common/native_web_keyboard_event.h +++ b/chrome/common/native_web_keyboard_event.h @@ -29,6 +29,9 @@ struct NativeWebKeyboardEvent : public WebKit::WebKeyboardEvent { NativeWebKeyboardEvent(HWND hwnd, UINT message, WPARAM wparam, LPARAM lparam); #elif defined(OS_MACOSX) explicit NativeWebKeyboardEvent(NSEvent *event); + NativeWebKeyboardEvent(wchar_t character, + int state, + double time_stamp_seconds); #elif defined(OS_LINUX) explicit NativeWebKeyboardEvent(const GdkEventKey* event); NativeWebKeyboardEvent(wchar_t character, diff --git a/chrome/common/native_web_keyboard_event_mac.mm b/chrome/common/native_web_keyboard_event_mac.mm index 04e2b46..b9922bb 100644 --- a/chrome/common/native_web_keyboard_event_mac.mm +++ b/chrome/common/native_web_keyboard_event_mac.mm @@ -19,6 +19,15 @@ NativeWebKeyboardEvent::NativeWebKeyboardEvent(NSEvent* event) os_event([event retain]) { } +NativeWebKeyboardEvent::NativeWebKeyboardEvent(wchar_t character, + int modifiers, + double time_stamp_seconds) + : WebKeyboardEvent(WebInputEventFactory::keyboardEvent(character, + modifiers, + time_stamp_seconds)), + os_event(NULL) { +} + NativeWebKeyboardEvent::NativeWebKeyboardEvent( const NativeWebKeyboardEvent& other) : WebKeyboardEvent(other), diff --git a/webkit/api/public/WebInputEvent.h b/webkit/api/public/WebInputEvent.h index dbb6602..a09cd37 100644 --- a/webkit/api/public/WebInputEvent.h +++ b/webkit/api/public/WebInputEvent.h @@ -55,18 +55,29 @@ namespace WebKit { , modifiers(0) , timeStampSeconds(0.0) { } - // There are two schemes used for keyboard input. On Windows (and, - // interestingly enough, on Mac Carbon) there are two events for a - // keypress. One is a raw keydown, which provides the keycode only. - // If the app doesn't handle that, then the system runs key translation - // to create an event containing the generated character and pumps that - // event. In such a scheme, those two events are translated to - // RAW_KEY_DOWN and CHAR events respectively. In Cocoa and Gtk, key - // events contain both the keycode and any translation into actual - // text. In such a case, WebCore will eventually need to split the - // events (see disambiguateKeyDownEvent and its callers) but we don't - // worry about that here. We just use a different type (KEY_DOWN) to - // indicate this. + // When we use an input method (or an input method editor), we receive + // two events for a keypress. The former event is a keydown, which + // provides a keycode, and the latter is a textinput, which provides + // a character processed by an input method. (The mapping from a + // keycode to a character code is not trivial for non-English + // keyboards.) + // To support input methods, Safari sends keydown events to WebKit for + // filtering. WebKit sends filtered keydown events back to Safari, + // which sends them to input methods. + // Unfortunately, it is hard to apply this design to Chrome because of + // our multiprocess architecture. An input method is running in a + // browser process. On the other hand, WebKit is running in a renderer + // process. So, this design results in increasing IPC messages. + // To support input methods without increasing IPC messages, Chrome + // handles keyboard events in a browser process and send asynchronous + // input events (to be translated to DOM events) to a renderer + // process. + // This design is mostly the same as the one of Windows and Mac Carbon. + // So, for what it's worth, our Linux and Mac front-ends emulate our + // Windows front-end. To emulate our Windows front-end, we can share + // our back-end code among Windows, Linux, and Mac. + // TODO(hbono): Issue 18064: remove the KeyDown type since it isn't + // used in Chrome any longer. enum Type { Undefined = -1, diff --git a/webkit/api/public/mac/WebInputEventFactory.h b/webkit/api/public/mac/WebInputEventFactory.h index 5b55613c..bb02e38 100644 --- a/webkit/api/public/mac/WebInputEventFactory.h +++ b/webkit/api/public/mac/WebInputEventFactory.h @@ -1,10 +1,10 @@ /* * Copyright (C) 2009 Google Inc. All rights reserved. - * + * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are * met: - * + * * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above @@ -14,7 +14,7 @@ * * Neither the name of Google Inc. nor the names of its * contributors may be used to endorse or promote products derived from * this software without specific prior written permission. - * + * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR @@ -50,6 +50,7 @@ namespace WebKit { class WebInputEventFactory { public: WEBKIT_API static WebKeyboardEvent keyboardEvent(NSEvent*); + WEBKIT_API static WebKeyboardEvent keyboardEvent(wchar_t character, int modifiers, double timeStampSeconds); WEBKIT_API static WebMouseEvent mouseEvent(NSEvent*, NSView*); WEBKIT_API static WebMouseWheelEvent mouseWheelEvent(NSEvent*, NSView*); }; diff --git a/webkit/api/src/mac/WebInputEventFactory.mm b/webkit/api/src/mac/WebInputEventFactory.mm index 02a85d2..b549c5f 100644 --- a/webkit/api/src/mac/WebInputEventFactory.mm +++ b/webkit/api/src/mac/WebInputEventFactory.mm @@ -846,7 +846,7 @@ WebKeyboardEvent WebInputEventFactory::keyboardEvent(NSEvent* event) WebKeyboardEvent result; result.type = - isKeyUpEvent(event) ? WebInputEvent::KeyUp : WebInputEvent::KeyDown; + isKeyUpEvent(event) ? WebInputEvent::KeyUp : WebInputEvent::RawKeyDown; result.modifiers = modifiersFromEvent(event); @@ -905,6 +905,25 @@ WebKeyboardEvent WebInputEventFactory::keyboardEvent(NSEvent* event) return result; } +WebKeyboardEvent WebInputEventFactory::keyboardEvent(wchar_t character, + int modifiers, + double timeStampSeconds) +{ + // keyboardEvent(NSEvent*) depends on the NSEvent object and + // it is hard to use it from methods of the NSTextInput protocol. For + // such methods, this function creates a WebInputEvent::Char event without + // using a NSEvent object. + WebKeyboardEvent result; + result.type = WebKit::WebInputEvent::Char; + result.timeStampSeconds = timeStampSeconds; + result.modifiers = modifiers; + result.windowsKeyCode = character; + result.nativeKeyCode = character; + result.text[0] = character; + result.unmodifiedText[0] = character; + return result; +} + // WebMouseEvent -------------------------------------------------------------- WebMouseEvent WebInputEventFactory::mouseEvent(NSEvent* event, NSView* view) |