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/gpu/gpu_surface_tracker.h"
18 #include "content/browser/renderer_host/render_widget_host_delegate.h"
19 #include "content/common/gpu/gpu_messages.h"
20 #include "content/common/input_messages.h"
21 #include "content/common/view_messages.h"
22 #include "content/public/browser/notification_types.h"
23 #include "content/public/browser/render_widget_host_view_mac_delegate.h"
24 #include "content/public/test/mock_render_process_host.h"
25 #include "content/public/test/test_browser_context.h"
26 #include "content/public/test/test_utils.h"
27 #include "content/test/test_render_view_host.h"
28 #include "testing/gmock/include/gmock/gmock.h"
29 #include "testing/gtest/include/gtest/gtest.h"
30 #import "third_party/ocmock/OCMock/OCMock.h"
31 #import "third_party/ocmock/ocmock_extensions.h"
32 #include "ui/events/test/cocoa_test_event_utils.h"
33 #import "ui/gfx/test/ui_cocoa_test_helper.h"
35 // Helper class with methods used to mock -[NSEvent phase], used by
36 // |MockScrollWheelEventWithPhase()|.
37 @interface MockPhaseMethods : NSObject {
40 - (NSEventPhase)phaseBegan;
41 - (NSEventPhase)phaseChanged;
42 - (NSEventPhase)phaseEnded;
45 @implementation MockPhaseMethods
47 - (NSEventPhase)phaseBegan {
48 return NSEventPhaseBegan;
50 - (NSEventPhase)phaseChanged {
51 return NSEventPhaseChanged;
53 - (NSEventPhase)phaseEnded {
54 return NSEventPhaseEnded;
59 @interface MockRenderWidgetHostViewMacDelegate
60 : NSObject<RenderWidgetHostViewMacDelegate> {
61 BOOL unhandledWheelEventReceived_;
64 @property(nonatomic) BOOL unhandledWheelEventReceived;
68 @implementation MockRenderWidgetHostViewMacDelegate
70 @synthesize unhandledWheelEventReceived = unhandledWheelEventReceived_;
72 - (void)rendererHandledWheelEvent:(const blink::WebMouseWheelEvent&)event
73 consumed:(BOOL)consumed {
75 unhandledWheelEventReceived_ = true;
77 - (void)touchesBeganWithEvent:(NSEvent*)event {}
78 - (void)touchesMovedWithEvent:(NSEvent*)event {}
79 - (void)touchesCancelledWithEvent:(NSEvent*)event {}
80 - (void)touchesEndedWithEvent:(NSEvent*)event {}
81 - (void)beginGestureWithEvent:(NSEvent*)event {}
82 - (void)endGestureWithEvent:(NSEvent*)event {}
83 - (BOOL)canRubberbandLeft:(NSView*)view {
86 - (BOOL)canRubberbandRight:(NSView*)view {
96 id MockGestureEvent(NSEventType type, double magnification) {
97 id event = [OCMockObject mockForClass:[NSEvent class]];
98 NSPoint locationInWindow = NSMakePoint(0, 0);
101 NSTimeInterval timestamp = 1;
102 NSUInteger modifierFlags = 0;
104 [(NSEvent*)[[event stub] andReturnValue:OCMOCK_VALUE(type)] type];
105 [(NSEvent*)[[event stub]
106 andReturnValue:OCMOCK_VALUE(locationInWindow)] locationInWindow];
107 [(NSEvent*)[[event stub] andReturnValue:OCMOCK_VALUE(deltaX)] deltaX];
108 [(NSEvent*)[[event stub] andReturnValue:OCMOCK_VALUE(deltaY)] deltaY];
109 [(NSEvent*)[[event stub] andReturnValue:OCMOCK_VALUE(timestamp)] timestamp];
110 [(NSEvent*)[[event stub]
111 andReturnValue:OCMOCK_VALUE(modifierFlags)] modifierFlags];
112 [(NSEvent*)[[event stub]
113 andReturnValue:OCMOCK_VALUE(magnification)] magnification];
117 class MockRenderWidgetHostDelegate : public RenderWidgetHostDelegate {
119 MockRenderWidgetHostDelegate() {}
120 ~MockRenderWidgetHostDelegate() override {}
123 void Cut() override {}
124 void Copy() override {}
125 void Paste() override {}
126 void SelectAll() override {}
129 class MockRenderWidgetHostImpl : public RenderWidgetHostImpl {
131 MockRenderWidgetHostImpl(RenderWidgetHostDelegate* delegate,
132 RenderProcessHost* process,
135 : RenderWidgetHostImpl(delegate, process, routing_id, surface_id, false) {
138 MOCK_METHOD0(Focus, void());
139 MOCK_METHOD0(Blur, void());
142 // Generates the |length| of composition rectangle vector and save them to
143 // |output|. It starts from |origin| and each rectangle contains |unit_size|.
144 void GenerateCompositionRectArray(const gfx::Point& origin,
145 const gfx::Size& unit_size,
147 const std::vector<size_t>& break_points,
148 std::vector<gfx::Rect>* output) {
152 std::queue<int> break_point_queue;
153 for (size_t i = 0; i < break_points.size(); ++i)
154 break_point_queue.push(break_points[i]);
155 break_point_queue.push(length);
156 size_t next_break_point = break_point_queue.front();
157 break_point_queue.pop();
159 gfx::Rect current_rect(origin, unit_size);
160 for (size_t i = 0; i < length; ++i) {
161 if (i == next_break_point) {
162 current_rect.set_x(origin.x());
163 current_rect.set_y(current_rect.y() + current_rect.height());
164 next_break_point = break_point_queue.front();
165 break_point_queue.pop();
167 output->push_back(current_rect);
168 current_rect.set_x(current_rect.right());
172 gfx::Rect GetExpectedRect(const gfx::Point& origin,
173 const gfx::Size& size,
174 const gfx::Range& range,
177 origin.x() + range.start() * size.width(),
178 origin.y() + line_no * size.height(),
179 range.length() * size.width(),
183 // Returns NSScrollWheel event that mocks -phase. |mockPhaseSelector| should
184 // correspond to a method in |MockPhaseMethods| that returns the desired phase.
185 NSEvent* MockScrollWheelEventWithPhase(SEL mockPhaseSelector, int32_t delta) {
186 CGEventRef cg_event =
187 CGEventCreateScrollWheelEvent(NULL, kCGScrollEventUnitLine, 1, delta, 0);
188 NSEvent* event = [NSEvent eventWithCGEvent:cg_event];
190 method_setImplementation(
191 class_getInstanceMethod([NSEvent class], @selector(phase)),
192 [MockPhaseMethods instanceMethodForSelector:mockPhaseSelector]);
198 class RenderWidgetHostViewMacTest : public RenderViewHostImplTestHarness {
200 RenderWidgetHostViewMacTest() : old_rwhv_(NULL), rwhv_mac_(NULL) {}
202 void SetUp() override {
203 RenderViewHostImplTestHarness::SetUp();
204 if (IsDelegatedRendererEnabled()) {
205 ImageTransportFactory::InitializeForUnitTests(
206 scoped_ptr<ImageTransportFactory>(
207 new NoTransportImageTransportFactory));
210 // TestRenderViewHost's destruction assumes that its view is a
211 // TestRenderWidgetHostView, so store its view and reset it back to the
212 // stored view in |TearDown()|.
213 old_rwhv_ = rvh()->GetView();
215 // Owned by its |cocoa_view()|, i.e. |rwhv_cocoa_|.
216 rwhv_mac_ = new RenderWidgetHostViewMac(rvh(), false);
217 rwhv_cocoa_.reset([rwhv_mac_->cocoa_view() retain]);
219 void TearDown() override {
220 // Make sure the rwhv_mac_ is gone once the superclass's |TearDown()| runs.
224 // See comment in SetUp().
225 test_rvh()->SetView(static_cast<RenderWidgetHostViewBase*>(old_rwhv_));
227 if (IsDelegatedRendererEnabled())
228 ImageTransportFactory::Terminate();
229 RenderViewHostImplTestHarness::TearDown();
232 void RecycleAndWait() {
234 base::MessageLoop::current()->RunUntilIdle();
239 // This class isn't derived from PlatformTest.
240 base::mac::ScopedNSAutoreleasePool pool_;
242 RenderWidgetHostView* old_rwhv_;
245 RenderWidgetHostViewMac* rwhv_mac_;
246 base::scoped_nsobject<RenderWidgetHostViewCocoa> rwhv_cocoa_;
249 DISALLOW_COPY_AND_ASSIGN(RenderWidgetHostViewMacTest);
252 TEST_F(RenderWidgetHostViewMacTest, Basic) {
255 TEST_F(RenderWidgetHostViewMacTest, AcceptsFirstResponder) {
256 // The RWHVCocoa should normally accept first responder status.
257 EXPECT_TRUE([rwhv_cocoa_.get() acceptsFirstResponder]);
260 TEST_F(RenderWidgetHostViewMacTest, Fullscreen) {
261 rwhv_mac_->InitAsFullscreen(NULL);
262 EXPECT_TRUE(rwhv_mac_->pepper_fullscreen_window());
264 // Break the reference cycle caused by pepper_fullscreen_window() without
265 // an <esc> event. See comment in
266 // release_pepper_fullscreen_window_for_testing().
267 rwhv_mac_->release_pepper_fullscreen_window_for_testing();
270 // Verify that escape key down in fullscreen mode suppressed the keyup event on
272 TEST_F(RenderWidgetHostViewMacTest, FullscreenCloseOnEscape) {
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 int32 routing_id = process_host->GetNextRoutingID();
279 int32 surface_id = GpuSurfaceTracker::Get()->AddSurfaceForRenderer(
280 process_host->GetID(), routing_id);
281 // Owned by its |cocoa_view()|.
282 RenderWidgetHostImpl* rwh = new RenderWidgetHostImpl(
283 &delegate, process_host, routing_id, surface_id, false);
284 RenderWidgetHostViewMac* view = new RenderWidgetHostViewMac(rwh, false);
286 view->InitAsFullscreen(rwhv_mac_);
288 WindowedNotificationObserver observer(
289 NOTIFICATION_RENDER_WIDGET_HOST_DESTROYED,
290 Source<RenderWidgetHost>(rwh));
291 EXPECT_FALSE([rwhv_mac_->cocoa_view() suppressNextEscapeKeyUp]);
293 // Escape key down. Should close window and set |suppressNextEscapeKeyUp| on
295 [view->cocoa_view() keyEvent:
296 cocoa_test_event_utils::KeyEventWithKeyCode(53, 27, NSKeyDown, 0)];
298 EXPECT_TRUE([rwhv_mac_->cocoa_view() suppressNextEscapeKeyUp]);
300 // Escape key up on the parent should clear |suppressNextEscapeKeyUp|.
301 [rwhv_mac_->cocoa_view() keyEvent:
302 cocoa_test_event_utils::KeyEventWithKeyCode(53, 27, NSKeyUp, 0)];
303 EXPECT_FALSE([rwhv_mac_->cocoa_view() suppressNextEscapeKeyUp]);
306 // Test that command accelerators which destroy the fullscreen window
307 // don't crash when forwarded via the window's responder machinery.
308 TEST_F(RenderWidgetHostViewMacTest, AcceleratorDestroy) {
309 // Use our own RWH since we need to destroy it.
310 MockRenderWidgetHostDelegate delegate;
311 TestBrowserContext browser_context;
312 MockRenderProcessHost* process_host =
313 new MockRenderProcessHost(&browser_context);
314 int32 routing_id = process_host->GetNextRoutingID();
315 int32 surface_id = GpuSurfaceTracker::Get()->AddSurfaceForRenderer(
316 process_host->GetID(), routing_id);
317 // Owned by its |cocoa_view()|.
318 RenderWidgetHostImpl* rwh = new RenderWidgetHostImpl(
319 &delegate, process_host, routing_id, surface_id, false);
320 RenderWidgetHostViewMac* view = new RenderWidgetHostViewMac(rwh, false);
322 view->InitAsFullscreen(rwhv_mac_);
324 WindowedNotificationObserver observer(
325 NOTIFICATION_RENDER_WIDGET_HOST_DESTROYED,
326 Source<RenderWidgetHost>(rwh));
328 // Command-ESC will destroy the view, while the window is still in
329 // |-performKeyEquivalent:|. There are other cases where this can
330 // happen, Command-ESC is the easiest to trigger.
331 [[view->cocoa_view() window] performKeyEquivalent:
332 cocoa_test_event_utils::KeyEventWithKeyCode(
333 53, 27, NSKeyDown, NSCommandKeyMask)];
337 TEST_F(RenderWidgetHostViewMacTest, GetFirstRectForCharacterRangeCaretCase) {
338 const base::string16 kDummyString = base::UTF8ToUTF16("hogehoge");
339 const size_t kDummyOffset = 0;
341 gfx::Rect caret_rect(10, 11, 0, 10);
342 gfx::Range caret_range(0, 0);
343 ViewHostMsg_SelectionBounds_Params params;
346 NSRange actual_range;
347 rwhv_mac_->SelectionChanged(kDummyString, kDummyOffset, caret_range);
348 params.anchor_rect = params.focus_rect = caret_rect;
349 params.anchor_dir = params.focus_dir = blink::WebTextDirectionLeftToRight;
350 rwhv_mac_->SelectionBoundsChanged(params);
351 EXPECT_TRUE(rwhv_mac_->GetCachedFirstRectForCharacterRange(
352 caret_range.ToNSRange(),
355 EXPECT_EQ(caret_rect, gfx::Rect(NSRectToCGRect(rect)));
356 EXPECT_EQ(caret_range, gfx::Range(actual_range));
358 EXPECT_FALSE(rwhv_mac_->GetCachedFirstRectForCharacterRange(
359 gfx::Range(0, 1).ToNSRange(),
362 EXPECT_FALSE(rwhv_mac_->GetCachedFirstRectForCharacterRange(
363 gfx::Range(1, 1).ToNSRange(),
366 EXPECT_FALSE(rwhv_mac_->GetCachedFirstRectForCharacterRange(
367 gfx::Range(2, 3).ToNSRange(),
372 caret_rect = gfx::Rect(20, 11, 0, 10);
373 caret_range = gfx::Range(1, 1);
374 params.anchor_rect = params.focus_rect = caret_rect;
375 rwhv_mac_->SelectionChanged(kDummyString, kDummyOffset, caret_range);
376 rwhv_mac_->SelectionBoundsChanged(params);
377 EXPECT_TRUE(rwhv_mac_->GetCachedFirstRectForCharacterRange(
378 caret_range.ToNSRange(),
381 EXPECT_EQ(caret_rect, gfx::Rect(NSRectToCGRect(rect)));
382 EXPECT_EQ(caret_range, gfx::Range(actual_range));
384 EXPECT_FALSE(rwhv_mac_->GetCachedFirstRectForCharacterRange(
385 gfx::Range(0, 0).ToNSRange(),
388 EXPECT_FALSE(rwhv_mac_->GetCachedFirstRectForCharacterRange(
389 gfx::Range(1, 2).ToNSRange(),
392 EXPECT_FALSE(rwhv_mac_->GetCachedFirstRectForCharacterRange(
393 gfx::Range(2, 3).ToNSRange(),
398 caret_range = gfx::Range(1, 2);
399 rwhv_mac_->SelectionChanged(kDummyString, kDummyOffset, caret_range);
400 params.anchor_rect = caret_rect;
401 params.focus_rect = gfx::Rect(30, 11, 0, 10);
402 rwhv_mac_->SelectionBoundsChanged(params);
403 EXPECT_FALSE(rwhv_mac_->GetCachedFirstRectForCharacterRange(
404 gfx::Range(0, 0).ToNSRange(),
407 EXPECT_FALSE(rwhv_mac_->GetCachedFirstRectForCharacterRange(
408 gfx::Range(0, 1).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(),
419 EXPECT_FALSE(rwhv_mac_->GetCachedFirstRectForCharacterRange(
420 gfx::Range(2, 2).ToNSRange(),
425 TEST_F(RenderWidgetHostViewMacTest, UpdateCompositionSinglelineCase) {
426 const gfx::Point kOrigin(10, 11);
427 const gfx::Size kBoundsUnit(10, 20);
430 // Make sure not crashing by passing NULL pointer instead of |actual_range|.
431 EXPECT_FALSE(rwhv_mac_->GetCachedFirstRectForCharacterRange(
432 gfx::Range(0, 0).ToNSRange(),
436 // If there are no update from renderer, always returned caret position.
437 NSRange actual_range;
438 EXPECT_FALSE(rwhv_mac_->GetCachedFirstRectForCharacterRange(
439 gfx::Range(0, 0).ToNSRange(),
442 EXPECT_FALSE(rwhv_mac_->GetCachedFirstRectForCharacterRange(
443 gfx::Range(0, 1).ToNSRange(),
446 EXPECT_FALSE(rwhv_mac_->GetCachedFirstRectForCharacterRange(
447 gfx::Range(1, 0).ToNSRange(),
450 EXPECT_FALSE(rwhv_mac_->GetCachedFirstRectForCharacterRange(
451 gfx::Range(1, 1).ToNSRange(),
454 EXPECT_FALSE(rwhv_mac_->GetCachedFirstRectForCharacterRange(
455 gfx::Range(1, 2).ToNSRange(),
459 // If the firstRectForCharacterRange is failed in renderer, empty rect vector
460 // is sent. Make sure this does not crash.
461 rwhv_mac_->ImeCompositionRangeChanged(gfx::Range(10, 12),
462 std::vector<gfx::Rect>());
463 EXPECT_FALSE(rwhv_mac_->GetCachedFirstRectForCharacterRange(
464 gfx::Range(10, 11).ToNSRange(),
468 const int kCompositionLength = 10;
469 std::vector<gfx::Rect> composition_bounds;
470 const int kCompositionStart = 3;
471 const gfx::Range kCompositionRange(kCompositionStart,
472 kCompositionStart + kCompositionLength);
473 GenerateCompositionRectArray(kOrigin,
476 std::vector<size_t>(),
477 &composition_bounds);
478 rwhv_mac_->ImeCompositionRangeChanged(kCompositionRange, composition_bounds);
480 // Out of range requests will return caret position.
481 EXPECT_FALSE(rwhv_mac_->GetCachedFirstRectForCharacterRange(
482 gfx::Range(0, 0).ToNSRange(),
485 EXPECT_FALSE(rwhv_mac_->GetCachedFirstRectForCharacterRange(
486 gfx::Range(1, 1).ToNSRange(),
489 EXPECT_FALSE(rwhv_mac_->GetCachedFirstRectForCharacterRange(
490 gfx::Range(1, 2).ToNSRange(),
493 EXPECT_FALSE(rwhv_mac_->GetCachedFirstRectForCharacterRange(
494 gfx::Range(2, 2).ToNSRange(),
497 EXPECT_FALSE(rwhv_mac_->GetCachedFirstRectForCharacterRange(
498 gfx::Range(13, 14).ToNSRange(),
501 EXPECT_FALSE(rwhv_mac_->GetCachedFirstRectForCharacterRange(
502 gfx::Range(14, 15).ToNSRange(),
506 for (int i = 0; i <= kCompositionLength; ++i) {
507 for (int j = 0; j <= kCompositionLength - i; ++j) {
508 const gfx::Range range(i, i + j);
509 const gfx::Rect expected_rect = GetExpectedRect(kOrigin,
513 const NSRange request_range = gfx::Range(
514 kCompositionStart + range.start(),
515 kCompositionStart + range.end()).ToNSRange();
516 EXPECT_TRUE(rwhv_mac_->GetCachedFirstRectForCharacterRange(
520 EXPECT_EQ(gfx::Range(request_range), gfx::Range(actual_range));
521 EXPECT_EQ(expected_rect, gfx::Rect(NSRectToCGRect(rect)));
523 // Make sure not crashing by passing NULL pointer instead of
525 EXPECT_TRUE(rwhv_mac_->GetCachedFirstRectForCharacterRange(
533 TEST_F(RenderWidgetHostViewMacTest, UpdateCompositionMultilineCase) {
534 const gfx::Point kOrigin(10, 11);
535 const gfx::Size kBoundsUnit(10, 20);
538 const int kCompositionLength = 30;
539 std::vector<gfx::Rect> composition_bounds;
540 const gfx::Range kCompositionRange(0, kCompositionLength);
541 // Set breaking point at 10 and 20.
542 std::vector<size_t> break_points;
543 break_points.push_back(10);
544 break_points.push_back(20);
545 GenerateCompositionRectArray(kOrigin,
549 &composition_bounds);
550 rwhv_mac_->ImeCompositionRangeChanged(kCompositionRange, composition_bounds);
552 // Range doesn't contain line breaking point.
554 range = gfx::Range(5, 8);
555 NSRange actual_range;
556 EXPECT_TRUE(rwhv_mac_->GetCachedFirstRectForCharacterRange(range.ToNSRange(),
559 EXPECT_EQ(range, gfx::Range(actual_range));
561 GetExpectedRect(kOrigin, kBoundsUnit, range, 0),
562 gfx::Rect(NSRectToCGRect(rect)));
563 range = gfx::Range(15, 18);
564 EXPECT_TRUE(rwhv_mac_->GetCachedFirstRectForCharacterRange(range.ToNSRange(),
567 EXPECT_EQ(range, gfx::Range(actual_range));
569 GetExpectedRect(kOrigin, kBoundsUnit, gfx::Range(5, 8), 1),
570 gfx::Rect(NSRectToCGRect(rect)));
571 range = gfx::Range(25, 28);
572 EXPECT_TRUE(rwhv_mac_->GetCachedFirstRectForCharacterRange(range.ToNSRange(),
575 EXPECT_EQ(range, gfx::Range(actual_range));
577 GetExpectedRect(kOrigin, kBoundsUnit, gfx::Range(5, 8), 2),
578 gfx::Rect(NSRectToCGRect(rect)));
580 // Range contains line breaking point.
581 range = gfx::Range(8, 12);
582 EXPECT_TRUE(rwhv_mac_->GetCachedFirstRectForCharacterRange(range.ToNSRange(),
585 EXPECT_EQ(gfx::Range(8, 10), gfx::Range(actual_range));
587 GetExpectedRect(kOrigin, kBoundsUnit, gfx::Range(8, 10), 0),
588 gfx::Rect(NSRectToCGRect(rect)));
589 range = gfx::Range(18, 22);
590 EXPECT_TRUE(rwhv_mac_->GetCachedFirstRectForCharacterRange(range.ToNSRange(),
593 EXPECT_EQ(gfx::Range(18, 20), gfx::Range(actual_range));
595 GetExpectedRect(kOrigin, kBoundsUnit, gfx::Range(8, 10), 1),
596 gfx::Rect(NSRectToCGRect(rect)));
598 // Start point is line breaking point.
599 range = gfx::Range(10, 12);
600 EXPECT_TRUE(rwhv_mac_->GetCachedFirstRectForCharacterRange(range.ToNSRange(),
603 EXPECT_EQ(gfx::Range(10, 12), gfx::Range(actual_range));
605 GetExpectedRect(kOrigin, kBoundsUnit, gfx::Range(0, 2), 1),
606 gfx::Rect(NSRectToCGRect(rect)));
607 range = gfx::Range(20, 22);
608 EXPECT_TRUE(rwhv_mac_->GetCachedFirstRectForCharacterRange(range.ToNSRange(),
611 EXPECT_EQ(gfx::Range(20, 22), gfx::Range(actual_range));
613 GetExpectedRect(kOrigin, kBoundsUnit, gfx::Range(0, 2), 2),
614 gfx::Rect(NSRectToCGRect(rect)));
616 // End point is line breaking point.
617 range = gfx::Range(5, 10);
618 EXPECT_TRUE(rwhv_mac_->GetCachedFirstRectForCharacterRange(range.ToNSRange(),
621 EXPECT_EQ(gfx::Range(5, 10), gfx::Range(actual_range));
623 GetExpectedRect(kOrigin, kBoundsUnit, gfx::Range(5, 10), 0),
624 gfx::Rect(NSRectToCGRect(rect)));
625 range = gfx::Range(15, 20);
626 EXPECT_TRUE(rwhv_mac_->GetCachedFirstRectForCharacterRange(range.ToNSRange(),
629 EXPECT_EQ(gfx::Range(15, 20), gfx::Range(actual_range));
631 GetExpectedRect(kOrigin, kBoundsUnit, gfx::Range(5, 10), 1),
632 gfx::Rect(NSRectToCGRect(rect)));
634 // Start and end point are same line breaking point.
635 range = gfx::Range(10, 10);
636 EXPECT_TRUE(rwhv_mac_->GetCachedFirstRectForCharacterRange(range.ToNSRange(),
639 EXPECT_EQ(gfx::Range(10, 10), gfx::Range(actual_range));
641 GetExpectedRect(kOrigin, kBoundsUnit, gfx::Range(0, 0), 1),
642 gfx::Rect(NSRectToCGRect(rect)));
643 range = gfx::Range(20, 20);
644 EXPECT_TRUE(rwhv_mac_->GetCachedFirstRectForCharacterRange(range.ToNSRange(),
647 EXPECT_EQ(gfx::Range(20, 20), gfx::Range(actual_range));
649 GetExpectedRect(kOrigin, kBoundsUnit, gfx::Range(0, 0), 2),
650 gfx::Rect(NSRectToCGRect(rect)));
652 // Start and end point are different line breaking point.
653 range = gfx::Range(10, 20);
654 EXPECT_TRUE(rwhv_mac_->GetCachedFirstRectForCharacterRange(range.ToNSRange(),
657 EXPECT_EQ(gfx::Range(10, 20), gfx::Range(actual_range));
659 GetExpectedRect(kOrigin, kBoundsUnit, gfx::Range(0, 10), 1),
660 gfx::Rect(NSRectToCGRect(rect)));
663 // Verify that |SetActive()| calls |RenderWidgetHostImpl::Blur()| and
664 // |RenderWidgetHostImp::Focus()|.
665 TEST_F(RenderWidgetHostViewMacTest, BlurAndFocusOnSetActive) {
666 MockRenderWidgetHostDelegate delegate;
667 TestBrowserContext browser_context;
668 MockRenderProcessHost* process_host =
669 new MockRenderProcessHost(&browser_context);
671 // Owned by its |cocoa_view()|.
672 int32 routing_id = process_host->GetNextRoutingID();
673 int32 surface_id = GpuSurfaceTracker::Get()->AddSurfaceForRenderer(
674 process_host->GetID(), routing_id);
675 MockRenderWidgetHostImpl* rwh = new MockRenderWidgetHostImpl(
676 &delegate, process_host, routing_id, surface_id);
677 RenderWidgetHostViewMac* view = new RenderWidgetHostViewMac(rwh, false);
679 base::scoped_nsobject<CocoaTestHelperWindow> window(
680 [[CocoaTestHelperWindow alloc] init]);
681 [[window contentView] addSubview:view->cocoa_view()];
683 EXPECT_CALL(*rwh, Focus());
684 [window makeFirstResponder:view->cocoa_view()];
685 testing::Mock::VerifyAndClearExpectations(rwh);
687 EXPECT_CALL(*rwh, Blur());
688 view->SetActive(false);
689 testing::Mock::VerifyAndClearExpectations(rwh);
691 EXPECT_CALL(*rwh, Focus());
692 view->SetActive(true);
693 testing::Mock::VerifyAndClearExpectations(rwh);
695 // Unsetting first responder should blur.
696 EXPECT_CALL(*rwh, Blur());
697 [window makeFirstResponder:nil];
698 testing::Mock::VerifyAndClearExpectations(rwh);
700 // |SetActive()| shoud not focus if view is not first responder.
701 EXPECT_CALL(*rwh, Focus()).Times(0);
702 view->SetActive(true);
703 testing::Mock::VerifyAndClearExpectations(rwh);
709 TEST_F(RenderWidgetHostViewMacTest, ScrollWheelEndEventDelivery) {
710 // This tests Lion+ functionality, so don't run the test pre-Lion.
711 if (!base::mac::IsOSLionOrLater())
714 // Initialize the view associated with a MockRenderWidgetHostImpl, rather than
715 // the MockRenderProcessHost that is set up by the test harness which mocks
716 // out |OnMessageReceived()|.
717 TestBrowserContext browser_context;
718 MockRenderProcessHost* process_host =
719 new MockRenderProcessHost(&browser_context);
720 process_host->Init();
721 MockRenderWidgetHostDelegate delegate;
722 int32 routing_id = process_host->GetNextRoutingID();
723 int32 surface_id = GpuSurfaceTracker::Get()->AddSurfaceForRenderer(
724 process_host->GetID(), routing_id);
725 MockRenderWidgetHostImpl* host = new MockRenderWidgetHostImpl(
726 &delegate, process_host, routing_id, surface_id);
727 RenderWidgetHostViewMac* view = new RenderWidgetHostViewMac(host, false);
729 // Send an initial wheel event with NSEventPhaseBegan to the view.
730 NSEvent* event1 = MockScrollWheelEventWithPhase(@selector(phaseBegan), 0);
731 [view->cocoa_view() scrollWheel:event1];
732 ASSERT_EQ(1U, process_host->sink().message_count());
734 // Send an ACK for the first wheel event, so that the queue will be flushed.
735 InputEventAck ack(blink::WebInputEvent::MouseWheel,
736 INPUT_EVENT_ACK_STATE_CONSUMED);
737 scoped_ptr<IPC::Message> response(
738 new InputHostMsg_HandleInputEvent_ACK(0, ack));
739 host->OnMessageReceived(*response);
741 // Post the NSEventPhaseEnded wheel event to NSApp and check whether the
742 // render view receives it.
743 NSEvent* event2 = MockScrollWheelEventWithPhase(@selector(phaseEnded), 0);
744 [NSApp postEvent:event2 atStart:NO];
745 base::MessageLoop::current()->RunUntilIdle();
746 ASSERT_EQ(2U, process_host->sink().message_count());
752 TEST_F(RenderWidgetHostViewMacTest, IgnoreEmptyUnhandledWheelEvent) {
753 // This tests Lion+ functionality, so don't run the test pre-Lion.
754 if (!base::mac::IsOSLionOrLater())
757 // Initialize the view associated with a MockRenderWidgetHostImpl, rather than
758 // the MockRenderProcessHost that is set up by the test harness which mocks
759 // out |OnMessageReceived()|.
760 TestBrowserContext browser_context;
761 MockRenderProcessHost* process_host =
762 new MockRenderProcessHost(&browser_context);
763 process_host->Init();
764 MockRenderWidgetHostDelegate delegate;
765 int32 routing_id = process_host->GetNextRoutingID();
766 int32 surface_id = GpuSurfaceTracker::Get()->AddSurfaceForRenderer(
767 process_host->GetID(), routing_id);
768 MockRenderWidgetHostImpl* host = new MockRenderWidgetHostImpl(
769 &delegate, process_host, routing_id, surface_id);
770 RenderWidgetHostViewMac* view = new RenderWidgetHostViewMac(host, false);
772 // Add a delegate to the view.
773 base::scoped_nsobject<MockRenderWidgetHostViewMacDelegate> view_delegate(
774 [[MockRenderWidgetHostViewMacDelegate alloc] init]);
775 view->SetDelegate(view_delegate.get());
777 // Send an initial wheel event for scrolling by 3 lines.
778 NSEvent* event1 = MockScrollWheelEventWithPhase(@selector(phaseBegan), 3);
779 [view->cocoa_view() scrollWheel:event1];
780 ASSERT_EQ(1U, process_host->sink().message_count());
781 process_host->sink().ClearMessages();
783 // Indicate that the wheel event was unhandled.
784 InputEventAck unhandled_ack(blink::WebInputEvent::MouseWheel,
785 INPUT_EVENT_ACK_STATE_NOT_CONSUMED);
786 scoped_ptr<IPC::Message> response1(
787 new InputHostMsg_HandleInputEvent_ACK(0, unhandled_ack));
788 host->OnMessageReceived(*response1);
790 // Check that the view delegate got an unhandled wheel event.
791 ASSERT_EQ(YES, view_delegate.get().unhandledWheelEventReceived);
792 view_delegate.get().unhandledWheelEventReceived = NO;
794 // Send another wheel event, this time for scrolling by 0 lines (empty event).
795 NSEvent* event2 = MockScrollWheelEventWithPhase(@selector(phaseChanged), 0);
796 [view->cocoa_view() scrollWheel:event2];
797 ASSERT_EQ(1U, process_host->sink().message_count());
799 // Indicate that the wheel event was also unhandled.
800 scoped_ptr<IPC::Message> response2(
801 new InputHostMsg_HandleInputEvent_ACK(0, unhandled_ack));
802 host->OnMessageReceived(*response2);
804 // Check that the view delegate ignored the empty unhandled wheel event.
805 ASSERT_EQ(NO, view_delegate.get().unhandledWheelEventReceived);
811 // Tests that when view initiated shutdown happens (i.e. RWHView is deleted
812 // before RWH), we clean up properly and don't leak the RWHVGuest.
813 TEST_F(RenderWidgetHostViewMacTest, GuestViewDoesNotLeak) {
814 MockRenderWidgetHostDelegate delegate;
815 TestBrowserContext browser_context;
816 MockRenderProcessHost* process_host =
817 new MockRenderProcessHost(&browser_context);
818 int32 routing_id = process_host->GetNextRoutingID();
819 int32 surface_id = GpuSurfaceTracker::Get()->AddSurfaceForRenderer(
820 process_host->GetID(), routing_id);
822 // Owned by its |cocoa_view()|.
823 MockRenderWidgetHostImpl* rwh = new MockRenderWidgetHostImpl(
824 &delegate, process_host, routing_id, surface_id);
825 RenderWidgetHostViewMac* view = new RenderWidgetHostViewMac(rwh, true);
827 // Add a delegate to the view.
828 base::scoped_nsobject<MockRenderWidgetHostViewMacDelegate> view_delegate(
829 [[MockRenderWidgetHostViewMacDelegate alloc] init]);
830 view->SetDelegate(view_delegate.get());
832 base::WeakPtr<RenderWidgetHostViewBase> guest_rwhv_weak =
833 (new RenderWidgetHostViewGuest(
834 rwh, NULL, view->GetWeakPtr()))->GetWeakPtr();
836 // Remove the cocoa_view() so |view| also goes away before |rwh|.
838 base::scoped_nsobject<RenderWidgetHostViewCocoa> rwhv_cocoa;
839 rwhv_cocoa.reset([view->cocoa_view() retain]);
846 // Let |guest_rwhv_weak| have a chance to delete itself.
847 base::RunLoop run_loop;
848 content::BrowserThread::PostTask(
849 content::BrowserThread::UI, FROM_HERE, run_loop.QuitClosure());
852 ASSERT_FALSE(guest_rwhv_weak.get());
855 // Tests setting background transparency. See also (disabled on Mac)
856 // RenderWidgetHostTest.Background. This test has some additional checks for
858 TEST_F(RenderWidgetHostViewMacTest, Background) {
859 TestBrowserContext browser_context;
860 MockRenderProcessHost* process_host =
861 new MockRenderProcessHost(&browser_context);
862 MockRenderWidgetHostDelegate delegate;
863 int32 routing_id = process_host->GetNextRoutingID();
864 int32 surface_id = GpuSurfaceTracker::Get()->AddSurfaceForRenderer(
865 process_host->GetID(), routing_id);
866 MockRenderWidgetHostImpl* host = new MockRenderWidgetHostImpl(
867 &delegate, process_host, routing_id, surface_id);
868 RenderWidgetHostViewMac* view = new RenderWidgetHostViewMac(host, false);
870 EXPECT_TRUE(view->GetBackgroundOpaque());
871 EXPECT_TRUE([view->cocoa_view() isOpaque]);
873 view->SetBackgroundColor(SK_ColorTRANSPARENT);
874 EXPECT_FALSE(view->GetBackgroundOpaque());
875 EXPECT_FALSE([view->cocoa_view() isOpaque]);
877 const IPC::Message* set_background;
878 set_background = process_host->sink().GetUniqueMessageMatching(
879 ViewMsg_SetBackgroundOpaque::ID);
880 ASSERT_TRUE(set_background);
881 base::Tuple<bool> sent_background;
882 ViewMsg_SetBackgroundOpaque::Read(set_background, &sent_background);
883 EXPECT_FALSE(base::get<0>(sent_background));
885 // Try setting it back.
886 process_host->sink().ClearMessages();
887 view->SetBackgroundColor(SK_ColorWHITE);
888 EXPECT_TRUE(view->GetBackgroundOpaque());
889 EXPECT_TRUE([view->cocoa_view() isOpaque]);
890 set_background = process_host->sink().GetUniqueMessageMatching(
891 ViewMsg_SetBackgroundOpaque::ID);
892 ASSERT_TRUE(set_background);
893 ViewMsg_SetBackgroundOpaque::Read(set_background, &sent_background);
894 EXPECT_TRUE(base::get<0>(sent_background));
899 class RenderWidgetHostViewMacPinchTest : public RenderWidgetHostViewMacTest {
901 RenderWidgetHostViewMacPinchTest() : process_host_(NULL) {}
903 bool ZoomDisabledForPinchUpdateMessage() {
904 const IPC::Message* message = NULL;
905 // The first message may be a PinchBegin. Go for the second message if
907 switch (process_host_->sink().message_count()) {
909 message = process_host_->sink().GetMessageAt(0);
912 message = process_host_->sink().GetMessageAt(1);
919 base::Tuple<IPC::WebInputEventPointer, ui::LatencyInfo, bool> data;
920 InputMsg_HandleInputEvent::Read(message, &data);
921 IPC::WebInputEventPointer ipc_event = base::get<0>(data);
922 const blink::WebGestureEvent* gesture_event =
923 static_cast<const blink::WebGestureEvent*>(ipc_event);
924 return gesture_event->data.pinchUpdate.zoomDisabled;
927 MockRenderProcessHost* process_host_;
930 TEST_F(RenderWidgetHostViewMacPinchTest, PinchThresholding) {
931 // This tests Lion+ functionality, so don't run the test pre-Lion.
932 if (!base::mac::IsOSLionOrLater())
935 // Initialize the view associated with a MockRenderWidgetHostImpl, rather than
936 // the MockRenderProcessHost that is set up by the test harness which mocks
937 // out |OnMessageReceived()|.
938 TestBrowserContext browser_context;
939 process_host_ = new MockRenderProcessHost(&browser_context);
940 process_host_->Init();
941 MockRenderWidgetHostDelegate delegate;
942 int32 routing_id = process_host_->GetNextRoutingID();
943 int32 surface_id = GpuSurfaceTracker::Get()->AddSurfaceForRenderer(
944 process_host_->GetID(), routing_id);
945 MockRenderWidgetHostImpl* host = new MockRenderWidgetHostImpl(
946 &delegate, process_host_, routing_id, surface_id);
947 RenderWidgetHostViewMac* view = new RenderWidgetHostViewMac(host, false);
949 // We'll use this IPC message to ack events.
950 InputEventAck ack(blink::WebInputEvent::GesturePinchUpdate,
951 INPUT_EVENT_ACK_STATE_CONSUMED);
952 scoped_ptr<IPC::Message> response(
953 new InputHostMsg_HandleInputEvent_ACK(0, ack));
955 // Do a gesture that crosses the threshold.
957 NSEvent* pinchBeginEvent =
958 MockGestureEvent(NSEventTypeBeginGesture, 0);
959 NSEvent* pinchUpdateEvents[3] = {
960 MockGestureEvent(NSEventTypeMagnify, 0.25),
961 MockGestureEvent(NSEventTypeMagnify, 0.25),
962 MockGestureEvent(NSEventTypeMagnify, 0.25),
964 NSEvent* pinchEndEvent =
965 MockGestureEvent(NSEventTypeEndGesture, 0);
967 [view->cocoa_view() beginGestureWithEvent:pinchBeginEvent];
968 EXPECT_EQ(0U, process_host_->sink().message_count());
970 // No zoom is sent for the first update event.
971 [view->cocoa_view() magnifyWithEvent:pinchUpdateEvents[0]];
972 host->OnMessageReceived(*response);
973 EXPECT_EQ(2U, process_host_->sink().message_count());
974 EXPECT_TRUE(ZoomDisabledForPinchUpdateMessage());
975 process_host_->sink().ClearMessages();
977 // The second update event crosses the threshold of 0.4, and so zoom is no
979 [view->cocoa_view() magnifyWithEvent:pinchUpdateEvents[1]];
980 EXPECT_FALSE(ZoomDisabledForPinchUpdateMessage());
981 host->OnMessageReceived(*response);
982 EXPECT_EQ(1U, process_host_->sink().message_count());
983 process_host_->sink().ClearMessages();
985 // The third update still has zoom enabled.
986 [view->cocoa_view() magnifyWithEvent:pinchUpdateEvents[2]];
987 EXPECT_FALSE(ZoomDisabledForPinchUpdateMessage());
988 host->OnMessageReceived(*response);
989 EXPECT_EQ(1U, process_host_->sink().message_count());
990 process_host_->sink().ClearMessages();
992 [view->cocoa_view() endGestureWithEvent:pinchEndEvent];
993 EXPECT_EQ(1U, process_host_->sink().message_count());
994 process_host_->sink().ClearMessages();
997 // Do a gesture that doesn't cross the threshold, but happens when we're not
998 // at page scale factor one, so it should be sent to the renderer.
1000 NSEvent* pinchBeginEvent = MockGestureEvent(NSEventTypeBeginGesture, 0);
1001 NSEvent* pinchUpdateEvent = MockGestureEvent(NSEventTypeMagnify, 0.25);
1002 NSEvent* pinchEndEvent = MockGestureEvent(NSEventTypeEndGesture, 0);
1004 view->page_at_minimum_scale_ = false;
1006 [view->cocoa_view() beginGestureWithEvent:pinchBeginEvent];
1007 EXPECT_EQ(0U, process_host_->sink().message_count());
1009 // Expect that a zoom happen because the time threshold has not passed.
1010 [view->cocoa_view() magnifyWithEvent:pinchUpdateEvent];
1011 EXPECT_FALSE(ZoomDisabledForPinchUpdateMessage());
1012 host->OnMessageReceived(*response);
1013 EXPECT_EQ(2U, process_host_->sink().message_count());
1014 process_host_->sink().ClearMessages();
1016 [view->cocoa_view() endGestureWithEvent:pinchEndEvent];
1017 EXPECT_EQ(1U, process_host_->sink().message_count());
1018 process_host_->sink().ClearMessages();
1021 // Do a gesture again, after the page scale is no longer at one, and ensure
1022 // that it is thresholded again.
1024 NSEvent* pinchBeginEvent = MockGestureEvent(NSEventTypeBeginGesture, 0);
1025 NSEvent* pinchUpdateEvent = MockGestureEvent(NSEventTypeMagnify, 0.25);
1026 NSEvent* pinchEndEvent = MockGestureEvent(NSEventTypeEndGesture, 0);
1028 view->page_at_minimum_scale_ = true;
1030 [view->cocoa_view() beginGestureWithEvent:pinchBeginEvent];
1031 EXPECT_EQ(0U, process_host_->sink().message_count());
1033 // Get back to zoom one right after the begin event. This should still keep
1034 // the thresholding in place (it is latched at the begin event).
1035 view->page_at_minimum_scale_ = false;
1037 // Expect that zoom be disabled because the time threshold has passed.
1038 [view->cocoa_view() magnifyWithEvent:pinchUpdateEvent];
1039 EXPECT_EQ(2U, process_host_->sink().message_count());
1040 EXPECT_TRUE(ZoomDisabledForPinchUpdateMessage());
1041 host->OnMessageReceived(*response);
1042 process_host_->sink().ClearMessages();
1044 [view->cocoa_view() endGestureWithEvent:pinchEndEvent];
1045 EXPECT_EQ(1U, process_host_->sink().message_count());
1046 process_host_->sink().ClearMessages();
1054 } // namespace content