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 <Cocoa/Cocoa.h>
9 #include "base/mac/mac_util.h"
10 #include "base/mac/scoped_nsautorelease_pool.h"
11 #include "base/mac/sdk_forward_declarations.h"
12 #include "base/strings/utf_string_conversions.h"
13 #include "content/browser/browser_thread_impl.h"
14 #include "content/browser/compositor/test/no_transport_image_transport_factory.h"
15 #include "content/browser/frame_host/render_widget_host_view_guest.h"
16 #include "content/browser/gpu/compositor_util.h"
17 #include "content/browser/renderer_host/render_widget_host_delegate.h"
18 #include "content/common/gpu/gpu_messages.h"
19 #include "content/common/input_messages.h"
20 #include "content/common/view_messages.h"
21 #include "content/public/browser/notification_types.h"
22 #include "content/public/browser/render_widget_host_view_mac_delegate.h"
23 #include "content/public/test/mock_render_process_host.h"
24 #include "content/public/test/test_browser_context.h"
25 #include "content/public/test/test_utils.h"
26 #include "content/test/test_render_view_host.h"
27 #include "testing/gmock/include/gmock/gmock.h"
28 #include "testing/gtest/include/gtest/gtest.h"
29 #import "third_party/ocmock/OCMock/OCMock.h"
30 #import "third_party/ocmock/ocmock_extensions.h"
31 #include "ui/events/test/cocoa_test_event_utils.h"
32 #import "ui/gfx/test/ui_cocoa_test_helper.h"
34 // Helper class with methods used to mock -[NSEvent phase], used by
35 // |MockScrollWheelEventWithPhase()|.
36 @interface MockPhaseMethods : NSObject {
39 - (NSEventPhase)phaseBegan;
40 - (NSEventPhase)phaseChanged;
41 - (NSEventPhase)phaseEnded;
44 @implementation MockPhaseMethods
46 - (NSEventPhase)phaseBegan {
47 return NSEventPhaseBegan;
49 - (NSEventPhase)phaseChanged {
50 return NSEventPhaseChanged;
52 - (NSEventPhase)phaseEnded {
53 return NSEventPhaseEnded;
58 @interface MockRenderWidgetHostViewMacDelegate
59 : NSObject<RenderWidgetHostViewMacDelegate> {
60 BOOL unhandledWheelEventReceived_;
63 @property(nonatomic) BOOL unhandledWheelEventReceived;
67 @implementation MockRenderWidgetHostViewMacDelegate
69 @synthesize unhandledWheelEventReceived = unhandledWheelEventReceived_;
71 - (void)rendererHandledWheelEvent:(const blink::WebMouseWheelEvent&)event
72 consumed:(BOOL)consumed {
74 unhandledWheelEventReceived_ = true;
76 - (void)touchesBeganWithEvent:(NSEvent*)event {}
77 - (void)touchesMovedWithEvent:(NSEvent*)event {}
78 - (void)touchesCancelledWithEvent:(NSEvent*)event {}
79 - (void)touchesEndedWithEvent:(NSEvent*)event {}
80 - (void)beginGestureWithEvent:(NSEvent*)event {}
81 - (void)endGestureWithEvent:(NSEvent*)event {}
82 - (BOOL)canRubberbandLeft:(NSView*)view {
85 - (BOOL)canRubberbandRight:(NSView*)view {
95 id MockGestureEvent(NSEventType type, double magnification) {
96 id event = [OCMockObject mockForClass:[NSEvent class]];
97 NSPoint locationInWindow = NSMakePoint(0, 0);
100 NSTimeInterval timestamp = 1;
101 NSUInteger modifierFlags = 0;
103 [(NSEvent*)[[event stub] andReturnValue:OCMOCK_VALUE(type)] type];
104 [(NSEvent*)[[event stub]
105 andReturnValue:OCMOCK_VALUE(locationInWindow)] locationInWindow];
106 [(NSEvent*)[[event stub] andReturnValue:OCMOCK_VALUE(deltaX)] deltaX];
107 [(NSEvent*)[[event stub] andReturnValue:OCMOCK_VALUE(deltaY)] deltaY];
108 [(NSEvent*)[[event stub] andReturnValue:OCMOCK_VALUE(timestamp)] timestamp];
109 [(NSEvent*)[[event stub]
110 andReturnValue:OCMOCK_VALUE(modifierFlags)] modifierFlags];
111 [(NSEvent*)[[event stub]
112 andReturnValue:OCMOCK_VALUE(magnification)] magnification];
116 class MockRenderWidgetHostDelegate : public RenderWidgetHostDelegate {
118 MockRenderWidgetHostDelegate() {}
119 ~MockRenderWidgetHostDelegate() override {}
122 void Cut() override {}
123 void Copy() override {}
124 void Paste() override {}
125 void SelectAll() override {}
128 class MockRenderWidgetHostImpl : public RenderWidgetHostImpl {
130 MockRenderWidgetHostImpl(RenderWidgetHostDelegate* delegate,
131 RenderProcessHost* process,
133 : RenderWidgetHostImpl(delegate, process, routing_id, false) {
136 MOCK_METHOD0(Focus, void());
137 MOCK_METHOD0(Blur, void());
140 // Generates the |length| of composition rectangle vector and save them to
141 // |output|. It starts from |origin| and each rectangle contains |unit_size|.
142 void GenerateCompositionRectArray(const gfx::Point& origin,
143 const gfx::Size& unit_size,
145 const std::vector<size_t>& break_points,
146 std::vector<gfx::Rect>* output) {
150 std::queue<int> break_point_queue;
151 for (size_t i = 0; i < break_points.size(); ++i)
152 break_point_queue.push(break_points[i]);
153 break_point_queue.push(length);
154 size_t next_break_point = break_point_queue.front();
155 break_point_queue.pop();
157 gfx::Rect current_rect(origin, unit_size);
158 for (size_t i = 0; i < length; ++i) {
159 if (i == next_break_point) {
160 current_rect.set_x(origin.x());
161 current_rect.set_y(current_rect.y() + current_rect.height());
162 next_break_point = break_point_queue.front();
163 break_point_queue.pop();
165 output->push_back(current_rect);
166 current_rect.set_x(current_rect.right());
170 gfx::Rect GetExpectedRect(const gfx::Point& origin,
171 const gfx::Size& size,
172 const gfx::Range& range,
175 origin.x() + range.start() * size.width(),
176 origin.y() + line_no * size.height(),
177 range.length() * size.width(),
181 // Returns NSScrollWheel event that mocks -phase. |mockPhaseSelector| should
182 // correspond to a method in |MockPhaseMethods| that returns the desired phase.
183 NSEvent* MockScrollWheelEventWithPhase(SEL mockPhaseSelector, int32_t delta) {
184 CGEventRef cg_event =
185 CGEventCreateScrollWheelEvent(NULL, kCGScrollEventUnitLine, 1, delta, 0);
186 NSEvent* event = [NSEvent eventWithCGEvent:cg_event];
188 method_setImplementation(
189 class_getInstanceMethod([NSEvent class], @selector(phase)),
190 [MockPhaseMethods instanceMethodForSelector:mockPhaseSelector]);
196 class RenderWidgetHostViewMacTest : public RenderViewHostImplTestHarness {
198 RenderWidgetHostViewMacTest() : old_rwhv_(NULL), rwhv_mac_(NULL) {}
200 void SetUp() override {
201 RenderViewHostImplTestHarness::SetUp();
202 if (IsDelegatedRendererEnabled()) {
203 ImageTransportFactory::InitializeForUnitTests(
204 scoped_ptr<ImageTransportFactory>(
205 new NoTransportImageTransportFactory));
208 // TestRenderViewHost's destruction assumes that its view is a
209 // TestRenderWidgetHostView, so store its view and reset it back to the
210 // stored view in |TearDown()|.
211 old_rwhv_ = rvh()->GetView();
213 // Owned by its |cocoa_view()|, i.e. |rwhv_cocoa_|.
214 rwhv_mac_ = new RenderWidgetHostViewMac(rvh(), false);
215 rwhv_cocoa_.reset([rwhv_mac_->cocoa_view() retain]);
217 void TearDown() override {
218 // Make sure the rwhv_mac_ is gone once the superclass's |TearDown()| runs.
222 // See comment in SetUp().
223 test_rvh()->SetView(static_cast<RenderWidgetHostViewBase*>(old_rwhv_));
225 if (IsDelegatedRendererEnabled())
226 ImageTransportFactory::Terminate();
227 RenderViewHostImplTestHarness::TearDown();
230 void RecycleAndWait() {
232 base::MessageLoop::current()->RunUntilIdle();
237 // This class isn't derived from PlatformTest.
238 base::mac::ScopedNSAutoreleasePool pool_;
240 RenderWidgetHostView* old_rwhv_;
243 RenderWidgetHostViewMac* rwhv_mac_;
244 base::scoped_nsobject<RenderWidgetHostViewCocoa> rwhv_cocoa_;
247 DISALLOW_COPY_AND_ASSIGN(RenderWidgetHostViewMacTest);
250 TEST_F(RenderWidgetHostViewMacTest, Basic) {
253 TEST_F(RenderWidgetHostViewMacTest, AcceptsFirstResponder) {
254 // The RWHVCocoa should normally accept first responder status.
255 EXPECT_TRUE([rwhv_cocoa_.get() acceptsFirstResponder]);
258 TEST_F(RenderWidgetHostViewMacTest, Fullscreen) {
259 rwhv_mac_->InitAsFullscreen(NULL);
260 EXPECT_TRUE(rwhv_mac_->pepper_fullscreen_window());
262 // Break the reference cycle caused by pepper_fullscreen_window() without
263 // an <esc> event. See comment in
264 // release_pepper_fullscreen_window_for_testing().
265 rwhv_mac_->release_pepper_fullscreen_window_for_testing();
268 // Verify that escape key down in fullscreen mode suppressed the keyup event on
270 TEST_F(RenderWidgetHostViewMacTest, FullscreenCloseOnEscape) {
271 // Use our own RWH since we need to destroy it.
272 MockRenderWidgetHostDelegate delegate;
273 TestBrowserContext browser_context;
274 MockRenderProcessHost* process_host =
275 new MockRenderProcessHost(&browser_context);
276 // Owned by its |cocoa_view()|.
277 RenderWidgetHostImpl* rwh = new RenderWidgetHostImpl(
278 &delegate, process_host, MSG_ROUTING_NONE, false);
279 RenderWidgetHostViewMac* view = new RenderWidgetHostViewMac(rwh, false);
281 view->InitAsFullscreen(rwhv_mac_);
283 WindowedNotificationObserver observer(
284 NOTIFICATION_RENDER_WIDGET_HOST_DESTROYED,
285 Source<RenderWidgetHost>(rwh));
286 EXPECT_FALSE([rwhv_mac_->cocoa_view() suppressNextEscapeKeyUp]);
288 // Escape key down. Should close window and set |suppressNextEscapeKeyUp| on
290 [view->cocoa_view() keyEvent:
291 cocoa_test_event_utils::KeyEventWithKeyCode(53, 27, NSKeyDown, 0)];
293 EXPECT_TRUE([rwhv_mac_->cocoa_view() suppressNextEscapeKeyUp]);
295 // Escape key up on the parent should clear |suppressNextEscapeKeyUp|.
296 [rwhv_mac_->cocoa_view() keyEvent:
297 cocoa_test_event_utils::KeyEventWithKeyCode(53, 27, NSKeyUp, 0)];
298 EXPECT_FALSE([rwhv_mac_->cocoa_view() suppressNextEscapeKeyUp]);
301 // Test that command accelerators which destroy the fullscreen window
302 // don't crash when forwarded via the window's responder machinery.
303 TEST_F(RenderWidgetHostViewMacTest, AcceleratorDestroy) {
304 // Use our own RWH since we need to destroy it.
305 MockRenderWidgetHostDelegate delegate;
306 TestBrowserContext browser_context;
307 MockRenderProcessHost* process_host =
308 new MockRenderProcessHost(&browser_context);
309 // Owned by its |cocoa_view()|.
310 RenderWidgetHostImpl* rwh = new RenderWidgetHostImpl(
311 &delegate, process_host, MSG_ROUTING_NONE, false);
312 RenderWidgetHostViewMac* view = new RenderWidgetHostViewMac(rwh, false);
314 view->InitAsFullscreen(rwhv_mac_);
316 WindowedNotificationObserver observer(
317 NOTIFICATION_RENDER_WIDGET_HOST_DESTROYED,
318 Source<RenderWidgetHost>(rwh));
320 // Command-ESC will destroy the view, while the window is still in
321 // |-performKeyEquivalent:|. There are other cases where this can
322 // happen, Command-ESC is the easiest to trigger.
323 [[view->cocoa_view() window] performKeyEquivalent:
324 cocoa_test_event_utils::KeyEventWithKeyCode(
325 53, 27, NSKeyDown, NSCommandKeyMask)];
329 TEST_F(RenderWidgetHostViewMacTest, GetFirstRectForCharacterRangeCaretCase) {
330 const base::string16 kDummyString = base::UTF8ToUTF16("hogehoge");
331 const size_t kDummyOffset = 0;
333 gfx::Rect caret_rect(10, 11, 0, 10);
334 gfx::Range caret_range(0, 0);
335 ViewHostMsg_SelectionBounds_Params params;
338 NSRange actual_range;
339 rwhv_mac_->SelectionChanged(kDummyString, kDummyOffset, caret_range);
340 params.anchor_rect = params.focus_rect = caret_rect;
341 params.anchor_dir = params.focus_dir = blink::WebTextDirectionLeftToRight;
342 rwhv_mac_->SelectionBoundsChanged(params);
343 EXPECT_TRUE(rwhv_mac_->GetCachedFirstRectForCharacterRange(
344 caret_range.ToNSRange(),
347 EXPECT_EQ(caret_rect, gfx::Rect(NSRectToCGRect(rect)));
348 EXPECT_EQ(caret_range, gfx::Range(actual_range));
350 EXPECT_FALSE(rwhv_mac_->GetCachedFirstRectForCharacterRange(
351 gfx::Range(0, 1).ToNSRange(),
354 EXPECT_FALSE(rwhv_mac_->GetCachedFirstRectForCharacterRange(
355 gfx::Range(1, 1).ToNSRange(),
358 EXPECT_FALSE(rwhv_mac_->GetCachedFirstRectForCharacterRange(
359 gfx::Range(2, 3).ToNSRange(),
364 caret_rect = gfx::Rect(20, 11, 0, 10);
365 caret_range = gfx::Range(1, 1);
366 params.anchor_rect = params.focus_rect = caret_rect;
367 rwhv_mac_->SelectionChanged(kDummyString, kDummyOffset, caret_range);
368 rwhv_mac_->SelectionBoundsChanged(params);
369 EXPECT_TRUE(rwhv_mac_->GetCachedFirstRectForCharacterRange(
370 caret_range.ToNSRange(),
373 EXPECT_EQ(caret_rect, gfx::Rect(NSRectToCGRect(rect)));
374 EXPECT_EQ(caret_range, gfx::Range(actual_range));
376 EXPECT_FALSE(rwhv_mac_->GetCachedFirstRectForCharacterRange(
377 gfx::Range(0, 0).ToNSRange(),
380 EXPECT_FALSE(rwhv_mac_->GetCachedFirstRectForCharacterRange(
381 gfx::Range(1, 2).ToNSRange(),
384 EXPECT_FALSE(rwhv_mac_->GetCachedFirstRectForCharacterRange(
385 gfx::Range(2, 3).ToNSRange(),
390 caret_range = gfx::Range(1, 2);
391 rwhv_mac_->SelectionChanged(kDummyString, kDummyOffset, caret_range);
392 params.anchor_rect = caret_rect;
393 params.focus_rect = gfx::Rect(30, 11, 0, 10);
394 rwhv_mac_->SelectionBoundsChanged(params);
395 EXPECT_FALSE(rwhv_mac_->GetCachedFirstRectForCharacterRange(
396 gfx::Range(0, 0).ToNSRange(),
399 EXPECT_FALSE(rwhv_mac_->GetCachedFirstRectForCharacterRange(
400 gfx::Range(0, 1).ToNSRange(),
403 EXPECT_FALSE(rwhv_mac_->GetCachedFirstRectForCharacterRange(
404 gfx::Range(1, 1).ToNSRange(),
407 EXPECT_FALSE(rwhv_mac_->GetCachedFirstRectForCharacterRange(
408 gfx::Range(1, 2).ToNSRange(),
411 EXPECT_FALSE(rwhv_mac_->GetCachedFirstRectForCharacterRange(
412 gfx::Range(2, 2).ToNSRange(),
417 TEST_F(RenderWidgetHostViewMacTest, UpdateCompositionSinglelineCase) {
418 const gfx::Point kOrigin(10, 11);
419 const gfx::Size kBoundsUnit(10, 20);
422 // Make sure not crashing by passing NULL pointer instead of |actual_range|.
423 EXPECT_FALSE(rwhv_mac_->GetCachedFirstRectForCharacterRange(
424 gfx::Range(0, 0).ToNSRange(),
428 // If there are no update from renderer, always returned caret position.
429 NSRange actual_range;
430 EXPECT_FALSE(rwhv_mac_->GetCachedFirstRectForCharacterRange(
431 gfx::Range(0, 0).ToNSRange(),
434 EXPECT_FALSE(rwhv_mac_->GetCachedFirstRectForCharacterRange(
435 gfx::Range(0, 1).ToNSRange(),
438 EXPECT_FALSE(rwhv_mac_->GetCachedFirstRectForCharacterRange(
439 gfx::Range(1, 0).ToNSRange(),
442 EXPECT_FALSE(rwhv_mac_->GetCachedFirstRectForCharacterRange(
443 gfx::Range(1, 1).ToNSRange(),
446 EXPECT_FALSE(rwhv_mac_->GetCachedFirstRectForCharacterRange(
447 gfx::Range(1, 2).ToNSRange(),
451 // If the firstRectForCharacterRange is failed in renderer, empty rect vector
452 // is sent. Make sure this does not crash.
453 rwhv_mac_->ImeCompositionRangeChanged(gfx::Range(10, 12),
454 std::vector<gfx::Rect>());
455 EXPECT_FALSE(rwhv_mac_->GetCachedFirstRectForCharacterRange(
456 gfx::Range(10, 11).ToNSRange(),
460 const int kCompositionLength = 10;
461 std::vector<gfx::Rect> composition_bounds;
462 const int kCompositionStart = 3;
463 const gfx::Range kCompositionRange(kCompositionStart,
464 kCompositionStart + kCompositionLength);
465 GenerateCompositionRectArray(kOrigin,
468 std::vector<size_t>(),
469 &composition_bounds);
470 rwhv_mac_->ImeCompositionRangeChanged(kCompositionRange, composition_bounds);
472 // Out of range requests will return caret position.
473 EXPECT_FALSE(rwhv_mac_->GetCachedFirstRectForCharacterRange(
474 gfx::Range(0, 0).ToNSRange(),
477 EXPECT_FALSE(rwhv_mac_->GetCachedFirstRectForCharacterRange(
478 gfx::Range(1, 1).ToNSRange(),
481 EXPECT_FALSE(rwhv_mac_->GetCachedFirstRectForCharacterRange(
482 gfx::Range(1, 2).ToNSRange(),
485 EXPECT_FALSE(rwhv_mac_->GetCachedFirstRectForCharacterRange(
486 gfx::Range(2, 2).ToNSRange(),
489 EXPECT_FALSE(rwhv_mac_->GetCachedFirstRectForCharacterRange(
490 gfx::Range(13, 14).ToNSRange(),
493 EXPECT_FALSE(rwhv_mac_->GetCachedFirstRectForCharacterRange(
494 gfx::Range(14, 15).ToNSRange(),
498 for (int i = 0; i <= kCompositionLength; ++i) {
499 for (int j = 0; j <= kCompositionLength - i; ++j) {
500 const gfx::Range range(i, i + j);
501 const gfx::Rect expected_rect = GetExpectedRect(kOrigin,
505 const NSRange request_range = gfx::Range(
506 kCompositionStart + range.start(),
507 kCompositionStart + range.end()).ToNSRange();
508 EXPECT_TRUE(rwhv_mac_->GetCachedFirstRectForCharacterRange(
512 EXPECT_EQ(gfx::Range(request_range), gfx::Range(actual_range));
513 EXPECT_EQ(expected_rect, gfx::Rect(NSRectToCGRect(rect)));
515 // Make sure not crashing by passing NULL pointer instead of
517 EXPECT_TRUE(rwhv_mac_->GetCachedFirstRectForCharacterRange(
525 TEST_F(RenderWidgetHostViewMacTest, UpdateCompositionMultilineCase) {
526 const gfx::Point kOrigin(10, 11);
527 const gfx::Size kBoundsUnit(10, 20);
530 const int kCompositionLength = 30;
531 std::vector<gfx::Rect> composition_bounds;
532 const gfx::Range kCompositionRange(0, kCompositionLength);
533 // Set breaking point at 10 and 20.
534 std::vector<size_t> break_points;
535 break_points.push_back(10);
536 break_points.push_back(20);
537 GenerateCompositionRectArray(kOrigin,
541 &composition_bounds);
542 rwhv_mac_->ImeCompositionRangeChanged(kCompositionRange, composition_bounds);
544 // Range doesn't contain line breaking point.
546 range = gfx::Range(5, 8);
547 NSRange actual_range;
548 EXPECT_TRUE(rwhv_mac_->GetCachedFirstRectForCharacterRange(range.ToNSRange(),
551 EXPECT_EQ(range, gfx::Range(actual_range));
553 GetExpectedRect(kOrigin, kBoundsUnit, range, 0),
554 gfx::Rect(NSRectToCGRect(rect)));
555 range = gfx::Range(15, 18);
556 EXPECT_TRUE(rwhv_mac_->GetCachedFirstRectForCharacterRange(range.ToNSRange(),
559 EXPECT_EQ(range, gfx::Range(actual_range));
561 GetExpectedRect(kOrigin, kBoundsUnit, gfx::Range(5, 8), 1),
562 gfx::Rect(NSRectToCGRect(rect)));
563 range = gfx::Range(25, 28);
564 EXPECT_TRUE(rwhv_mac_->GetCachedFirstRectForCharacterRange(range.ToNSRange(),
567 EXPECT_EQ(range, gfx::Range(actual_range));
569 GetExpectedRect(kOrigin, kBoundsUnit, gfx::Range(5, 8), 2),
570 gfx::Rect(NSRectToCGRect(rect)));
572 // Range contains line breaking point.
573 range = gfx::Range(8, 12);
574 EXPECT_TRUE(rwhv_mac_->GetCachedFirstRectForCharacterRange(range.ToNSRange(),
577 EXPECT_EQ(gfx::Range(8, 10), gfx::Range(actual_range));
579 GetExpectedRect(kOrigin, kBoundsUnit, gfx::Range(8, 10), 0),
580 gfx::Rect(NSRectToCGRect(rect)));
581 range = gfx::Range(18, 22);
582 EXPECT_TRUE(rwhv_mac_->GetCachedFirstRectForCharacterRange(range.ToNSRange(),
585 EXPECT_EQ(gfx::Range(18, 20), gfx::Range(actual_range));
587 GetExpectedRect(kOrigin, kBoundsUnit, gfx::Range(8, 10), 1),
588 gfx::Rect(NSRectToCGRect(rect)));
590 // Start point is line breaking point.
591 range = gfx::Range(10, 12);
592 EXPECT_TRUE(rwhv_mac_->GetCachedFirstRectForCharacterRange(range.ToNSRange(),
595 EXPECT_EQ(gfx::Range(10, 12), gfx::Range(actual_range));
597 GetExpectedRect(kOrigin, kBoundsUnit, gfx::Range(0, 2), 1),
598 gfx::Rect(NSRectToCGRect(rect)));
599 range = gfx::Range(20, 22);
600 EXPECT_TRUE(rwhv_mac_->GetCachedFirstRectForCharacterRange(range.ToNSRange(),
603 EXPECT_EQ(gfx::Range(20, 22), gfx::Range(actual_range));
605 GetExpectedRect(kOrigin, kBoundsUnit, gfx::Range(0, 2), 2),
606 gfx::Rect(NSRectToCGRect(rect)));
608 // End point is line breaking point.
609 range = gfx::Range(5, 10);
610 EXPECT_TRUE(rwhv_mac_->GetCachedFirstRectForCharacterRange(range.ToNSRange(),
613 EXPECT_EQ(gfx::Range(5, 10), gfx::Range(actual_range));
615 GetExpectedRect(kOrigin, kBoundsUnit, gfx::Range(5, 10), 0),
616 gfx::Rect(NSRectToCGRect(rect)));
617 range = gfx::Range(15, 20);
618 EXPECT_TRUE(rwhv_mac_->GetCachedFirstRectForCharacterRange(range.ToNSRange(),
621 EXPECT_EQ(gfx::Range(15, 20), gfx::Range(actual_range));
623 GetExpectedRect(kOrigin, kBoundsUnit, gfx::Range(5, 10), 1),
624 gfx::Rect(NSRectToCGRect(rect)));
626 // Start and end point are same line breaking point.
627 range = gfx::Range(10, 10);
628 EXPECT_TRUE(rwhv_mac_->GetCachedFirstRectForCharacterRange(range.ToNSRange(),
631 EXPECT_EQ(gfx::Range(10, 10), gfx::Range(actual_range));
633 GetExpectedRect(kOrigin, kBoundsUnit, gfx::Range(0, 0), 1),
634 gfx::Rect(NSRectToCGRect(rect)));
635 range = gfx::Range(20, 20);
636 EXPECT_TRUE(rwhv_mac_->GetCachedFirstRectForCharacterRange(range.ToNSRange(),
639 EXPECT_EQ(gfx::Range(20, 20), gfx::Range(actual_range));
641 GetExpectedRect(kOrigin, kBoundsUnit, gfx::Range(0, 0), 2),
642 gfx::Rect(NSRectToCGRect(rect)));
644 // Start and end point are different line breaking point.
645 range = gfx::Range(10, 20);
646 EXPECT_TRUE(rwhv_mac_->GetCachedFirstRectForCharacterRange(range.ToNSRange(),
649 EXPECT_EQ(gfx::Range(10, 20), gfx::Range(actual_range));
651 GetExpectedRect(kOrigin, kBoundsUnit, gfx::Range(0, 10), 1),
652 gfx::Rect(NSRectToCGRect(rect)));
655 // Verify that |SetActive()| calls |RenderWidgetHostImpl::Blur()| and
656 // |RenderWidgetHostImp::Focus()|.
657 TEST_F(RenderWidgetHostViewMacTest, BlurAndFocusOnSetActive) {
658 MockRenderWidgetHostDelegate delegate;
659 TestBrowserContext browser_context;
660 MockRenderProcessHost* process_host =
661 new MockRenderProcessHost(&browser_context);
663 // Owned by its |cocoa_view()|.
664 MockRenderWidgetHostImpl* rwh = new MockRenderWidgetHostImpl(
665 &delegate, process_host, MSG_ROUTING_NONE);
666 RenderWidgetHostViewMac* view = new RenderWidgetHostViewMac(rwh, false);
668 base::scoped_nsobject<CocoaTestHelperWindow> window(
669 [[CocoaTestHelperWindow alloc] init]);
670 [[window contentView] addSubview:view->cocoa_view()];
672 EXPECT_CALL(*rwh, Focus());
673 [window makeFirstResponder:view->cocoa_view()];
674 testing::Mock::VerifyAndClearExpectations(rwh);
676 EXPECT_CALL(*rwh, Blur());
677 view->SetActive(false);
678 testing::Mock::VerifyAndClearExpectations(rwh);
680 EXPECT_CALL(*rwh, Focus());
681 view->SetActive(true);
682 testing::Mock::VerifyAndClearExpectations(rwh);
684 // Unsetting first responder should blur.
685 EXPECT_CALL(*rwh, Blur());
686 [window makeFirstResponder:nil];
687 testing::Mock::VerifyAndClearExpectations(rwh);
689 // |SetActive()| shoud not focus if view is not first responder.
690 EXPECT_CALL(*rwh, Focus()).Times(0);
691 view->SetActive(true);
692 testing::Mock::VerifyAndClearExpectations(rwh);
698 TEST_F(RenderWidgetHostViewMacTest, ScrollWheelEndEventDelivery) {
699 // This tests Lion+ functionality, so don't run the test pre-Lion.
700 if (!base::mac::IsOSLionOrLater())
703 // Initialize the view associated with a MockRenderWidgetHostImpl, rather than
704 // the MockRenderProcessHost that is set up by the test harness which mocks
705 // out |OnMessageReceived()|.
706 TestBrowserContext browser_context;
707 MockRenderProcessHost* process_host =
708 new MockRenderProcessHost(&browser_context);
709 process_host->Init();
710 MockRenderWidgetHostDelegate delegate;
711 MockRenderWidgetHostImpl* host = new MockRenderWidgetHostImpl(
712 &delegate, process_host, MSG_ROUTING_NONE);
713 RenderWidgetHostViewMac* view = new RenderWidgetHostViewMac(host, false);
715 // Send an initial wheel event with NSEventPhaseBegan to the view.
716 NSEvent* event1 = MockScrollWheelEventWithPhase(@selector(phaseBegan), 0);
717 [view->cocoa_view() scrollWheel:event1];
718 ASSERT_EQ(1U, process_host->sink().message_count());
720 // Send an ACK for the first wheel event, so that the queue will be flushed.
721 InputEventAck ack(blink::WebInputEvent::MouseWheel,
722 INPUT_EVENT_ACK_STATE_CONSUMED);
723 scoped_ptr<IPC::Message> response(
724 new InputHostMsg_HandleInputEvent_ACK(0, ack));
725 host->OnMessageReceived(*response);
727 // Post the NSEventPhaseEnded wheel event to NSApp and check whether the
728 // render view receives it.
729 NSEvent* event2 = MockScrollWheelEventWithPhase(@selector(phaseEnded), 0);
730 [NSApp postEvent:event2 atStart:NO];
731 base::MessageLoop::current()->RunUntilIdle();
732 ASSERT_EQ(2U, process_host->sink().message_count());
738 TEST_F(RenderWidgetHostViewMacTest, IgnoreEmptyUnhandledWheelEvent) {
739 // This tests Lion+ functionality, so don't run the test pre-Lion.
740 if (!base::mac::IsOSLionOrLater())
743 // Initialize the view associated with a MockRenderWidgetHostImpl, rather than
744 // the MockRenderProcessHost that is set up by the test harness which mocks
745 // out |OnMessageReceived()|.
746 TestBrowserContext browser_context;
747 MockRenderProcessHost* process_host =
748 new MockRenderProcessHost(&browser_context);
749 process_host->Init();
750 MockRenderWidgetHostDelegate delegate;
751 MockRenderWidgetHostImpl* host = new MockRenderWidgetHostImpl(
752 &delegate, process_host, MSG_ROUTING_NONE);
753 RenderWidgetHostViewMac* view = new RenderWidgetHostViewMac(host, false);
755 // Add a delegate to the view.
756 base::scoped_nsobject<MockRenderWidgetHostViewMacDelegate> view_delegate(
757 [[MockRenderWidgetHostViewMacDelegate alloc] init]);
758 view->SetDelegate(view_delegate.get());
760 // Send an initial wheel event for scrolling by 3 lines.
761 NSEvent* event1 = MockScrollWheelEventWithPhase(@selector(phaseBegan), 3);
762 [view->cocoa_view() scrollWheel:event1];
763 ASSERT_EQ(1U, process_host->sink().message_count());
764 process_host->sink().ClearMessages();
766 // Indicate that the wheel event was unhandled.
767 InputEventAck unhandled_ack(blink::WebInputEvent::MouseWheel,
768 INPUT_EVENT_ACK_STATE_NOT_CONSUMED);
769 scoped_ptr<IPC::Message> response1(
770 new InputHostMsg_HandleInputEvent_ACK(0, unhandled_ack));
771 host->OnMessageReceived(*response1);
773 // Check that the view delegate got an unhandled wheel event.
774 ASSERT_EQ(YES, view_delegate.get().unhandledWheelEventReceived);
775 view_delegate.get().unhandledWheelEventReceived = NO;
777 // Send another wheel event, this time for scrolling by 0 lines (empty event).
778 NSEvent* event2 = MockScrollWheelEventWithPhase(@selector(phaseChanged), 0);
779 [view->cocoa_view() scrollWheel:event2];
780 ASSERT_EQ(1U, process_host->sink().message_count());
782 // Indicate that the wheel event was also unhandled.
783 scoped_ptr<IPC::Message> response2(
784 new InputHostMsg_HandleInputEvent_ACK(0, unhandled_ack));
785 host->OnMessageReceived(*response2);
787 // Check that the view delegate ignored the empty unhandled wheel event.
788 ASSERT_EQ(NO, view_delegate.get().unhandledWheelEventReceived);
794 // Tests that when view initiated shutdown happens (i.e. RWHView is deleted
795 // before RWH), we clean up properly and don't leak the RWHVGuest.
796 TEST_F(RenderWidgetHostViewMacTest, GuestViewDoesNotLeak) {
797 MockRenderWidgetHostDelegate delegate;
798 TestBrowserContext browser_context;
799 MockRenderProcessHost* process_host =
800 new MockRenderProcessHost(&browser_context);
802 // Owned by its |cocoa_view()|.
803 MockRenderWidgetHostImpl* rwh = new MockRenderWidgetHostImpl(
804 &delegate, process_host, MSG_ROUTING_NONE);
805 RenderWidgetHostViewMac* view = new RenderWidgetHostViewMac(rwh, true);
807 // Add a delegate to the view.
808 base::scoped_nsobject<MockRenderWidgetHostViewMacDelegate> view_delegate(
809 [[MockRenderWidgetHostViewMacDelegate alloc] init]);
810 view->SetDelegate(view_delegate.get());
812 base::WeakPtr<RenderWidgetHostViewBase> guest_rwhv_weak =
813 (new RenderWidgetHostViewGuest(
814 rwh, NULL, view->GetWeakPtr()))->GetWeakPtr();
816 // Remove the cocoa_view() so |view| also goes away before |rwh|.
818 base::scoped_nsobject<RenderWidgetHostViewCocoa> rwhv_cocoa;
819 rwhv_cocoa.reset([view->cocoa_view() retain]);
826 // Let |guest_rwhv_weak| have a chance to delete itself.
827 base::RunLoop run_loop;
828 content::BrowserThread::PostTask(
829 content::BrowserThread::UI, FROM_HERE, run_loop.QuitClosure());
832 ASSERT_FALSE(guest_rwhv_weak.get());
835 // Tests setting background transparency. See also (disabled on Mac)
836 // RenderWidgetHostTest.Background. This test has some additional checks for
838 TEST_F(RenderWidgetHostViewMacTest, Background) {
839 TestBrowserContext browser_context;
840 MockRenderProcessHost* process_host =
841 new MockRenderProcessHost(&browser_context);
842 MockRenderWidgetHostDelegate delegate;
843 MockRenderWidgetHostImpl* host = new MockRenderWidgetHostImpl(
844 &delegate, process_host, MSG_ROUTING_NONE);
845 RenderWidgetHostViewMac* view = new RenderWidgetHostViewMac(host, false);
847 EXPECT_TRUE(view->GetBackgroundOpaque());
848 EXPECT_TRUE([view->cocoa_view() isOpaque]);
850 view->SetBackgroundColor(SK_ColorTRANSPARENT);
851 EXPECT_FALSE(view->GetBackgroundOpaque());
852 EXPECT_FALSE([view->cocoa_view() isOpaque]);
854 const IPC::Message* set_background;
855 set_background = process_host->sink().GetUniqueMessageMatching(
856 ViewMsg_SetBackgroundOpaque::ID);
857 ASSERT_TRUE(set_background);
858 base::Tuple<bool> sent_background;
859 ViewMsg_SetBackgroundOpaque::Read(set_background, &sent_background);
860 EXPECT_FALSE(base::get<0>(sent_background));
862 // Try setting it back.
863 process_host->sink().ClearMessages();
864 view->SetBackgroundColor(SK_ColorWHITE);
865 EXPECT_TRUE(view->GetBackgroundOpaque());
866 EXPECT_TRUE([view->cocoa_view() isOpaque]);
867 set_background = process_host->sink().GetUniqueMessageMatching(
868 ViewMsg_SetBackgroundOpaque::ID);
869 ASSERT_TRUE(set_background);
870 ViewMsg_SetBackgroundOpaque::Read(set_background, &sent_background);
871 EXPECT_TRUE(base::get<0>(sent_background));
876 class RenderWidgetHostViewMacPinchTest : public RenderWidgetHostViewMacTest {
878 RenderWidgetHostViewMacPinchTest() : process_host_(NULL) {}
880 bool ZoomDisabledForPinchUpdateMessage() {
881 const IPC::Message* message = NULL;
882 // The first message may be a PinchBegin. Go for the second message if
884 switch (process_host_->sink().message_count()) {
886 message = process_host_->sink().GetMessageAt(0);
889 message = process_host_->sink().GetMessageAt(1);
896 base::Tuple<IPC::WebInputEventPointer, ui::LatencyInfo, bool> data;
897 InputMsg_HandleInputEvent::Read(message, &data);
898 IPC::WebInputEventPointer ipc_event = base::get<0>(data);
899 const blink::WebGestureEvent* gesture_event =
900 static_cast<const blink::WebGestureEvent*>(ipc_event);
901 return gesture_event->data.pinchUpdate.zoomDisabled;
904 MockRenderProcessHost* process_host_;
907 TEST_F(RenderWidgetHostViewMacPinchTest, PinchThresholding) {
908 // This tests Lion+ functionality, so don't run the test pre-Lion.
909 if (!base::mac::IsOSLionOrLater())
912 // Initialize the view associated with a MockRenderWidgetHostImpl, rather than
913 // the MockRenderProcessHost that is set up by the test harness which mocks
914 // out |OnMessageReceived()|.
915 TestBrowserContext browser_context;
916 process_host_ = new MockRenderProcessHost(&browser_context);
917 process_host_->Init();
918 MockRenderWidgetHostDelegate delegate;
919 MockRenderWidgetHostImpl* host = new MockRenderWidgetHostImpl(
920 &delegate, process_host_, MSG_ROUTING_NONE);
921 RenderWidgetHostViewMac* view = new RenderWidgetHostViewMac(host, false);
923 // We'll use this IPC message to ack events.
924 InputEventAck ack(blink::WebInputEvent::GesturePinchUpdate,
925 INPUT_EVENT_ACK_STATE_CONSUMED);
926 scoped_ptr<IPC::Message> response(
927 new InputHostMsg_HandleInputEvent_ACK(0, ack));
929 // Do a gesture that crosses the threshold.
931 NSEvent* pinchBeginEvent =
932 MockGestureEvent(NSEventTypeBeginGesture, 0);
933 NSEvent* pinchUpdateEvents[3] = {
934 MockGestureEvent(NSEventTypeMagnify, 0.25),
935 MockGestureEvent(NSEventTypeMagnify, 0.25),
936 MockGestureEvent(NSEventTypeMagnify, 0.25),
938 NSEvent* pinchEndEvent =
939 MockGestureEvent(NSEventTypeEndGesture, 0);
941 [view->cocoa_view() beginGestureWithEvent:pinchBeginEvent];
942 EXPECT_EQ(0U, process_host_->sink().message_count());
944 // No zoom is sent for the first update event.
945 [view->cocoa_view() magnifyWithEvent:pinchUpdateEvents[0]];
946 host->OnMessageReceived(*response);
947 EXPECT_EQ(2U, process_host_->sink().message_count());
948 EXPECT_TRUE(ZoomDisabledForPinchUpdateMessage());
949 process_host_->sink().ClearMessages();
951 // The second update event crosses the threshold of 0.4, and so zoom is no
953 [view->cocoa_view() magnifyWithEvent:pinchUpdateEvents[1]];
954 EXPECT_FALSE(ZoomDisabledForPinchUpdateMessage());
955 host->OnMessageReceived(*response);
956 EXPECT_EQ(1U, process_host_->sink().message_count());
957 process_host_->sink().ClearMessages();
959 // The third update still has zoom enabled.
960 [view->cocoa_view() magnifyWithEvent:pinchUpdateEvents[2]];
961 EXPECT_FALSE(ZoomDisabledForPinchUpdateMessage());
962 host->OnMessageReceived(*response);
963 EXPECT_EQ(1U, process_host_->sink().message_count());
964 process_host_->sink().ClearMessages();
966 [view->cocoa_view() endGestureWithEvent:pinchEndEvent];
967 EXPECT_EQ(1U, process_host_->sink().message_count());
968 process_host_->sink().ClearMessages();
971 // Do a gesture that doesn't cross the threshold, but happens when we're not
972 // at page scale factor one, so it should be sent to the renderer.
974 NSEvent* pinchBeginEvent = MockGestureEvent(NSEventTypeBeginGesture, 0);
975 NSEvent* pinchUpdateEvent = MockGestureEvent(NSEventTypeMagnify, 0.25);
976 NSEvent* pinchEndEvent = MockGestureEvent(NSEventTypeEndGesture, 0);
978 view->page_at_minimum_scale_ = false;
980 [view->cocoa_view() beginGestureWithEvent:pinchBeginEvent];
981 EXPECT_EQ(0U, process_host_->sink().message_count());
983 // Expect that a zoom happen because the time threshold has not passed.
984 [view->cocoa_view() magnifyWithEvent:pinchUpdateEvent];
985 EXPECT_FALSE(ZoomDisabledForPinchUpdateMessage());
986 host->OnMessageReceived(*response);
987 EXPECT_EQ(2U, process_host_->sink().message_count());
988 process_host_->sink().ClearMessages();
990 [view->cocoa_view() endGestureWithEvent:pinchEndEvent];
991 EXPECT_EQ(1U, process_host_->sink().message_count());
992 process_host_->sink().ClearMessages();
995 // Do a gesture again, after the page scale is no longer at one, and ensure
996 // that it is thresholded again.
998 NSEvent* pinchBeginEvent = MockGestureEvent(NSEventTypeBeginGesture, 0);
999 NSEvent* pinchUpdateEvent = MockGestureEvent(NSEventTypeMagnify, 0.25);
1000 NSEvent* pinchEndEvent = MockGestureEvent(NSEventTypeEndGesture, 0);
1002 view->page_at_minimum_scale_ = true;
1004 [view->cocoa_view() beginGestureWithEvent:pinchBeginEvent];
1005 EXPECT_EQ(0U, process_host_->sink().message_count());
1007 // Get back to zoom one right after the begin event. This should still keep
1008 // the thresholding in place (it is latched at the begin event).
1009 view->page_at_minimum_scale_ = false;
1011 // Expect that zoom be disabled because the time threshold has passed.
1012 [view->cocoa_view() magnifyWithEvent:pinchUpdateEvent];
1013 EXPECT_EQ(2U, process_host_->sink().message_count());
1014 EXPECT_TRUE(ZoomDisabledForPinchUpdateMessage());
1015 host->OnMessageReceived(*response);
1016 process_host_->sink().ClearMessages();
1018 [view->cocoa_view() endGestureWithEvent:pinchEndEvent];
1019 EXPECT_EQ(1U, process_host_->sink().message_count());
1020 process_host_->sink().ClearMessages();
1028 } // namespace content