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 #include "content/browser/renderer_host/render_widget_host_view_mac.h"
7 #include "base/mac/mac_util.h"
8 #include "base/mac/scoped_nsautorelease_pool.h"
9 #include "base/mac/sdk_forward_declarations.h"
10 #include "base/strings/utf_string_conversions.h"
11 #include "content/browser/browser_thread_impl.h"
12 #include "content/browser/compositor/test/no_transport_image_transport_factory.h"
13 #include "content/browser/frame_host/render_widget_host_view_guest.h"
14 #include "content/browser/gpu/compositor_util.h"
15 #include "content/browser/renderer_host/render_widget_host_delegate.h"
16 #include "content/common/gpu/gpu_messages.h"
17 #include "content/common/input_messages.h"
18 #include "content/common/view_messages.h"
19 #include "content/public/browser/notification_types.h"
20 #include "content/public/browser/render_widget_host_view_mac_delegate.h"
21 #include "content/public/test/mock_render_process_host.h"
22 #include "content/public/test/test_browser_context.h"
23 #include "content/public/test/test_utils.h"
24 #include "content/test/test_render_view_host.h"
25 #include "testing/gmock/include/gmock/gmock.h"
26 #include "testing/gtest/include/gtest/gtest.h"
27 #include "ui/events/test/cocoa_test_event_utils.h"
28 #import "ui/gfx/test/ui_cocoa_test_helper.h"
30 // Helper class with methods used to mock -[NSEvent phase], used by
31 // |MockScrollWheelEventWithPhase()|.
32 @interface MockPhaseMethods : NSObject {
35 - (NSEventPhase)phaseBegan;
36 - (NSEventPhase)phaseChanged;
37 - (NSEventPhase)phaseEnded;
40 @implementation MockPhaseMethods
42 - (NSEventPhase)phaseBegan {
43 return NSEventPhaseBegan;
45 - (NSEventPhase)phaseChanged {
46 return NSEventPhaseChanged;
48 - (NSEventPhase)phaseEnded {
49 return NSEventPhaseEnded;
54 @interface MockRenderWidgetHostViewMacDelegate
55 : NSObject<RenderWidgetHostViewMacDelegate> {
56 BOOL unhandledWheelEventReceived_;
59 @property(nonatomic) BOOL unhandledWheelEventReceived;
63 @implementation MockRenderWidgetHostViewMacDelegate
65 @synthesize unhandledWheelEventReceived = unhandledWheelEventReceived_;
67 - (void)rendererHandledWheelEvent:(const blink::WebMouseWheelEvent&)event
68 consumed:(BOOL)consumed {
70 unhandledWheelEventReceived_ = true;
72 - (void)touchesBeganWithEvent:(NSEvent*)event {}
73 - (void)touchesMovedWithEvent:(NSEvent*)event {}
74 - (void)touchesCancelledWithEvent:(NSEvent*)event {}
75 - (void)touchesEndedWithEvent:(NSEvent*)event {}
76 - (void)beginGestureWithEvent:(NSEvent*)event {}
77 - (void)endGestureWithEvent:(NSEvent*)event {}
78 - (BOOL)canRubberbandLeft:(NSView*)view {
81 - (BOOL)canRubberbandRight:(NSView*)view {
91 class MockRenderWidgetHostDelegate : public RenderWidgetHostDelegate {
93 MockRenderWidgetHostDelegate() {}
94 ~MockRenderWidgetHostDelegate() override {}
97 class MockRenderWidgetHostImpl : public RenderWidgetHostImpl {
99 MockRenderWidgetHostImpl(RenderWidgetHostDelegate* delegate,
100 RenderProcessHost* process,
102 : RenderWidgetHostImpl(delegate, process, routing_id, false) {
105 MOCK_METHOD0(Focus, void());
106 MOCK_METHOD0(Blur, void());
109 // Generates the |length| of composition rectangle vector and save them to
110 // |output|. It starts from |origin| and each rectangle contains |unit_size|.
111 void GenerateCompositionRectArray(const gfx::Point& origin,
112 const gfx::Size& unit_size,
114 const std::vector<size_t>& break_points,
115 std::vector<gfx::Rect>* output) {
119 std::queue<int> break_point_queue;
120 for (size_t i = 0; i < break_points.size(); ++i)
121 break_point_queue.push(break_points[i]);
122 break_point_queue.push(length);
123 size_t next_break_point = break_point_queue.front();
124 break_point_queue.pop();
126 gfx::Rect current_rect(origin, unit_size);
127 for (size_t i = 0; i < length; ++i) {
128 if (i == next_break_point) {
129 current_rect.set_x(origin.x());
130 current_rect.set_y(current_rect.y() + current_rect.height());
131 next_break_point = break_point_queue.front();
132 break_point_queue.pop();
134 output->push_back(current_rect);
135 current_rect.set_x(current_rect.right());
139 gfx::Rect GetExpectedRect(const gfx::Point& origin,
140 const gfx::Size& size,
141 const gfx::Range& range,
144 origin.x() + range.start() * size.width(),
145 origin.y() + line_no * size.height(),
146 range.length() * size.width(),
150 // Returns NSScrollWheel event that mocks -phase. |mockPhaseSelector| should
151 // correspond to a method in |MockPhaseMethods| that returns the desired phase.
152 NSEvent* MockScrollWheelEventWithPhase(SEL mockPhaseSelector, int32_t delta) {
153 CGEventRef cg_event =
154 CGEventCreateScrollWheelEvent(NULL, kCGScrollEventUnitLine, 1, delta, 0);
155 NSEvent* event = [NSEvent eventWithCGEvent:cg_event];
157 method_setImplementation(
158 class_getInstanceMethod([NSEvent class], @selector(phase)),
159 [MockPhaseMethods instanceMethodForSelector:mockPhaseSelector]);
165 class RenderWidgetHostViewMacTest : public RenderViewHostImplTestHarness {
167 RenderWidgetHostViewMacTest() : old_rwhv_(NULL), rwhv_mac_(NULL) {}
169 void SetUp() override {
170 RenderViewHostImplTestHarness::SetUp();
171 if (IsDelegatedRendererEnabled()) {
172 ImageTransportFactory::InitializeForUnitTests(
173 scoped_ptr<ImageTransportFactory>(
174 new NoTransportImageTransportFactory));
177 // TestRenderViewHost's destruction assumes that its view is a
178 // TestRenderWidgetHostView, so store its view and reset it back to the
179 // stored view in |TearDown()|.
180 old_rwhv_ = rvh()->GetView();
182 // Owned by its |cocoa_view()|, i.e. |rwhv_cocoa_|.
183 rwhv_mac_ = new RenderWidgetHostViewMac(rvh(), false);
184 rwhv_cocoa_.reset([rwhv_mac_->cocoa_view() retain]);
186 void TearDown() override {
187 // Make sure the rwhv_mac_ is gone once the superclass's |TearDown()| runs.
191 // See comment in SetUp().
192 test_rvh()->SetView(static_cast<RenderWidgetHostViewBase*>(old_rwhv_));
194 if (IsDelegatedRendererEnabled())
195 ImageTransportFactory::Terminate();
196 RenderViewHostImplTestHarness::TearDown();
199 void RecycleAndWait() {
201 base::MessageLoop::current()->RunUntilIdle();
206 // This class isn't derived from PlatformTest.
207 base::mac::ScopedNSAutoreleasePool pool_;
209 RenderWidgetHostView* old_rwhv_;
212 RenderWidgetHostViewMac* rwhv_mac_;
213 base::scoped_nsobject<RenderWidgetHostViewCocoa> rwhv_cocoa_;
216 DISALLOW_COPY_AND_ASSIGN(RenderWidgetHostViewMacTest);
219 TEST_F(RenderWidgetHostViewMacTest, Basic) {
222 TEST_F(RenderWidgetHostViewMacTest, AcceptsFirstResponder) {
223 // The RWHVCocoa should normally accept first responder status.
224 EXPECT_TRUE([rwhv_cocoa_.get() acceptsFirstResponder]);
227 TEST_F(RenderWidgetHostViewMacTest, Fullscreen) {
228 rwhv_mac_->InitAsFullscreen(NULL);
229 EXPECT_TRUE(rwhv_mac_->pepper_fullscreen_window());
231 // Break the reference cycle caused by pepper_fullscreen_window() without
232 // an <esc> event. See comment in
233 // release_pepper_fullscreen_window_for_testing().
234 rwhv_mac_->release_pepper_fullscreen_window_for_testing();
237 // Verify that escape key down in fullscreen mode suppressed the keyup event on
239 TEST_F(RenderWidgetHostViewMacTest, FullscreenCloseOnEscape) {
240 // Use our own RWH since we need to destroy it.
241 MockRenderWidgetHostDelegate delegate;
242 TestBrowserContext browser_context;
243 MockRenderProcessHost* process_host =
244 new MockRenderProcessHost(&browser_context);
245 // Owned by its |cocoa_view()|.
246 RenderWidgetHostImpl* rwh = new RenderWidgetHostImpl(
247 &delegate, process_host, MSG_ROUTING_NONE, false);
248 RenderWidgetHostViewMac* view = new RenderWidgetHostViewMac(rwh, false);
250 view->InitAsFullscreen(rwhv_mac_);
252 WindowedNotificationObserver observer(
253 NOTIFICATION_RENDER_WIDGET_HOST_DESTROYED,
254 Source<RenderWidgetHost>(rwh));
255 EXPECT_FALSE([rwhv_mac_->cocoa_view() suppressNextEscapeKeyUp]);
257 // Escape key down. Should close window and set |suppressNextEscapeKeyUp| on
259 [view->cocoa_view() keyEvent:
260 cocoa_test_event_utils::KeyEventWithKeyCode(53, 27, NSKeyDown, 0)];
262 EXPECT_TRUE([rwhv_mac_->cocoa_view() suppressNextEscapeKeyUp]);
264 // Escape key up on the parent should clear |suppressNextEscapeKeyUp|.
265 [rwhv_mac_->cocoa_view() keyEvent:
266 cocoa_test_event_utils::KeyEventWithKeyCode(53, 27, NSKeyUp, 0)];
267 EXPECT_FALSE([rwhv_mac_->cocoa_view() suppressNextEscapeKeyUp]);
270 // Test that command accelerators which destroy the fullscreen window
271 // don't crash when forwarded via the window's responder machinery.
272 TEST_F(RenderWidgetHostViewMacTest, AcceleratorDestroy) {
273 // Use our own RWH since we need to destroy it.
274 MockRenderWidgetHostDelegate delegate;
275 TestBrowserContext browser_context;
276 MockRenderProcessHost* process_host =
277 new MockRenderProcessHost(&browser_context);
278 // Owned by its |cocoa_view()|.
279 RenderWidgetHostImpl* rwh = new RenderWidgetHostImpl(
280 &delegate, process_host, MSG_ROUTING_NONE, false);
281 RenderWidgetHostViewMac* view = new RenderWidgetHostViewMac(rwh, false);
283 view->InitAsFullscreen(rwhv_mac_);
285 WindowedNotificationObserver observer(
286 NOTIFICATION_RENDER_WIDGET_HOST_DESTROYED,
287 Source<RenderWidgetHost>(rwh));
289 // Command-ESC will destroy the view, while the window is still in
290 // |-performKeyEquivalent:|. There are other cases where this can
291 // happen, Command-ESC is the easiest to trigger.
292 [[view->cocoa_view() window] performKeyEquivalent:
293 cocoa_test_event_utils::KeyEventWithKeyCode(
294 53, 27, NSKeyDown, NSCommandKeyMask)];
298 TEST_F(RenderWidgetHostViewMacTest, GetFirstRectForCharacterRangeCaretCase) {
299 const base::string16 kDummyString = base::UTF8ToUTF16("hogehoge");
300 const size_t kDummyOffset = 0;
302 gfx::Rect caret_rect(10, 11, 0, 10);
303 gfx::Range caret_range(0, 0);
304 ViewHostMsg_SelectionBounds_Params params;
307 NSRange actual_range;
308 rwhv_mac_->SelectionChanged(kDummyString, kDummyOffset, caret_range);
309 params.anchor_rect = params.focus_rect = caret_rect;
310 params.anchor_dir = params.focus_dir = blink::WebTextDirectionLeftToRight;
311 rwhv_mac_->SelectionBoundsChanged(params);
312 EXPECT_TRUE(rwhv_mac_->GetCachedFirstRectForCharacterRange(
313 caret_range.ToNSRange(),
316 EXPECT_EQ(caret_rect, gfx::Rect(NSRectToCGRect(rect)));
317 EXPECT_EQ(caret_range, gfx::Range(actual_range));
319 EXPECT_FALSE(rwhv_mac_->GetCachedFirstRectForCharacterRange(
320 gfx::Range(0, 1).ToNSRange(),
323 EXPECT_FALSE(rwhv_mac_->GetCachedFirstRectForCharacterRange(
324 gfx::Range(1, 1).ToNSRange(),
327 EXPECT_FALSE(rwhv_mac_->GetCachedFirstRectForCharacterRange(
328 gfx::Range(2, 3).ToNSRange(),
333 caret_rect = gfx::Rect(20, 11, 0, 10);
334 caret_range = gfx::Range(1, 1);
335 params.anchor_rect = params.focus_rect = caret_rect;
336 rwhv_mac_->SelectionChanged(kDummyString, kDummyOffset, caret_range);
337 rwhv_mac_->SelectionBoundsChanged(params);
338 EXPECT_TRUE(rwhv_mac_->GetCachedFirstRectForCharacterRange(
339 caret_range.ToNSRange(),
342 EXPECT_EQ(caret_rect, gfx::Rect(NSRectToCGRect(rect)));
343 EXPECT_EQ(caret_range, gfx::Range(actual_range));
345 EXPECT_FALSE(rwhv_mac_->GetCachedFirstRectForCharacterRange(
346 gfx::Range(0, 0).ToNSRange(),
349 EXPECT_FALSE(rwhv_mac_->GetCachedFirstRectForCharacterRange(
350 gfx::Range(1, 2).ToNSRange(),
353 EXPECT_FALSE(rwhv_mac_->GetCachedFirstRectForCharacterRange(
354 gfx::Range(2, 3).ToNSRange(),
359 caret_range = gfx::Range(1, 2);
360 rwhv_mac_->SelectionChanged(kDummyString, kDummyOffset, caret_range);
361 params.anchor_rect = caret_rect;
362 params.focus_rect = gfx::Rect(30, 11, 0, 10);
363 rwhv_mac_->SelectionBoundsChanged(params);
364 EXPECT_FALSE(rwhv_mac_->GetCachedFirstRectForCharacterRange(
365 gfx::Range(0, 0).ToNSRange(),
368 EXPECT_FALSE(rwhv_mac_->GetCachedFirstRectForCharacterRange(
369 gfx::Range(0, 1).ToNSRange(),
372 EXPECT_FALSE(rwhv_mac_->GetCachedFirstRectForCharacterRange(
373 gfx::Range(1, 1).ToNSRange(),
376 EXPECT_FALSE(rwhv_mac_->GetCachedFirstRectForCharacterRange(
377 gfx::Range(1, 2).ToNSRange(),
380 EXPECT_FALSE(rwhv_mac_->GetCachedFirstRectForCharacterRange(
381 gfx::Range(2, 2).ToNSRange(),
386 TEST_F(RenderWidgetHostViewMacTest, UpdateCompositionSinglelineCase) {
387 const gfx::Point kOrigin(10, 11);
388 const gfx::Size kBoundsUnit(10, 20);
391 // Make sure not crashing by passing NULL pointer instead of |actual_range|.
392 EXPECT_FALSE(rwhv_mac_->GetCachedFirstRectForCharacterRange(
393 gfx::Range(0, 0).ToNSRange(),
397 // If there are no update from renderer, always returned caret position.
398 NSRange actual_range;
399 EXPECT_FALSE(rwhv_mac_->GetCachedFirstRectForCharacterRange(
400 gfx::Range(0, 0).ToNSRange(),
403 EXPECT_FALSE(rwhv_mac_->GetCachedFirstRectForCharacterRange(
404 gfx::Range(0, 1).ToNSRange(),
407 EXPECT_FALSE(rwhv_mac_->GetCachedFirstRectForCharacterRange(
408 gfx::Range(1, 0).ToNSRange(),
411 EXPECT_FALSE(rwhv_mac_->GetCachedFirstRectForCharacterRange(
412 gfx::Range(1, 1).ToNSRange(),
415 EXPECT_FALSE(rwhv_mac_->GetCachedFirstRectForCharacterRange(
416 gfx::Range(1, 2).ToNSRange(),
420 // If the firstRectForCharacterRange is failed in renderer, empty rect vector
421 // is sent. Make sure this does not crash.
422 rwhv_mac_->ImeCompositionRangeChanged(gfx::Range(10, 12),
423 std::vector<gfx::Rect>());
424 EXPECT_FALSE(rwhv_mac_->GetCachedFirstRectForCharacterRange(
425 gfx::Range(10, 11).ToNSRange(),
429 const int kCompositionLength = 10;
430 std::vector<gfx::Rect> composition_bounds;
431 const int kCompositionStart = 3;
432 const gfx::Range kCompositionRange(kCompositionStart,
433 kCompositionStart + kCompositionLength);
434 GenerateCompositionRectArray(kOrigin,
437 std::vector<size_t>(),
438 &composition_bounds);
439 rwhv_mac_->ImeCompositionRangeChanged(kCompositionRange, composition_bounds);
441 // Out of range requests will return caret position.
442 EXPECT_FALSE(rwhv_mac_->GetCachedFirstRectForCharacterRange(
443 gfx::Range(0, 0).ToNSRange(),
446 EXPECT_FALSE(rwhv_mac_->GetCachedFirstRectForCharacterRange(
447 gfx::Range(1, 1).ToNSRange(),
450 EXPECT_FALSE(rwhv_mac_->GetCachedFirstRectForCharacterRange(
451 gfx::Range(1, 2).ToNSRange(),
454 EXPECT_FALSE(rwhv_mac_->GetCachedFirstRectForCharacterRange(
455 gfx::Range(2, 2).ToNSRange(),
458 EXPECT_FALSE(rwhv_mac_->GetCachedFirstRectForCharacterRange(
459 gfx::Range(13, 14).ToNSRange(),
462 EXPECT_FALSE(rwhv_mac_->GetCachedFirstRectForCharacterRange(
463 gfx::Range(14, 15).ToNSRange(),
467 for (int i = 0; i <= kCompositionLength; ++i) {
468 for (int j = 0; j <= kCompositionLength - i; ++j) {
469 const gfx::Range range(i, i + j);
470 const gfx::Rect expected_rect = GetExpectedRect(kOrigin,
474 const NSRange request_range = gfx::Range(
475 kCompositionStart + range.start(),
476 kCompositionStart + range.end()).ToNSRange();
477 EXPECT_TRUE(rwhv_mac_->GetCachedFirstRectForCharacterRange(
481 EXPECT_EQ(gfx::Range(request_range), gfx::Range(actual_range));
482 EXPECT_EQ(expected_rect, gfx::Rect(NSRectToCGRect(rect)));
484 // Make sure not crashing by passing NULL pointer instead of
486 EXPECT_TRUE(rwhv_mac_->GetCachedFirstRectForCharacterRange(
494 TEST_F(RenderWidgetHostViewMacTest, UpdateCompositionMultilineCase) {
495 const gfx::Point kOrigin(10, 11);
496 const gfx::Size kBoundsUnit(10, 20);
499 const int kCompositionLength = 30;
500 std::vector<gfx::Rect> composition_bounds;
501 const gfx::Range kCompositionRange(0, kCompositionLength);
502 // Set breaking point at 10 and 20.
503 std::vector<size_t> break_points;
504 break_points.push_back(10);
505 break_points.push_back(20);
506 GenerateCompositionRectArray(kOrigin,
510 &composition_bounds);
511 rwhv_mac_->ImeCompositionRangeChanged(kCompositionRange, composition_bounds);
513 // Range doesn't contain line breaking point.
515 range = gfx::Range(5, 8);
516 NSRange actual_range;
517 EXPECT_TRUE(rwhv_mac_->GetCachedFirstRectForCharacterRange(range.ToNSRange(),
520 EXPECT_EQ(range, gfx::Range(actual_range));
522 GetExpectedRect(kOrigin, kBoundsUnit, range, 0),
523 gfx::Rect(NSRectToCGRect(rect)));
524 range = gfx::Range(15, 18);
525 EXPECT_TRUE(rwhv_mac_->GetCachedFirstRectForCharacterRange(range.ToNSRange(),
528 EXPECT_EQ(range, gfx::Range(actual_range));
530 GetExpectedRect(kOrigin, kBoundsUnit, gfx::Range(5, 8), 1),
531 gfx::Rect(NSRectToCGRect(rect)));
532 range = gfx::Range(25, 28);
533 EXPECT_TRUE(rwhv_mac_->GetCachedFirstRectForCharacterRange(range.ToNSRange(),
536 EXPECT_EQ(range, gfx::Range(actual_range));
538 GetExpectedRect(kOrigin, kBoundsUnit, gfx::Range(5, 8), 2),
539 gfx::Rect(NSRectToCGRect(rect)));
541 // Range contains line breaking point.
542 range = gfx::Range(8, 12);
543 EXPECT_TRUE(rwhv_mac_->GetCachedFirstRectForCharacterRange(range.ToNSRange(),
546 EXPECT_EQ(gfx::Range(8, 10), gfx::Range(actual_range));
548 GetExpectedRect(kOrigin, kBoundsUnit, gfx::Range(8, 10), 0),
549 gfx::Rect(NSRectToCGRect(rect)));
550 range = gfx::Range(18, 22);
551 EXPECT_TRUE(rwhv_mac_->GetCachedFirstRectForCharacterRange(range.ToNSRange(),
554 EXPECT_EQ(gfx::Range(18, 20), gfx::Range(actual_range));
556 GetExpectedRect(kOrigin, kBoundsUnit, gfx::Range(8, 10), 1),
557 gfx::Rect(NSRectToCGRect(rect)));
559 // Start point is line breaking point.
560 range = gfx::Range(10, 12);
561 EXPECT_TRUE(rwhv_mac_->GetCachedFirstRectForCharacterRange(range.ToNSRange(),
564 EXPECT_EQ(gfx::Range(10, 12), gfx::Range(actual_range));
566 GetExpectedRect(kOrigin, kBoundsUnit, gfx::Range(0, 2), 1),
567 gfx::Rect(NSRectToCGRect(rect)));
568 range = gfx::Range(20, 22);
569 EXPECT_TRUE(rwhv_mac_->GetCachedFirstRectForCharacterRange(range.ToNSRange(),
572 EXPECT_EQ(gfx::Range(20, 22), gfx::Range(actual_range));
574 GetExpectedRect(kOrigin, kBoundsUnit, gfx::Range(0, 2), 2),
575 gfx::Rect(NSRectToCGRect(rect)));
577 // End point is line breaking point.
578 range = gfx::Range(5, 10);
579 EXPECT_TRUE(rwhv_mac_->GetCachedFirstRectForCharacterRange(range.ToNSRange(),
582 EXPECT_EQ(gfx::Range(5, 10), gfx::Range(actual_range));
584 GetExpectedRect(kOrigin, kBoundsUnit, gfx::Range(5, 10), 0),
585 gfx::Rect(NSRectToCGRect(rect)));
586 range = gfx::Range(15, 20);
587 EXPECT_TRUE(rwhv_mac_->GetCachedFirstRectForCharacterRange(range.ToNSRange(),
590 EXPECT_EQ(gfx::Range(15, 20), gfx::Range(actual_range));
592 GetExpectedRect(kOrigin, kBoundsUnit, gfx::Range(5, 10), 1),
593 gfx::Rect(NSRectToCGRect(rect)));
595 // Start and end point are same line breaking point.
596 range = gfx::Range(10, 10);
597 EXPECT_TRUE(rwhv_mac_->GetCachedFirstRectForCharacterRange(range.ToNSRange(),
600 EXPECT_EQ(gfx::Range(10, 10), gfx::Range(actual_range));
602 GetExpectedRect(kOrigin, kBoundsUnit, gfx::Range(0, 0), 1),
603 gfx::Rect(NSRectToCGRect(rect)));
604 range = gfx::Range(20, 20);
605 EXPECT_TRUE(rwhv_mac_->GetCachedFirstRectForCharacterRange(range.ToNSRange(),
608 EXPECT_EQ(gfx::Range(20, 20), gfx::Range(actual_range));
610 GetExpectedRect(kOrigin, kBoundsUnit, gfx::Range(0, 0), 2),
611 gfx::Rect(NSRectToCGRect(rect)));
613 // Start and end point are different line breaking point.
614 range = gfx::Range(10, 20);
615 EXPECT_TRUE(rwhv_mac_->GetCachedFirstRectForCharacterRange(range.ToNSRange(),
618 EXPECT_EQ(gfx::Range(10, 20), gfx::Range(actual_range));
620 GetExpectedRect(kOrigin, kBoundsUnit, gfx::Range(0, 10), 1),
621 gfx::Rect(NSRectToCGRect(rect)));
624 // Verify that |SetActive()| calls |RenderWidgetHostImpl::Blur()| and
625 // |RenderWidgetHostImp::Focus()|.
626 TEST_F(RenderWidgetHostViewMacTest, BlurAndFocusOnSetActive) {
627 MockRenderWidgetHostDelegate delegate;
628 TestBrowserContext browser_context;
629 MockRenderProcessHost* process_host =
630 new MockRenderProcessHost(&browser_context);
632 // Owned by its |cocoa_view()|.
633 MockRenderWidgetHostImpl* rwh = new MockRenderWidgetHostImpl(
634 &delegate, process_host, MSG_ROUTING_NONE);
635 RenderWidgetHostViewMac* view = new RenderWidgetHostViewMac(rwh, false);
637 base::scoped_nsobject<CocoaTestHelperWindow> window(
638 [[CocoaTestHelperWindow alloc] init]);
639 [[window contentView] addSubview:view->cocoa_view()];
641 EXPECT_CALL(*rwh, Focus());
642 [window makeFirstResponder:view->cocoa_view()];
643 testing::Mock::VerifyAndClearExpectations(rwh);
645 EXPECT_CALL(*rwh, Blur());
646 view->SetActive(false);
647 testing::Mock::VerifyAndClearExpectations(rwh);
649 EXPECT_CALL(*rwh, Focus());
650 view->SetActive(true);
651 testing::Mock::VerifyAndClearExpectations(rwh);
653 // Unsetting first responder should blur.
654 EXPECT_CALL(*rwh, Blur());
655 [window makeFirstResponder:nil];
656 testing::Mock::VerifyAndClearExpectations(rwh);
658 // |SetActive()| shoud not focus if view is not first responder.
659 EXPECT_CALL(*rwh, Focus()).Times(0);
660 view->SetActive(true);
661 testing::Mock::VerifyAndClearExpectations(rwh);
667 TEST_F(RenderWidgetHostViewMacTest, ScrollWheelEndEventDelivery) {
668 // This tests Lion+ functionality, so don't run the test pre-Lion.
669 if (!base::mac::IsOSLionOrLater())
672 // Initialize the view associated with a MockRenderWidgetHostImpl, rather than
673 // the MockRenderProcessHost that is set up by the test harness which mocks
674 // out |OnMessageReceived()|.
675 TestBrowserContext browser_context;
676 MockRenderProcessHost* process_host =
677 new MockRenderProcessHost(&browser_context);
678 MockRenderWidgetHostDelegate delegate;
679 MockRenderWidgetHostImpl* host = new MockRenderWidgetHostImpl(
680 &delegate, process_host, MSG_ROUTING_NONE);
681 RenderWidgetHostViewMac* view = new RenderWidgetHostViewMac(host, false);
683 // Send an initial wheel event with NSEventPhaseBegan to the view.
684 NSEvent* event1 = MockScrollWheelEventWithPhase(@selector(phaseBegan), 0);
685 [view->cocoa_view() scrollWheel:event1];
686 ASSERT_EQ(1U, process_host->sink().message_count());
688 // Send an ACK for the first wheel event, so that the queue will be flushed.
689 InputHostMsg_HandleInputEvent_ACK_Params ack;
690 ack.type = blink::WebInputEvent::MouseWheel;
691 ack.state = INPUT_EVENT_ACK_STATE_CONSUMED;
692 scoped_ptr<IPC::Message> response(
693 new InputHostMsg_HandleInputEvent_ACK(0, ack));
694 host->OnMessageReceived(*response);
696 // Post the NSEventPhaseEnded wheel event to NSApp and check whether the
697 // render view receives it.
698 NSEvent* event2 = MockScrollWheelEventWithPhase(@selector(phaseEnded), 0);
699 [NSApp postEvent:event2 atStart:NO];
700 base::MessageLoop::current()->RunUntilIdle();
701 ASSERT_EQ(2U, process_host->sink().message_count());
707 TEST_F(RenderWidgetHostViewMacTest, IgnoreEmptyUnhandledWheelEvent) {
708 // This tests Lion+ functionality, so don't run the test pre-Lion.
709 if (!base::mac::IsOSLionOrLater())
712 // Initialize the view associated with a MockRenderWidgetHostImpl, rather than
713 // the MockRenderProcessHost that is set up by the test harness which mocks
714 // out |OnMessageReceived()|.
715 TestBrowserContext browser_context;
716 MockRenderProcessHost* process_host =
717 new MockRenderProcessHost(&browser_context);
718 MockRenderWidgetHostDelegate delegate;
719 MockRenderWidgetHostImpl* host = new MockRenderWidgetHostImpl(
720 &delegate, process_host, MSG_ROUTING_NONE);
721 RenderWidgetHostViewMac* view = new RenderWidgetHostViewMac(host, false);
723 // Add a delegate to the view.
724 base::scoped_nsobject<MockRenderWidgetHostViewMacDelegate> view_delegate(
725 [[MockRenderWidgetHostViewMacDelegate alloc] init]);
726 view->SetDelegate(view_delegate.get());
728 // Send an initial wheel event for scrolling by 3 lines.
729 NSEvent* event1 = MockScrollWheelEventWithPhase(@selector(phaseBegan), 3);
730 [view->cocoa_view() scrollWheel:event1];
731 ASSERT_EQ(1U, process_host->sink().message_count());
732 process_host->sink().ClearMessages();
734 // Indicate that the wheel event was unhandled.
735 InputHostMsg_HandleInputEvent_ACK_Params unhandled_ack;
736 unhandled_ack.type = blink::WebInputEvent::MouseWheel;
737 unhandled_ack.state = INPUT_EVENT_ACK_STATE_NOT_CONSUMED;
738 scoped_ptr<IPC::Message> response1(
739 new InputHostMsg_HandleInputEvent_ACK(0, unhandled_ack));
740 host->OnMessageReceived(*response1);
742 // Check that the view delegate got an unhandled wheel event.
743 ASSERT_EQ(YES, view_delegate.get().unhandledWheelEventReceived);
744 view_delegate.get().unhandledWheelEventReceived = NO;
746 // Send another wheel event, this time for scrolling by 0 lines (empty event).
747 NSEvent* event2 = MockScrollWheelEventWithPhase(@selector(phaseChanged), 0);
748 [view->cocoa_view() scrollWheel:event2];
749 ASSERT_EQ(1U, process_host->sink().message_count());
751 // Indicate that the wheel event was also unhandled.
752 scoped_ptr<IPC::Message> response2(
753 new InputHostMsg_HandleInputEvent_ACK(0, unhandled_ack));
754 host->OnMessageReceived(*response2);
756 // Check that the view delegate ignored the empty unhandled wheel event.
757 ASSERT_EQ(NO, view_delegate.get().unhandledWheelEventReceived);
763 // Tests that when view initiated shutdown happens (i.e. RWHView is deleted
764 // before RWH), we clean up properly and don't leak the RWHVGuest.
765 TEST_F(RenderWidgetHostViewMacTest, GuestViewDoesNotLeak) {
766 MockRenderWidgetHostDelegate delegate;
767 TestBrowserContext browser_context;
768 MockRenderProcessHost* process_host =
769 new MockRenderProcessHost(&browser_context);
771 // Owned by its |cocoa_view()|.
772 MockRenderWidgetHostImpl* rwh = new MockRenderWidgetHostImpl(
773 &delegate, process_host, MSG_ROUTING_NONE);
774 RenderWidgetHostViewMac* view = new RenderWidgetHostViewMac(rwh, true);
776 // Add a delegate to the view.
777 base::scoped_nsobject<MockRenderWidgetHostViewMacDelegate> view_delegate(
778 [[MockRenderWidgetHostViewMacDelegate alloc] init]);
779 view->SetDelegate(view_delegate.get());
781 base::WeakPtr<RenderWidgetHostViewBase> guest_rwhv_weak =
782 (new RenderWidgetHostViewGuest(
783 rwh, NULL, view->GetWeakPtr()))->GetWeakPtr();
785 // Remove the cocoa_view() so |view| also goes away before |rwh|.
787 base::scoped_nsobject<RenderWidgetHostViewCocoa> rwhv_cocoa;
788 rwhv_cocoa.reset([view->cocoa_view() retain]);
795 // Let |guest_rwhv_weak| have a chance to delete itself.
796 base::RunLoop run_loop;
797 content::BrowserThread::PostTask(
798 content::BrowserThread::UI, FROM_HERE, run_loop.QuitClosure());
801 ASSERT_FALSE(guest_rwhv_weak.get());
804 // Tests setting background transparency. See also (disabled on Mac)
805 // RenderWidgetHostTest.Background. This test has some additional checks for
807 TEST_F(RenderWidgetHostViewMacTest, Background) {
808 TestBrowserContext browser_context;
809 MockRenderProcessHost* process_host =
810 new MockRenderProcessHost(&browser_context);
811 MockRenderWidgetHostDelegate delegate;
812 MockRenderWidgetHostImpl* host = new MockRenderWidgetHostImpl(
813 &delegate, process_host, MSG_ROUTING_NONE);
814 RenderWidgetHostViewMac* view = new RenderWidgetHostViewMac(host, false);
816 EXPECT_TRUE(view->GetBackgroundOpaque());
817 EXPECT_TRUE([view->cocoa_view() isOpaque]);
819 view->SetBackgroundColor(SK_ColorTRANSPARENT);
820 EXPECT_FALSE(view->GetBackgroundOpaque());
821 EXPECT_FALSE([view->cocoa_view() isOpaque]);
823 const IPC::Message* set_background;
824 set_background = process_host->sink().GetUniqueMessageMatching(
825 ViewMsg_SetBackgroundOpaque::ID);
826 ASSERT_TRUE(set_background);
827 Tuple<bool> sent_background;
828 ViewMsg_SetBackgroundOpaque::Read(set_background, &sent_background);
829 EXPECT_FALSE(get<0>(sent_background));
831 // Try setting it back.
832 process_host->sink().ClearMessages();
833 view->SetBackgroundColor(SK_ColorWHITE);
834 EXPECT_TRUE(view->GetBackgroundOpaque());
835 EXPECT_TRUE([view->cocoa_view() isOpaque]);
836 set_background = process_host->sink().GetUniqueMessageMatching(
837 ViewMsg_SetBackgroundOpaque::ID);
838 ASSERT_TRUE(set_background);
839 ViewMsg_SetBackgroundOpaque::Read(set_background, &sent_background);
840 EXPECT_TRUE(get<0>(sent_background));
845 } // namespace content