| // Copyright (c) 2012 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #import "content/browser/renderer_host/text_input_client_mac.h" |
| |
| #include "base/bind.h" |
| #include "base/message_loop/message_loop.h" |
| #include "base/threading/thread.h" |
| #include "content/browser/renderer_host/render_process_host_impl.h" |
| #include "content/browser/renderer_host/render_widget_host_delegate.h" |
| #include "content/browser/renderer_host/render_widget_host_impl.h" |
| #include "content/browser/renderer_host/text_input_client_message_filter.h" |
| #include "content/common/text_input_client_messages.h" |
| #include "content/public/test/mock_render_process_host.h" |
| #include "content/public/test/test_browser_context.h" |
| #include "ipc/ipc_test_sink.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| #include "testing/gtest_mac.h" |
| |
| namespace content { |
| |
| namespace { |
| const int64 kTaskDelayMs = 200; |
| |
| class MockRenderWidgetHostDelegate : public RenderWidgetHostDelegate { |
| public: |
| MockRenderWidgetHostDelegate() {} |
| virtual ~MockRenderWidgetHostDelegate() {} |
| }; |
| |
| // This test does not test the WebKit side of the dictionary system (which |
| // performs the actual data fetching), but rather this just tests that the |
| // service's signaling system works. |
| class TextInputClientMacTest : public testing::Test { |
| public: |
| TextInputClientMacTest() |
| : browser_context_(), |
| process_factory_(), |
| delegate_(), |
| widget_(&delegate_, |
| process_factory_.CreateRenderProcessHost( |
| &browser_context_, NULL), |
| MSG_ROUTING_NONE, false), |
| thread_("TextInputClientMacTestThread") {} |
| |
| // Accessor for the TextInputClientMac instance. |
| TextInputClientMac* service() { |
| return TextInputClientMac::GetInstance(); |
| } |
| |
| // Helper method to post a task on the testing thread's MessageLoop after |
| // a short delay. |
| void PostTask(const tracked_objects::Location& from_here, |
| const base::Closure& task) { |
| PostTask(from_here, task, base::TimeDelta::FromMilliseconds(kTaskDelayMs)); |
| } |
| |
| void PostTask(const tracked_objects::Location& from_here, |
| const base::Closure& task, |
| const base::TimeDelta delay) { |
| thread_.message_loop()->PostDelayedTask(from_here, task, delay); |
| } |
| |
| RenderWidgetHostImpl* widget() { |
| return &widget_; |
| } |
| |
| IPC::TestSink& ipc_sink() { |
| return static_cast<MockRenderProcessHost*>(widget()->GetProcess())->sink(); |
| } |
| |
| private: |
| friend class ScopedTestingThread; |
| |
| base::MessageLoopForUI message_loop_; |
| TestBrowserContext browser_context_; |
| |
| // Gets deleted when the last RWH in the "process" gets destroyed. |
| MockRenderProcessHostFactory process_factory_; |
| MockRenderWidgetHostDelegate delegate_; |
| RenderWidgetHostImpl widget_; |
| |
| base::Thread thread_; |
| }; |
| |
| //////////////////////////////////////////////////////////////////////////////// |
| |
| // Helper class that Start()s and Stop()s a thread according to the scope of the |
| // object. |
| class ScopedTestingThread { |
| public: |
| ScopedTestingThread(TextInputClientMacTest* test) : thread_(test->thread_) { |
| thread_.Start(); |
| } |
| ~ScopedTestingThread() { |
| thread_.Stop(); |
| } |
| |
| private: |
| base::Thread& thread_; |
| }; |
| |
| // Adapter for OnMessageReceived to ignore return type so it can be posted |
| // to a MessageLoop. |
| void CallOnMessageReceived(scoped_refptr<TextInputClientMessageFilter> filter, |
| const IPC::Message& message) { |
| filter->OnMessageReceived(message); |
| } |
| |
| } // namespace |
| |
| // Test Cases ////////////////////////////////////////////////////////////////// |
| |
| TEST_F(TextInputClientMacTest, GetCharacterIndex) { |
| ScopedTestingThread thread(this); |
| const NSUInteger kSuccessValue = 42; |
| |
| PostTask(FROM_HERE, |
| base::Bind(&TextInputClientMac::SetCharacterIndexAndSignal, |
| base::Unretained(service()), kSuccessValue)); |
| NSUInteger index = service()->GetCharacterIndexAtPoint( |
| widget(), gfx::Point(2, 2)); |
| |
| EXPECT_EQ(1U, ipc_sink().message_count()); |
| EXPECT_TRUE(ipc_sink().GetUniqueMessageMatching( |
| TextInputClientMsg_CharacterIndexForPoint::ID)); |
| EXPECT_EQ(kSuccessValue, index); |
| } |
| |
| TEST_F(TextInputClientMacTest, TimeoutCharacterIndex) { |
| NSUInteger index = service()->GetCharacterIndexAtPoint( |
| widget(), gfx::Point(2, 2)); |
| EXPECT_EQ(1U, ipc_sink().message_count()); |
| EXPECT_TRUE(ipc_sink().GetUniqueMessageMatching( |
| TextInputClientMsg_CharacterIndexForPoint::ID)); |
| EXPECT_EQ(NSNotFound, index); |
| } |
| |
| TEST_F(TextInputClientMacTest, NotFoundCharacterIndex) { |
| ScopedTestingThread thread(this); |
| const NSUInteger kPreviousValue = 42; |
| const size_t kNotFoundValue = static_cast<size_t>(-1); |
| |
| // Set an arbitrary value to ensure the index is not |NSNotFound|. |
| PostTask(FROM_HERE, |
| base::Bind(&TextInputClientMac::SetCharacterIndexAndSignal, |
| base::Unretained(service()), kPreviousValue)); |
| |
| scoped_refptr<TextInputClientMessageFilter> filter( |
| new TextInputClientMessageFilter(widget()->GetProcess()->GetID())); |
| scoped_ptr<IPC::Message> message( |
| new TextInputClientReplyMsg_GotCharacterIndexForPoint( |
| widget()->GetRoutingID(), kNotFoundValue)); |
| // Set |WTF::notFound| to the index |kTaskDelayMs| after the previous |
| // setting. |
| PostTask(FROM_HERE, |
| base::Bind(&CallOnMessageReceived, filter, *message), |
| base::TimeDelta::FromMilliseconds(kTaskDelayMs) * 2); |
| |
| NSUInteger index = service()->GetCharacterIndexAtPoint( |
| widget(), gfx::Point(2, 2)); |
| EXPECT_EQ(kPreviousValue, index); |
| index = service()->GetCharacterIndexAtPoint(widget(), gfx::Point(2, 2)); |
| EXPECT_EQ(NSNotFound, index); |
| |
| EXPECT_EQ(2U, ipc_sink().message_count()); |
| for (size_t i = 0; i < ipc_sink().message_count(); ++i) { |
| const IPC::Message* ipc_message = ipc_sink().GetMessageAt(i); |
| EXPECT_EQ(ipc_message->type(), |
| TextInputClientMsg_CharacterIndexForPoint::ID); |
| } |
| } |
| |
| TEST_F(TextInputClientMacTest, GetRectForRange) { |
| ScopedTestingThread thread(this); |
| const NSRect kSuccessValue = NSMakeRect(42, 43, 44, 45); |
| |
| PostTask(FROM_HERE, |
| base::Bind(&TextInputClientMac::SetFirstRectAndSignal, |
| base::Unretained(service()), kSuccessValue)); |
| NSRect rect = service()->GetFirstRectForRange(widget(), NSMakeRange(0, 32)); |
| |
| EXPECT_EQ(1U, ipc_sink().message_count()); |
| EXPECT_TRUE(ipc_sink().GetUniqueMessageMatching( |
| TextInputClientMsg_FirstRectForCharacterRange::ID)); |
| EXPECT_TRUE(NSEqualRects(kSuccessValue, rect)); |
| } |
| |
| TEST_F(TextInputClientMacTest, TimeoutRectForRange) { |
| NSRect rect = service()->GetFirstRectForRange(widget(), NSMakeRange(0, 32)); |
| EXPECT_EQ(1U, ipc_sink().message_count()); |
| EXPECT_TRUE(ipc_sink().GetUniqueMessageMatching( |
| TextInputClientMsg_FirstRectForCharacterRange::ID)); |
| EXPECT_TRUE(NSEqualRects(NSZeroRect, rect)); |
| } |
| |
| TEST_F(TextInputClientMacTest, GetSubstring) { |
| ScopedTestingThread thread(this); |
| NSDictionary* attributes = |
| [NSDictionary dictionaryWithObject:[NSColor purpleColor] |
| forKey:NSForegroundColorAttributeName]; |
| base::scoped_nsobject<NSAttributedString> kSuccessValue( |
| [[NSAttributedString alloc] initWithString:@"Barney is a purple dinosaur" |
| attributes:attributes]); |
| |
| PostTask(FROM_HERE, |
| base::Bind(&TextInputClientMac::SetSubstringAndSignal, |
| base::Unretained(service()), |
| base::Unretained(kSuccessValue.get()))); |
| NSAttributedString* string = service()->GetAttributedSubstringFromRange( |
| widget(), NSMakeRange(0, 32)); |
| |
| EXPECT_NSEQ(kSuccessValue, string); |
| EXPECT_NE(kSuccessValue.get(), string); // |string| should be a copy. |
| EXPECT_EQ(1U, ipc_sink().message_count()); |
| EXPECT_TRUE(ipc_sink().GetUniqueMessageMatching( |
| TextInputClientMsg_StringForRange::ID)); |
| } |
| |
| TEST_F(TextInputClientMacTest, TimeoutSubstring) { |
| NSAttributedString* string = service()->GetAttributedSubstringFromRange( |
| widget(), NSMakeRange(0, 32)); |
| EXPECT_EQ(nil, string); |
| EXPECT_EQ(1U, ipc_sink().message_count()); |
| EXPECT_TRUE(ipc_sink().GetUniqueMessageMatching( |
| TextInputClientMsg_StringForRange::ID)); |
| } |
| |
| } // namespace content |