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()
38 : message_loop_(base::MessageLoop::TYPE_UI),
43 process_factory_.CreateRenderProcessHost(
44 &browser_context_, NULL),
45 MSG_ROUTING_NONE, false),
46 thread_("TextInputClientMacTestThread") {}
48 // Accessor for the TextInputClientMac instance.
49 TextInputClientMac* service() {
50 return TextInputClientMac::GetInstance();
53 // Helper method to post a task on the testing thread's MessageLoop after
55 void PostTask(const tracked_objects::Location& from_here,
56 const base::Closure& task) {
57 PostTask(from_here, task, base::TimeDelta::FromMilliseconds(kTaskDelayMs));
60 void PostTask(const tracked_objects::Location& from_here,
61 const base::Closure& task,
62 const base::TimeDelta delay) {
63 thread_.message_loop()->PostDelayedTask(from_here, task, delay);
66 RenderWidgetHostImpl* widget() {
70 IPC::TestSink& ipc_sink() {
71 return static_cast<MockRenderProcessHost*>(widget()->GetProcess())->sink();
75 friend class ScopedTestingThread;
77 base::MessageLoop message_loop_;
78 TestBrowserContext browser_context_;
80 // Gets deleted when the last RWH in the "process" gets destroyed.
81 MockRenderProcessHostFactory process_factory_;
82 MockRenderWidgetHostDelegate delegate_;
83 RenderWidgetHostImpl widget_;
88 ////////////////////////////////////////////////////////////////////////////////
90 // Helper class that Start()s and Stop()s a thread according to the scope of the
92 class ScopedTestingThread {
94 ScopedTestingThread(TextInputClientMacTest* test) : thread_(test->thread_) {
97 ~ScopedTestingThread() {
102 base::Thread& thread_;
105 // Adapter for OnMessageReceived to ignore return type so it can be posted
107 void CallOnMessageReceived(scoped_refptr<TextInputClientMessageFilter> filter,
108 const IPC::Message& message,
109 bool* message_was_ok) {
110 filter->OnMessageReceived(message, message_was_ok);
115 // Test Cases //////////////////////////////////////////////////////////////////
117 TEST_F(TextInputClientMacTest, GetCharacterIndex) {
118 ScopedTestingThread thread(this);
119 const NSUInteger kSuccessValue = 42;
122 base::Bind(&TextInputClientMac::SetCharacterIndexAndSignal,
123 base::Unretained(service()), kSuccessValue));
124 NSUInteger index = service()->GetCharacterIndexAtPoint(
125 widget(), gfx::Point(2, 2));
127 EXPECT_EQ(1U, ipc_sink().message_count());
128 EXPECT_TRUE(ipc_sink().GetUniqueMessageMatching(
129 TextInputClientMsg_CharacterIndexForPoint::ID));
130 EXPECT_EQ(kSuccessValue, index);
133 TEST_F(TextInputClientMacTest, TimeoutCharacterIndex) {
134 NSUInteger index = service()->GetCharacterIndexAtPoint(
135 widget(), gfx::Point(2, 2));
136 EXPECT_EQ(1U, ipc_sink().message_count());
137 EXPECT_TRUE(ipc_sink().GetUniqueMessageMatching(
138 TextInputClientMsg_CharacterIndexForPoint::ID));
139 EXPECT_EQ(NSNotFound, index);
142 TEST_F(TextInputClientMacTest, NotFoundCharacterIndex) {
143 ScopedTestingThread thread(this);
144 const NSUInteger kPreviousValue = 42;
145 const size_t kNotFoundValue = static_cast<size_t>(-1);
147 // Set an arbitrary value to ensure the index is not |NSNotFound|.
149 base::Bind(&TextInputClientMac::SetCharacterIndexAndSignal,
150 base::Unretained(service()), kPreviousValue));
152 scoped_refptr<TextInputClientMessageFilter> filter(
153 new TextInputClientMessageFilter(widget()->GetProcess()->GetID()));
154 scoped_ptr<IPC::Message> message(
155 new TextInputClientReplyMsg_GotCharacterIndexForPoint(
156 widget()->GetRoutingID(), kNotFoundValue));
157 bool message_ok = true;
158 // Set |WTF::notFound| to the index |kTaskDelayMs| after the previous
161 base::Bind(&CallOnMessageReceived, filter, *message, &message_ok),
162 base::TimeDelta::FromMilliseconds(kTaskDelayMs) * 2);
164 NSUInteger index = service()->GetCharacterIndexAtPoint(
165 widget(), gfx::Point(2, 2));
166 EXPECT_EQ(kPreviousValue, index);
167 index = service()->GetCharacterIndexAtPoint(widget(), gfx::Point(2, 2));
168 EXPECT_EQ(NSNotFound, index);
170 EXPECT_EQ(2U, ipc_sink().message_count());
171 for (size_t i = 0; i < ipc_sink().message_count(); ++i) {
172 const IPC::Message* ipc_message = ipc_sink().GetMessageAt(i);
173 EXPECT_EQ(ipc_message->type(),
174 TextInputClientMsg_CharacterIndexForPoint::ID);
178 TEST_F(TextInputClientMacTest, GetRectForRange) {
179 ScopedTestingThread thread(this);
180 const NSRect kSuccessValue = NSMakeRect(42, 43, 44, 45);
183 base::Bind(&TextInputClientMac::SetFirstRectAndSignal,
184 base::Unretained(service()), kSuccessValue));
185 NSRect rect = service()->GetFirstRectForRange(widget(), NSMakeRange(0, 32));
187 EXPECT_EQ(1U, ipc_sink().message_count());
188 EXPECT_TRUE(ipc_sink().GetUniqueMessageMatching(
189 TextInputClientMsg_FirstRectForCharacterRange::ID));
190 EXPECT_TRUE(NSEqualRects(kSuccessValue, rect));
193 TEST_F(TextInputClientMacTest, TimeoutRectForRange) {
194 NSRect rect = service()->GetFirstRectForRange(widget(), NSMakeRange(0, 32));
195 EXPECT_EQ(1U, ipc_sink().message_count());
196 EXPECT_TRUE(ipc_sink().GetUniqueMessageMatching(
197 TextInputClientMsg_FirstRectForCharacterRange::ID));
198 EXPECT_TRUE(NSEqualRects(NSZeroRect, rect));
201 TEST_F(TextInputClientMacTest, GetSubstring) {
202 ScopedTestingThread thread(this);
203 NSDictionary* attributes =
204 [NSDictionary dictionaryWithObject:[NSColor purpleColor]
205 forKey:NSForegroundColorAttributeName];
206 base::scoped_nsobject<NSAttributedString> kSuccessValue(
207 [[NSAttributedString alloc] initWithString:@"Barney is a purple dinosaur"
208 attributes:attributes]);
211 base::Bind(&TextInputClientMac::SetSubstringAndSignal,
212 base::Unretained(service()),
213 base::Unretained(kSuccessValue.get())));
214 NSAttributedString* string = service()->GetAttributedSubstringFromRange(
215 widget(), NSMakeRange(0, 32));
217 EXPECT_NSEQ(kSuccessValue, string);
218 EXPECT_NE(kSuccessValue.get(), string); // |string| should be a copy.
219 EXPECT_EQ(1U, ipc_sink().message_count());
220 EXPECT_TRUE(ipc_sink().GetUniqueMessageMatching(
221 TextInputClientMsg_StringForRange::ID));
224 TEST_F(TextInputClientMacTest, TimeoutSubstring) {
225 NSAttributedString* string = service()->GetAttributedSubstringFromRange(
226 widget(), NSMakeRange(0, 32));
227 EXPECT_EQ(nil, string);
228 EXPECT_EQ(1U, ipc_sink().message_count());
229 EXPECT_TRUE(ipc_sink().GetUniqueMessageMatching(
230 TextInputClientMsg_StringForRange::ID));
233 } // namespace content