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 ~MockRenderWidgetHostDelegate() override {}
32 void Cut() override {}
33 void Copy() override {}
34 void Paste() override {}
35 void SelectAll() override {}
38 // This test does not test the WebKit side of the dictionary system (which
39 // performs the actual data fetching), but rather this just tests that the
40 // service's signaling system works.
41 class TextInputClientMacTest : public testing::Test {
43 TextInputClientMacTest()
48 process_factory_.CreateRenderProcessHost(
49 &browser_context_, NULL),
50 MSG_ROUTING_NONE, false),
51 thread_("TextInputClientMacTestThread") {}
53 // Accessor for the TextInputClientMac instance.
54 TextInputClientMac* service() {
55 return TextInputClientMac::GetInstance();
58 // Helper method to post a task on the testing thread's MessageLoop after
60 void PostTask(const tracked_objects::Location& from_here,
61 const base::Closure& task) {
62 PostTask(from_here, task, base::TimeDelta::FromMilliseconds(kTaskDelayMs));
65 void PostTask(const tracked_objects::Location& from_here,
66 const base::Closure& task,
67 const base::TimeDelta delay) {
68 thread_.message_loop()->PostDelayedTask(from_here, task, delay);
71 RenderWidgetHostImpl* widget() {
75 IPC::TestSink& ipc_sink() {
76 return static_cast<MockRenderProcessHost*>(widget()->GetProcess())->sink();
80 friend class ScopedTestingThread;
82 base::MessageLoopForUI message_loop_;
83 TestBrowserContext browser_context_;
85 // Gets deleted when the last RWH in the "process" gets destroyed.
86 MockRenderProcessHostFactory process_factory_;
87 MockRenderWidgetHostDelegate delegate_;
88 RenderWidgetHostImpl widget_;
93 ////////////////////////////////////////////////////////////////////////////////
95 // Helper class that Start()s and Stop()s a thread according to the scope of the
97 class ScopedTestingThread {
99 ScopedTestingThread(TextInputClientMacTest* test) : thread_(test->thread_) {
102 ~ScopedTestingThread() {
107 base::Thread& thread_;
110 // Adapter for OnMessageReceived to ignore return type so it can be posted
112 void CallOnMessageReceived(scoped_refptr<TextInputClientMessageFilter> filter,
113 const IPC::Message& message) {
114 filter->OnMessageReceived(message);
119 // Test Cases //////////////////////////////////////////////////////////////////
121 TEST_F(TextInputClientMacTest, GetCharacterIndex) {
122 ScopedTestingThread thread(this);
123 const NSUInteger kSuccessValue = 42;
126 base::Bind(&TextInputClientMac::SetCharacterIndexAndSignal,
127 base::Unretained(service()), kSuccessValue));
128 NSUInteger index = service()->GetCharacterIndexAtPoint(
129 widget(), gfx::Point(2, 2));
131 EXPECT_EQ(1U, ipc_sink().message_count());
132 EXPECT_TRUE(ipc_sink().GetUniqueMessageMatching(
133 TextInputClientMsg_CharacterIndexForPoint::ID));
134 EXPECT_EQ(kSuccessValue, index);
137 TEST_F(TextInputClientMacTest, TimeoutCharacterIndex) {
138 NSUInteger index = service()->GetCharacterIndexAtPoint(
139 widget(), gfx::Point(2, 2));
140 EXPECT_EQ(1U, ipc_sink().message_count());
141 EXPECT_TRUE(ipc_sink().GetUniqueMessageMatching(
142 TextInputClientMsg_CharacterIndexForPoint::ID));
143 EXPECT_EQ(NSNotFound, index);
146 TEST_F(TextInputClientMacTest, NotFoundCharacterIndex) {
147 ScopedTestingThread thread(this);
148 const NSUInteger kPreviousValue = 42;
149 const size_t kNotFoundValue = static_cast<size_t>(-1);
151 // Set an arbitrary value to ensure the index is not |NSNotFound|.
153 base::Bind(&TextInputClientMac::SetCharacterIndexAndSignal,
154 base::Unretained(service()), kPreviousValue));
156 scoped_refptr<TextInputClientMessageFilter> filter(
157 new TextInputClientMessageFilter(widget()->GetProcess()->GetID()));
158 scoped_ptr<IPC::Message> message(
159 new TextInputClientReplyMsg_GotCharacterIndexForPoint(
160 widget()->GetRoutingID(), kNotFoundValue));
161 // Set |WTF::notFound| to the index |kTaskDelayMs| after the previous
164 base::Bind(&CallOnMessageReceived, filter, *message),
165 base::TimeDelta::FromMilliseconds(kTaskDelayMs) * 2);
167 NSUInteger index = service()->GetCharacterIndexAtPoint(
168 widget(), gfx::Point(2, 2));
169 EXPECT_EQ(kPreviousValue, index);
170 index = service()->GetCharacterIndexAtPoint(widget(), gfx::Point(2, 2));
171 EXPECT_EQ(NSNotFound, index);
173 EXPECT_EQ(2U, ipc_sink().message_count());
174 for (size_t i = 0; i < ipc_sink().message_count(); ++i) {
175 const IPC::Message* ipc_message = ipc_sink().GetMessageAt(i);
176 EXPECT_EQ(ipc_message->type(),
177 TextInputClientMsg_CharacterIndexForPoint::ID);
181 TEST_F(TextInputClientMacTest, GetRectForRange) {
182 ScopedTestingThread thread(this);
183 const NSRect kSuccessValue = NSMakeRect(42, 43, 44, 45);
186 base::Bind(&TextInputClientMac::SetFirstRectAndSignal,
187 base::Unretained(service()), kSuccessValue));
188 NSRect rect = service()->GetFirstRectForRange(widget(), NSMakeRange(0, 32));
190 EXPECT_EQ(1U, ipc_sink().message_count());
191 EXPECT_TRUE(ipc_sink().GetUniqueMessageMatching(
192 TextInputClientMsg_FirstRectForCharacterRange::ID));
193 EXPECT_NSEQ(kSuccessValue, rect);
196 TEST_F(TextInputClientMacTest, TimeoutRectForRange) {
197 NSRect rect = service()->GetFirstRectForRange(widget(), NSMakeRange(0, 32));
198 EXPECT_EQ(1U, ipc_sink().message_count());
199 EXPECT_TRUE(ipc_sink().GetUniqueMessageMatching(
200 TextInputClientMsg_FirstRectForCharacterRange::ID));
201 EXPECT_NSEQ(NSZeroRect, rect);
204 TEST_F(TextInputClientMacTest, GetSubstring) {
205 ScopedTestingThread thread(this);
206 NSDictionary* attributes =
207 [NSDictionary dictionaryWithObject:[NSColor purpleColor]
208 forKey:NSForegroundColorAttributeName];
209 base::scoped_nsobject<NSMutableAttributedString> kSuccessValue(
210 [[NSMutableAttributedString alloc]
211 initWithString:@"Barney is a purple dinosaur"
212 attributes:attributes]);
215 base::Bind(&TextInputClientMac::SetSubstringAndSignal,
216 base::Unretained(service()),
217 base::Unretained(kSuccessValue.get())));
218 NSAttributedString* string = service()->GetAttributedSubstringFromRange(
219 widget(), NSMakeRange(0, 32));
221 EXPECT_NSEQ(kSuccessValue, string);
222 EXPECT_NE(kSuccessValue.get(), string); // |string| should be a copy.
223 EXPECT_EQ(1U, ipc_sink().message_count());
224 EXPECT_TRUE(ipc_sink().GetUniqueMessageMatching(
225 TextInputClientMsg_StringForRange::ID));
228 TEST_F(TextInputClientMacTest, TimeoutSubstring) {
229 NSAttributedString* string = service()->GetAttributedSubstringFromRange(
230 widget(), NSMakeRange(0, 32));
231 EXPECT_EQ(nil, string);
232 EXPECT_EQ(1U, ipc_sink().message_count());
233 EXPECT_TRUE(ipc_sink().GetUniqueMessageMatching(
234 TextInputClientMsg_StringForRange::ID));
237 } // namespace content