OLD | NEW |
1 // Copyright (c) 2006-2008 The Chromium Authors. All rights reserved. | 1 // Copyright (c) 2006-2008 The Chromium Authors. All rights reserved. |
2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
4 | 4 |
5 #include "chrome/browser/renderer_host/render_widget_host_view_mac.h" | 5 #include "chrome/browser/renderer_host/render_widget_host_view_mac.h" |
6 | 6 |
7 #include "base/histogram.h" | 7 #include "base/histogram.h" |
| 8 #include "base/string_util.h" |
8 #include "base/sys_string_conversions.h" | 9 #include "base/sys_string_conversions.h" |
9 #include "chrome/browser/browser_trial.h" | 10 #include "chrome/browser/browser_trial.h" |
10 #import "chrome/browser/cocoa/rwhvm_editcommand_helper.h" | 11 #import "chrome/browser/cocoa/rwhvm_editcommand_helper.h" |
11 #include "chrome/browser/renderer_host/backing_store.h" | 12 #include "chrome/browser/renderer_host/backing_store.h" |
12 #include "chrome/browser/renderer_host/render_process_host.h" | 13 #include "chrome/browser/renderer_host/render_process_host.h" |
13 #include "chrome/browser/renderer_host/render_widget_host.h" | 14 #include "chrome/browser/renderer_host/render_widget_host.h" |
14 #include "chrome/common/native_web_keyboard_event.h" | 15 #include "chrome/common/native_web_keyboard_event.h" |
15 #include "skia/ext/platform_canvas.h" | 16 #include "skia/ext/platform_canvas.h" |
16 #include "webkit/api/public/mac/WebInputEventFactory.h" | 17 #include "webkit/api/public/mac/WebInputEventFactory.h" |
17 #include "webkit/api/public/WebInputEvent.h" | 18 #include "webkit/api/public/WebInputEvent.h" |
(...skipping 22 matching lines...) Expand all Loading... |
40 RenderWidgetHost* widget) { | 41 RenderWidgetHost* widget) { |
41 return new RenderWidgetHostViewMac(widget); | 42 return new RenderWidgetHostViewMac(widget); |
42 } | 43 } |
43 | 44 |
44 /////////////////////////////////////////////////////////////////////////////// | 45 /////////////////////////////////////////////////////////////////////////////// |
45 // RenderWidgetHostViewMac, public: | 46 // RenderWidgetHostViewMac, public: |
46 | 47 |
47 RenderWidgetHostViewMac::RenderWidgetHostViewMac(RenderWidgetHost* widget) | 48 RenderWidgetHostViewMac::RenderWidgetHostViewMac(RenderWidgetHost* widget) |
48 : render_widget_host_(widget), | 49 : render_widget_host_(widget), |
49 about_to_validate_and_paint_(false), | 50 about_to_validate_and_paint_(false), |
| 51 im_attributes_(NULL), |
| 52 im_composing_(false), |
50 is_loading_(false), | 53 is_loading_(false), |
51 is_hidden_(false), | 54 is_hidden_(false), |
52 shutdown_factory_(this), | 55 shutdown_factory_(this), |
53 parent_view_(NULL) { | 56 parent_view_(NULL) { |
54 cocoa_view_ = [[[RenderWidgetHostViewCocoa alloc] | 57 cocoa_view_ = [[[RenderWidgetHostViewCocoa alloc] |
55 initWithRenderWidgetHostViewMac:this] autorelease]; | 58 initWithRenderWidgetHostViewMac:this] autorelease]; |
56 render_widget_host_->set_view(this); | 59 render_widget_host_->set_view(this); |
57 } | 60 } |
58 | 61 |
59 RenderWidgetHostViewMac::~RenderWidgetHostViewMac() { | 62 RenderWidgetHostViewMac::~RenderWidgetHostViewMac() { |
(...skipping 141 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
201 [ns_cursor set]; | 204 [ns_cursor set]; |
202 } | 205 } |
203 | 206 |
204 void RenderWidgetHostViewMac::SetIsLoading(bool is_loading) { | 207 void RenderWidgetHostViewMac::SetIsLoading(bool is_loading) { |
205 is_loading_ = is_loading; | 208 is_loading_ = is_loading; |
206 UpdateCursorIfOverSelf(); | 209 UpdateCursorIfOverSelf(); |
207 } | 210 } |
208 | 211 |
209 void RenderWidgetHostViewMac::IMEUpdateStatus(int control, | 212 void RenderWidgetHostViewMac::IMEUpdateStatus(int control, |
210 const gfx::Rect& caret_rect) { | 213 const gfx::Rect& caret_rect) { |
211 NOTIMPLEMENTED(); | 214 // The renderer updates its IME status. |
| 215 // We need to control the input method according to the given message. |
| 216 |
| 217 // We need to convert the coordinate of the cursor rectangle sent from the |
| 218 // renderer and save it. Our IME backend uses a coordinate system whose |
| 219 // origin is the upper-left corner of this view. On the other hand, Cocoa |
| 220 // uses a coordinate system whose origin is the lower-left corner of this |
| 221 // view. So, we convert the cursor rectangle and save it. |
| 222 NSRect view_rect = [cocoa_view_ bounds]; |
| 223 const int y_offset = static_cast<int>(view_rect.size.height); |
| 224 im_caret_rect_ = NSMakeRect(caret_rect.x(), |
| 225 y_offset - caret_rect.y() - caret_rect.height(), |
| 226 caret_rect.width(), caret_rect.height()); |
212 } | 227 } |
213 | 228 |
214 void RenderWidgetHostViewMac::DidPaintRect(const gfx::Rect& rect) { | 229 void RenderWidgetHostViewMac::DidPaintRect(const gfx::Rect& rect) { |
215 if (is_hidden_) | 230 if (is_hidden_) |
216 return; | 231 return; |
217 | 232 |
218 NSRect ns_rect = [cocoa_view_ RectToNSRect:rect]; | 233 NSRect ns_rect = [cocoa_view_ RectToNSRect:rect]; |
219 | 234 |
220 if (about_to_validate_and_paint_) { | 235 if (about_to_validate_and_paint_) { |
221 // As much as we'd like to use -setNeedsDisplayInRect: here, we can't. We're | 236 // As much as we'd like to use -setNeedsDisplayInRect: here, we can't. We're |
(...skipping 229 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
451 } | 466 } |
452 | 467 |
453 - (void)keyEvent:(NSEvent *)theEvent { | 468 - (void)keyEvent:(NSEvent *)theEvent { |
454 // TODO(avi): Possibly kill self? See RenderWidgetHostViewWin::OnKeyEvent and | 469 // TODO(avi): Possibly kill self? See RenderWidgetHostViewWin::OnKeyEvent and |
455 // http://b/issue?id=1192881 . | 470 // http://b/issue?id=1192881 . |
456 | 471 |
457 // Don't cancel child popups; the key events are probably what's triggering | 472 // Don't cancel child popups; the key events are probably what's triggering |
458 // the popup in the first place. | 473 // the popup in the first place. |
459 | 474 |
460 NativeWebKeyboardEvent event(theEvent); | 475 NativeWebKeyboardEvent event(theEvent); |
| 476 |
| 477 // Save the modifier keys so the insertText method can use it when it sends |
| 478 // a Char event, which is dispatched as an onkeypress() event of JavaScript. |
| 479 renderWidgetHostView_->im_modifiers_ = event.modifiers; |
| 480 |
| 481 // To emulate Windows, over-write |event.windowsKeyCode| to VK_PROCESSKEY |
| 482 // while an input method is composing a text. |
| 483 // Gmail checks this code in its onkeydown handler to stop auto-completing |
| 484 // e-mail addresses while composing a CJK text. |
| 485 if ([theEvent type] == NSKeyDown && renderWidgetHostView_->im_composing_) |
| 486 event.windowsKeyCode = 0xE5; |
| 487 |
| 488 // Dispatch this keyboard event to the renderer. |
461 if (renderWidgetHostView_->render_widget_host_) | 489 if (renderWidgetHostView_->render_widget_host_) |
462 renderWidgetHostView_->render_widget_host_->ForwardKeyboardEvent(event); | 490 renderWidgetHostView_->render_widget_host_->ForwardKeyboardEvent(event); |
| 491 |
| 492 // Dispatch a NSKeyDown event to an input method. |
| 493 // To send an onkeydown() event before an onkeypress() event, we should |
| 494 // dispatch this NSKeyDown event AFTER sending it to the renderer. |
| 495 // (See <https://bugs.webkit.org/show_bug.cgi?id=25119>). |
| 496 if ([theEvent type] == NSKeyDown) |
| 497 [self interpretKeyEvents:[NSArray arrayWithObject:theEvent]]; |
463 } | 498 } |
464 | 499 |
465 - (void)scrollWheel:(NSEvent *)theEvent { | 500 - (void)scrollWheel:(NSEvent *)theEvent { |
466 [self cancelChildPopups]; | 501 [self cancelChildPopups]; |
467 | 502 |
468 const WebMouseWheelEvent& event = | 503 const WebMouseWheelEvent& event = |
469 WebInputEventFactory::mouseWheelEvent(theEvent, self); | 504 WebInputEventFactory::mouseWheelEvent(theEvent, self); |
470 if (renderWidgetHostView_->render_widget_host_) | 505 if (renderWidgetHostView_->render_widget_host_) |
471 renderWidgetHostView_->render_widget_host_->ForwardWheelEvent(event); | 506 renderWidgetHostView_->render_widget_host_->ForwardWheelEvent(event); |
472 } | 507 } |
(...skipping 339 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
812 } | 847 } |
813 | 848 |
814 // NSView calls this to get the text when displaying the tooltip. | 849 // NSView calls this to get the text when displaying the tooltip. |
815 - (NSString *)view:(NSView *)view | 850 - (NSString *)view:(NSView *)view |
816 stringForToolTip:(NSToolTipTag)tag | 851 stringForToolTip:(NSToolTipTag)tag |
817 point:(NSPoint)point | 852 point:(NSPoint)point |
818 userData:(void *)data { | 853 userData:(void *)data { |
819 return [[toolTip_ copy] autorelease]; | 854 return [[toolTip_ copy] autorelease]; |
820 } | 855 } |
821 | 856 |
| 857 // Below is our NSTextInput implementation. |
| 858 // |
| 859 // When WebHTMLView receives a NSKeyDown event, WebHTMLView calls the following |
| 860 // functions to process this event. |
| 861 // |
| 862 // [WebHTMLView keyDown] -> |
| 863 // EventHandler::keyEvent() -> |
| 864 // ... |
| 865 // [WebEditorClient handleKeyboardEvent] -> |
| 866 // [WebHTMLView _interceptEditingKeyEvent] -> |
| 867 // [NSResponder interpretKeyEvents] -> |
| 868 // [WebHTMLView insertText] -> |
| 869 // Editor::insertText() |
| 870 // |
| 871 // Unfortunately, it is hard for Chromium to use this implementation because |
| 872 // it causes key-typing jank. |
| 873 // RenderWidgetHostViewMac is running in a browser process. On the other |
| 874 // hand, Editor and EventHandler are running in a renderer process. |
| 875 // So, if we used this implementation, a NSKeyDown event is dispatched to |
| 876 // the following functions of Chromium. |
| 877 // |
| 878 // [RenderWidgetHostViewMac keyEvent] (browser) -> |
| 879 // |Sync IPC (KeyDown)| (*1) -> |
| 880 // EventHandler::keyEvent() (renderer) -> |
| 881 // ... |
| 882 // EditorClientImpl::handleKeyboardEvent() (renderer) -> |
| 883 // |Sync IPC| (*2) -> |
| 884 // [RenderWidgetHostViewMac _interceptEditingKeyEvent] (browser) -> |
| 885 // [self interpretKeyEvents] -> |
| 886 // [RenderWidgetHostViewMac insertText] (browser) -> |
| 887 // |Async IPC| -> |
| 888 // Editor::insertText() (renderer) |
| 889 // |
| 890 // (*1) we need to wait until this call finishes since WebHTMLView uses the |
| 891 // result of EventHandler::keyEvent(). |
| 892 // (*2) we need to wait until this call finishes since WebEditorClient uses |
| 893 // the result of [WebHTMLView _interceptEditingKeyEvent]. |
| 894 // |
| 895 // This needs many sync IPC messages sent between a browser and a renderer for |
| 896 // each key event, which would probably result in key-typing jank. |
| 897 // To avoid this problem, this implementation processes key events (and IME |
| 898 // events) totally in a browser process and sends asynchronous input events, |
| 899 // almost same as KeyboardEvents (and TextEvents) of DOM Level 3, to a |
| 900 // renderer process. |
| 901 // |
| 902 // [RenderWidgetHostViewMac keyEvent] (browser) -> |
| 903 // |Async IPC (RawKeyDown)| -> |
| 904 // [self interpretKeyEvents] -> |
| 905 // [RenderWidgetHostViewMac insertText] (browser) -> |
| 906 // |Async IPC (Char)| -> |
| 907 // Editor::insertText() (renderer) |
| 908 // |
| 909 // Since this implementation doesn't have to wait any IPC calls, this doesn't |
| 910 // make any key-typing jank. --hbono 7/23/09 |
| 911 // |
| 912 extern "C" { |
| 913 extern NSString *NSTextInputReplacementRangeAttributeName; |
| 914 } |
| 915 |
| 916 - (NSArray *)validAttributesForMarkedText { |
| 917 // This code is just copied from WebKit except renaming variables. |
| 918 if (!renderWidgetHostView_->im_attributes_) { |
| 919 renderWidgetHostView_->im_attributes_ = [[NSArray alloc] initWithObjects: |
| 920 NSUnderlineStyleAttributeName, |
| 921 NSUnderlineColorAttributeName, |
| 922 NSMarkedClauseSegmentAttributeName, |
| 923 NSTextInputReplacementRangeAttributeName, |
| 924 nil]; |
| 925 } |
| 926 return renderWidgetHostView_->im_attributes_; |
| 927 } |
| 928 |
| 929 - (NSUInteger)characterIndexForPoint:(NSPoint)thePoint { |
| 930 NOTIMPLEMENTED(); |
| 931 return NSNotFound; |
| 932 } |
| 933 |
| 934 - (NSRect)firstRectForCharacterRange:(NSRange)theRange { |
| 935 // An input method requests a cursor rectangle to display its candidate |
| 936 // window. |
| 937 // Calculate the screen coordinate of the cursor rectangle saved in |
| 938 // RenderWidgetHostViewMac::IMEUpdateStatus() and send it to the IME. |
| 939 // Since this window may be moved since we receive the cursor rectangle last |
| 940 // time we sent the cursor rectangle to the IME, so we should map from the |
| 941 // view coordinate to the screen coordinate every time when an IME need it. |
| 942 NSRect resultRect = renderWidgetHostView_->im_caret_rect_; |
| 943 resultRect = [self convertRect:resultRect toView:nil]; |
| 944 NSWindow* window = [self window]; |
| 945 if (window) |
| 946 resultRect.origin = [window convertBaseToScreen:resultRect.origin]; |
| 947 return resultRect; |
| 948 } |
| 949 |
| 950 - (NSRange)selectedRange { |
| 951 // Return the selected range saved in the setMarkedText method. |
| 952 return renderWidgetHostView_->im_selected_range_; |
| 953 } |
| 954 |
| 955 - (NSRange)markedRange { |
| 956 // An input method calls this method to check if an application really has |
| 957 // a text being composed when hasMarkedText call returns true. |
| 958 // Returns the range saved in the setMarkedText method so the input method |
| 959 // calls the setMarkedText method and we can update the composition node |
| 960 // there. (When this method returns an empty range, the input method doesn't |
| 961 // call the setMarkedText method.) |
| 962 return renderWidgetHostView_->im_marked_range_; |
| 963 } |
| 964 |
| 965 - (NSAttributedString *)attributedSubstringFromRange:(NSRange)nsRange { |
| 966 // TODO(hbono): Even though many input method works without implementing |
| 967 // this method, we need to save a copy of the string in the setMarkedText |
| 968 // method and create a NSAttributedString with the given range. |
| 969 NOTIMPLEMENTED(); |
| 970 return nil; |
| 971 } |
| 972 |
| 973 - (NSInteger)conversationIdentifier { |
| 974 return reinterpret_cast<NSInteger>(self); |
| 975 } |
| 976 |
| 977 - (BOOL)hasMarkedText { |
| 978 // An input method calls this function to figure out whether or not an |
| 979 // application is really composing a text. If it is composing, it calls |
| 980 // the markedRange method, and maybe calls the setMarkedTest method. |
| 981 // It seems an input method usually calls this function when it is about to |
| 982 // cancel an ongoing composition. If an application has a non-empty marked |
| 983 // range, it calls the setMarkedText method to delete the range. |
| 984 return renderWidgetHostView_->im_composing_ ? YES : NO; |
| 985 } |
| 986 |
| 987 - (void)unmarkText { |
| 988 // Delete the composition node of the renderer and finish an ongoing |
| 989 // composition. |
| 990 // It seems an input method calls the setMarkedText method and set an empty |
| 991 // text when it cancels an ongoing composition, i.e. I have never seen an |
| 992 // input method calls this method. |
| 993 renderWidgetHostView_->render_widget_host_->ImeCancelComposition(); |
| 994 renderWidgetHostView_->im_composing_ = false; |
| 995 } |
| 996 |
| 997 - (void)setMarkedText:(id)string selectedRange:(NSRange)newSelRange { |
| 998 // An input method updates the composition string. |
| 999 // We send the given text and range to the renderer so it can update the |
| 1000 // composition node of WebKit. |
| 1001 BOOL isAttributedString = [string isKindOfClass:[NSAttributedString class]]; |
| 1002 NSString* im_text = isAttributedString ? [string string] : string; |
| 1003 int length = [im_text length]; |
| 1004 int cursor; |
| 1005 int target_start; |
| 1006 int target_end; |
| 1007 if (!newSelRange.length) { |
| 1008 // The given text doesn't have any range to be highlighted. |
| 1009 // Put the cursor to the end of this text and clear the selection range. |
| 1010 cursor = length; |
| 1011 target_start = 0; |
| 1012 target_end = 0; |
| 1013 } else { |
| 1014 // The given text has a range to be highlighted. |
| 1015 // Set the selection range to the given one and put the cursor at the |
| 1016 // beginning of the selection range. |
| 1017 cursor = newSelRange.location; |
| 1018 target_start = newSelRange.location; |
| 1019 target_end = newSelRange.location + newSelRange.length; |
| 1020 } |
| 1021 |
| 1022 // Dispatch this IME event to the renderer and update the IME state of this |
| 1023 // object. |
| 1024 // Input methods of Mac use setMarkedText calls with an empty text to cancel |
| 1025 // an ongoing composition. So, we should check whether or not the given text |
| 1026 // is empty to update the IME state. (Our IME backend can automatically |
| 1027 // cancels an ongoing composition when we send an empty text. So, it is OK |
| 1028 // to send an empty text to the renderer.) |
| 1029 renderWidgetHostView_->render_widget_host_->ImeSetComposition( |
| 1030 UTF8ToUTF16([im_text UTF8String]), cursor, target_start, target_end); |
| 1031 renderWidgetHostView_->GetRenderWidgetHost()->ImeSetInputMode(true); |
| 1032 renderWidgetHostView_->im_composing_ = length > 0; |
| 1033 renderWidgetHostView_->im_marked_range_.location = 0; |
| 1034 renderWidgetHostView_->im_marked_range_.length = length; |
| 1035 renderWidgetHostView_->im_selected_range_.location = newSelRange.location; |
| 1036 renderWidgetHostView_->im_selected_range_.length = newSelRange.length; |
| 1037 } |
| 1038 |
| 1039 - (void)doCommandBySelector:(SEL)selector { |
| 1040 // An input method calls this function to dispatch an editing command to be |
| 1041 // handled by this view. |
| 1042 // Even though most editing commands has been already handled by the |
| 1043 // RWHVMEditCommandHelper object, we need to handle an insertNewline: command |
| 1044 // and send a '\r' character to WebKit so that WebKit dispatches this |
| 1045 // character to onkeypress() event handlers. |
| 1046 // TODO(hbono): need to handle more commands? |
| 1047 if (selector == @selector(insertNewline:)) { |
| 1048 NativeWebKeyboardEvent event('\r', renderWidgetHostView_->im_modifiers_, |
| 1049 base::Time::Now().ToDoubleT()); |
| 1050 renderWidgetHostView_->render_widget_host_->ForwardKeyboardEvent(event); |
| 1051 } |
| 1052 } |
| 1053 |
| 1054 - (void)insertText:(id)string { |
| 1055 // An input method has characters to be inserted. |
| 1056 // Same as Linux, Mac calls this method not only: |
| 1057 // * when an input method finishs composing text, but also; |
| 1058 // * when we type an ASCII character (without using input methods). |
| 1059 // When we aren't using input methods, we should send the given character as |
| 1060 // a Char event so it is dispatched to an onkeypress() event handler of |
| 1061 // JavaScript. |
| 1062 // On the other hand, when we are using input methods, we should send the |
| 1063 // given characters as an IME event and prevent the characters from being |
| 1064 // dispatched to onkeypress() event handlers. |
| 1065 BOOL isAttributedString = [string isKindOfClass:[NSAttributedString class]]; |
| 1066 NSString* im_text = isAttributedString ? [string string] : string; |
| 1067 if (!renderWidgetHostView_->im_composing_ && [im_text length] == 1) { |
| 1068 NativeWebKeyboardEvent event([im_text characterAtIndex:0], |
| 1069 renderWidgetHostView_->im_modifiers_, |
| 1070 base::Time::Now().ToDoubleT()); |
| 1071 renderWidgetHostView_->render_widget_host_->ForwardKeyboardEvent(event); |
| 1072 } else { |
| 1073 renderWidgetHostView_->render_widget_host_->ImeConfirmComposition( |
| 1074 UTF8ToUTF16([im_text UTF8String])); |
| 1075 } |
| 1076 renderWidgetHostView_->im_composing_ = false; |
| 1077 } |
| 1078 |
822 @end | 1079 @end |
823 | 1080 |
OLD | NEW |