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 class MockRenderWidgetHostImpl : public RenderWidgetHostImpl {
124 MockRenderWidgetHostImpl(RenderWidgetHostDelegate* delegate,
125 RenderProcessHost* process,
127 : RenderWidgetHostImpl(delegate, process, routing_id, false) {
130 MOCK_METHOD0(Focus, void());
131 MOCK_METHOD0(Blur, void());
134 // Generates the |length| of composition rectangle vector and save them to
135 // |output|. It starts from |origin| and each rectangle contains |unit_size|.
136 void GenerateCompositionRectArray(const gfx::Point& origin,
137 const gfx::Size& unit_size,
139 const std::vector<size_t>& break_points,
140 std::vector<gfx::Rect>* output) {
144 std::queue<int> break_point_queue;
145 for (size_t i = 0; i < break_points.size(); ++i)
146 break_point_queue.push(break_points[i]);
147 break_point_queue.push(length);
148 size_t next_break_point = break_point_queue.front();
149 break_point_queue.pop();
151 gfx::Rect current_rect(origin, unit_size);
152 for (size_t i = 0; i < length; ++i) {
153 if (i == next_break_point) {
154 current_rect.set_x(origin.x());
155 current_rect.set_y(current_rect.y() + current_rect.height());
156 next_break_point = break_point_queue.front();
157 break_point_queue.pop();
159 output->push_back(current_rect);
160 current_rect.set_x(current_rect.right());
164 gfx::Rect GetExpectedRect(const gfx::Point& origin,
165 const gfx::Size& size,
166 const gfx::Range& range,
169 origin.x() + range.start() * size.width(),
170 origin.y() + line_no * size.height(),
171 range.length() * size.width(),
175 // Returns NSScrollWheel event that mocks -phase. |mockPhaseSelector| should
176 // correspond to a method in |MockPhaseMethods| that returns the desired phase.
177 NSEvent* MockScrollWheelEventWithPhase(SEL mockPhaseSelector, int32_t delta) {
178 CGEventRef cg_event =
179 CGEventCreateScrollWheelEvent(NULL, kCGScrollEventUnitLine, 1, delta, 0);
180 NSEvent* event = [NSEvent eventWithCGEvent:cg_event];
182 method_setImplementation(
183 class_getInstanceMethod([NSEvent class], @selector(phase)),
184 [MockPhaseMethods instanceMethodForSelector:mockPhaseSelector]);
190 class RenderWidgetHostViewMacTest : public RenderViewHostImplTestHarness {
192 RenderWidgetHostViewMacTest() : old_rwhv_(NULL), rwhv_mac_(NULL) {}
194 void SetUp() override {
195 RenderViewHostImplTestHarness::SetUp();
196 if (IsDelegatedRendererEnabled()) {
197 ImageTransportFactory::InitializeForUnitTests(
198 scoped_ptr<ImageTransportFactory>(
199 new NoTransportImageTransportFactory));
202 // TestRenderViewHost's destruction assumes that its view is a
203 // TestRenderWidgetHostView, so store its view and reset it back to the
204 // stored view in |TearDown()|.
205 old_rwhv_ = rvh()->GetView();
207 // Owned by its |cocoa_view()|, i.e. |rwhv_cocoa_|.
208 rwhv_mac_ = new RenderWidgetHostViewMac(rvh(), false);
209 rwhv_cocoa_.reset([rwhv_mac_->cocoa_view() retain]);
211 void TearDown() override {
212 // Make sure the rwhv_mac_ is gone once the superclass's |TearDown()| runs.
216 // See comment in SetUp().
217 test_rvh()->SetView(static_cast<RenderWidgetHostViewBase*>(old_rwhv_));
219 if (IsDelegatedRendererEnabled())
220 ImageTransportFactory::Terminate();
221 RenderViewHostImplTestHarness::TearDown();
224 void RecycleAndWait() {
226 base::MessageLoop::current()->RunUntilIdle();
231 // This class isn't derived from PlatformTest.
232 base::mac::ScopedNSAutoreleasePool pool_;
234 RenderWidgetHostView* old_rwhv_;
237 RenderWidgetHostViewMac* rwhv_mac_;
238 base::scoped_nsobject<RenderWidgetHostViewCocoa> rwhv_cocoa_;
241 DISALLOW_COPY_AND_ASSIGN(RenderWidgetHostViewMacTest);
244 TEST_F(RenderWidgetHostViewMacTest, Basic) {
247 TEST_F(RenderWidgetHostViewMacTest, AcceptsFirstResponder) {
248 // The RWHVCocoa should normally accept first responder status.
249 EXPECT_TRUE([rwhv_cocoa_.get() acceptsFirstResponder]);
252 TEST_F(RenderWidgetHostViewMacTest, Fullscreen) {
253 rwhv_mac_->InitAsFullscreen(NULL);
254 EXPECT_TRUE(rwhv_mac_->pepper_fullscreen_window());
256 // Break the reference cycle caused by pepper_fullscreen_window() without
257 // an <esc> event. See comment in
258 // release_pepper_fullscreen_window_for_testing().
259 rwhv_mac_->release_pepper_fullscreen_window_for_testing();
262 // Verify that escape key down in fullscreen mode suppressed the keyup event on
264 TEST_F(RenderWidgetHostViewMacTest, FullscreenCloseOnEscape) {
265 // Use our own RWH since we need to destroy it.
266 MockRenderWidgetHostDelegate delegate;
267 TestBrowserContext browser_context;
268 MockRenderProcessHost* process_host =
269 new MockRenderProcessHost(&browser_context);
270 // Owned by its |cocoa_view()|.
271 RenderWidgetHostImpl* rwh = new RenderWidgetHostImpl(
272 &delegate, process_host, MSG_ROUTING_NONE, false);
273 RenderWidgetHostViewMac* view = new RenderWidgetHostViewMac(rwh, false);
275 view->InitAsFullscreen(rwhv_mac_);
277 WindowedNotificationObserver observer(
278 NOTIFICATION_RENDER_WIDGET_HOST_DESTROYED,
279 Source<RenderWidgetHost>(rwh));
280 EXPECT_FALSE([rwhv_mac_->cocoa_view() suppressNextEscapeKeyUp]);
282 // Escape key down. Should close window and set |suppressNextEscapeKeyUp| on
284 [view->cocoa_view() keyEvent:
285 cocoa_test_event_utils::KeyEventWithKeyCode(53, 27, NSKeyDown, 0)];
287 EXPECT_TRUE([rwhv_mac_->cocoa_view() suppressNextEscapeKeyUp]);
289 // Escape key up on the parent should clear |suppressNextEscapeKeyUp|.
290 [rwhv_mac_->cocoa_view() keyEvent:
291 cocoa_test_event_utils::KeyEventWithKeyCode(53, 27, NSKeyUp, 0)];
292 EXPECT_FALSE([rwhv_mac_->cocoa_view() suppressNextEscapeKeyUp]);
295 // Test that command accelerators which destroy the fullscreen window
296 // don't crash when forwarded via the window's responder machinery.
297 TEST_F(RenderWidgetHostViewMacTest, AcceleratorDestroy) {
298 // Use our own RWH since we need to destroy it.
299 MockRenderWidgetHostDelegate delegate;
300 TestBrowserContext browser_context;
301 MockRenderProcessHost* process_host =
302 new MockRenderProcessHost(&browser_context);
303 // Owned by its |cocoa_view()|.
304 RenderWidgetHostImpl* rwh = new RenderWidgetHostImpl(
305 &delegate, process_host, MSG_ROUTING_NONE, false);
306 RenderWidgetHostViewMac* view = new RenderWidgetHostViewMac(rwh, false);
308 view->InitAsFullscreen(rwhv_mac_);
310 WindowedNotificationObserver observer(
311 NOTIFICATION_RENDER_WIDGET_HOST_DESTROYED,
312 Source<RenderWidgetHost>(rwh));
314 // Command-ESC will destroy the view, while the window is still in
315 // |-performKeyEquivalent:|. There are other cases where this can
316 // happen, Command-ESC is the easiest to trigger.
317 [[view->cocoa_view() window] performKeyEquivalent:
318 cocoa_test_event_utils::KeyEventWithKeyCode(
319 53, 27, NSKeyDown, NSCommandKeyMask)];
323 TEST_F(RenderWidgetHostViewMacTest, GetFirstRectForCharacterRangeCaretCase) {
324 const base::string16 kDummyString = base::UTF8ToUTF16("hogehoge");
325 const size_t kDummyOffset = 0;
327 gfx::Rect caret_rect(10, 11, 0, 10);
328 gfx::Range caret_range(0, 0);
329 ViewHostMsg_SelectionBounds_Params params;
332 NSRange actual_range;
333 rwhv_mac_->SelectionChanged(kDummyString, kDummyOffset, caret_range);
334 params.anchor_rect = params.focus_rect = caret_rect;
335 params.anchor_dir = params.focus_dir = blink::WebTextDirectionLeftToRight;
336 rwhv_mac_->SelectionBoundsChanged(params);
337 EXPECT_TRUE(rwhv_mac_->GetCachedFirstRectForCharacterRange(
338 caret_range.ToNSRange(),
341 EXPECT_EQ(caret_rect, gfx::Rect(NSRectToCGRect(rect)));
342 EXPECT_EQ(caret_range, gfx::Range(actual_range));
344 EXPECT_FALSE(rwhv_mac_->GetCachedFirstRectForCharacterRange(
345 gfx::Range(0, 1).ToNSRange(),
348 EXPECT_FALSE(rwhv_mac_->GetCachedFirstRectForCharacterRange(
349 gfx::Range(1, 1).ToNSRange(),
352 EXPECT_FALSE(rwhv_mac_->GetCachedFirstRectForCharacterRange(
353 gfx::Range(2, 3).ToNSRange(),
358 caret_rect = gfx::Rect(20, 11, 0, 10);
359 caret_range = gfx::Range(1, 1);
360 params.anchor_rect = params.focus_rect = caret_rect;
361 rwhv_mac_->SelectionChanged(kDummyString, kDummyOffset, caret_range);
362 rwhv_mac_->SelectionBoundsChanged(params);
363 EXPECT_TRUE(rwhv_mac_->GetCachedFirstRectForCharacterRange(
364 caret_range.ToNSRange(),
367 EXPECT_EQ(caret_rect, gfx::Rect(NSRectToCGRect(rect)));
368 EXPECT_EQ(caret_range, gfx::Range(actual_range));
370 EXPECT_FALSE(rwhv_mac_->GetCachedFirstRectForCharacterRange(
371 gfx::Range(0, 0).ToNSRange(),
374 EXPECT_FALSE(rwhv_mac_->GetCachedFirstRectForCharacterRange(
375 gfx::Range(1, 2).ToNSRange(),
378 EXPECT_FALSE(rwhv_mac_->GetCachedFirstRectForCharacterRange(
379 gfx::Range(2, 3).ToNSRange(),
384 caret_range = gfx::Range(1, 2);
385 rwhv_mac_->SelectionChanged(kDummyString, kDummyOffset, caret_range);
386 params.anchor_rect = caret_rect;
387 params.focus_rect = gfx::Rect(30, 11, 0, 10);
388 rwhv_mac_->SelectionBoundsChanged(params);
389 EXPECT_FALSE(rwhv_mac_->GetCachedFirstRectForCharacterRange(
390 gfx::Range(0, 0).ToNSRange(),
393 EXPECT_FALSE(rwhv_mac_->GetCachedFirstRectForCharacterRange(
394 gfx::Range(0, 1).ToNSRange(),
397 EXPECT_FALSE(rwhv_mac_->GetCachedFirstRectForCharacterRange(
398 gfx::Range(1, 1).ToNSRange(),
401 EXPECT_FALSE(rwhv_mac_->GetCachedFirstRectForCharacterRange(
402 gfx::Range(1, 2).ToNSRange(),
405 EXPECT_FALSE(rwhv_mac_->GetCachedFirstRectForCharacterRange(
406 gfx::Range(2, 2).ToNSRange(),
411 TEST_F(RenderWidgetHostViewMacTest, UpdateCompositionSinglelineCase) {
412 const gfx::Point kOrigin(10, 11);
413 const gfx::Size kBoundsUnit(10, 20);
416 // Make sure not crashing by passing NULL pointer instead of |actual_range|.
417 EXPECT_FALSE(rwhv_mac_->GetCachedFirstRectForCharacterRange(
418 gfx::Range(0, 0).ToNSRange(),
422 // If there are no update from renderer, always returned caret position.
423 NSRange actual_range;
424 EXPECT_FALSE(rwhv_mac_->GetCachedFirstRectForCharacterRange(
425 gfx::Range(0, 0).ToNSRange(),
428 EXPECT_FALSE(rwhv_mac_->GetCachedFirstRectForCharacterRange(
429 gfx::Range(0, 1).ToNSRange(),
432 EXPECT_FALSE(rwhv_mac_->GetCachedFirstRectForCharacterRange(
433 gfx::Range(1, 0).ToNSRange(),
436 EXPECT_FALSE(rwhv_mac_->GetCachedFirstRectForCharacterRange(
437 gfx::Range(1, 1).ToNSRange(),
440 EXPECT_FALSE(rwhv_mac_->GetCachedFirstRectForCharacterRange(
441 gfx::Range(1, 2).ToNSRange(),
445 // If the firstRectForCharacterRange is failed in renderer, empty rect vector
446 // is sent. Make sure this does not crash.
447 rwhv_mac_->ImeCompositionRangeChanged(gfx::Range(10, 12),
448 std::vector<gfx::Rect>());
449 EXPECT_FALSE(rwhv_mac_->GetCachedFirstRectForCharacterRange(
450 gfx::Range(10, 11).ToNSRange(),
454 const int kCompositionLength = 10;
455 std::vector<gfx::Rect> composition_bounds;
456 const int kCompositionStart = 3;
457 const gfx::Range kCompositionRange(kCompositionStart,
458 kCompositionStart + kCompositionLength);
459 GenerateCompositionRectArray(kOrigin,
462 std::vector<size_t>(),
463 &composition_bounds);
464 rwhv_mac_->ImeCompositionRangeChanged(kCompositionRange, composition_bounds);
466 // Out of range requests will return caret position.
467 EXPECT_FALSE(rwhv_mac_->GetCachedFirstRectForCharacterRange(
468 gfx::Range(0, 0).ToNSRange(),
471 EXPECT_FALSE(rwhv_mac_->GetCachedFirstRectForCharacterRange(
472 gfx::Range(1, 1).ToNSRange(),
475 EXPECT_FALSE(rwhv_mac_->GetCachedFirstRectForCharacterRange(
476 gfx::Range(1, 2).ToNSRange(),
479 EXPECT_FALSE(rwhv_mac_->GetCachedFirstRectForCharacterRange(
480 gfx::Range(2, 2).ToNSRange(),
483 EXPECT_FALSE(rwhv_mac_->GetCachedFirstRectForCharacterRange(
484 gfx::Range(13, 14).ToNSRange(),
487 EXPECT_FALSE(rwhv_mac_->GetCachedFirstRectForCharacterRange(
488 gfx::Range(14, 15).ToNSRange(),
492 for (int i = 0; i <= kCompositionLength; ++i) {
493 for (int j = 0; j <= kCompositionLength - i; ++j) {
494 const gfx::Range range(i, i + j);
495 const gfx::Rect expected_rect = GetExpectedRect(kOrigin,
499 const NSRange request_range = gfx::Range(
500 kCompositionStart + range.start(),
501 kCompositionStart + range.end()).ToNSRange();
502 EXPECT_TRUE(rwhv_mac_->GetCachedFirstRectForCharacterRange(
506 EXPECT_EQ(gfx::Range(request_range), gfx::Range(actual_range));
507 EXPECT_EQ(expected_rect, gfx::Rect(NSRectToCGRect(rect)));
509 // Make sure not crashing by passing NULL pointer instead of
511 EXPECT_TRUE(rwhv_mac_->GetCachedFirstRectForCharacterRange(
519 TEST_F(RenderWidgetHostViewMacTest, UpdateCompositionMultilineCase) {
520 const gfx::Point kOrigin(10, 11);
521 const gfx::Size kBoundsUnit(10, 20);
524 const int kCompositionLength = 30;
525 std::vector<gfx::Rect> composition_bounds;
526 const gfx::Range kCompositionRange(0, kCompositionLength);
527 // Set breaking point at 10 and 20.
528 std::vector<size_t> break_points;
529 break_points.push_back(10);
530 break_points.push_back(20);
531 GenerateCompositionRectArray(kOrigin,
535 &composition_bounds);
536 rwhv_mac_->ImeCompositionRangeChanged(kCompositionRange, composition_bounds);
538 // Range doesn't contain line breaking point.
540 range = gfx::Range(5, 8);
541 NSRange actual_range;
542 EXPECT_TRUE(rwhv_mac_->GetCachedFirstRectForCharacterRange(range.ToNSRange(),
545 EXPECT_EQ(range, gfx::Range(actual_range));
547 GetExpectedRect(kOrigin, kBoundsUnit, range, 0),
548 gfx::Rect(NSRectToCGRect(rect)));
549 range = gfx::Range(15, 18);
550 EXPECT_TRUE(rwhv_mac_->GetCachedFirstRectForCharacterRange(range.ToNSRange(),
553 EXPECT_EQ(range, gfx::Range(actual_range));
555 GetExpectedRect(kOrigin, kBoundsUnit, gfx::Range(5, 8), 1),
556 gfx::Rect(NSRectToCGRect(rect)));
557 range = gfx::Range(25, 28);
558 EXPECT_TRUE(rwhv_mac_->GetCachedFirstRectForCharacterRange(range.ToNSRange(),
561 EXPECT_EQ(range, gfx::Range(actual_range));
563 GetExpectedRect(kOrigin, kBoundsUnit, gfx::Range(5, 8), 2),
564 gfx::Rect(NSRectToCGRect(rect)));
566 // Range contains line breaking point.
567 range = gfx::Range(8, 12);
568 EXPECT_TRUE(rwhv_mac_->GetCachedFirstRectForCharacterRange(range.ToNSRange(),
571 EXPECT_EQ(gfx::Range(8, 10), gfx::Range(actual_range));
573 GetExpectedRect(kOrigin, kBoundsUnit, gfx::Range(8, 10), 0),
574 gfx::Rect(NSRectToCGRect(rect)));
575 range = gfx::Range(18, 22);
576 EXPECT_TRUE(rwhv_mac_->GetCachedFirstRectForCharacterRange(range.ToNSRange(),
579 EXPECT_EQ(gfx::Range(18, 20), gfx::Range(actual_range));
581 GetExpectedRect(kOrigin, kBoundsUnit, gfx::Range(8, 10), 1),
582 gfx::Rect(NSRectToCGRect(rect)));
584 // Start point is line breaking point.
585 range = gfx::Range(10, 12);
586 EXPECT_TRUE(rwhv_mac_->GetCachedFirstRectForCharacterRange(range.ToNSRange(),
589 EXPECT_EQ(gfx::Range(10, 12), gfx::Range(actual_range));
591 GetExpectedRect(kOrigin, kBoundsUnit, gfx::Range(0, 2), 1),
592 gfx::Rect(NSRectToCGRect(rect)));
593 range = gfx::Range(20, 22);
594 EXPECT_TRUE(rwhv_mac_->GetCachedFirstRectForCharacterRange(range.ToNSRange(),
597 EXPECT_EQ(gfx::Range(20, 22), gfx::Range(actual_range));
599 GetExpectedRect(kOrigin, kBoundsUnit, gfx::Range(0, 2), 2),
600 gfx::Rect(NSRectToCGRect(rect)));
602 // End point is line breaking point.
603 range = gfx::Range(5, 10);
604 EXPECT_TRUE(rwhv_mac_->GetCachedFirstRectForCharacterRange(range.ToNSRange(),
607 EXPECT_EQ(gfx::Range(5, 10), gfx::Range(actual_range));
609 GetExpectedRect(kOrigin, kBoundsUnit, gfx::Range(5, 10), 0),
610 gfx::Rect(NSRectToCGRect(rect)));
611 range = gfx::Range(15, 20);
612 EXPECT_TRUE(rwhv_mac_->GetCachedFirstRectForCharacterRange(range.ToNSRange(),
615 EXPECT_EQ(gfx::Range(15, 20), gfx::Range(actual_range));
617 GetExpectedRect(kOrigin, kBoundsUnit, gfx::Range(5, 10), 1),
618 gfx::Rect(NSRectToCGRect(rect)));
620 // Start and end point are same line breaking point.
621 range = gfx::Range(10, 10);
622 EXPECT_TRUE(rwhv_mac_->GetCachedFirstRectForCharacterRange(range.ToNSRange(),
625 EXPECT_EQ(gfx::Range(10, 10), gfx::Range(actual_range));
627 GetExpectedRect(kOrigin, kBoundsUnit, gfx::Range(0, 0), 1),
628 gfx::Rect(NSRectToCGRect(rect)));
629 range = gfx::Range(20, 20);
630 EXPECT_TRUE(rwhv_mac_->GetCachedFirstRectForCharacterRange(range.ToNSRange(),
633 EXPECT_EQ(gfx::Range(20, 20), gfx::Range(actual_range));
635 GetExpectedRect(kOrigin, kBoundsUnit, gfx::Range(0, 0), 2),
636 gfx::Rect(NSRectToCGRect(rect)));
638 // Start and end point are different line breaking point.
639 range = gfx::Range(10, 20);
640 EXPECT_TRUE(rwhv_mac_->GetCachedFirstRectForCharacterRange(range.ToNSRange(),
643 EXPECT_EQ(gfx::Range(10, 20), gfx::Range(actual_range));
645 GetExpectedRect(kOrigin, kBoundsUnit, gfx::Range(0, 10), 1),
646 gfx::Rect(NSRectToCGRect(rect)));
649 // Verify that |SetActive()| calls |RenderWidgetHostImpl::Blur()| and
650 // |RenderWidgetHostImp::Focus()|.
651 TEST_F(RenderWidgetHostViewMacTest, BlurAndFocusOnSetActive) {
652 MockRenderWidgetHostDelegate delegate;
653 TestBrowserContext browser_context;
654 MockRenderProcessHost* process_host =
655 new MockRenderProcessHost(&browser_context);
657 // Owned by its |cocoa_view()|.
658 MockRenderWidgetHostImpl* rwh = new MockRenderWidgetHostImpl(
659 &delegate, process_host, MSG_ROUTING_NONE);
660 RenderWidgetHostViewMac* view = new RenderWidgetHostViewMac(rwh, false);
662 base::scoped_nsobject<CocoaTestHelperWindow> window(
663 [[CocoaTestHelperWindow alloc] init]);
664 [[window contentView] addSubview:view->cocoa_view()];
666 EXPECT_CALL(*rwh, Focus());
667 [window makeFirstResponder:view->cocoa_view()];
668 testing::Mock::VerifyAndClearExpectations(rwh);
670 EXPECT_CALL(*rwh, Blur());
671 view->SetActive(false);
672 testing::Mock::VerifyAndClearExpectations(rwh);
674 EXPECT_CALL(*rwh, Focus());
675 view->SetActive(true);
676 testing::Mock::VerifyAndClearExpectations(rwh);
678 // Unsetting first responder should blur.
679 EXPECT_CALL(*rwh, Blur());
680 [window makeFirstResponder:nil];
681 testing::Mock::VerifyAndClearExpectations(rwh);
683 // |SetActive()| shoud not focus if view is not first responder.
684 EXPECT_CALL(*rwh, Focus()).Times(0);
685 view->SetActive(true);
686 testing::Mock::VerifyAndClearExpectations(rwh);
692 TEST_F(RenderWidgetHostViewMacTest, ScrollWheelEndEventDelivery) {
693 // This tests Lion+ functionality, so don't run the test pre-Lion.
694 if (!base::mac::IsOSLionOrLater())
697 // Initialize the view associated with a MockRenderWidgetHostImpl, rather than
698 // the MockRenderProcessHost that is set up by the test harness which mocks
699 // out |OnMessageReceived()|.
700 TestBrowserContext browser_context;
701 MockRenderProcessHost* process_host =
702 new MockRenderProcessHost(&browser_context);
703 process_host->Init();
704 MockRenderWidgetHostDelegate delegate;
705 MockRenderWidgetHostImpl* host = new MockRenderWidgetHostImpl(
706 &delegate, process_host, MSG_ROUTING_NONE);
707 RenderWidgetHostViewMac* view = new RenderWidgetHostViewMac(host, false);
709 // Send an initial wheel event with NSEventPhaseBegan to the view.
710 NSEvent* event1 = MockScrollWheelEventWithPhase(@selector(phaseBegan), 0);
711 [view->cocoa_view() scrollWheel:event1];
712 ASSERT_EQ(1U, process_host->sink().message_count());
714 // Send an ACK for the first wheel event, so that the queue will be flushed.
715 InputEventAck ack(blink::WebInputEvent::MouseWheel,
716 INPUT_EVENT_ACK_STATE_CONSUMED);
717 scoped_ptr<IPC::Message> response(
718 new InputHostMsg_HandleInputEvent_ACK(0, ack));
719 host->OnMessageReceived(*response);
721 // Post the NSEventPhaseEnded wheel event to NSApp and check whether the
722 // render view receives it.
723 NSEvent* event2 = MockScrollWheelEventWithPhase(@selector(phaseEnded), 0);
724 [NSApp postEvent:event2 atStart:NO];
725 base::MessageLoop::current()->RunUntilIdle();
726 ASSERT_EQ(2U, process_host->sink().message_count());
732 TEST_F(RenderWidgetHostViewMacTest, IgnoreEmptyUnhandledWheelEvent) {
733 // This tests Lion+ functionality, so don't run the test pre-Lion.
734 if (!base::mac::IsOSLionOrLater())
737 // Initialize the view associated with a MockRenderWidgetHostImpl, rather than
738 // the MockRenderProcessHost that is set up by the test harness which mocks
739 // out |OnMessageReceived()|.
740 TestBrowserContext browser_context;
741 MockRenderProcessHost* process_host =
742 new MockRenderProcessHost(&browser_context);
743 process_host->Init();
744 MockRenderWidgetHostDelegate delegate;
745 MockRenderWidgetHostImpl* host = new MockRenderWidgetHostImpl(
746 &delegate, process_host, MSG_ROUTING_NONE);
747 RenderWidgetHostViewMac* view = new RenderWidgetHostViewMac(host, false);
749 // Add a delegate to the view.
750 base::scoped_nsobject<MockRenderWidgetHostViewMacDelegate> view_delegate(
751 [[MockRenderWidgetHostViewMacDelegate alloc] init]);
752 view->SetDelegate(view_delegate.get());
754 // Send an initial wheel event for scrolling by 3 lines.
755 NSEvent* event1 = MockScrollWheelEventWithPhase(@selector(phaseBegan), 3);
756 [view->cocoa_view() scrollWheel:event1];
757 ASSERT_EQ(1U, process_host->sink().message_count());
758 process_host->sink().ClearMessages();
760 // Indicate that the wheel event was unhandled.
761 InputEventAck unhandled_ack(blink::WebInputEvent::MouseWheel,
762 INPUT_EVENT_ACK_STATE_NOT_CONSUMED);
763 scoped_ptr<IPC::Message> response1(
764 new InputHostMsg_HandleInputEvent_ACK(0, unhandled_ack));
765 host->OnMessageReceived(*response1);
767 // Check that the view delegate got an unhandled wheel event.
768 ASSERT_EQ(YES, view_delegate.get().unhandledWheelEventReceived);
769 view_delegate.get().unhandledWheelEventReceived = NO;
771 // Send another wheel event, this time for scrolling by 0 lines (empty event).
772 NSEvent* event2 = MockScrollWheelEventWithPhase(@selector(phaseChanged), 0);
773 [view->cocoa_view() scrollWheel:event2];
774 ASSERT_EQ(1U, process_host->sink().message_count());
776 // Indicate that the wheel event was also unhandled.
777 scoped_ptr<IPC::Message> response2(
778 new InputHostMsg_HandleInputEvent_ACK(0, unhandled_ack));
779 host->OnMessageReceived(*response2);
781 // Check that the view delegate ignored the empty unhandled wheel event.
782 ASSERT_EQ(NO, view_delegate.get().unhandledWheelEventReceived);
788 // Tests that when view initiated shutdown happens (i.e. RWHView is deleted
789 // before RWH), we clean up properly and don't leak the RWHVGuest.
790 TEST_F(RenderWidgetHostViewMacTest, GuestViewDoesNotLeak) {
791 MockRenderWidgetHostDelegate delegate;
792 TestBrowserContext browser_context;
793 MockRenderProcessHost* process_host =
794 new MockRenderProcessHost(&browser_context);
796 // Owned by its |cocoa_view()|.
797 MockRenderWidgetHostImpl* rwh = new MockRenderWidgetHostImpl(
798 &delegate, process_host, MSG_ROUTING_NONE);
799 RenderWidgetHostViewMac* view = new RenderWidgetHostViewMac(rwh, true);
801 // Add a delegate to the view.
802 base::scoped_nsobject<MockRenderWidgetHostViewMacDelegate> view_delegate(
803 [[MockRenderWidgetHostViewMacDelegate alloc] init]);
804 view->SetDelegate(view_delegate.get());
806 base::WeakPtr<RenderWidgetHostViewBase> guest_rwhv_weak =
807 (new RenderWidgetHostViewGuest(
808 rwh, NULL, view->GetWeakPtr()))->GetWeakPtr();
810 // Remove the cocoa_view() so |view| also goes away before |rwh|.
812 base::scoped_nsobject<RenderWidgetHostViewCocoa> rwhv_cocoa;
813 rwhv_cocoa.reset([view->cocoa_view() retain]);
820 // Let |guest_rwhv_weak| have a chance to delete itself.
821 base::RunLoop run_loop;
822 content::BrowserThread::PostTask(
823 content::BrowserThread::UI, FROM_HERE, run_loop.QuitClosure());
826 ASSERT_FALSE(guest_rwhv_weak.get());
829 // Tests setting background transparency. See also (disabled on Mac)
830 // RenderWidgetHostTest.Background. This test has some additional checks for
832 TEST_F(RenderWidgetHostViewMacTest, Background) {
833 TestBrowserContext browser_context;
834 MockRenderProcessHost* process_host =
835 new MockRenderProcessHost(&browser_context);
836 MockRenderWidgetHostDelegate delegate;
837 MockRenderWidgetHostImpl* host = new MockRenderWidgetHostImpl(
838 &delegate, process_host, MSG_ROUTING_NONE);
839 RenderWidgetHostViewMac* view = new RenderWidgetHostViewMac(host, false);
841 EXPECT_TRUE(view->GetBackgroundOpaque());
842 EXPECT_TRUE([view->cocoa_view() isOpaque]);
844 view->SetBackgroundColor(SK_ColorTRANSPARENT);
845 EXPECT_FALSE(view->GetBackgroundOpaque());
846 EXPECT_FALSE([view->cocoa_view() isOpaque]);
848 const IPC::Message* set_background;
849 set_background = process_host->sink().GetUniqueMessageMatching(
850 ViewMsg_SetBackgroundOpaque::ID);
851 ASSERT_TRUE(set_background);
852 base::Tuple<bool> sent_background;
853 ViewMsg_SetBackgroundOpaque::Read(set_background, &sent_background);
854 EXPECT_FALSE(base::get<0>(sent_background));
856 // Try setting it back.
857 process_host->sink().ClearMessages();
858 view->SetBackgroundColor(SK_ColorWHITE);
859 EXPECT_TRUE(view->GetBackgroundOpaque());
860 EXPECT_TRUE([view->cocoa_view() isOpaque]);
861 set_background = process_host->sink().GetUniqueMessageMatching(
862 ViewMsg_SetBackgroundOpaque::ID);
863 ASSERT_TRUE(set_background);
864 ViewMsg_SetBackgroundOpaque::Read(set_background, &sent_background);
865 EXPECT_TRUE(base::get<0>(sent_background));
870 class RenderWidgetHostViewMacPinchTest : public RenderWidgetHostViewMacTest {
872 RenderWidgetHostViewMacPinchTest() : process_host_(NULL) {}
874 bool ZoomDisabledForPinchUpdateMessage() {
875 const IPC::Message* message = NULL;
876 // The first message may be a PinchBegin. Go for the second message if
878 switch (process_host_->sink().message_count()) {
880 message = process_host_->sink().GetMessageAt(0);
883 message = process_host_->sink().GetMessageAt(1);
890 base::Tuple<IPC::WebInputEventPointer, ui::LatencyInfo, bool> data;
891 InputMsg_HandleInputEvent::Read(message, &data);
892 IPC::WebInputEventPointer ipc_event = base::get<0>(data);
893 const blink::WebGestureEvent* gesture_event =
894 static_cast<const blink::WebGestureEvent*>(ipc_event);
895 return gesture_event->data.pinchUpdate.zoomDisabled;
898 MockRenderProcessHost* process_host_;
901 TEST_F(RenderWidgetHostViewMacPinchTest, PinchThresholding) {
902 // This tests Lion+ functionality, so don't run the test pre-Lion.
903 if (!base::mac::IsOSLionOrLater())
906 // Initialize the view associated with a MockRenderWidgetHostImpl, rather than
907 // the MockRenderProcessHost that is set up by the test harness which mocks
908 // out |OnMessageReceived()|.
909 TestBrowserContext browser_context;
910 process_host_ = new MockRenderProcessHost(&browser_context);
911 process_host_->Init();
912 MockRenderWidgetHostDelegate delegate;
913 MockRenderWidgetHostImpl* host = new MockRenderWidgetHostImpl(
914 &delegate, process_host_, MSG_ROUTING_NONE);
915 RenderWidgetHostViewMac* view = new RenderWidgetHostViewMac(host, false);
917 // We'll use this IPC message to ack events.
918 InputEventAck ack(blink::WebInputEvent::GesturePinchUpdate,
919 INPUT_EVENT_ACK_STATE_CONSUMED);
920 scoped_ptr<IPC::Message> response(
921 new InputHostMsg_HandleInputEvent_ACK(0, ack));
923 // Do a gesture that crosses the threshold.
925 NSEvent* pinchBeginEvent =
926 MockGestureEvent(NSEventTypeBeginGesture, 0);
927 NSEvent* pinchUpdateEvents[3] = {
928 MockGestureEvent(NSEventTypeMagnify, 0.25),
929 MockGestureEvent(NSEventTypeMagnify, 0.25),
930 MockGestureEvent(NSEventTypeMagnify, 0.25),
932 NSEvent* pinchEndEvent =
933 MockGestureEvent(NSEventTypeEndGesture, 0);
935 [view->cocoa_view() beginGestureWithEvent:pinchBeginEvent];
936 EXPECT_EQ(0U, process_host_->sink().message_count());
938 // No zoom is sent for the first update event.
939 [view->cocoa_view() magnifyWithEvent:pinchUpdateEvents[0]];
940 host->OnMessageReceived(*response);
941 EXPECT_EQ(2U, process_host_->sink().message_count());
942 EXPECT_TRUE(ZoomDisabledForPinchUpdateMessage());
943 process_host_->sink().ClearMessages();
945 // The second update event crosses the threshold of 0.4, and so zoom is no
947 [view->cocoa_view() magnifyWithEvent:pinchUpdateEvents[1]];
948 EXPECT_FALSE(ZoomDisabledForPinchUpdateMessage());
949 host->OnMessageReceived(*response);
950 EXPECT_EQ(1U, process_host_->sink().message_count());
951 process_host_->sink().ClearMessages();
953 // The third update still has zoom enabled.
954 [view->cocoa_view() magnifyWithEvent:pinchUpdateEvents[2]];
955 EXPECT_FALSE(ZoomDisabledForPinchUpdateMessage());
956 host->OnMessageReceived(*response);
957 EXPECT_EQ(1U, process_host_->sink().message_count());
958 process_host_->sink().ClearMessages();
960 [view->cocoa_view() endGestureWithEvent:pinchEndEvent];
961 EXPECT_EQ(1U, process_host_->sink().message_count());
962 process_host_->sink().ClearMessages();
965 // Do a gesture that doesn't cross the threshold, but happens when we're not
966 // at page scale factor one, so it should be sent to the renderer.
968 NSEvent* pinchBeginEvent = MockGestureEvent(NSEventTypeBeginGesture, 0);
969 NSEvent* pinchUpdateEvent = MockGestureEvent(NSEventTypeMagnify, 0.25);
970 NSEvent* pinchEndEvent = MockGestureEvent(NSEventTypeEndGesture, 0);
972 view->page_at_minimum_scale_ = false;
974 [view->cocoa_view() beginGestureWithEvent:pinchBeginEvent];
975 EXPECT_EQ(0U, process_host_->sink().message_count());
977 // Expect that a zoom happen because the time threshold has not passed.
978 [view->cocoa_view() magnifyWithEvent:pinchUpdateEvent];
979 EXPECT_FALSE(ZoomDisabledForPinchUpdateMessage());
980 host->OnMessageReceived(*response);
981 EXPECT_EQ(2U, process_host_->sink().message_count());
982 process_host_->sink().ClearMessages();
984 [view->cocoa_view() endGestureWithEvent:pinchEndEvent];
985 EXPECT_EQ(1U, process_host_->sink().message_count());
986 process_host_->sink().ClearMessages();
989 // Do a gesture again, after the page scale is no longer at one, and ensure
990 // that it is thresholded again.
992 NSEvent* pinchBeginEvent = MockGestureEvent(NSEventTypeBeginGesture, 0);
993 NSEvent* pinchUpdateEvent = MockGestureEvent(NSEventTypeMagnify, 0.25);
994 NSEvent* pinchEndEvent = MockGestureEvent(NSEventTypeEndGesture, 0);
996 view->page_at_minimum_scale_ = true;
998 [view->cocoa_view() beginGestureWithEvent:pinchBeginEvent];
999 EXPECT_EQ(0U, process_host_->sink().message_count());
1001 // Get back to zoom one right after the begin event. This should still keep
1002 // the thresholding in place (it is latched at the begin event).
1003 view->page_at_minimum_scale_ = false;
1005 // Expect that zoom be disabled because the time threshold has passed.
1006 [view->cocoa_view() magnifyWithEvent:pinchUpdateEvent];
1007 EXPECT_EQ(2U, process_host_->sink().message_count());
1008 EXPECT_TRUE(ZoomDisabledForPinchUpdateMessage());
1009 host->OnMessageReceived(*response);
1010 process_host_->sink().ClearMessages();
1012 [view->cocoa_view() endGestureWithEvent:pinchEndEvent];
1013 EXPECT_EQ(1U, process_host_->sink().message_count());
1014 process_host_->sink().ClearMessages();
1022 } // namespace content