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.
4
5#import "content/browser/renderer_host/text_input_client_mac.h"
6
7#include "base/bind.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"
20
21namespace content {
22
23namespace {
24const int64 kTaskDelayMs = 200;
25
26class MockRenderWidgetHostDelegate : public RenderWidgetHostDelegate {
27 public:
28  MockRenderWidgetHostDelegate() {}
29  virtual ~MockRenderWidgetHostDelegate() {}
30};
31
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.
35class TextInputClientMacTest : public testing::Test {
36 public:
37  TextInputClientMacTest()
38      : browser_context_(),
39        process_factory_(),
40        delegate_(),
41        widget_(&delegate_,
42                process_factory_.CreateRenderProcessHost(
43                    &browser_context_, NULL),
44                MSG_ROUTING_NONE, false),
45        thread_("TextInputClientMacTestThread") {}
46
47  // Accessor for the TextInputClientMac instance.
48  TextInputClientMac* service() {
49    return TextInputClientMac::GetInstance();
50  }
51
52  // Helper method to post a task on the testing thread's MessageLoop after
53  // a short delay.
54  void PostTask(const tracked_objects::Location& from_here,
55                const base::Closure& task) {
56    PostTask(from_here, task, base::TimeDelta::FromMilliseconds(kTaskDelayMs));
57  }
58
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);
63  }
64
65  RenderWidgetHostImpl* widget() {
66    return &widget_;
67  }
68
69  IPC::TestSink& ipc_sink() {
70    return static_cast<MockRenderProcessHost*>(widget()->GetProcess())->sink();
71  }
72
73 private:
74  friend class ScopedTestingThread;
75
76  base::MessageLoopForUI message_loop_;
77  TestBrowserContext browser_context_;
78
79  // Gets deleted when the last RWH in the "process" gets destroyed.
80  MockRenderProcessHostFactory process_factory_;
81  MockRenderWidgetHostDelegate delegate_;
82  RenderWidgetHostImpl widget_;
83
84  base::Thread thread_;
85};
86
87////////////////////////////////////////////////////////////////////////////////
88
89// Helper class that Start()s and Stop()s a thread according to the scope of the
90// object.
91class ScopedTestingThread {
92 public:
93  ScopedTestingThread(TextInputClientMacTest* test) : thread_(test->thread_) {
94    thread_.Start();
95  }
96  ~ScopedTestingThread() {
97    thread_.Stop();
98  }
99
100 private:
101  base::Thread& thread_;
102};
103
104// Adapter for OnMessageReceived to ignore return type so it can be posted
105// to a MessageLoop.
106void CallOnMessageReceived(scoped_refptr<TextInputClientMessageFilter> filter,
107                           const IPC::Message& message) {
108  filter->OnMessageReceived(message);
109}
110
111}  // namespace
112
113// Test Cases //////////////////////////////////////////////////////////////////
114
115TEST_F(TextInputClientMacTest, GetCharacterIndex) {
116  ScopedTestingThread thread(this);
117  const NSUInteger kSuccessValue = 42;
118
119  PostTask(FROM_HERE,
120           base::Bind(&TextInputClientMac::SetCharacterIndexAndSignal,
121                      base::Unretained(service()), kSuccessValue));
122  NSUInteger index = service()->GetCharacterIndexAtPoint(
123      widget(), gfx::Point(2, 2));
124
125  EXPECT_EQ(1U, ipc_sink().message_count());
126  EXPECT_TRUE(ipc_sink().GetUniqueMessageMatching(
127      TextInputClientMsg_CharacterIndexForPoint::ID));
128  EXPECT_EQ(kSuccessValue, index);
129}
130
131TEST_F(TextInputClientMacTest, TimeoutCharacterIndex) {
132  NSUInteger index = service()->GetCharacterIndexAtPoint(
133      widget(), gfx::Point(2, 2));
134  EXPECT_EQ(1U, ipc_sink().message_count());
135  EXPECT_TRUE(ipc_sink().GetUniqueMessageMatching(
136      TextInputClientMsg_CharacterIndexForPoint::ID));
137  EXPECT_EQ(NSNotFound, index);
138}
139
140TEST_F(TextInputClientMacTest, NotFoundCharacterIndex) {
141  ScopedTestingThread thread(this);
142  const NSUInteger kPreviousValue = 42;
143  const size_t kNotFoundValue = static_cast<size_t>(-1);
144
145  // Set an arbitrary value to ensure the index is not |NSNotFound|.
146  PostTask(FROM_HERE,
147           base::Bind(&TextInputClientMac::SetCharacterIndexAndSignal,
148                      base::Unretained(service()), kPreviousValue));
149
150  scoped_refptr<TextInputClientMessageFilter> filter(
151      new TextInputClientMessageFilter(widget()->GetProcess()->GetID()));
152  scoped_ptr<IPC::Message> message(
153      new TextInputClientReplyMsg_GotCharacterIndexForPoint(
154          widget()->GetRoutingID(), kNotFoundValue));
155  // Set |WTF::notFound| to the index |kTaskDelayMs| after the previous
156  // setting.
157  PostTask(FROM_HERE,
158           base::Bind(&CallOnMessageReceived, filter, *message),
159           base::TimeDelta::FromMilliseconds(kTaskDelayMs) * 2);
160
161  NSUInteger index = service()->GetCharacterIndexAtPoint(
162      widget(), gfx::Point(2, 2));
163  EXPECT_EQ(kPreviousValue, index);
164  index = service()->GetCharacterIndexAtPoint(widget(), gfx::Point(2, 2));
165  EXPECT_EQ(NSNotFound, index);
166
167  EXPECT_EQ(2U, ipc_sink().message_count());
168  for (size_t i = 0; i < ipc_sink().message_count(); ++i) {
169    const IPC::Message* ipc_message = ipc_sink().GetMessageAt(i);
170    EXPECT_EQ(ipc_message->type(),
171              TextInputClientMsg_CharacterIndexForPoint::ID);
172  }
173}
174
175TEST_F(TextInputClientMacTest, GetRectForRange) {
176  ScopedTestingThread thread(this);
177  const NSRect kSuccessValue = NSMakeRect(42, 43, 44, 45);
178
179  PostTask(FROM_HERE,
180           base::Bind(&TextInputClientMac::SetFirstRectAndSignal,
181                      base::Unretained(service()), kSuccessValue));
182  NSRect rect = service()->GetFirstRectForRange(widget(), NSMakeRange(0, 32));
183
184  EXPECT_EQ(1U, ipc_sink().message_count());
185  EXPECT_TRUE(ipc_sink().GetUniqueMessageMatching(
186      TextInputClientMsg_FirstRectForCharacterRange::ID));
187  EXPECT_TRUE(NSEqualRects(kSuccessValue, rect));
188}
189
190TEST_F(TextInputClientMacTest, TimeoutRectForRange) {
191  NSRect rect = service()->GetFirstRectForRange(widget(), NSMakeRange(0, 32));
192  EXPECT_EQ(1U, ipc_sink().message_count());
193  EXPECT_TRUE(ipc_sink().GetUniqueMessageMatching(
194      TextInputClientMsg_FirstRectForCharacterRange::ID));
195  EXPECT_TRUE(NSEqualRects(NSZeroRect, rect));
196}
197
198TEST_F(TextInputClientMacTest, GetSubstring) {
199  ScopedTestingThread thread(this);
200  NSDictionary* attributes =
201      [NSDictionary dictionaryWithObject:[NSColor purpleColor]
202                                  forKey:NSForegroundColorAttributeName];
203  base::scoped_nsobject<NSAttributedString> kSuccessValue(
204      [[NSAttributedString alloc] initWithString:@"Barney is a purple dinosaur"
205                                      attributes:attributes]);
206
207  PostTask(FROM_HERE,
208           base::Bind(&TextInputClientMac::SetSubstringAndSignal,
209                      base::Unretained(service()),
210                      base::Unretained(kSuccessValue.get())));
211  NSAttributedString* string = service()->GetAttributedSubstringFromRange(
212      widget(), NSMakeRange(0, 32));
213
214  EXPECT_NSEQ(kSuccessValue, string);
215  EXPECT_NE(kSuccessValue.get(), string);  // |string| should be a copy.
216  EXPECT_EQ(1U, ipc_sink().message_count());
217  EXPECT_TRUE(ipc_sink().GetUniqueMessageMatching(
218      TextInputClientMsg_StringForRange::ID));
219}
220
221TEST_F(TextInputClientMacTest, TimeoutSubstring) {
222  NSAttributedString* string = service()->GetAttributedSubstringFromRange(
223      widget(), NSMakeRange(0, 32));
224  EXPECT_EQ(nil, string);
225  EXPECT_EQ(1U, ipc_sink().message_count());
226  EXPECT_TRUE(ipc_sink().GetUniqueMessageMatching(
227      TextInputClientMsg_StringForRange::ID));
228}
229
230}  // namespace content
231