1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #import "content/browser/renderer_host/text_input_client_mac.h"
8 #include "base/message_loop/message_loop.h"
9 #include "base/threading/thread.h"
10 #include "content/browser/renderer_host/render_process_host_impl.h"
11 #include "content/browser/renderer_host/render_widget_host_delegate.h"
12 #include "content/browser/renderer_host/render_widget_host_impl.h"
13 #include "content/browser/renderer_host/text_input_client_message_filter.h"
14 #include "content/common/text_input_client_messages.h"
15 #include "content/public/test/mock_render_process_host.h"
16 #include "content/public/test/test_browser_context.h"
17 #include "ipc/ipc_test_sink.h"
18 #include "testing/gtest/include/gtest/gtest.h"
19 #include "testing/gtest_mac.h"
24 const int64 kTaskDelayMs = 200;
26 class MockRenderWidgetHostDelegate : public RenderWidgetHostDelegate {
28 MockRenderWidgetHostDelegate() {}
29 virtual ~MockRenderWidgetHostDelegate() {}
32 // This test does not test the WebKit side of the dictionary system (which
33 // performs the actual data fetching), but rather this just tests that the
34 // service's signaling system works.
35 class TextInputClientMacTest : public testing::Test {
37 TextInputClientMacTest()
42 process_factory_.CreateRenderProcessHost(
43 &browser_context_, NULL),
44 MSG_ROUTING_NONE, false),
45 thread_("TextInputClientMacTestThread") {}
47 // Accessor for the TextInputClientMac instance.
48 TextInputClientMac* service() {
49 return TextInputClientMac::GetInstance();
52 // Helper method to post a task on the testing thread's MessageLoop after
54 void PostTask(const tracked_objects::Location& from_here,
55 const base::Closure& task) {
56 PostTask(from_here, task, base::TimeDelta::FromMilliseconds(kTaskDelayMs));
59 void PostTask(const tracked_objects::Location& from_here,
60 const base::Closure& task,
61 const base::TimeDelta delay) {
62 thread_.message_loop()->PostDelayedTask(from_here, task, delay);
65 RenderWidgetHostImpl* widget() {
69 IPC::TestSink& ipc_sink() {
70 return static_cast<MockRenderProcessHost*>(widget()->GetProcess())->sink();
74 friend class ScopedTestingThread;
76 base::MessageLoopForUI message_loop_;
77 TestBrowserContext browser_context_;
79 // Gets deleted when the last RWH in the "process" gets destroyed.
80 MockRenderProcessHostFactory process_factory_;
81 MockRenderWidgetHostDelegate delegate_;
82 RenderWidgetHostImpl widget_;
87 ////////////////////////////////////////////////////////////////////////////////
89 // Helper class that Start()s and Stop()s a thread according to the scope of the
91 class ScopedTestingThread {
93 ScopedTestingThread(TextInputClientMacTest* test) : thread_(test->thread_) {
96 ~ScopedTestingThread() {
101 base::Thread& thread_;
104 // Adapter for OnMessageReceived to ignore return type so it can be posted
106 void CallOnMessageReceived(scoped_refptr<TextInputClientMessageFilter> filter,
107 const IPC::Message& message,
108 bool* message_was_ok) {
109 filter->OnMessageReceived(message, message_was_ok);
114 // Test Cases //////////////////////////////////////////////////////////////////
116 TEST_F(TextInputClientMacTest, GetCharacterIndex) {
117 ScopedTestingThread thread(this);
118 const NSUInteger kSuccessValue = 42;
121 base::Bind(&TextInputClientMac::SetCharacterIndexAndSignal,
122 base::Unretained(service()), kSuccessValue));
123 NSUInteger index = service()->GetCharacterIndexAtPoint(
124 widget(), gfx::Point(2, 2));
126 EXPECT_EQ(1U, ipc_sink().message_count());
127 EXPECT_TRUE(ipc_sink().GetUniqueMessageMatching(
128 TextInputClientMsg_CharacterIndexForPoint::ID));
129 EXPECT_EQ(kSuccessValue, index);
132 TEST_F(TextInputClientMacTest, TimeoutCharacterIndex) {
133 NSUInteger index = service()->GetCharacterIndexAtPoint(
134 widget(), gfx::Point(2, 2));
135 EXPECT_EQ(1U, ipc_sink().message_count());
136 EXPECT_TRUE(ipc_sink().GetUniqueMessageMatching(
137 TextInputClientMsg_CharacterIndexForPoint::ID));
138 EXPECT_EQ(NSNotFound, index);
141 TEST_F(TextInputClientMacTest, NotFoundCharacterIndex) {
142 ScopedTestingThread thread(this);
143 const NSUInteger kPreviousValue = 42;
144 const size_t kNotFoundValue = static_cast<size_t>(-1);
146 // Set an arbitrary value to ensure the index is not |NSNotFound|.
148 base::Bind(&TextInputClientMac::SetCharacterIndexAndSignal,
149 base::Unretained(service()), kPreviousValue));
151 scoped_refptr<TextInputClientMessageFilter> filter(
152 new TextInputClientMessageFilter(widget()->GetProcess()->GetID()));
153 scoped_ptr<IPC::Message> message(
154 new TextInputClientReplyMsg_GotCharacterIndexForPoint(
155 widget()->GetRoutingID(), kNotFoundValue));
156 bool message_ok = true;
157 // Set |WTF::notFound| to the index |kTaskDelayMs| after the previous
160 base::Bind(&CallOnMessageReceived, filter, *message, &message_ok),
161 base::TimeDelta::FromMilliseconds(kTaskDelayMs) * 2);
163 NSUInteger index = service()->GetCharacterIndexAtPoint(
164 widget(), gfx::Point(2, 2));
165 EXPECT_EQ(kPreviousValue, index);
166 index = service()->GetCharacterIndexAtPoint(widget(), gfx::Point(2, 2));
167 EXPECT_EQ(NSNotFound, index);
169 EXPECT_EQ(2U, ipc_sink().message_count());
170 for (size_t i = 0; i < ipc_sink().message_count(); ++i) {
171 const IPC::Message* ipc_message = ipc_sink().GetMessageAt(i);
172 EXPECT_EQ(ipc_message->type(),
173 TextInputClientMsg_CharacterIndexForPoint::ID);
177 TEST_F(TextInputClientMacTest, GetRectForRange) {
178 ScopedTestingThread thread(this);
179 const NSRect kSuccessValue = NSMakeRect(42, 43, 44, 45);
182 base::Bind(&TextInputClientMac::SetFirstRectAndSignal,
183 base::Unretained(service()), kSuccessValue));
184 NSRect rect = service()->GetFirstRectForRange(widget(), NSMakeRange(0, 32));
186 EXPECT_EQ(1U, ipc_sink().message_count());
187 EXPECT_TRUE(ipc_sink().GetUniqueMessageMatching(
188 TextInputClientMsg_FirstRectForCharacterRange::ID));
189 EXPECT_TRUE(NSEqualRects(kSuccessValue, rect));
192 TEST_F(TextInputClientMacTest, TimeoutRectForRange) {
193 NSRect rect = service()->GetFirstRectForRange(widget(), NSMakeRange(0, 32));
194 EXPECT_EQ(1U, ipc_sink().message_count());
195 EXPECT_TRUE(ipc_sink().GetUniqueMessageMatching(
196 TextInputClientMsg_FirstRectForCharacterRange::ID));
197 EXPECT_TRUE(NSEqualRects(NSZeroRect, rect));
200 TEST_F(TextInputClientMacTest, GetSubstring) {
201 ScopedTestingThread thread(this);
202 NSDictionary* attributes =
203 [NSDictionary dictionaryWithObject:[NSColor purpleColor]
204 forKey:NSForegroundColorAttributeName];
205 base::scoped_nsobject<NSAttributedString> kSuccessValue(
206 [[NSAttributedString alloc] initWithString:@"Barney is a purple dinosaur"
207 attributes:attributes]);
210 base::Bind(&TextInputClientMac::SetSubstringAndSignal,
211 base::Unretained(service()),
212 base::Unretained(kSuccessValue.get())));
213 NSAttributedString* string = service()->GetAttributedSubstringFromRange(
214 widget(), NSMakeRange(0, 32));
216 EXPECT_NSEQ(kSuccessValue, string);
217 EXPECT_NE(kSuccessValue.get(), string); // |string| should be a copy.
218 EXPECT_EQ(1U, ipc_sink().message_count());
219 EXPECT_TRUE(ipc_sink().GetUniqueMessageMatching(
220 TextInputClientMsg_StringForRange::ID));
223 TEST_F(TextInputClientMacTest, TimeoutSubstring) {
224 NSAttributedString* string = service()->GetAttributedSubstringFromRange(
225 widget(), NSMakeRange(0, 32));
226 EXPECT_EQ(nil, string);
227 EXPECT_EQ(1U, ipc_sink().message_count());
228 EXPECT_TRUE(ipc_sink().GetUniqueMessageMatching(
229 TextInputClientMsg_StringForRange::ID));
232 } // namespace content