diff options
author | rsesek@chromium.org <rsesek@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2011-05-02 15:46:01 +0000 |
---|---|---|
committer | rsesek@chromium.org <rsesek@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2011-05-02 15:46:01 +0000 |
commit | d4cff27a73a9dc6caa6b37a8b28ef72ecc35fa0d (patch) | |
tree | 22759b5ed8332e11983609a93432dd47e9a65a79 | |
parent | c5b003c0bbadd824f84421390945f685f0fc9cd1 (diff) | |
download | chromium_src-d4cff27a73a9dc6caa6b37a8b28ef72ecc35fa0d.zip chromium_src-d4cff27a73a9dc6caa6b37a8b28ef72ecc35fa0d.tar.gz chromium_src-d4cff27a73a9dc6caa6b37a8b28ef72ecc35fa0d.tar.bz2 |
[Mac] Implement the system dictionary popup by implementing NSTextInput methods.
This is a two-sided patch; the Chromium side is plumbing to marshall data from
the renderer to the system APIs.
Note that just hitting Cmd+Ctrl+D usually does not bring up the popup. I think
this may be an Apple bug, but I have not yet found a work-around.
BUG=17951,37715,47141
TEST=Hold Cmd+Ctrl+D on a web page and mouse around. The dictionary popup should follow the mouse and show the definition of the current word.
TEST=In a text area, the dictionary popup should work only if the text area has focus.
R=avi,suzhe,jam
Review URL: http://codereview.chromium.org/6289009
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@83723 0039d316-1c4b-4281-b951-d872f2087c98
41 files changed, 1296 insertions, 86 deletions
diff --git a/chrome/browser/chrome_content_browser_client.cc b/chrome/browser/chrome_content_browser_client.cc index 1851177..f2328d2 100644 --- a/chrome/browser/chrome_content_browser_client.cc +++ b/chrome/browser/chrome_content_browser_client.cc @@ -15,6 +15,7 @@ #include "chrome/browser/profiles/profile.h" #include "chrome/browser/renderer_host/chrome_render_message_filter.h" #include "chrome/browser/renderer_host/chrome_render_view_host_observer.h" +#include "chrome/browser/renderer_host/text_input_client_message_filter.h" #include "chrome/browser/search_engines/search_provider_install_state_message_filter.h" #include "chrome/browser/spellcheck_message_filter.h" #include "chrome/browser/ui/webui/chrome_web_ui_factory.h" @@ -61,6 +62,9 @@ void ChromeContentBrowserClient::BrowserRenderProcessHostCreated( host->channel()->AddFilter( new SearchProviderInstallStateMessageFilter(id, profile)); host->channel()->AddFilter(new SpellCheckMessageFilter(id)); +#if defined(OS_MACOSX) + host->channel()->AddFilter(new TextInputClientMessageFilter(host->id())); +#endif } content::WebUIFactory* ChromeContentBrowserClient::GetWebUIFactory() { diff --git a/chrome/browser/renderer_host/render_widget_host_view_gtk.cc b/chrome/browser/renderer_host/render_widget_host_view_gtk.cc index 9a5a340..d4c9781a 100644 --- a/chrome/browser/renderer_host/render_widget_host_view_gtk.cc +++ b/chrome/browser/renderer_host/render_widget_host_view_gtk.cc @@ -830,7 +830,8 @@ void RenderWidgetHostViewGtk::SetTooltipText(const std::wstring& tooltip_text) { } } -void RenderWidgetHostViewGtk::SelectionChanged(const std::string& text) { +void RenderWidgetHostViewGtk::SelectionChanged(const std::string& text, + const ui::Range& range) { if (!text.empty()) { GtkClipboard* x_clipboard = gtk_clipboard_get(GDK_SELECTION_PRIMARY); gtk_clipboard_set_text(x_clipboard, text.c_str(), text.length()); diff --git a/chrome/browser/renderer_host/render_widget_host_view_gtk.h b/chrome/browser/renderer_host/render_widget_host_view_gtk.h index 0cf950a..d70dfbc 100644 --- a/chrome/browser/renderer_host/render_widget_host_view_gtk.h +++ b/chrome/browser/renderer_host/render_widget_host_view_gtk.h @@ -84,7 +84,8 @@ class RenderWidgetHostViewGtk : public RenderWidgetHostView, virtual void Destroy(); virtual void WillDestroyRenderWidget(RenderWidgetHost* rwh) {} virtual void SetTooltipText(const std::wstring& tooltip_text); - virtual void SelectionChanged(const std::string& text); + virtual void SelectionChanged(const std::string& text, + const ui::Range& range); virtual void ShowingContextMenu(bool showing); virtual BackingStore* AllocBackingStore(const gfx::Size& size); virtual void SetBackground(const SkBitmap& background); 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 048f402..576ce97 100644 --- a/chrome/browser/renderer_host/render_widget_host_view_mac.h +++ b/chrome/browser/renderer_host/render_widget_host_view_mac.h @@ -73,12 +73,6 @@ class RWHVMEditCommandHelper; // Represents the input-method attributes supported by this object. scoped_nsobject<NSArray> validAttributesForMarkedText_; - // 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 caretRect_; - // Indicates if we are currently handling a key down event. BOOL handlingKeyDown_; @@ -95,11 +89,7 @@ class RWHVMEditCommandHelper; // the whole content yet. NSRange markedRange_; - // The selected range inside current marked text. - // TODO(suzhe): Currently it's only valid when there is any marked text. - // In the future, we may need to support accessing the whole content of the - // DOM node being edited, then this should be the selected range inside the - // DOM node. + // The selected range, cached from a message sent by the renderer. NSRange selectedRange_; // Text to be inserted which was generated by handling a key down event. @@ -130,7 +120,7 @@ class RWHVMEditCommandHelper; BOOL mouseEventWasIgnored_; } -@property(assign, nonatomic) NSRect caretRect; +@property(nonatomic, readonly) NSRange selectedRange; - (void)setCanBeKeyView:(BOOL)can; - (void)setTakesFocusOnlyOnMouseDown:(BOOL)b; @@ -206,6 +196,7 @@ class RenderWidgetHostViewMac : public RenderWidgetHostView { virtual void ImeUpdateTextInputState(WebKit::WebTextInputType state, const gfx::Rect& caret_rect); virtual void ImeCancelComposition(); + virtual void ImeCompositionRangeChanged(const ui::Range& range); virtual void DidUpdateBackingStore( const gfx::Rect& scroll_rect, int scroll_dx, int scroll_dy, const std::vector<gfx::Rect>& copy_rects); @@ -214,7 +205,8 @@ class RenderWidgetHostViewMac : public RenderWidgetHostView { virtual void WillDestroyRenderWidget(RenderWidgetHost* rwh) {}; virtual void Destroy(); virtual void SetTooltipText(const std::wstring& tooltip_text); - virtual void SelectionChanged(const std::string& text); + virtual void SelectionChanged(const std::string& text, + const ui::Range& range); virtual BackingStore* AllocBackingStore(const gfx::Size& size); virtual void SetTakesFocusOnlyOnMouseDown(bool flag); // See comment in RenderWidgetHostView! 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 14ac88f..e2eb64f 100644 --- a/chrome/browser/renderer_host/render_widget_host_view_mac.mm +++ b/chrome/browser/renderer_host/render_widget_host_view_mac.mm @@ -18,6 +18,7 @@ #import "chrome/browser/accessibility/browser_accessibility_cocoa.h" #include "chrome/browser/accessibility/browser_accessibility_state.h" #include "chrome/browser/browser_trial.h" +#import "chrome/browser/renderer_host/text_input_client_mac.h" #include "chrome/browser/spellchecker_platform_engine.h" #import "chrome/browser/ui/cocoa/rwhvm_editcommand_helper.h" #import "chrome/browser/ui/cocoa/view_id_util.h" @@ -43,6 +44,7 @@ #include "third_party/WebKit/Source/WebKit/chromium/public/mac/WebInputEventFactory.h" #include "third_party/WebKit/Source/WebKit/chromium/public/WebInputEvent.h" #include "ui/gfx/gl/gl_switches.h" +#include "ui/gfx/point.h" #include "ui/gfx/scoped_ns_graphics_context_save_gstate_mac.h" #include "ui/gfx/surface/io_surface_support_mac.h" #include "webkit/glue/webaccessibility.h" @@ -62,10 +64,14 @@ static inline int ToWebKitModifiers(NSUInteger flags) { return modifiers; } -@interface RenderWidgetHostViewCocoa (Private) +// Private methods: +@interface RenderWidgetHostViewCocoa () +@property(nonatomic, assign) NSRange selectedRange; +@property(nonatomic, assign) NSRange markedRange; + + (BOOL)shouldAutohideCursorForEvent:(NSEvent*)event; - (id)initWithRenderWidgetHostViewMac:(RenderWidgetHostViewMac*)r; -- (void)keyEvent:(NSEvent *)theEvent wasKeyEquivalent:(BOOL)equiv; +- (void)keyEvent:(NSEvent*)theEvent wasKeyEquivalent:(BOOL)equiv; - (void)cancelChildPopups; - (void)checkForPluginImeCancellation; @end @@ -854,19 +860,19 @@ void RenderWidgetHostViewMac::ImeUpdateTextInputState( if (HasFocus()) SetTextInputActive(true); } - - // We need to convert the coordinate of the cursor rectangle sent from the - // renderer and save it. Our input method 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. - [cocoa_view_ setCaretRect:[cocoa_view_ flipRectToNSRect:caret_rect]]; } void RenderWidgetHostViewMac::ImeCancelComposition() { [cocoa_view_ cancelComposition]; } +void RenderWidgetHostViewMac::ImeCompositionRangeChanged( + const ui::Range& range) { + // The RangeChanged message is only sent with valid values. The current + // caret position (start == end) will be sent if there is no IME range. + [cocoa_view_ setMarkedRange:range.ToNSRange()]; +} + void RenderWidgetHostViewMac::DidUpdateBackingStore( const gfx::Rect& scroll_rect, int scroll_dx, int scroll_dy, const std::vector<gfx::Rect>& copy_rects) { @@ -983,8 +989,10 @@ void RenderWidgetHostViewMac::SetTooltipText(const std::wstring& tooltip_text) { // RenderWidgetHostViewCocoa uses the stored selection text, // which implements NSServicesRequests protocol. // -void RenderWidgetHostViewMac::SelectionChanged(const std::string& text) { +void RenderWidgetHostViewMac::SelectionChanged(const std::string& text, + const ui::Range& range) { selected_text_ = text; + [cocoa_view_ setSelectedRange:range.ToNSRange()]; } bool RenderWidgetHostViewMac::IsPopup() const { @@ -1386,7 +1394,8 @@ void RenderWidgetHostViewMac::SetTextInputActive(bool active) { @implementation RenderWidgetHostViewCocoa -@synthesize caretRect = caretRect_; +@synthesize selectedRange = selectedRange_; +@synthesize markedRange = markedRange_; - (id)initWithRenderWidgetHostViewMac:(RenderWidgetHostViewMac*)r { self = [super initWithFrame:NSZeroRect]; @@ -2511,31 +2520,34 @@ extern NSString *NSTextInputReplacementRangeAttributeName; } - (NSUInteger)characterIndexForPoint:(NSPoint)thePoint { - NOTIMPLEMENTED(); - return NSNotFound; + DCHECK([self window]); + // |thePoint| is in screen coordinates, but needs to be converted to WebKit + // coordinates (upper left origin). Scroll offsets will be taken care of in + // the renderer. + thePoint = [[self window] convertScreenToBase:thePoint]; + thePoint = [self convertPoint:thePoint fromView:nil]; + thePoint.y = NSHeight([self frame]) - thePoint.y; + + NSUInteger point = + TextInputClientMac::GetInstance()->GetCharacterIndexAtPoint( + renderWidgetHostView_->render_widget_host_, + gfx::Point(thePoint.x, thePoint.y)); + return point; } - (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::ImeUpdateTextInputState() and send it to the - // input method. - // Since this window may be moved since we receive the cursor rectangle last - // time we sent the cursor rectangle to the input method, so we should map - // from the view coordinate to the screen coordinate every time when an input - // method need it. - NSRect resultRect = [self convertRect:caretRect_ toView:nil]; - NSWindow* window = [self window]; - if (window) - resultRect.origin = [window convertBaseToScreen:resultRect.origin]; - - return resultRect; -} + NSRect rect = TextInputClientMac::GetInstance()->GetFirstRectForRange( + renderWidgetHostView_->render_widget_host_, theRange); -- (NSRange)selectedRange { - // Return the selected range saved in the setMarkedText method. - return hasMarkedText_ ? selectedRange_ : NSMakeRange(NSNotFound, 0); + // The returned rectangle is in WebKit coordinates (upper left origin), so + // flip the coordinate system and then convert it into screen coordinates for + // return. + NSRect viewFrame = [self frame]; + rect.origin.y = NSHeight(viewFrame) - rect.origin.y; + rect.origin.y -= rect.size.height; + rect = [self convertRectToBase:rect]; + rect.origin = [[self window] convertBaseToScreen:rect.origin]; + return rect; } - (NSRange)markedRange { @@ -2548,12 +2560,11 @@ extern NSString *NSTextInputReplacementRangeAttributeName; return hasMarkedText_ ? markedRange_ : NSMakeRange(NSNotFound, 0); } -- (NSAttributedString *)attributedSubstringFromRange:(NSRange)range { - // 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. - // http://crbug.com/37715 - return nil; +- (NSAttributedString*)attributedSubstringFromRange:(NSRange)range { + NSAttributedString* str = + TextInputClientMac::GetInstance()->GetAttributedSubstringFromRange( + renderWidgetHostView_->render_widget_host_, range); + return str; } - (NSInteger)conversationIdentifier { @@ -2612,7 +2623,7 @@ extern NSString *NSTextInputReplacementRangeAttributeName; NSString* im_text = isAttributedString ? [string string] : string; int length = [im_text length]; - markedRange_ = NSMakeRange(0, length); + // |markedRange_| will get set on a callback from ImeSetComposition(). selectedRange_ = newSelRange; markedText_ = base::SysNSStringToUTF16(im_text); hasMarkedText_ = (length > 0); diff --git a/chrome/browser/renderer_host/render_widget_host_view_views.cc b/chrome/browser/renderer_host/render_widget_host_view_views.cc index ee2c43f..be7e8fc 100644 --- a/chrome/browser/renderer_host/render_widget_host_view_views.cc +++ b/chrome/browser/renderer_host/render_widget_host_view_views.cc @@ -355,7 +355,8 @@ void RenderWidgetHostViewViews::SetTooltipText(const std::wstring& tip) { // NOTIMPLEMENTED(); ... too annoying, it triggers for every mousemove } -void RenderWidgetHostViewViews::SelectionChanged(const std::string& text) { +void RenderWidgetHostViewViews::SelectionChanged(const std::string& text, + const ui::Range& range) { // TODO(anicolao): deal with the clipboard without GTK NOTIMPLEMENTED(); } diff --git a/chrome/browser/renderer_host/render_widget_host_view_views.h b/chrome/browser/renderer_host/render_widget_host_view_views.h index 177f50b..955afa9 100644 --- a/chrome/browser/renderer_host/render_widget_host_view_views.h +++ b/chrome/browser/renderer_host/render_widget_host_view_views.h @@ -70,7 +70,8 @@ class RenderWidgetHostViewViews : public RenderWidgetHostView, virtual void Destroy() OVERRIDE; virtual void WillDestroyRenderWidget(RenderWidgetHost* rwh) OVERRIDE {} virtual void SetTooltipText(const std::wstring& tooltip_text) OVERRIDE; - virtual void SelectionChanged(const std::string& text) OVERRIDE; + virtual void SelectionChanged(const std::string& text, + const ui::Range& range) OVERRIDE; virtual void ShowingContextMenu(bool showing) OVERRIDE; virtual BackingStore* AllocBackingStore(const gfx::Size& size) OVERRIDE; virtual void SetBackground(const SkBitmap& background) OVERRIDE; diff --git a/chrome/browser/renderer_host/text_input_client_mac.h b/chrome/browser/renderer_host/text_input_client_mac.h new file mode 100644 index 0000000..00c2e29 --- /dev/null +++ b/chrome/browser/renderer_host/text_input_client_mac.h @@ -0,0 +1,86 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CHROME_BROWSER_RENDERER_HOST_TEXT_INPUT_CLIENT_MAC_H_ +#define CHROME_BROWSER_RENDERER_HOST_TEXT_INPUT_CLIENT_MAC_H_ + +#import <Cocoa/Cocoa.h> + +#include "base/memory/scoped_nsobject.h" +#include "base/synchronization/condition_variable.h" +#include "base/synchronization/lock.h" +#include "ui/gfx/point.h" + +template <typename T> struct DefaultSingletonTraits; + +class RenderWidgetHost; + +// This class helps with the Mac OS X dictionary popup. For the design overview, +// look at this document: +// http://dev.chromium.org/developers/design-documents/system-dictionary-pop-up-architecture +// +// This service is used to marshall information for these three methods that are +// implemented in RenderWidgetHostViewMac: +// -[NSTextInput characterIndexForPoint:] +// -[NSTextInput attributedSubstringFromRange:] +// -[NSTextInput firstRectForCharacterRange:] +// +// Because these methods are part of a synchronous system API, implementing them +// requires getting information from the renderer synchronously. Rather than +// using an actual sync IPC message, a normal async ViewMsg is used with a lock +// and condition (managed by this service). +class TextInputClientMac { + public: + // Returns the singleton instance. + static TextInputClientMac* GetInstance(); + + // Each of the three methods mentioned above has an associated pair of methods + // to get data from the renderer. The Get*() methods block the calling thread + // (always the UI thread) with a short timeout after the async message has + // been sent to the renderer to lookup the information needed to respond to + // the system. The Set*AndSignal() methods store the looked up information in + // this service and signal the condition to allow the Get*() methods to + // unlock and return that stored value. + // + // Returns NSNotFound if the request times out or is not completed. + NSUInteger GetCharacterIndexAtPoint(RenderWidgetHost* rwh, gfx::Point point); + // Returns nil if the request times out or is completed. + NSAttributedString* GetAttributedSubstringFromRange(RenderWidgetHost* rwh, + NSRange range); + // Returns NSZeroRect if the request times out or is not completed. The result + // is in WebKit coordinates. + NSRect GetFirstRectForRange(RenderWidgetHost* rwh, NSRange range); + + // When the renderer sends the ViewHostMsg reply, the RenderMessageFilter will + // call the corresponding method on the IO thread to unlock the condition and + // allow the Get*() methods to continue/return. + void SetCharacterIndexAndSignal(NSUInteger index); + void SetFirstRectAndSignal(NSRect first_rect); + void SetSubstringAndSignal(NSAttributedString* string); + + private: + friend struct DefaultSingletonTraits<TextInputClientMac>; + TextInputClientMac(); + ~TextInputClientMac(); + + // The critical sections that the Condition guards are in Get*() methods. + // These methods lock the internal condition for use before the asynchronous + // message is sent to the renderer to lookup the required information. These + // are only used on the UI thread. + void BeforeRequest(); + // Called at the end of a critical section. This will release the lock and + // condition. + void AfterRequest(); + + NSUInteger character_index_; + NSRect first_rect_; + scoped_nsobject<NSAttributedString> substring_; + + base::Lock lock_; + base::ConditionVariable condition_; + + DISALLOW_COPY_AND_ASSIGN(TextInputClientMac); +}; + +#endif // CHROME_BROWSER_RENDERER_HOST_TEXT_INPUT_CLIENT_MAC_H_ diff --git a/chrome/browser/renderer_host/text_input_client_mac.mm b/chrome/browser/renderer_host/text_input_client_mac.mm new file mode 100644 index 0000000..f78f8b3 --- /dev/null +++ b/chrome/browser/renderer_host/text_input_client_mac.mm @@ -0,0 +1,122 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#import "chrome/browser/renderer_host/text_input_client_mac.h" + +#include "base/memory/singleton.h" +#include "base/metrics/histogram.h" +#include "base/time.h" +#include "chrome/common/text_input_client_messages.h" +#include "content/browser/renderer_host/render_widget_host.h" + +// The amount of time in milliseconds that the browser process will wait for a +// response from the renderer. +// TODO(rsesek): Using the histogram data, find the best upper-bound for this +// value. +const float kWaitTimeout = 1500; + +TextInputClientMac::TextInputClientMac() + : character_index_(NSNotFound), + lock_(), + condition_(&lock_) { +} + +TextInputClientMac::~TextInputClientMac() { +} + +// static +TextInputClientMac* TextInputClientMac::GetInstance() { + return Singleton<TextInputClientMac>::get(); +} + +NSUInteger TextInputClientMac::GetCharacterIndexAtPoint(RenderWidgetHost* rwh, + gfx::Point point) { + base::TimeTicks start = base::TimeTicks::Now(); + + BeforeRequest(); + rwh->Send(new TextInputClientMsg_CharacterIndexForPoint(rwh->routing_id(), + point)); + condition_.TimedWait(base::TimeDelta::FromMilliseconds(kWaitTimeout)); + AfterRequest(); + + base::TimeDelta delta(base::TimeTicks::Now() - start); + UMA_HISTOGRAM_TIMES("TextInputClient.CharacterIndex", + delta * base::Time::kMicrosecondsPerMillisecond); + + return character_index_; +} + +NSRect TextInputClientMac::GetFirstRectForRange(RenderWidgetHost* rwh, + NSRange range) { + base::TimeTicks start = base::TimeTicks::Now(); + + BeforeRequest(); + rwh->Send(new TextInputClientMsg_FirstRectForCharacterRange(rwh->routing_id(), + ui::Range(range))); + condition_.TimedWait(base::TimeDelta::FromMilliseconds(kWaitTimeout)); + AfterRequest(); + + base::TimeDelta delta(base::TimeTicks::Now() - start); + UMA_HISTOGRAM_TIMES("TextInputClient.FirstRect", + delta * base::Time::kMicrosecondsPerMillisecond); + + return first_rect_; +} + +NSAttributedString* TextInputClientMac::GetAttributedSubstringFromRange( + RenderWidgetHost* rwh, + NSRange range) { + base::TimeTicks start = base::TimeTicks::Now(); + + BeforeRequest(); + rwh->Send(new TextInputClientMsg_StringForRange(rwh->routing_id(), + ui::Range(range))); + condition_.TimedWait(base::TimeDelta::FromMilliseconds(kWaitTimeout)); + AfterRequest(); + + base::TimeDelta delta(base::TimeTicks::Now() - start); + UMA_HISTOGRAM_TIMES("TextInputClient.Substring", + delta * base::Time::kMicrosecondsPerMillisecond); + + return substring_.get(); +} + +void TextInputClientMac::SetCharacterIndexAndSignal(NSUInteger index) { + lock_.Acquire(); + character_index_ = index; + lock_.Release(); + condition_.Signal(); +} + +void TextInputClientMac::SetFirstRectAndSignal(NSRect first_rect) { + lock_.Acquire(); + first_rect_ = first_rect; + lock_.Release(); + condition_.Signal(); +} + +void TextInputClientMac::SetSubstringAndSignal(NSAttributedString* string) { + lock_.Acquire(); + substring_.reset([string copy]); + lock_.Release(); + condition_.Signal(); +} + +void TextInputClientMac::BeforeRequest() { + base::TimeTicks start = base::TimeTicks::Now(); + + lock_.Acquire(); + + base::TimeDelta delta(base::TimeTicks::Now() - start); + UMA_HISTOGRAM_TIMES("TextInputClient.LockWait", + delta * base::Time::kMicrosecondsPerMillisecond); + + character_index_ = NSNotFound; + first_rect_ = NSZeroRect; + substring_.reset(); +} + +void TextInputClientMac::AfterRequest() { + lock_.Release(); +} diff --git a/chrome/browser/renderer_host/text_input_client_mac_unittest.mm b/chrome/browser/renderer_host/text_input_client_mac_unittest.mm new file mode 100644 index 0000000..8d1c794 --- /dev/null +++ b/chrome/browser/renderer_host/text_input_client_mac_unittest.mm @@ -0,0 +1,160 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#import "chrome/browser/renderer_host/text_input_client_mac.h" + +#include "base/bind.h" +#include "base/message_loop.h" +#include "base/threading/thread.h" +#include "chrome/common/text_input_client_messages.h" +#include "chrome/test/testing_profile.h" +#include "content/browser/renderer_host/mock_render_process_host.h" +#include "content/browser/renderer_host/render_widget_host.h" +#include "ipc/ipc_test_sink.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "testing/gtest_mac.h" + +namespace { + +// This test does not test the WebKit side of the dictionary system (which +// performs the actual data fetching), but rather this just tests that the +// service's signaling system works. +class TextInputClientMacTest : public testing::Test { + public: + TextInputClientMacTest() + : message_loop_(MessageLoop::TYPE_UI), + profile_(), + process_(new MockRenderProcessHost(&profile_)), + widget_(process_, 1), + thread_("TextInputClientMacTestThread") {} + + // Accessor for the TextInputClientMac instance. + TextInputClientMac* service() { + return TextInputClientMac::GetInstance(); + } + + // Helper method to post a task on the testing thread's MessageLoop after + // a short delay. + void PostTask(const base::Closure& task) { + const int64 kTaskDelayMs = 200; + thread_.message_loop()->PostDelayedTask(FROM_HERE, task, kTaskDelayMs); + } + + RenderWidgetHost* widget() { + return &widget_; + } + + IPC::TestSink& ipc_sink() { + return process_->sink(); + } + + private: + friend class ScopedTestingThread; + + MessageLoop message_loop_; + TestingProfile profile_; + + // Gets deleted when the last RWH in the "process" gets destroyed. + MockRenderProcessHost* process_; + RenderWidgetHost widget_; + + base::Thread thread_; +}; + +//////////////////////////////////////////////////////////////////////////////// + +// Helper class that Start()s and Stop()s a thread according to the scope of the +// object. +class ScopedTestingThread { + public: + ScopedTestingThread(TextInputClientMacTest* test) : thread_(test->thread_) { + thread_.Start(); + } + ~ScopedTestingThread() { + thread_.Stop(); + } + + private: + base::Thread& thread_; +}; + +// Test Cases ////////////////////////////////////////////////////////////////// + +TEST_F(TextInputClientMacTest, GetCharacterIndex) { + ScopedTestingThread thread(this); + const NSUInteger kSuccessValue = 42; + + PostTask(base::Bind(&TextInputClientMac::SetCharacterIndexAndSignal, + base::Unretained(service()), kSuccessValue)); + NSUInteger index = service()->GetCharacterIndexAtPoint( + widget(), gfx::Point(2, 2)); + + EXPECT_EQ(1U, ipc_sink().message_count()); + EXPECT_TRUE(ipc_sink().GetUniqueMessageMatching( + TextInputClientMsg_CharacterIndexForPoint::ID)); + EXPECT_EQ(kSuccessValue, index); +} + +TEST_F(TextInputClientMacTest, TimeoutCharacterIndex) { + NSUInteger index = service()->GetCharacterIndexAtPoint( + widget(), gfx::Point(2, 2)); + EXPECT_EQ(1U, ipc_sink().message_count()); + EXPECT_TRUE(ipc_sink().GetUniqueMessageMatching( + TextInputClientMsg_CharacterIndexForPoint::ID)); + EXPECT_EQ(NSNotFound, index); +} + +TEST_F(TextInputClientMacTest, GetRectForRange) { + ScopedTestingThread thread(this); + const NSRect kSuccessValue = NSMakeRect(42, 43, 44, 45); + + PostTask(base::Bind(&TextInputClientMac::SetFirstRectAndSignal, + base::Unretained(service()), kSuccessValue)); + NSRect rect = service()->GetFirstRectForRange(widget(), NSMakeRange(0, 32)); + + EXPECT_EQ(1U, ipc_sink().message_count()); + EXPECT_TRUE(ipc_sink().GetUniqueMessageMatching( + TextInputClientMsg_FirstRectForCharacterRange::ID)); + EXPECT_TRUE(NSEqualRects(kSuccessValue, rect)); +} + +TEST_F(TextInputClientMacTest, TimeoutRectForRange) { + NSRect rect = service()->GetFirstRectForRange(widget(), NSMakeRange(0, 32)); + EXPECT_EQ(1U, ipc_sink().message_count()); + EXPECT_TRUE(ipc_sink().GetUniqueMessageMatching( + TextInputClientMsg_FirstRectForCharacterRange::ID)); + EXPECT_TRUE(NSEqualRects(NSZeroRect, rect)); +} + +TEST_F(TextInputClientMacTest, GetSubstring) { + ScopedTestingThread thread(this); + NSDictionary* attributes = + [NSDictionary dictionaryWithObject:[NSColor purpleColor] + forKey:NSForegroundColorAttributeName]; + scoped_nsobject<NSAttributedString> kSuccessValue( + [[NSAttributedString alloc] initWithString:@"Barney is a purple dinosaur" + attributes:attributes]); + + PostTask(base::Bind(&TextInputClientMac::SetSubstringAndSignal, + base::Unretained(service()), base::Unretained(kSuccessValue.get()))); + NSAttributedString* string = service()->GetAttributedSubstringFromRange( + widget(), NSMakeRange(0, 32)); + + EXPECT_NSEQ(kSuccessValue, string); + EXPECT_NE(kSuccessValue.get(), string); // |string| should be a copy. + EXPECT_EQ(1U, ipc_sink().message_count()); + EXPECT_TRUE(ipc_sink().GetUniqueMessageMatching( + TextInputClientMsg_StringForRange::ID)); +} + +TEST_F(TextInputClientMacTest, TimeoutSubstring) { + NSAttributedString* string = service()->GetAttributedSubstringFromRange( + widget(), NSMakeRange(0, 32)); + EXPECT_EQ(nil, string); + EXPECT_EQ(1U, ipc_sink().message_count()); + EXPECT_TRUE(ipc_sink().GetUniqueMessageMatching( + TextInputClientMsg_StringForRange::ID)); +} + +} // namespace diff --git a/chrome/browser/renderer_host/text_input_client_message_filter.h b/chrome/browser/renderer_host/text_input_client_message_filter.h new file mode 100644 index 0000000..17c022b --- /dev/null +++ b/chrome/browser/renderer_host/text_input_client_message_filter.h @@ -0,0 +1,44 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CHROME_BROWSER_RENDERER_HOST_TEXT_INPUT_CLIENT_MESSAGE_FILTER_H_ +#define CHROME_BROWSER_RENDERER_HOST_TEXT_INPUT_CLIENT_MESSAGE_FILTER_H_ + +#include "chrome/common/attributed_string_coder_mac.h" +#include "content/browser/browser_message_filter.h" + +namespace gfx { +class Rect; +} + +namespace ui { +class Range; +} + +// This is a browser-side message filter that lives on the IO thread to handle +// replies to messages sent by the TextInputClientMac. See +// chrome/browser/renderer_host/text_input_client_mac.h for more information. +class TextInputClientMessageFilter : public BrowserMessageFilter { + public: + explicit TextInputClientMessageFilter(int child_id); + virtual ~TextInputClientMessageFilter(); + + // BrowserMessageFilter override: + virtual bool OnMessageReceived(const IPC::Message& message, + bool* message_was_ok); + + private: + // IPC Message handlers: + void OnGotCharacterIndexForPoint(size_t index); + void OnGotFirstRectForRange(const gfx::Rect& rect); + void OnGotStringFromRange( + const mac::AttributedStringCoder::EncodedString& string); + + // Child process id. + int child_process_id_; + + DISALLOW_COPY_AND_ASSIGN(TextInputClientMessageFilter); +}; + +#endif // CHROME_BROWSER_RENDERER_HOST_TEXT_INPUT_CLIENT_MESSAGE_FILTER_H_ diff --git a/chrome/browser/renderer_host/text_input_client_message_filter.mm b/chrome/browser/renderer_host/text_input_client_message_filter.mm new file mode 100644 index 0000000..7bfbdfc --- /dev/null +++ b/chrome/browser/renderer_host/text_input_client_message_filter.mm @@ -0,0 +1,63 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "chrome/browser/renderer_host/text_input_client_message_filter.h" + +#include "base/memory/scoped_nsobject.h" +#include "base/string16.h" +#include "chrome/browser/renderer_host/text_input_client_mac.h" +#include "chrome/common/attributed_string_coder_mac.h" +#include "chrome/common/text_input_client_messages.h" +#include "content/browser/browser_thread.h" +#include "content/browser/renderer_host/render_view_host.h" +#include "content/browser/renderer_host/render_widget_host_view.h" +#include "ipc/ipc_message_macros.h" +#include "ui/base/range/range.h" +#include "ui/gfx/rect.h" + +TextInputClientMessageFilter::TextInputClientMessageFilter(int child_id) + : BrowserMessageFilter(), + child_process_id_(child_id) { +} + +TextInputClientMessageFilter::~TextInputClientMessageFilter() { +} + +bool TextInputClientMessageFilter::OnMessageReceived( + const IPC::Message& message, + bool* message_was_ok) { + bool handled = true; + IPC_BEGIN_MESSAGE_MAP_EX(TextInputClientMessageFilter, message, + *message_was_ok) + IPC_MESSAGE_HANDLER(TextInputClientReplyMsg_GotCharacterIndexForPoint, + OnGotCharacterIndexForPoint) + IPC_MESSAGE_HANDLER(TextInputClientReplyMsg_GotFirstRectForRange, + OnGotFirstRectForRange) + IPC_MESSAGE_HANDLER(TextInputClientReplyMsg_GotStringForRange, + OnGotStringFromRange) + IPC_MESSAGE_UNHANDLED(handled = false) + IPC_END_MESSAGE_MAP_EX() + return handled; +} + +void TextInputClientMessageFilter::OnGotCharacterIndexForPoint(size_t index) { + TextInputClientMac* service = TextInputClientMac::GetInstance(); + service->SetCharacterIndexAndSignal(index); +} + +void TextInputClientMessageFilter::OnGotFirstRectForRange( + const gfx::Rect& rect) { + TextInputClientMac* service = TextInputClientMac::GetInstance(); + service->SetFirstRectAndSignal(NSRectFromCGRect(rect.ToCGRect())); +} + +void TextInputClientMessageFilter::OnGotStringFromRange( + const mac::AttributedStringCoder::EncodedString& encoded_string) { + TextInputClientMac* service = TextInputClientMac::GetInstance(); + NSAttributedString* string = + mac::AttributedStringCoder::Decode(&encoded_string); + if (![string length]) + string = nil; + service->SetSubstringAndSignal(string); +} diff --git a/chrome/chrome_browser.gypi b/chrome/chrome_browser.gypi index bb70bd0..2613bbb 100644 --- a/chrome/chrome_browser.gypi +++ b/chrome/chrome_browser.gypi @@ -1718,6 +1718,10 @@ 'browser/renderer_host/safe_browsing_resource_handler.h', 'browser/renderer_host/save_file_resource_handler.cc', 'browser/renderer_host/save_file_resource_handler.h', + 'browser/renderer_host/text_input_client_mac.h', + 'browser/renderer_host/text_input_client_mac.mm', + 'browser/renderer_host/text_input_client_message_filter.h', + 'browser/renderer_host/text_input_client_message_filter.mm', 'browser/renderer_host/web_cache_manager.cc', 'browser/renderer_host/web_cache_manager.h', 'browser/renderer_preferences_util.cc', diff --git a/chrome/chrome_common.gypi b/chrome/chrome_common.gypi index 942d4ee..451402d6 100644 --- a/chrome/chrome_common.gypi +++ b/chrome/chrome_common.gypi @@ -28,6 +28,8 @@ 'common/app_mode_common_mac.h', 'common/app_mode_common_mac.mm', 'common/attrition_experiments.h', + 'common/attributed_string_coder_mac.h', + 'common/attributed_string_coder_mac.mm', 'common/auto_start_linux.cc', 'common/auto_start_linux.h', 'common/autofill_messages.h', @@ -228,6 +230,8 @@ 'common/spellcheck_messages.h', 'common/sqlite_utils.cc', 'common/sqlite_utils.h', + 'common/text_input_client_messages.cc', + 'common/text_input_client_messages.h', 'common/thumbnail_score.cc', 'common/thumbnail_score.h', 'common/url_constants.cc', diff --git a/chrome/chrome_renderer.gypi b/chrome/chrome_renderer.gypi index 043e487..7c43d77 100644 --- a/chrome/chrome_renderer.gypi +++ b/chrome/chrome_renderer.gypi @@ -168,6 +168,8 @@ 'renderer/spellchecker/spellcheck_worditerator.h', 'renderer/translate_helper.cc', 'renderer/translate_helper.h', + 'renderer/text_input_client_observer.cc', + 'renderer/text_input_client_observer.h', 'renderer/visitedlink_slave.cc', 'renderer/visitedlink_slave.h', ], diff --git a/chrome/chrome_tests.gypi b/chrome/chrome_tests.gypi index 6bbfd4d..31514a2 100644 --- a/chrome/chrome_tests.gypi +++ b/chrome/chrome_tests.gypi @@ -1476,6 +1476,7 @@ 'browser/remoting/directory_add_request_unittest.cc', 'browser/renderer_host/gtk_key_bindings_handler_unittest.cc', 'browser/renderer_host/render_widget_host_view_mac_unittest.mm', + 'browser/renderer_host/text_input_client_mac_unittest.mm', 'browser/renderer_host/web_cache_manager_unittest.cc', 'browser/resources_util_unittest.cc', 'browser/rlz/rlz_unittest.cc', @@ -1787,6 +1788,7 @@ 'browser/webdata/web_data_service_test_util.h', 'browser/webdata/web_data_service_unittest.cc', 'browser/webdata/web_database_migration_unittest.cc', + 'common/attributed_string_coder_mac_unittest.mm', 'common/bzip2_unittest.cc', 'common/child_process_logging_mac_unittest.mm', 'common/chrome_paths_unittest.cc', diff --git a/chrome/common/attributed_string_coder_mac.h b/chrome/common/attributed_string_coder_mac.h new file mode 100644 index 0000000..4ac02eb --- /dev/null +++ b/chrome/common/attributed_string_coder_mac.h @@ -0,0 +1,118 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CHROME_COMMON_ATTRIBUTED_STRING_CODER_MAC_H_ +#define CHROME_COMMON_ATTRIBUTED_STRING_CODER_MAC_H_ + +#include <set> + +#include "base/memory/scoped_ptr.h" +#include "base/string16.h" +#include "content/common/font_descriptor_mac.h" +#include "ipc/ipc_message_utils.h" +#include "ui/base/range/range.h" + +#if __OBJC__ +@class NSAttributedString; +@class NSDictionary; +#else +class NSAttributedString; +class NSDictionary; +#endif + +namespace mac { + +// This class will serialize the font information of an NSAttributedString so +// that it can be sent over IPC. This class only stores the information of the +// NSFontAttributeName. The motive is that of security: using NSArchiver and +// friends to send objects from the renderer to the browser could lead to +// deserialization of arbitrary objects. This class restricts serialization to +// a specific object class and specific attributes of that object. +class AttributedStringCoder { + public: + // A C++ IPC-friendly representation of the NSFontAttributeName attribute + // set. + class FontAttribute { + public: + FontAttribute(NSDictionary* ns_attributes, ui::Range effective_range); + FontAttribute(FontDescriptor font, ui::Range range); + FontAttribute(); + ~FontAttribute(); + + // Creates an autoreleased NSDictionary that can be attached to an + // NSAttributedString. + NSDictionary* ToAttributesDictionary() const; + + // Whether or not the attribute should be placed in the EncodedString. This + // can return false, e.g. if the Cocoa-based constructor can't find any + // information to encode. + bool ShouldEncode() const; + + // Accessors: + FontDescriptor font_descriptor() const { return font_descriptor_; } + ui::Range effective_range() const { return effective_range_; } + + private: + FontDescriptor font_descriptor_; + ui::Range effective_range_; + }; + + // A class that contains the pertinent information from an NSAttributedString, + // which can be serialized over IPC. + class EncodedString { + public: + explicit EncodedString(string16 string); + EncodedString(); + ~EncodedString(); + + // Accessors: + string16 string() const { return string_; } + const std::vector<FontAttribute>& attributes() const { + return attributes_; + } + std::vector<FontAttribute>* attributes() { return &attributes_; } + + private: + // The plain-text string. + string16 string_; + // The set of attributes that style |string_|. + std::vector<FontAttribute> attributes_; + }; + + // Takes an NSAttributedString, extracts the pertinent attributes, and returns + // an object that represents it. Caller owns the result. + static const EncodedString* Encode(NSAttributedString* str); + + // Returns an autoreleased NSAttributedString from an encoded representation. + static NSAttributedString* Decode(const EncodedString* str); + + private: + AttributedStringCoder(); +}; + +} // namespace mac + +// IPC ParamTraits specialization ////////////////////////////////////////////// + +namespace IPC { + +template <> +struct ParamTraits<mac::AttributedStringCoder::EncodedString> { + typedef mac::AttributedStringCoder::EncodedString param_type; + static void Write(Message* m, const param_type& p); + static bool Read(const Message* m, void** iter, param_type* r); + static void Log(const param_type& p, std::string* l); +}; + +template <> +struct ParamTraits<mac::AttributedStringCoder::FontAttribute> { + typedef mac::AttributedStringCoder::FontAttribute param_type; + static void Write(Message* m, const param_type& p); + static bool Read(const Message* m, void** iter, param_type* r); + static void Log(const param_type& p, std::string* l); +}; + +} // namespace IPC + +#endif // CHROME_COMMON_ATTRIBUTED_STRING_CODER_MAC_H_ diff --git a/chrome/common/attributed_string_coder_mac.mm b/chrome/common/attributed_string_coder_mac.mm new file mode 100644 index 0000000..793d3fb --- /dev/null +++ b/chrome/common/attributed_string_coder_mac.mm @@ -0,0 +1,171 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "chrome/common/attributed_string_coder_mac.h" + +#include <AppKit/AppKit.h> + +#include "base/logging.h" +#include "base/memory/scoped_nsobject.h" +#include "base/sys_string_conversions.h" +#include "base/utf_string_conversions.h" +#include "content/common/common_param_traits.h" +#include "content/common/view_messages.h" +#include "ipc/ipc_message_utils.h" + +namespace mac { + +// static +const AttributedStringCoder::EncodedString* AttributedStringCoder::Encode( + NSAttributedString* str) { + // Create the return value. + EncodedString* encoded_string = + new EncodedString(base::SysNSStringToUTF16([str string])); + // Iterate over all the attributes in the string. + NSUInteger length = [str length]; + for (NSUInteger i = 0; i < length; ) { + NSRange effective_range; + NSDictionary* ns_attributes = [str attributesAtIndex:i + effectiveRange:&effective_range]; + // Convert the attributes to IPC-friendly types. + FontAttribute attrs(ns_attributes, ui::Range(effective_range)); + // Only encode the attributes if the filtered set contains font information. + if (attrs.ShouldEncode()) { + encoded_string->attributes()->push_back(attrs); + } + // Advance the iterator to the position outside of the effective range. + i = NSMaxRange(effective_range); + } + return encoded_string; +} + +// static +NSAttributedString* AttributedStringCoder::Decode( + const AttributedStringCoder::EncodedString* str) { + // Create the return value. + NSString* plain_text = base::SysUTF16ToNSString(str->string()); + scoped_nsobject<NSMutableAttributedString> decoded_string( + [[NSMutableAttributedString alloc] initWithString:plain_text]); + // Iterate over all the encoded attributes, attaching each to the string. + const std::vector<FontAttribute> attributes = str->attributes(); + for (std::vector<FontAttribute>::const_iterator it = attributes.begin(); + it != attributes.end(); ++it) { + // Protect against ranges that are outside the range of the string. + const ui::Range& range = it->effective_range(); + if (range.GetMin() > [plain_text length] || + range.GetMax() > [plain_text length]) { + continue; + } + [decoded_string addAttributes:it->ToAttributesDictionary() + range:range.ToNSRange()]; + } + return [decoded_string.release() autorelease]; +} + +// Data Types ////////////////////////////////////////////////////////////////// + +AttributedStringCoder::EncodedString::EncodedString(string16 string) + : string_(string) { +} + +AttributedStringCoder::EncodedString::EncodedString() + : string_() { +} + +AttributedStringCoder::EncodedString::~EncodedString() { +} + +AttributedStringCoder::FontAttribute::FontAttribute(NSDictionary* dict, + ui::Range effective_range) + : font_descriptor_(), + effective_range_(effective_range) { + NSFont* font = [dict objectForKey:NSFontAttributeName]; + if (font) { + font_descriptor_ = FontDescriptor(font); + } +} + +AttributedStringCoder::FontAttribute::FontAttribute(FontDescriptor font, + ui::Range range) + : font_descriptor_(font), + effective_range_(range) { +} + +AttributedStringCoder::FontAttribute::FontAttribute() + : font_descriptor_(), + effective_range_() { +} + +AttributedStringCoder::FontAttribute::~FontAttribute() { +} + +NSDictionary* AttributedStringCoder::FontAttribute::ToAttributesDictionary( + void) const { + DCHECK(ShouldEncode()); + NSFont* font = font_descriptor_.ToNSFont(); + return [NSDictionary dictionaryWithObject:font forKey:NSFontAttributeName]; +} + +bool AttributedStringCoder::FontAttribute::ShouldEncode() const { + return !font_descriptor_.font_name.empty(); +} + +} // namespace mac + +// IPC ParamTraits specialization ////////////////////////////////////////////// + +namespace IPC { + +using mac::AttributedStringCoder; + +void ParamTraits<AttributedStringCoder::EncodedString>::Write( + Message* m, const param_type& p) { + WriteParam(m, p.string()); + WriteParam(m, p.attributes()); +} + +bool ParamTraits<AttributedStringCoder::EncodedString>::Read( + const Message* m, void** iter, param_type* p) { + bool success = true; + + string16 result; + success &= ReadParam(m, iter, &result); + *p = AttributedStringCoder::EncodedString(result); + + success &= ReadParam(m, iter, p->attributes()); + return success; +} + +void ParamTraits<AttributedStringCoder::EncodedString>::Log( + const param_type& p, std::string* l) { + l->append(UTF16ToUTF8(p.string())); +} + +void ParamTraits<AttributedStringCoder::FontAttribute>::Write( + Message* m, const param_type& p) { + WriteParam(m, p.font_descriptor()); + WriteParam(m, p.effective_range()); +} + +bool ParamTraits<AttributedStringCoder::FontAttribute>::Read( + const Message* m, void** iter, param_type* p) { + bool success = true; + + FontDescriptor font; + success &= ReadParam(m, iter, &font); + + ui::Range range; + success &= ReadParam(m, iter, &range); + + if (success) { + *p = AttributedStringCoder::FontAttribute(font, range); + } + return success; +} + +void ParamTraits<AttributedStringCoder::FontAttribute>::Log( + const param_type& p, std::string* l) { +} + +} // namespace IPC diff --git a/chrome/common/attributed_string_coder_mac_unittest.mm b/chrome/common/attributed_string_coder_mac_unittest.mm new file mode 100644 index 0000000..bf7ebff --- /dev/null +++ b/chrome/common/attributed_string_coder_mac_unittest.mm @@ -0,0 +1,135 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include <AppKit/AppKit.h> + +#include "base/memory/scoped_nsobject.h" +#include "base/memory/scoped_ptr.h" +#include "base/utf_string_conversions.h" +#import "chrome/common/attributed_string_coder_mac.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "testing/gtest_mac.h" + +using mac::AttributedStringCoder; + +class AttributedStringCoderTest : public testing::Test { + public: + NSMutableAttributedString* NewAttrString() { + NSString* str = @"The quick brown fox jumped over the lazy dog."; + return [[NSMutableAttributedString alloc] initWithString:str]; + } + + NSDictionary* FontAttribute(NSString* name, CGFloat size) { + NSFont* font = [NSFont fontWithName:name size:size]; + return [NSDictionary dictionaryWithObject:font forKey:NSFontAttributeName]; + } + + NSAttributedString* EncodeAndDecode(NSAttributedString* str) { + scoped_ptr<const AttributedStringCoder::EncodedString> encoded_str( + AttributedStringCoder::Encode(str)); + return AttributedStringCoder::Decode(encoded_str.get()); + } +}; + +TEST_F(AttributedStringCoderTest, SimpleString) { + scoped_nsobject<NSMutableAttributedString> attr_str(NewAttrString()); + [attr_str addAttributes:FontAttribute(@"Helvetica", 12.5) + range:NSMakeRange(0, [attr_str length])]; + + NSAttributedString* decoded = EncodeAndDecode(attr_str.get()); + EXPECT_NSEQ(attr_str.get(), decoded); +} + +TEST_F(AttributedStringCoderTest, NoAttributes) { + scoped_nsobject<NSAttributedString> attr_str(NewAttrString()); + NSAttributedString* decoded = EncodeAndDecode(attr_str.get()); + EXPECT_NSEQ(attr_str.get(), decoded); +} + +TEST_F(AttributedStringCoderTest, StripColor) { + scoped_nsobject<NSMutableAttributedString> attr_str(NewAttrString()); + const NSUInteger kStringLength = [attr_str length]; + [attr_str addAttribute:NSFontAttributeName + value:[NSFont systemFontOfSize:26] + range:NSMakeRange(0, kStringLength)]; + [attr_str addAttribute:NSForegroundColorAttributeName + value:[NSColor redColor] + range:NSMakeRange(0, kStringLength)]; + + NSAttributedString* decoded = EncodeAndDecode(attr_str.get()); + + NSRange range; + NSDictionary* attrs = [decoded attributesAtIndex:0 effectiveRange:&range]; + EXPECT_TRUE(NSEqualRanges(NSMakeRange(0, kStringLength), range)); + EXPECT_NSEQ([NSFont systemFontOfSize:26], + [attrs objectForKey:NSFontAttributeName]); + EXPECT_FALSE([attrs objectForKey:NSForegroundColorAttributeName]); +} + +TEST_F(AttributedStringCoderTest, MultipleFonts) { + scoped_nsobject<NSMutableAttributedString> attr_str(NewAttrString()); + [attr_str setAttributes:FontAttribute(@"Courier", 12) + range:NSMakeRange(0, 10)]; + [attr_str addAttributes:FontAttribute(@"Helvetica", 16) + range:NSMakeRange(12, 6)]; + [attr_str addAttributes:FontAttribute(@"Helvetica", 14) + range:NSMakeRange(15, 5)]; + + NSAttributedString* decoded = EncodeAndDecode(attr_str); + + EXPECT_NSEQ(attr_str.get(), decoded); +} + +TEST_F(AttributedStringCoderTest, NoPertinentAttributes) { + scoped_nsobject<NSMutableAttributedString> attr_str(NewAttrString()); + [attr_str addAttribute:NSForegroundColorAttributeName + value:[NSColor blueColor] + range:NSMakeRange(0, 10)]; + [attr_str addAttribute:NSBackgroundColorAttributeName + value:[NSColor blueColor] + range:NSMakeRange(15, 5)]; + [attr_str addAttribute:NSKernAttributeName + value:[NSNumber numberWithFloat:2.6] + range:NSMakeRange(11, 3)]; + + NSAttributedString* decoded = EncodeAndDecode(attr_str.get()); + + scoped_nsobject<NSAttributedString> expected(NewAttrString()); + EXPECT_NSEQ(expected.get(), decoded); +} + +TEST_F(AttributedStringCoderTest, NilString) { + NSAttributedString* decoded = EncodeAndDecode(nil); + EXPECT_TRUE(decoded); + EXPECT_EQ(0U, [decoded length]); +} + +TEST_F(AttributedStringCoderTest, OutOfRange) { + AttributedStringCoder::EncodedString encoded(ASCIIToUTF16("Hello World")); + encoded.attributes()->push_back( + AttributedStringCoder::FontAttribute( + FontDescriptor([NSFont systemFontOfSize:12]), + ui::Range(0, 5))); + encoded.attributes()->push_back( + AttributedStringCoder::FontAttribute( + FontDescriptor([NSFont systemFontOfSize:14]), + ui::Range(5, 100))); + encoded.attributes()->push_back( + AttributedStringCoder::FontAttribute( + FontDescriptor([NSFont systemFontOfSize:16]), + ui::Range(100, 5))); + + NSAttributedString* decoded = AttributedStringCoder::Decode(&encoded); + EXPECT_TRUE(decoded); + + NSRange range; + NSDictionary* attrs = [decoded attributesAtIndex:0 effectiveRange:&range]; + EXPECT_NSEQ([NSFont systemFontOfSize:12], + [attrs objectForKey:NSFontAttributeName]); + EXPECT_TRUE(NSEqualRanges(range, NSMakeRange(0, 5))); + + attrs = [decoded attributesAtIndex:5 effectiveRange:&range]; + EXPECT_FALSE([attrs objectForKey:NSFontAttributeName]); + EXPECT_EQ(0U, [attrs count]); +} diff --git a/chrome/common/text_input_client_messages.cc b/chrome/common/text_input_client_messages.cc new file mode 100644 index 0000000..1a85234 --- /dev/null +++ b/chrome/common/text_input_client_messages.cc @@ -0,0 +1,8 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "content/common/common_param_traits.h" + +#define IPC_MESSAGE_IMPL +#include "chrome/common/text_input_client_messages.h" diff --git a/chrome/common/text_input_client_messages.h b/chrome/common/text_input_client_messages.h new file mode 100644 index 0000000..a840c2c --- /dev/null +++ b/chrome/common/text_input_client_messages.h @@ -0,0 +1,55 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CHROME_COMMON_TEXT_INPUT_CLIENT_MESSAGES_H_ +#define CHROME_COMMON_TEXT_INPUT_CLIENT_MESSAGES_H_ + +#include "ipc/ipc_message_macros.h" +#include "ui/base/range/range.h" +#include "ui/gfx/rect.h" + +#if defined(OS_MACOSX) +#include "chrome/common/attributed_string_coder_mac.h" +#endif + +#define IPC_MESSAGE_START TextInputClientMsgStart + +// Browser -> Renderer Messages //////////////////////////////////////////////// +// These messages are sent from the browser to the renderer. Each one has a +// corresponding reply message. +//////////////////////////////////////////////////////////////////////////////// + +// Tells the renderer to send back the character index for a point. +IPC_MESSAGE_ROUTED1(TextInputClientMsg_CharacterIndexForPoint, + gfx::Point) + +// Tells the renderer to send back the rectangle for a given character range. +IPC_MESSAGE_ROUTED1(TextInputClientMsg_FirstRectForCharacterRange, + ui::Range) + +// Tells the renderer to send back the text fragment in a given range. +IPC_MESSAGE_ROUTED1(TextInputClientMsg_StringForRange, + ui::Range) + +//////////////////////////////////////////////////////////////////////////////// + +// Renderer -> Browser Replies ///////////////////////////////////////////////// +// These messages are sent in reply to the above messages. +//////////////////////////////////////////////////////////////////////////////// + +// Reply message for TextInputClientMsg_CharacterIndexForPoint. +IPC_MESSAGE_ROUTED1(TextInputClientReplyMsg_GotCharacterIndexForPoint, + size_t /* character index */) + +// Reply message for TextInputClientMsg_FirstRectForCharacterRange. +IPC_MESSAGE_ROUTED1(TextInputClientReplyMsg_GotFirstRectForRange, + gfx::Rect /* frame rectangle */) + +#if defined(OS_MACOSX) +// Reply message for TextInputClientMsg_StringForRange. +IPC_MESSAGE_ROUTED1(TextInputClientReplyMsg_GotStringForRange, + mac::AttributedStringCoder::EncodedString) +#endif // defined(OS_MACOSX) + +#endif // CHROME_COMMON_TEXT_INPUT_CLIENT_MESSAGES_H_ diff --git a/chrome/renderer/chrome_content_renderer_client.cc b/chrome/renderer/chrome_content_renderer_client.cc index abedf12..c786c45 100644 --- a/chrome/renderer/chrome_content_renderer_client.cc +++ b/chrome/renderer/chrome_content_renderer_client.cc @@ -55,6 +55,7 @@ #include "chrome/renderer/searchbox_extension.h" #include "chrome/renderer/spellchecker/spellcheck.h" #include "chrome/renderer/spellchecker/spellcheck_provider.h" +#include "chrome/renderer/text_input_client_observer.h" #include "chrome/renderer/translate_helper.h" #include "chrome/renderer/visitedlink_slave.h" #include "content/common/view_messages.h" @@ -194,6 +195,10 @@ void ChromeContentRendererClient::RenderViewCreated(RenderView* render_view) { new SpellCheckProvider(render_view, spellcheck_.get()); new safe_browsing::MalwareDOMDetails(render_view); +#if defined(OS_MACOSX) + new TextInputClientObserver(render_view); +#endif // defined(OS_MACOSX) + PasswordAutofillManager* password_autofill_manager = new PasswordAutofillManager(render_view); AutofillAgent* autofill_agent = new AutofillAgent(render_view, diff --git a/chrome/renderer/text_input_client_observer.cc b/chrome/renderer/text_input_client_observer.cc new file mode 100644 index 0000000..2102267 --- /dev/null +++ b/chrome/renderer/text_input_client_observer.cc @@ -0,0 +1,70 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "chrome/renderer/text_input_client_observer.h" + +#include "base/memory/scoped_ptr.h" +#include "chrome/common/text_input_client_messages.h" +#include "content/renderer/render_view.h" +#include "ipc/ipc_message_macros.h" +#include "third_party/WebKit/Source/WebKit/chromium/public/mac/WebSubstringUtil.h" +#include "third_party/WebKit/Source/WebKit/chromium/public/WebFrame.h" +#include "third_party/WebKit/Source/WebKit/chromium/public/WebPoint.h" +#include "third_party/WebKit/Source/WebKit/chromium/public/WebRect.h" +#include "third_party/WebKit/Source/WebKit/chromium/public/WebString.h" +#include "third_party/WebKit/Source/WebKit/chromium/public/WebView.h" +#include "ui/gfx/rect.h" + +TextInputClientObserver::TextInputClientObserver(RenderView* render_view) + : RenderViewObserver(render_view) { +} + +TextInputClientObserver::~TextInputClientObserver() { +} + +bool TextInputClientObserver::OnMessageReceived(const IPC::Message& message) { + bool handled = true; + IPC_BEGIN_MESSAGE_MAP(TextInputClientObserver, message) + IPC_MESSAGE_HANDLER(TextInputClientMsg_CharacterIndexForPoint, + OnCharacterIndexForPoint) + IPC_MESSAGE_HANDLER(TextInputClientMsg_FirstRectForCharacterRange, + OnFirstRectForCharacterRange) + IPC_MESSAGE_HANDLER(TextInputClientMsg_StringForRange, OnStringForRange) + IPC_MESSAGE_UNHANDLED(handled = false) + IPC_END_MESSAGE_MAP() + return handled; +} + +WebKit::WebView* TextInputClientObserver::webview() { + return render_view()->webview(); +} + +void TextInputClientObserver::OnCharacterIndexForPoint(gfx::Point point) { + WebKit::WebPoint web_point(point); + size_t index = webview()->focusedFrame()->characterIndexForPoint(web_point); + Send(new TextInputClientReplyMsg_GotCharacterIndexForPoint(routing_id(), + index)); +} + +void TextInputClientObserver::OnFirstRectForCharacterRange(ui::Range range) { + WebKit::WebFrame* frame = webview()->focusedFrame(); + WebKit::WebRect web_rect; + frame->firstRectForCharacterRange(range.start(), range.length(), web_rect); + gfx::Rect rect(web_rect); + Send(new TextInputClientReplyMsg_GotFirstRectForRange(routing_id(), rect)); +} + +void TextInputClientObserver::OnStringForRange(ui::Range range) { +#if defined(OS_MACOSX) + NSAttributedString* string = + WebKit::WebSubstringUtil::attributedSubstringInRange( + webview()->focusedFrame(), range.start(), range.length()); + scoped_ptr<const mac::AttributedStringCoder::EncodedString> encoded( + mac::AttributedStringCoder::Encode(string)); + Send(new TextInputClientReplyMsg_GotStringForRange(routing_id(), + *encoded.get())); +#else + NOTIMPLEMENTED(); +#endif +} diff --git a/chrome/renderer/text_input_client_observer.h b/chrome/renderer/text_input_client_observer.h new file mode 100644 index 0000000..bbd265a --- /dev/null +++ b/chrome/renderer/text_input_client_observer.h @@ -0,0 +1,41 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CHROME_RENDERER_TEXT_INPUT_CLIENT_OBSERVER_H_ +#define CHROME_RENDERER_TEXT_INPUT_CLIENT_OBSERVER_H_ + +#include "base/basictypes.h" +#include "build/build_config.h" +#include "content/renderer/render_view_observer.h" +#include "ui/base/range/range.h" +#include "ui/gfx/point.h" + +namespace WebKit { +class WebView; +} + +// This is the renderer-side message filter that generates the replies for the +// messages sent by the TextInputClientMac. See +// chrome/browser/renderer_host/text_input_client_mac.h for more information. +class TextInputClientObserver : public RenderViewObserver { + public: + explicit TextInputClientObserver(RenderView* render_view); + virtual ~TextInputClientObserver(); + + // RenderViewObserver overrides: + virtual bool OnMessageReceived(const IPC::Message& message); + + private: + // Returns the WebView of the RenderView. + WebKit::WebView* webview(); + + // IPC Message handlers: + void OnCharacterIndexForPoint(gfx::Point point); + void OnFirstRectForCharacterRange(ui::Range range); + void OnStringForRange(ui::Range range); + + DISALLOW_COPY_AND_ASSIGN(TextInputClientObserver); +}; + +#endif // CHROME_RENDERER_TEXT_INPUT_CLIENT_OBSERVER_H_ diff --git a/content/browser/renderer_host/render_message_filter.cc b/content/browser/renderer_host/render_message_filter.cc index 16a20be..1cc7ec9 100644 --- a/content/browser/renderer_host/render_message_filter.cc +++ b/content/browser/renderer_host/render_message_filter.cc @@ -558,7 +558,7 @@ void RenderMessageFilter::OnLoadFont(const FontDescriptor& font, base::SharedMemoryHandle* handle) { base::SharedMemory font_data; uint32 font_data_size = 0; - bool ok = FontLoader::LoadFontIntoBuffer(font.nsFont(), &font_data, + bool ok = FontLoader::LoadFontIntoBuffer(font.ToNSFont(), &font_data, &font_data_size); if (!ok || font_data_size == 0) { LOG(ERROR) << "Couldn't load font data for " << font.font_name << diff --git a/content/browser/renderer_host/render_view_host.cc b/content/browser/renderer_host/render_view_host.cc index 98bcbf8..da09850 100644 --- a/content/browser/renderer_host/render_view_host.cc +++ b/content/browser/renderer_host/render_view_host.cc @@ -1080,9 +1080,10 @@ void RenderViewHost::OnMsgSetTooltipText( view()->SetTooltipText(UTF16ToWide(wrapped_tooltip_text)); } -void RenderViewHost::OnMsgSelectionChanged(const std::string& text) { +void RenderViewHost::OnMsgSelectionChanged(const std::string& text, + const ui::Range& range) { if (view()) - view()->SelectionChanged(text); + view()->SelectionChanged(text, range); } void RenderViewHost::OnMsgRunJavaScriptMessage( diff --git a/content/browser/renderer_host/render_view_host.h b/content/browser/renderer_host/render_view_host.h index 14ecddd..2936218 100644 --- a/content/browser/renderer_host/render_view_host.h +++ b/content/browser/renderer_host/render_view_host.h @@ -52,6 +52,10 @@ namespace gfx { class Point; } // namespace gfx +namespace ui { +class Range; +} // namespace ui + namespace webkit_glue { struct CustomContextMenuContext; struct WebAccessibility; @@ -496,7 +500,7 @@ class RenderViewHost : public RenderWidgetHost { const std::string& target); void OnMsgSetTooltipText(const std::wstring& tooltip_text, WebKit::WebTextDirection text_direction_hint); - void OnMsgSelectionChanged(const std::string& text); + void OnMsgSelectionChanged(const std::string& text, const ui::Range& range); void OnMsgPasteFromSelectionClipboard(); void OnMsgRunJavaScriptMessage(const std::wstring& message, const std::wstring& default_prompt, diff --git a/content/browser/renderer_host/render_widget_host.cc b/content/browser/renderer_host/render_widget_host.cc index 77db54b..339b310 100644 --- a/content/browser/renderer_host/render_widget_host.cc +++ b/content/browser/renderer_host/render_widget_host.cc @@ -174,6 +174,8 @@ bool RenderWidgetHost::OnMessageReceived(const IPC::Message &msg) { IPC_MESSAGE_HANDLER(ViewHostMsg_SetCursor, OnMsgSetCursor) IPC_MESSAGE_HANDLER(ViewHostMsg_ImeUpdateTextInputState, OnMsgImeUpdateTextInputState) + IPC_MESSAGE_HANDLER(ViewHostMsg_ImeCompositionRangeChanged, + OnMsgImeCompositionRangeChanged) IPC_MESSAGE_HANDLER(ViewHostMsg_ImeCancelComposition, OnMsgImeCancelComposition) IPC_MESSAGE_HANDLER(ViewHostMsg_DidActivateAcceleratedCompositing, @@ -1029,6 +1031,11 @@ void RenderWidgetHost::OnMsgImeUpdateTextInputState( view_->ImeUpdateTextInputState(type, caret_rect); } +void RenderWidgetHost::OnMsgImeCompositionRangeChanged(const ui::Range& range) { + if (view_) + view_->ImeCompositionRangeChanged(range); +} + void RenderWidgetHost::OnMsgImeCancelComposition() { if (view_) view_->ImeCancelComposition(); diff --git a/content/browser/renderer_host/render_widget_host.h b/content/browser/renderer_host/render_widget_host.h index d110436..13aa909 100644 --- a/content/browser/renderer_host/render_widget_host.h +++ b/content/browser/renderer_host/render_widget_host.h @@ -31,6 +31,10 @@ namespace gfx { class Rect; } +namespace ui { +class Range; +} + namespace WebKit { class WebInputEvent; class WebMouseEvent; @@ -475,6 +479,7 @@ class RenderWidgetHost : public IPC::Channel::Listener, void OnMsgSetCursor(const WebCursor& cursor); void OnMsgImeUpdateTextInputState(WebKit::WebTextInputType type, const gfx::Rect& caret_rect); + void OnMsgImeCompositionRangeChanged(const ui::Range& range); void OnMsgImeCancelComposition(); void OnMsgDidActivateAcceleratedCompositing(bool activated); diff --git a/content/browser/renderer_host/render_widget_host_view.h b/content/browser/renderer_host/render_widget_host_view.h index 76c1db4..f443d1c 100644 --- a/content/browser/renderer_host/render_widget_host_view.h +++ b/content/browser/renderer_host/render_widget_host_view.h @@ -18,6 +18,7 @@ #include "third_party/skia/include/core/SkColor.h" #include "third_party/WebKit/Source/WebKit/chromium/public/WebPopupType.h" #include "third_party/WebKit/Source/WebKit/chromium/public/WebTextInputType.h" +#include "ui/base/range/range.h" #include "ui/gfx/native_widget_types.h" #include "ui/gfx/rect.h" #include "ui/gfx/surface/transport_dib.h" @@ -139,6 +140,9 @@ class RenderWidgetHostView { // Cancel the ongoing composition of the input method attached to the view. virtual void ImeCancelComposition() = 0; + // Updates the range of the marked text in an IME composition. + virtual void ImeCompositionRangeChanged(const ui::Range& range) {} + // Informs the view that a portion of the widget's backing store was scrolled // and/or painted. The view should ensure this gets copied to the screen. // @@ -176,7 +180,8 @@ class RenderWidgetHostView { virtual void SetTooltipText(const std::wstring& tooltip_text) = 0; // Notifies the View that the renderer text selection has changed. - virtual void SelectionChanged(const std::string& text) {} + virtual void SelectionChanged(const std::string& text, + const ui::Range& range) {} // Tells the View whether the context menu is showing. This is used on Linux // to suppress updates to webkit focus for the duration of the show. diff --git a/content/common/common_param_traits.cc b/content/common/common_param_traits.cc index 1e0ffd0..5e31003 100644 --- a/content/common/common_param_traits.cc +++ b/content/common/common_param_traits.cc @@ -9,6 +9,7 @@ #include "net/base/upload_data.h" #include "net/http/http_response_headers.h" #include "third_party/skia/include/core/SkBitmap.h" +#include "ui/base/range/range.h" #include "ui/gfx/rect.h" namespace { @@ -501,6 +502,24 @@ void ParamTraits<gfx::Rect>::Log(const gfx::Rect& p, std::string* l) { p.width(), p.height())); } +void ParamTraits<ui::Range>::Write(Message* m, const ui::Range& r) { + m->WriteSize(r.start()); + m->WriteSize(r.end()); +} + +bool ParamTraits<ui::Range>::Read(const Message* m, void** iter, ui::Range* r) { + size_t start, end; + if (!m->ReadSize(iter, &start) || !m->ReadSize(iter, &end)) + return false; + r->set_start(start); + r->set_end(end); + return true; +} + +void ParamTraits<ui::Range>::Log(const ui::Range& r, std::string* l) { + l->append(base::StringPrintf("(%"PRIuS", %"PRIuS")", r.start(), r.end())); +} + void ParamTraits<SkBitmap>::Write(Message* m, const SkBitmap& p) { size_t fixed_size = sizeof(SkBitmap_Data); SkBitmap_Data bmp_data; diff --git a/content/common/common_param_traits.h b/content/common/common_param_traits.h index ae102cb..41e0326 100644 --- a/content/common/common_param_traits.h +++ b/content/common/common_param_traits.h @@ -38,6 +38,10 @@ class HostPortPair; class UploadData; } +namespace ui { +class Range; +} + namespace IPC { template <> @@ -165,6 +169,14 @@ struct ParamTraits<gfx::NativeWindow> { } }; +template <> +struct ParamTraits<ui::Range> { + typedef ui::Range param_type; + static void Write(Message* m, const param_type& p); + static bool Read(const Message* m, void** iter, param_type* r); + static void Log(const param_type& p, std::string* l); +}; + #if defined(OS_WIN) template<> struct ParamTraits<TransportDIB::Id> { diff --git a/content/common/font_descriptor_mac.h b/content/common/font_descriptor_mac.h index 91bb338..c3ac5a8 100644 --- a/content/common/font_descriptor_mac.h +++ b/content/common/font_descriptor_mac.h @@ -17,11 +17,12 @@ class NSFont; // Container to allow serializing an NSFont over IPC. struct FontDescriptor { explicit FontDescriptor(NSFont* font); + FontDescriptor(string16 name, float size); FontDescriptor() : font_point_size(0) {} // Return an autoreleased NSFont corresponding to the font description. - NSFont* nsFont() const; + NSFont* ToNSFont() const; // Name of the font. string16 font_name; diff --git a/content/common/font_descriptor_mac.mm b/content/common/font_descriptor_mac.mm index 90c7986..c8e3112 100644 --- a/content/common/font_descriptor_mac.mm +++ b/content/common/font_descriptor_mac.mm @@ -13,7 +13,12 @@ FontDescriptor::FontDescriptor(NSFont* font) { font_point_size = [font pointSize]; } -NSFont* FontDescriptor::nsFont() const { +FontDescriptor::FontDescriptor(string16 name, float size) { + font_name = name; + font_point_size = size; +} + +NSFont* FontDescriptor::ToNSFont() const { NSString* font_name_ns = base::SysUTF16ToNSString(font_name); NSFont* font = [NSFont fontWithName:font_name_ns size:font_point_size]; return font; diff --git a/content/common/font_descriptor_mac_unittest.mm b/content/common/font_descriptor_mac_unittest.mm index d4a74d1..f8fdfc8 100644 --- a/content/common/font_descriptor_mac_unittest.mm +++ b/content/common/font_descriptor_mac_unittest.mm @@ -72,7 +72,7 @@ NSFont* MakeNSFont(const std::string& font_name, float font_point_size) { FontDescriptor desc; desc.font_name = UTF8ToUTF16(font_name); desc.font_point_size = font_point_size; - return desc.nsFont(); + return desc.ToNSFont(); } // Verify that serialization and deserialization of fonts with various styles @@ -81,12 +81,12 @@ TEST_F(FontSerializationTest, StyledFonts) { NSFont* plain_font = [NSFont systemFontOfSize:12.0]; ASSERT_TRUE(plain_font != nil); FontDescriptor desc_plain(plain_font); - EXPECT_TRUE(CompareFonts(plain_font, desc_plain.nsFont())); + EXPECT_TRUE(CompareFonts(plain_font, desc_plain.ToNSFont())); NSFont* bold_font = [NSFont boldSystemFontOfSize:30.0]; ASSERT_TRUE(bold_font != nil); FontDescriptor desc_bold(bold_font); - EXPECT_TRUE(CompareFonts(bold_font, desc_bold.nsFont())); + EXPECT_TRUE(CompareFonts(bold_font, desc_bold.ToNSFont())); NSFont* italic_bold_font = [[NSFontManager sharedFontManager] @@ -96,7 +96,7 @@ TEST_F(FontSerializationTest, StyledFonts) { size:18.0]; ASSERT_TRUE(italic_bold_font != nil); FontDescriptor desc_italic_bold(italic_bold_font); - EXPECT_TRUE(CompareFonts(italic_bold_font, desc_italic_bold.nsFont())); + EXPECT_TRUE(CompareFonts(italic_bold_font, desc_italic_bold.ToNSFont())); } // Test that FontDescriptor doesn't crash when used with bad parameters. diff --git a/content/common/view_messages.h b/content/common/view_messages.h index 55a118c..ea324b7 100644 --- a/content/common/view_messages.h +++ b/content/common/view_messages.h @@ -25,6 +25,7 @@ #include "third_party/WebKit/Source/WebKit/chromium/public/WebPopupType.h" #include "third_party/WebKit/Source/WebKit/chromium/public/WebScreenInfo.h" #include "third_party/WebKit/Source/WebKit/chromium/public/WebTextInputType.h" +#include "ui/base/range/range.h" #include "ui/gfx/rect.h" #include "webkit/glue/context_menu.h" #include "webkit/glue/password_form.h" @@ -1676,8 +1677,9 @@ IPC_MESSAGE_ROUTED2(ViewHostMsg_SetTooltipText, WebKit::WebTextDirection /* text direction hint */) // Notification that the text selection has changed. -IPC_MESSAGE_ROUTED1(ViewHostMsg_SelectionChanged, - std::string /* currently selected text */) +IPC_MESSAGE_ROUTED2(ViewHostMsg_SelectionChanged, + std::string /* currently selected text */, + ui::Range /* selection range */) // Asks the browser to display the file chooser. The result is returned in a // ViewHost_RunFileChooserResponse message. @@ -1709,6 +1711,11 @@ IPC_MESSAGE_ROUTED2(ViewHostMsg_ImeUpdateTextInputState, WebKit::WebTextInputType, /* text_input_type */ gfx::Rect /* caret_rect */) + +// Message sent when the IME text composition range changes. +IPC_MESSAGE_ROUTED1(ViewHostMsg_ImeCompositionRangeChanged, + ui::Range /* composition range */) + // Required for cancelling an ongoing input method composition. IPC_MESSAGE_ROUTED0(ViewHostMsg_ImeCancelComposition) diff --git a/content/content_common.gypi b/content/content_common.gypi index 998641c..24ef258 100644 --- a/content/content_common.gypi +++ b/content/content_common.gypi @@ -15,6 +15,7 @@ '../third_party/npapi/npapi.gyp:npapi', '../third_party/WebKit/Source/WebKit/chromium/WebKit.gyp:webkit', '../ui/gfx/gl/gl.gyp:gl', + '../ui/ui.gyp:ui_base', '../webkit/support/webkit_support.gyp:appcache', '../webkit/support/webkit_support.gyp:blob', '../webkit/support/webkit_support.gyp:database', diff --git a/content/renderer/render_view.cc b/content/renderer/render_view.cc index 19bbaac..eafbd1d 100644 --- a/content/renderer/render_view.cc +++ b/content/renderer/render_view.cc @@ -1423,25 +1423,27 @@ void RenderView::didChangeSelection(bool is_empty_selection) { #if defined(OS_POSIX) if (!handling_input_event_) return; - // TODO(estade): investigate incremental updates to the selection so that we - // don't send the entire selection over IPC every time. - if (!is_empty_selection) { + + if (is_empty_selection) { + last_selection_.clear(); + } else { // Sometimes we get repeated didChangeSelection calls from webkit when // the selection hasn't actually changed. We don't want to report these // because it will cause us to continually claim the X clipboard. - const std::string& this_selection = - webview()->focusedFrame()->selectionAsText().utf8(); + WebFrame* frame = webview()->focusedFrame(); + const std::string& this_selection = frame->selectionAsText().utf8(); if (this_selection == last_selection_) return; - - Send(new ViewHostMsg_SelectionChanged(routing_id_, - this_selection)); last_selection_ = this_selection; - } else { - last_selection_.clear(); - Send(new ViewHostMsg_SelectionChanged(routing_id_, - last_selection_)); } + + ui::Range range(ui::Range::InvalidRange()); + size_t location, length; + if (webview()->caretOrSelectionRange(&location, &length)) { + range.set_start(location); + range.set_end(location + length); + } + Send(new ViewHostMsg_SelectionChanged(routing_id_, last_selection_, range)); #endif // defined(OS_POSIX) } diff --git a/content/renderer/render_widget.cc b/content/renderer/render_widget.cc index 26f3f99..6b190b3 100644 --- a/content/renderer/render_widget.cc +++ b/content/renderer/render_widget.cc @@ -22,6 +22,7 @@ #include "third_party/WebKit/Source/WebKit/chromium/public/WebCursorInfo.h" #include "third_party/WebKit/Source/WebKit/chromium/public/WebPopupMenu.h" #include "third_party/WebKit/Source/WebKit/chromium/public/WebPopupMenuInfo.h" +#include "third_party/WebKit/Source/WebKit/chromium/public/WebRange.h" #include "third_party/WebKit/Source/WebKit/chromium/public/WebRect.h" #include "third_party/WebKit/Source/WebKit/chromium/public/WebScreenInfo.h" #include "third_party/WebKit/Source/WebKit/chromium/public/WebSize.h" @@ -48,6 +49,7 @@ using WebKit::WebNavigationPolicy; using WebKit::WebPopupMenu; using WebKit::WebPopupMenuInfo; using WebKit::WebPopupType; +using WebKit::WebRange; using WebKit::WebRect; using WebKit::WebScreenInfo; using WebKit::WebSize; @@ -886,19 +888,51 @@ void RenderWidget::OnImeSetComposition( int selection_start, int selection_end) { if (!webwidget_) return; - if (!webwidget_->setComposition( + if (webwidget_->setComposition( text, WebVector<WebCompositionUnderline>(underlines), selection_start, selection_end)) { + // Setting the IME composition was successful. Send the new composition + // range to the browser. + ui::Range range(ui::Range::InvalidRange()); + size_t location, length; + if (webwidget_->compositionRange(&location, &length)) { + range.set_start(location); + range.set_end(location + length); + } + // The IME was cancelled via the Esc key, so just send back the caret. + else if (webwidget_->caretOrSelectionRange(&location, &length)) { + range.set_start(location); + range.set_end(location + length); + } + Send(new ViewHostMsg_ImeCompositionRangeChanged(routing_id(), range)); + } else { // If we failed to set the composition text, then we need to let the browser // process to cancel the input method's ongoing composition session, to make // sure we are in a consistent state. Send(new ViewHostMsg_ImeCancelComposition(routing_id())); + + // Send an updated IME range with just the caret range. + ui::Range range(ui::Range::InvalidRange()); + size_t location, length; + if (webwidget_->caretOrSelectionRange(&location, &length)) { + range.set_start(location); + range.set_end(location + length); + } + Send(new ViewHostMsg_ImeCompositionRangeChanged(routing_id(), range)); } } void RenderWidget::OnImeConfirmComposition(const string16& text) { if (webwidget_) webwidget_->confirmComposition(text); + // Send an updated IME range with just the caret range. + ui::Range range(ui::Range::InvalidRange()); + size_t location, length; + if (webwidget_->caretOrSelectionRange(&location, &length)) { + range.set_start(location); + range.set_end(location + length); + } + Send(new ViewHostMsg_ImeCompositionRangeChanged(routing_id(), range)); } // This message causes the renderer to render an image of the @@ -1090,6 +1124,15 @@ void RenderWidget::resetInputMethod() { if (webwidget_->confirmComposition()) Send(new ViewHostMsg_ImeCancelComposition(routing_id())); } + + // Send an updated IME range with the current caret rect. + ui::Range range(ui::Range::InvalidRange()); + size_t location, length; + if (webwidget_->caretOrSelectionRange(&location, &length)) { + range.set_start(location); + range.set_end(location + length); + } + Send(new ViewHostMsg_ImeCompositionRangeChanged(routing_id(), range)); } void RenderWidget::SchedulePluginMove( diff --git a/content/renderer/render_widget_fullscreen_pepper.cc b/content/renderer/render_widget_fullscreen_pepper.cc index d2ab734..ae8aeb7 100644 --- a/content/renderer/render_widget_fullscreen_pepper.cc +++ b/content/renderer/render_widget_fullscreen_pepper.cc @@ -161,10 +161,6 @@ class PepperWidget : public WebWidget { return false; } - virtual bool selectionRange(size_t *location, size_t *length) { - return false; - } - virtual bool caretOrSelectionRange(size_t* location, size_t* length) { return false; } diff --git a/ipc/ipc_message_utils.h b/ipc/ipc_message_utils.h index c353e95..1aff129 100644 --- a/ipc/ipc_message_utils.h +++ b/ipc/ipc_message_utils.h @@ -89,6 +89,7 @@ enum IPCMessageStart { VideoCaptureMsgStart, QuotaMsgStart, IconMsgStart, + TextInputClientMsgStart, LastIPCMsgStart // Must come last. }; |