// 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. #include "content/browser/renderer_host/render_widget_host_view_mac.h" #include "base/mac/mac_util.h" #include "base/mac/scoped_nsautorelease_pool.h" #include "base/mac/sdk_forward_declarations.h" #include "base/strings/utf_string_conversions.h" #include "content/browser/browser_thread_impl.h" #include "content/browser/compositor/test/no_transport_image_transport_factory.h" #include "content/browser/frame_host/render_widget_host_view_guest.h" #include "content/browser/gpu/compositor_util.h" #include "content/browser/renderer_host/render_widget_host_delegate.h" #include "content/common/gpu/gpu_messages.h" #include "content/common/input_messages.h" #include "content/common/view_messages.h" #include "content/public/browser/notification_types.h" #include "content/public/browser/render_widget_host_view_mac_delegate.h" #include "content/public/test/mock_render_process_host.h" #include "content/public/test/test_browser_context.h" #include "content/public/test/test_utils.h" #include "content/test/test_render_view_host.h" #include "testing/gmock/include/gmock/gmock.h" #include "testing/gtest/include/gtest/gtest.h" #include "ui/events/test/cocoa_test_event_utils.h" #import "ui/gfx/test/ui_cocoa_test_helper.h" // Helper class with methods used to mock -[NSEvent phase], used by // |MockScrollWheelEventWithPhase()|. @interface MockPhaseMethods : NSObject { } - (NSEventPhase)phaseBegan; - (NSEventPhase)phaseChanged; - (NSEventPhase)phaseEnded; @end @implementation MockPhaseMethods - (NSEventPhase)phaseBegan { return NSEventPhaseBegan; } - (NSEventPhase)phaseChanged { return NSEventPhaseChanged; } - (NSEventPhase)phaseEnded { return NSEventPhaseEnded; } @end @interface MockRenderWidgetHostViewMacDelegate : NSObject { BOOL unhandledWheelEventReceived_; } @property(nonatomic) BOOL unhandledWheelEventReceived; @end @implementation MockRenderWidgetHostViewMacDelegate @synthesize unhandledWheelEventReceived = unhandledWheelEventReceived_; - (void)rendererHandledWheelEvent:(const blink::WebMouseWheelEvent&)event consumed:(BOOL)consumed { if (!consumed) unhandledWheelEventReceived_ = true; } - (void)touchesBeganWithEvent:(NSEvent*)event {} - (void)touchesMovedWithEvent:(NSEvent*)event {} - (void)touchesCancelledWithEvent:(NSEvent*)event {} - (void)touchesEndedWithEvent:(NSEvent*)event {} - (void)beginGestureWithEvent:(NSEvent*)event {} - (void)endGestureWithEvent:(NSEvent*)event {} - (BOOL)canRubberbandLeft:(NSView*)view { return true; } - (BOOL)canRubberbandRight:(NSView*)view { return true; } @end namespace content { namespace { class MockRenderWidgetHostDelegate : public RenderWidgetHostDelegate { public: MockRenderWidgetHostDelegate() {} ~MockRenderWidgetHostDelegate() override {} }; class MockRenderWidgetHostImpl : public RenderWidgetHostImpl { public: MockRenderWidgetHostImpl(RenderWidgetHostDelegate* delegate, RenderProcessHost* process, int routing_id) : RenderWidgetHostImpl(delegate, process, routing_id, false) { } MOCK_METHOD0(Focus, void()); MOCK_METHOD0(Blur, void()); }; // Generates the |length| of composition rectangle vector and save them to // |output|. It starts from |origin| and each rectangle contains |unit_size|. void GenerateCompositionRectArray(const gfx::Point& origin, const gfx::Size& unit_size, size_t length, const std::vector& break_points, std::vector* output) { DCHECK(output); output->clear(); std::queue break_point_queue; for (size_t i = 0; i < break_points.size(); ++i) break_point_queue.push(break_points[i]); break_point_queue.push(length); size_t next_break_point = break_point_queue.front(); break_point_queue.pop(); gfx::Rect current_rect(origin, unit_size); for (size_t i = 0; i < length; ++i) { if (i == next_break_point) { current_rect.set_x(origin.x()); current_rect.set_y(current_rect.y() + current_rect.height()); next_break_point = break_point_queue.front(); break_point_queue.pop(); } output->push_back(current_rect); current_rect.set_x(current_rect.right()); } } gfx::Rect GetExpectedRect(const gfx::Point& origin, const gfx::Size& size, const gfx::Range& range, int line_no) { return gfx::Rect( origin.x() + range.start() * size.width(), origin.y() + line_no * size.height(), range.length() * size.width(), size.height()); } // Returns NSScrollWheel event that mocks -phase. |mockPhaseSelector| should // correspond to a method in |MockPhaseMethods| that returns the desired phase. NSEvent* MockScrollWheelEventWithPhase(SEL mockPhaseSelector, int32_t delta) { CGEventRef cg_event = CGEventCreateScrollWheelEvent(NULL, kCGScrollEventUnitLine, 1, delta, 0); NSEvent* event = [NSEvent eventWithCGEvent:cg_event]; CFRelease(cg_event); method_setImplementation( class_getInstanceMethod([NSEvent class], @selector(phase)), [MockPhaseMethods instanceMethodForSelector:mockPhaseSelector]); return event; } } // namespace class RenderWidgetHostViewMacTest : public RenderViewHostImplTestHarness { public: RenderWidgetHostViewMacTest() : old_rwhv_(NULL), rwhv_mac_(NULL) {} virtual void SetUp() { RenderViewHostImplTestHarness::SetUp(); if (IsDelegatedRendererEnabled()) { ImageTransportFactory::InitializeForUnitTests( scoped_ptr( new NoTransportImageTransportFactory)); } // TestRenderViewHost's destruction assumes that its view is a // TestRenderWidgetHostView, so store its view and reset it back to the // stored view in |TearDown()|. old_rwhv_ = rvh()->GetView(); // Owned by its |cocoa_view()|, i.e. |rwhv_cocoa_|. rwhv_mac_ = new RenderWidgetHostViewMac(rvh(), false); rwhv_cocoa_.reset([rwhv_mac_->cocoa_view() retain]); } virtual void TearDown() { // Make sure the rwhv_mac_ is gone once the superclass's |TearDown()| runs. rwhv_cocoa_.reset(); RecycleAndWait(); // See comment in SetUp(). test_rvh()->SetView(static_cast(old_rwhv_)); if (IsDelegatedRendererEnabled()) ImageTransportFactory::Terminate(); RenderViewHostImplTestHarness::TearDown(); } void RecycleAndWait() { pool_.Recycle(); base::MessageLoop::current()->RunUntilIdle(); pool_.Recycle(); } protected: private: // This class isn't derived from PlatformTest. base::mac::ScopedNSAutoreleasePool pool_; RenderWidgetHostView* old_rwhv_; protected: RenderWidgetHostViewMac* rwhv_mac_; base::scoped_nsobject rwhv_cocoa_; private: DISALLOW_COPY_AND_ASSIGN(RenderWidgetHostViewMacTest); }; TEST_F(RenderWidgetHostViewMacTest, Basic) { } TEST_F(RenderWidgetHostViewMacTest, AcceptsFirstResponder) { // The RWHVCocoa should normally accept first responder status. EXPECT_TRUE([rwhv_cocoa_.get() acceptsFirstResponder]); } TEST_F(RenderWidgetHostViewMacTest, Fullscreen) { rwhv_mac_->InitAsFullscreen(NULL); EXPECT_TRUE(rwhv_mac_->pepper_fullscreen_window()); // Break the reference cycle caused by pepper_fullscreen_window() without // an event. See comment in // release_pepper_fullscreen_window_for_testing(). rwhv_mac_->release_pepper_fullscreen_window_for_testing(); } // Verify that escape key down in fullscreen mode suppressed the keyup event on // the parent. TEST_F(RenderWidgetHostViewMacTest, FullscreenCloseOnEscape) { // Use our own RWH since we need to destroy it. MockRenderWidgetHostDelegate delegate; TestBrowserContext browser_context; MockRenderProcessHost* process_host = new MockRenderProcessHost(&browser_context); // Owned by its |cocoa_view()|. RenderWidgetHostImpl* rwh = new RenderWidgetHostImpl( &delegate, process_host, MSG_ROUTING_NONE, false); RenderWidgetHostViewMac* view = new RenderWidgetHostViewMac(rwh, false); view->InitAsFullscreen(rwhv_mac_); WindowedNotificationObserver observer( NOTIFICATION_RENDER_WIDGET_HOST_DESTROYED, Source(rwh)); EXPECT_FALSE([rwhv_mac_->cocoa_view() suppressNextEscapeKeyUp]); // Escape key down. Should close window and set |suppressNextEscapeKeyUp| on // the parent. [view->cocoa_view() keyEvent: cocoa_test_event_utils::KeyEventWithKeyCode(53, 27, NSKeyDown, 0)]; observer.Wait(); EXPECT_TRUE([rwhv_mac_->cocoa_view() suppressNextEscapeKeyUp]); // Escape key up on the parent should clear |suppressNextEscapeKeyUp|. [rwhv_mac_->cocoa_view() keyEvent: cocoa_test_event_utils::KeyEventWithKeyCode(53, 27, NSKeyUp, 0)]; EXPECT_FALSE([rwhv_mac_->cocoa_view() suppressNextEscapeKeyUp]); } // Test that command accelerators which destroy the fullscreen window // don't crash when forwarded via the window's responder machinery. TEST_F(RenderWidgetHostViewMacTest, AcceleratorDestroy) { // Use our own RWH since we need to destroy it. MockRenderWidgetHostDelegate delegate; TestBrowserContext browser_context; MockRenderProcessHost* process_host = new MockRenderProcessHost(&browser_context); // Owned by its |cocoa_view()|. RenderWidgetHostImpl* rwh = new RenderWidgetHostImpl( &delegate, process_host, MSG_ROUTING_NONE, false); RenderWidgetHostViewMac* view = new RenderWidgetHostViewMac(rwh, false); view->InitAsFullscreen(rwhv_mac_); WindowedNotificationObserver observer( NOTIFICATION_RENDER_WIDGET_HOST_DESTROYED, Source(rwh)); // Command-ESC will destroy the view, while the window is still in // |-performKeyEquivalent:|. There are other cases where this can // happen, Command-ESC is the easiest to trigger. [[view->cocoa_view() window] performKeyEquivalent: cocoa_test_event_utils::KeyEventWithKeyCode( 53, 27, NSKeyDown, NSCommandKeyMask)]; observer.Wait(); } TEST_F(RenderWidgetHostViewMacTest, GetFirstRectForCharacterRangeCaretCase) { const base::string16 kDummyString = base::UTF8ToUTF16("hogehoge"); const size_t kDummyOffset = 0; gfx::Rect caret_rect(10, 11, 0, 10); gfx::Range caret_range(0, 0); ViewHostMsg_SelectionBounds_Params params; NSRect rect; NSRange actual_range; rwhv_mac_->SelectionChanged(kDummyString, kDummyOffset, caret_range); params.anchor_rect = params.focus_rect = caret_rect; params.anchor_dir = params.focus_dir = blink::WebTextDirectionLeftToRight; rwhv_mac_->SelectionBoundsChanged(params); EXPECT_TRUE(rwhv_mac_->GetCachedFirstRectForCharacterRange( caret_range.ToNSRange(), &rect, &actual_range)); EXPECT_EQ(caret_rect, gfx::Rect(NSRectToCGRect(rect))); EXPECT_EQ(caret_range, gfx::Range(actual_range)); EXPECT_FALSE(rwhv_mac_->GetCachedFirstRectForCharacterRange( gfx::Range(0, 1).ToNSRange(), &rect, &actual_range)); EXPECT_FALSE(rwhv_mac_->GetCachedFirstRectForCharacterRange( gfx::Range(1, 1).ToNSRange(), &rect, &actual_range)); EXPECT_FALSE(rwhv_mac_->GetCachedFirstRectForCharacterRange( gfx::Range(2, 3).ToNSRange(), &rect, &actual_range)); // Caret moved. caret_rect = gfx::Rect(20, 11, 0, 10); caret_range = gfx::Range(1, 1); params.anchor_rect = params.focus_rect = caret_rect; rwhv_mac_->SelectionChanged(kDummyString, kDummyOffset, caret_range); rwhv_mac_->SelectionBoundsChanged(params); EXPECT_TRUE(rwhv_mac_->GetCachedFirstRectForCharacterRange( caret_range.ToNSRange(), &rect, &actual_range)); EXPECT_EQ(caret_rect, gfx::Rect(NSRectToCGRect(rect))); EXPECT_EQ(caret_range, gfx::Range(actual_range)); EXPECT_FALSE(rwhv_mac_->GetCachedFirstRectForCharacterRange( gfx::Range(0, 0).ToNSRange(), &rect, &actual_range)); EXPECT_FALSE(rwhv_mac_->GetCachedFirstRectForCharacterRange( gfx::Range(1, 2).ToNSRange(), &rect, &actual_range)); EXPECT_FALSE(rwhv_mac_->GetCachedFirstRectForCharacterRange( gfx::Range(2, 3).ToNSRange(), &rect, &actual_range)); // No caret. caret_range = gfx::Range(1, 2); rwhv_mac_->SelectionChanged(kDummyString, kDummyOffset, caret_range); params.anchor_rect = caret_rect; params.focus_rect = gfx::Rect(30, 11, 0, 10); rwhv_mac_->SelectionBoundsChanged(params); EXPECT_FALSE(rwhv_mac_->GetCachedFirstRectForCharacterRange( gfx::Range(0, 0).ToNSRange(), &rect, &actual_range)); EXPECT_FALSE(rwhv_mac_->GetCachedFirstRectForCharacterRange( gfx::Range(0, 1).ToNSRange(), &rect, &actual_range)); EXPECT_FALSE(rwhv_mac_->GetCachedFirstRectForCharacterRange( gfx::Range(1, 1).ToNSRange(), &rect, &actual_range)); EXPECT_FALSE(rwhv_mac_->GetCachedFirstRectForCharacterRange( gfx::Range(1, 2).ToNSRange(), &rect, &actual_range)); EXPECT_FALSE(rwhv_mac_->GetCachedFirstRectForCharacterRange( gfx::Range(2, 2).ToNSRange(), &rect, &actual_range)); } TEST_F(RenderWidgetHostViewMacTest, UpdateCompositionSinglelineCase) { const gfx::Point kOrigin(10, 11); const gfx::Size kBoundsUnit(10, 20); NSRect rect; // Make sure not crashing by passing NULL pointer instead of |actual_range|. EXPECT_FALSE(rwhv_mac_->GetCachedFirstRectForCharacterRange( gfx::Range(0, 0).ToNSRange(), &rect, NULL)); // If there are no update from renderer, always returned caret position. NSRange actual_range; EXPECT_FALSE(rwhv_mac_->GetCachedFirstRectForCharacterRange( gfx::Range(0, 0).ToNSRange(), &rect, &actual_range)); EXPECT_FALSE(rwhv_mac_->GetCachedFirstRectForCharacterRange( gfx::Range(0, 1).ToNSRange(), &rect, &actual_range)); EXPECT_FALSE(rwhv_mac_->GetCachedFirstRectForCharacterRange( gfx::Range(1, 0).ToNSRange(), &rect, &actual_range)); EXPECT_FALSE(rwhv_mac_->GetCachedFirstRectForCharacterRange( gfx::Range(1, 1).ToNSRange(), &rect, &actual_range)); EXPECT_FALSE(rwhv_mac_->GetCachedFirstRectForCharacterRange( gfx::Range(1, 2).ToNSRange(), &rect, &actual_range)); // If the firstRectForCharacterRange is failed in renderer, empty rect vector // is sent. Make sure this does not crash. rwhv_mac_->ImeCompositionRangeChanged(gfx::Range(10, 12), std::vector()); EXPECT_FALSE(rwhv_mac_->GetCachedFirstRectForCharacterRange( gfx::Range(10, 11).ToNSRange(), &rect, NULL)); const int kCompositionLength = 10; std::vector composition_bounds; const int kCompositionStart = 3; const gfx::Range kCompositionRange(kCompositionStart, kCompositionStart + kCompositionLength); GenerateCompositionRectArray(kOrigin, kBoundsUnit, kCompositionLength, std::vector(), &composition_bounds); rwhv_mac_->ImeCompositionRangeChanged(kCompositionRange, composition_bounds); // Out of range requests will return caret position. EXPECT_FALSE(rwhv_mac_->GetCachedFirstRectForCharacterRange( gfx::Range(0, 0).ToNSRange(), &rect, &actual_range)); EXPECT_FALSE(rwhv_mac_->GetCachedFirstRectForCharacterRange( gfx::Range(1, 1).ToNSRange(), &rect, &actual_range)); EXPECT_FALSE(rwhv_mac_->GetCachedFirstRectForCharacterRange( gfx::Range(1, 2).ToNSRange(), &rect, &actual_range)); EXPECT_FALSE(rwhv_mac_->GetCachedFirstRectForCharacterRange( gfx::Range(2, 2).ToNSRange(), &rect, &actual_range)); EXPECT_FALSE(rwhv_mac_->GetCachedFirstRectForCharacterRange( gfx::Range(13, 14).ToNSRange(), &rect, &actual_range)); EXPECT_FALSE(rwhv_mac_->GetCachedFirstRectForCharacterRange( gfx::Range(14, 15).ToNSRange(), &rect, &actual_range)); for (int i = 0; i <= kCompositionLength; ++i) { for (int j = 0; j <= kCompositionLength - i; ++j) { const gfx::Range range(i, i + j); const gfx::Rect expected_rect = GetExpectedRect(kOrigin, kBoundsUnit, range, 0); const NSRange request_range = gfx::Range( kCompositionStart + range.start(), kCompositionStart + range.end()).ToNSRange(); EXPECT_TRUE(rwhv_mac_->GetCachedFirstRectForCharacterRange( request_range, &rect, &actual_range)); EXPECT_EQ(gfx::Range(request_range), gfx::Range(actual_range)); EXPECT_EQ(expected_rect, gfx::Rect(NSRectToCGRect(rect))); // Make sure not crashing by passing NULL pointer instead of // |actual_range|. EXPECT_TRUE(rwhv_mac_->GetCachedFirstRectForCharacterRange( request_range, &rect, NULL)); } } } TEST_F(RenderWidgetHostViewMacTest, UpdateCompositionMultilineCase) { const gfx::Point kOrigin(10, 11); const gfx::Size kBoundsUnit(10, 20); NSRect rect; const int kCompositionLength = 30; std::vector composition_bounds; const gfx::Range kCompositionRange(0, kCompositionLength); // Set breaking point at 10 and 20. std::vector break_points; break_points.push_back(10); break_points.push_back(20); GenerateCompositionRectArray(kOrigin, kBoundsUnit, kCompositionLength, break_points, &composition_bounds); rwhv_mac_->ImeCompositionRangeChanged(kCompositionRange, composition_bounds); // Range doesn't contain line breaking point. gfx::Range range; range = gfx::Range(5, 8); NSRange actual_range; EXPECT_TRUE(rwhv_mac_->GetCachedFirstRectForCharacterRange(range.ToNSRange(), &rect, &actual_range)); EXPECT_EQ(range, gfx::Range(actual_range)); EXPECT_EQ( GetExpectedRect(kOrigin, kBoundsUnit, range, 0), gfx::Rect(NSRectToCGRect(rect))); range = gfx::Range(15, 18); EXPECT_TRUE(rwhv_mac_->GetCachedFirstRectForCharacterRange(range.ToNSRange(), &rect, &actual_range)); EXPECT_EQ(range, gfx::Range(actual_range)); EXPECT_EQ( GetExpectedRect(kOrigin, kBoundsUnit, gfx::Range(5, 8), 1), gfx::Rect(NSRectToCGRect(rect))); range = gfx::Range(25, 28); EXPECT_TRUE(rwhv_mac_->GetCachedFirstRectForCharacterRange(range.ToNSRange(), &rect, &actual_range)); EXPECT_EQ(range, gfx::Range(actual_range)); EXPECT_EQ( GetExpectedRect(kOrigin, kBoundsUnit, gfx::Range(5, 8), 2), gfx::Rect(NSRectToCGRect(rect))); // Range contains line breaking point. range = gfx::Range(8, 12); EXPECT_TRUE(rwhv_mac_->GetCachedFirstRectForCharacterRange(range.ToNSRange(), &rect, &actual_range)); EXPECT_EQ(gfx::Range(8, 10), gfx::Range(actual_range)); EXPECT_EQ( GetExpectedRect(kOrigin, kBoundsUnit, gfx::Range(8, 10), 0), gfx::Rect(NSRectToCGRect(rect))); range = gfx::Range(18, 22); EXPECT_TRUE(rwhv_mac_->GetCachedFirstRectForCharacterRange(range.ToNSRange(), &rect, &actual_range)); EXPECT_EQ(gfx::Range(18, 20), gfx::Range(actual_range)); EXPECT_EQ( GetExpectedRect(kOrigin, kBoundsUnit, gfx::Range(8, 10), 1), gfx::Rect(NSRectToCGRect(rect))); // Start point is line breaking point. range = gfx::Range(10, 12); EXPECT_TRUE(rwhv_mac_->GetCachedFirstRectForCharacterRange(range.ToNSRange(), &rect, &actual_range)); EXPECT_EQ(gfx::Range(10, 12), gfx::Range(actual_range)); EXPECT_EQ( GetExpectedRect(kOrigin, kBoundsUnit, gfx::Range(0, 2), 1), gfx::Rect(NSRectToCGRect(rect))); range = gfx::Range(20, 22); EXPECT_TRUE(rwhv_mac_->GetCachedFirstRectForCharacterRange(range.ToNSRange(), &rect, &actual_range)); EXPECT_EQ(gfx::Range(20, 22), gfx::Range(actual_range)); EXPECT_EQ( GetExpectedRect(kOrigin, kBoundsUnit, gfx::Range(0, 2), 2), gfx::Rect(NSRectToCGRect(rect))); // End point is line breaking point. range = gfx::Range(5, 10); EXPECT_TRUE(rwhv_mac_->GetCachedFirstRectForCharacterRange(range.ToNSRange(), &rect, &actual_range)); EXPECT_EQ(gfx::Range(5, 10), gfx::Range(actual_range)); EXPECT_EQ( GetExpectedRect(kOrigin, kBoundsUnit, gfx::Range(5, 10), 0), gfx::Rect(NSRectToCGRect(rect))); range = gfx::Range(15, 20); EXPECT_TRUE(rwhv_mac_->GetCachedFirstRectForCharacterRange(range.ToNSRange(), &rect, &actual_range)); EXPECT_EQ(gfx::Range(15, 20), gfx::Range(actual_range)); EXPECT_EQ( GetExpectedRect(kOrigin, kBoundsUnit, gfx::Range(5, 10), 1), gfx::Rect(NSRectToCGRect(rect))); // Start and end point are same line breaking point. range = gfx::Range(10, 10); EXPECT_TRUE(rwhv_mac_->GetCachedFirstRectForCharacterRange(range.ToNSRange(), &rect, &actual_range)); EXPECT_EQ(gfx::Range(10, 10), gfx::Range(actual_range)); EXPECT_EQ( GetExpectedRect(kOrigin, kBoundsUnit, gfx::Range(0, 0), 1), gfx::Rect(NSRectToCGRect(rect))); range = gfx::Range(20, 20); EXPECT_TRUE(rwhv_mac_->GetCachedFirstRectForCharacterRange(range.ToNSRange(), &rect, &actual_range)); EXPECT_EQ(gfx::Range(20, 20), gfx::Range(actual_range)); EXPECT_EQ( GetExpectedRect(kOrigin, kBoundsUnit, gfx::Range(0, 0), 2), gfx::Rect(NSRectToCGRect(rect))); // Start and end point are different line breaking point. range = gfx::Range(10, 20); EXPECT_TRUE(rwhv_mac_->GetCachedFirstRectForCharacterRange(range.ToNSRange(), &rect, &actual_range)); EXPECT_EQ(gfx::Range(10, 20), gfx::Range(actual_range)); EXPECT_EQ( GetExpectedRect(kOrigin, kBoundsUnit, gfx::Range(0, 10), 1), gfx::Rect(NSRectToCGRect(rect))); } // Verify that |SetActive()| calls |RenderWidgetHostImpl::Blur()| and // |RenderWidgetHostImp::Focus()|. TEST_F(RenderWidgetHostViewMacTest, BlurAndFocusOnSetActive) { MockRenderWidgetHostDelegate delegate; TestBrowserContext browser_context; MockRenderProcessHost* process_host = new MockRenderProcessHost(&browser_context); // Owned by its |cocoa_view()|. MockRenderWidgetHostImpl* rwh = new MockRenderWidgetHostImpl( &delegate, process_host, MSG_ROUTING_NONE); RenderWidgetHostViewMac* view = new RenderWidgetHostViewMac(rwh, false); base::scoped_nsobject window( [[CocoaTestHelperWindow alloc] init]); [[window contentView] addSubview:view->cocoa_view()]; EXPECT_CALL(*rwh, Focus()); [window makeFirstResponder:view->cocoa_view()]; testing::Mock::VerifyAndClearExpectations(rwh); EXPECT_CALL(*rwh, Blur()); view->SetActive(false); testing::Mock::VerifyAndClearExpectations(rwh); EXPECT_CALL(*rwh, Focus()); view->SetActive(true); testing::Mock::VerifyAndClearExpectations(rwh); // Unsetting first responder should blur. EXPECT_CALL(*rwh, Blur()); [window makeFirstResponder:nil]; testing::Mock::VerifyAndClearExpectations(rwh); // |SetActive()| shoud not focus if view is not first responder. EXPECT_CALL(*rwh, Focus()).Times(0); view->SetActive(true); testing::Mock::VerifyAndClearExpectations(rwh); // Clean up. rwh->Shutdown(); } TEST_F(RenderWidgetHostViewMacTest, ScrollWheelEndEventDelivery) { // This tests Lion+ functionality, so don't run the test pre-Lion. if (!base::mac::IsOSLionOrLater()) return; // Initialize the view associated with a MockRenderWidgetHostImpl, rather than // the MockRenderProcessHost that is set up by the test harness which mocks // out |OnMessageReceived()|. TestBrowserContext browser_context; MockRenderProcessHost* process_host = new MockRenderProcessHost(&browser_context); MockRenderWidgetHostDelegate delegate; MockRenderWidgetHostImpl* host = new MockRenderWidgetHostImpl( &delegate, process_host, MSG_ROUTING_NONE); RenderWidgetHostViewMac* view = new RenderWidgetHostViewMac(host, false); // Send an initial wheel event with NSEventPhaseBegan to the view. NSEvent* event1 = MockScrollWheelEventWithPhase(@selector(phaseBegan), 0); [view->cocoa_view() scrollWheel:event1]; ASSERT_EQ(1U, process_host->sink().message_count()); // Send an ACK for the first wheel event, so that the queue will be flushed. InputHostMsg_HandleInputEvent_ACK_Params ack; ack.type = blink::WebInputEvent::MouseWheel; ack.state = INPUT_EVENT_ACK_STATE_CONSUMED; scoped_ptr response( new InputHostMsg_HandleInputEvent_ACK(0, ack)); host->OnMessageReceived(*response); // Post the NSEventPhaseEnded wheel event to NSApp and check whether the // render view receives it. NSEvent* event2 = MockScrollWheelEventWithPhase(@selector(phaseEnded), 0); [NSApp postEvent:event2 atStart:NO]; base::MessageLoop::current()->RunUntilIdle(); ASSERT_EQ(2U, process_host->sink().message_count()); // Clean up. host->Shutdown(); } TEST_F(RenderWidgetHostViewMacTest, IgnoreEmptyUnhandledWheelEvent) { // This tests Lion+ functionality, so don't run the test pre-Lion. if (!base::mac::IsOSLionOrLater()) return; // Initialize the view associated with a MockRenderWidgetHostImpl, rather than // the MockRenderProcessHost that is set up by the test harness which mocks // out |OnMessageReceived()|. TestBrowserContext browser_context; MockRenderProcessHost* process_host = new MockRenderProcessHost(&browser_context); MockRenderWidgetHostDelegate delegate; MockRenderWidgetHostImpl* host = new MockRenderWidgetHostImpl( &delegate, process_host, MSG_ROUTING_NONE); RenderWidgetHostViewMac* view = new RenderWidgetHostViewMac(host, false); // Add a delegate to the view. base::scoped_nsobject view_delegate( [[MockRenderWidgetHostViewMacDelegate alloc] init]); view->SetDelegate(view_delegate.get()); // Send an initial wheel event for scrolling by 3 lines. NSEvent* event1 = MockScrollWheelEventWithPhase(@selector(phaseBegan), 3); [view->cocoa_view() scrollWheel:event1]; ASSERT_EQ(1U, process_host->sink().message_count()); process_host->sink().ClearMessages(); // Indicate that the wheel event was unhandled. InputHostMsg_HandleInputEvent_ACK_Params unhandled_ack; unhandled_ack.type = blink::WebInputEvent::MouseWheel; unhandled_ack.state = INPUT_EVENT_ACK_STATE_NOT_CONSUMED; scoped_ptr response1( new InputHostMsg_HandleInputEvent_ACK(0, unhandled_ack)); host->OnMessageReceived(*response1); // Check that the view delegate got an unhandled wheel event. ASSERT_EQ(YES, view_delegate.get().unhandledWheelEventReceived); view_delegate.get().unhandledWheelEventReceived = NO; // Send another wheel event, this time for scrolling by 0 lines (empty event). NSEvent* event2 = MockScrollWheelEventWithPhase(@selector(phaseChanged), 0); [view->cocoa_view() scrollWheel:event2]; ASSERT_EQ(1U, process_host->sink().message_count()); // Indicate that the wheel event was also unhandled. scoped_ptr response2( new InputHostMsg_HandleInputEvent_ACK(0, unhandled_ack)); host->OnMessageReceived(*response2); // Check that the view delegate ignored the empty unhandled wheel event. ASSERT_EQ(NO, view_delegate.get().unhandledWheelEventReceived); // Clean up. host->Shutdown(); } // Tests that when view initiated shutdown happens (i.e. RWHView is deleted // before RWH), we clean up properly and don't leak the RWHVGuest. TEST_F(RenderWidgetHostViewMacTest, GuestViewDoesNotLeak) { MockRenderWidgetHostDelegate delegate; TestBrowserContext browser_context; MockRenderProcessHost* process_host = new MockRenderProcessHost(&browser_context); // Owned by its |cocoa_view()|. MockRenderWidgetHostImpl* rwh = new MockRenderWidgetHostImpl( &delegate, process_host, MSG_ROUTING_NONE); RenderWidgetHostViewMac* view = new RenderWidgetHostViewMac(rwh, true); // Add a delegate to the view. base::scoped_nsobject view_delegate( [[MockRenderWidgetHostViewMacDelegate alloc] init]); view->SetDelegate(view_delegate.get()); base::WeakPtr guest_rwhv_weak = (new RenderWidgetHostViewGuest( rwh, NULL, view->GetWeakPtr()))->GetWeakPtr(); // Remove the cocoa_view() so |view| also goes away before |rwh|. { base::scoped_nsobject rwhv_cocoa; rwhv_cocoa.reset([view->cocoa_view() retain]); } RecycleAndWait(); // Clean up. rwh->Shutdown(); // Let |guest_rwhv_weak| have a chance to delete itself. base::RunLoop run_loop; content::BrowserThread::PostTask( content::BrowserThread::UI, FROM_HERE, run_loop.QuitClosure()); run_loop.Run(); ASSERT_FALSE(guest_rwhv_weak.get()); } } // namespace content