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