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 {
96 NSEventType type, NSTimeInterval timestamp, double magnification) {
97 id event = [OCMockObject mockForClass:[NSEvent class]];
98 NSPoint locationInWindow = NSMakePoint(0, 0);
101 NSUInteger modifierFlags = 0;
102 [(NSEvent*)[[event stub] andReturnValue:OCMOCK_VALUE(type)] type];
103 [(NSEvent*)[[event stub]
104 andReturnValue:OCMOCK_VALUE(locationInWindow)] locationInWindow];
105 [(NSEvent*)[[event stub] andReturnValue:OCMOCK_VALUE(deltaX)] deltaX];
106 [(NSEvent*)[[event stub] andReturnValue:OCMOCK_VALUE(deltaY)] deltaY];
107 [(NSEvent*)[[event stub] andReturnValue:OCMOCK_VALUE(timestamp)] timestamp];
108 [(NSEvent*)[[event stub]
109 andReturnValue:OCMOCK_VALUE(modifierFlags)] modifierFlags];
110 [(NSEvent*)[[event stub]
111 andReturnValue:OCMOCK_VALUE(magnification)] magnification];
115 class MockRenderWidgetHostDelegate : public RenderWidgetHostDelegate {
117 MockRenderWidgetHostDelegate() {}
118 ~MockRenderWidgetHostDelegate() override {}
121 class MockRenderWidgetHostImpl : public RenderWidgetHostImpl {
123 MockRenderWidgetHostImpl(RenderWidgetHostDelegate* delegate,
124 RenderProcessHost* process,
126 : RenderWidgetHostImpl(delegate, process, routing_id, false) {
129 MOCK_METHOD0(Focus, void());
130 MOCK_METHOD0(Blur, void());
133 // Generates the |length| of composition rectangle vector and save them to
134 // |output|. It starts from |origin| and each rectangle contains |unit_size|.
135 void GenerateCompositionRectArray(const gfx::Point& origin,
136 const gfx::Size& unit_size,
138 const std::vector<size_t>& break_points,
139 std::vector<gfx::Rect>* output) {
143 std::queue<int> break_point_queue;
144 for (size_t i = 0; i < break_points.size(); ++i)
145 break_point_queue.push(break_points[i]);
146 break_point_queue.push(length);
147 size_t next_break_point = break_point_queue.front();
148 break_point_queue.pop();
150 gfx::Rect current_rect(origin, unit_size);
151 for (size_t i = 0; i < length; ++i) {
152 if (i == next_break_point) {
153 current_rect.set_x(origin.x());
154 current_rect.set_y(current_rect.y() + current_rect.height());
155 next_break_point = break_point_queue.front();
156 break_point_queue.pop();
158 output->push_back(current_rect);
159 current_rect.set_x(current_rect.right());
163 gfx::Rect GetExpectedRect(const gfx::Point& origin,
164 const gfx::Size& size,
165 const gfx::Range& range,
168 origin.x() + range.start() * size.width(),
169 origin.y() + line_no * size.height(),
170 range.length() * size.width(),
174 // Returns NSScrollWheel event that mocks -phase. |mockPhaseSelector| should
175 // correspond to a method in |MockPhaseMethods| that returns the desired phase.
176 NSEvent* MockScrollWheelEventWithPhase(SEL mockPhaseSelector, int32_t delta) {
177 CGEventRef cg_event =
178 CGEventCreateScrollWheelEvent(NULL, kCGScrollEventUnitLine, 1, delta, 0);
179 NSEvent* event = [NSEvent eventWithCGEvent:cg_event];
181 method_setImplementation(
182 class_getInstanceMethod([NSEvent class], @selector(phase)),
183 [MockPhaseMethods instanceMethodForSelector:mockPhaseSelector]);
189 class RenderWidgetHostViewMacTest : public RenderViewHostImplTestHarness {
191 RenderWidgetHostViewMacTest() : old_rwhv_(NULL), rwhv_mac_(NULL) {}
193 void SetUp() override {
194 RenderViewHostImplTestHarness::SetUp();
195 if (IsDelegatedRendererEnabled()) {
196 ImageTransportFactory::InitializeForUnitTests(
197 scoped_ptr<ImageTransportFactory>(
198 new NoTransportImageTransportFactory));
201 // TestRenderViewHost's destruction assumes that its view is a
202 // TestRenderWidgetHostView, so store its view and reset it back to the
203 // stored view in |TearDown()|.
204 old_rwhv_ = rvh()->GetView();
206 // Owned by its |cocoa_view()|, i.e. |rwhv_cocoa_|.
207 rwhv_mac_ = new RenderWidgetHostViewMac(rvh(), false);
208 rwhv_cocoa_.reset([rwhv_mac_->cocoa_view() retain]);
210 void TearDown() override {
211 // Make sure the rwhv_mac_ is gone once the superclass's |TearDown()| runs.
215 // See comment in SetUp().
216 test_rvh()->SetView(static_cast<RenderWidgetHostViewBase*>(old_rwhv_));
218 if (IsDelegatedRendererEnabled())
219 ImageTransportFactory::Terminate();
220 RenderViewHostImplTestHarness::TearDown();
223 void RecycleAndWait() {
225 base::MessageLoop::current()->RunUntilIdle();
230 // This class isn't derived from PlatformTest.
231 base::mac::ScopedNSAutoreleasePool pool_;
233 RenderWidgetHostView* old_rwhv_;
236 RenderWidgetHostViewMac* rwhv_mac_;
237 base::scoped_nsobject<RenderWidgetHostViewCocoa> rwhv_cocoa_;
240 DISALLOW_COPY_AND_ASSIGN(RenderWidgetHostViewMacTest);
243 TEST_F(RenderWidgetHostViewMacTest, Basic) {
246 TEST_F(RenderWidgetHostViewMacTest, AcceptsFirstResponder) {
247 // The RWHVCocoa should normally accept first responder status.
248 EXPECT_TRUE([rwhv_cocoa_.get() acceptsFirstResponder]);
251 TEST_F(RenderWidgetHostViewMacTest, Fullscreen) {
252 rwhv_mac_->InitAsFullscreen(NULL);
253 EXPECT_TRUE(rwhv_mac_->pepper_fullscreen_window());
255 // Break the reference cycle caused by pepper_fullscreen_window() without
256 // an <esc> event. See comment in
257 // release_pepper_fullscreen_window_for_testing().
258 rwhv_mac_->release_pepper_fullscreen_window_for_testing();
261 // Verify that escape key down in fullscreen mode suppressed the keyup event on
263 TEST_F(RenderWidgetHostViewMacTest, FullscreenCloseOnEscape) {
264 // Use our own RWH since we need to destroy it.
265 MockRenderWidgetHostDelegate delegate;
266 TestBrowserContext browser_context;
267 MockRenderProcessHost* process_host =
268 new MockRenderProcessHost(&browser_context);
269 // Owned by its |cocoa_view()|.
270 RenderWidgetHostImpl* rwh = new RenderWidgetHostImpl(
271 &delegate, process_host, MSG_ROUTING_NONE, false);
272 RenderWidgetHostViewMac* view = new RenderWidgetHostViewMac(rwh, false);
274 view->InitAsFullscreen(rwhv_mac_);
276 WindowedNotificationObserver observer(
277 NOTIFICATION_RENDER_WIDGET_HOST_DESTROYED,
278 Source<RenderWidgetHost>(rwh));
279 EXPECT_FALSE([rwhv_mac_->cocoa_view() suppressNextEscapeKeyUp]);
281 // Escape key down. Should close window and set |suppressNextEscapeKeyUp| on
283 [view->cocoa_view() keyEvent:
284 cocoa_test_event_utils::KeyEventWithKeyCode(53, 27, NSKeyDown, 0)];
286 EXPECT_TRUE([rwhv_mac_->cocoa_view() suppressNextEscapeKeyUp]);
288 // Escape key up on the parent should clear |suppressNextEscapeKeyUp|.
289 [rwhv_mac_->cocoa_view() keyEvent:
290 cocoa_test_event_utils::KeyEventWithKeyCode(53, 27, NSKeyUp, 0)];
291 EXPECT_FALSE([rwhv_mac_->cocoa_view() suppressNextEscapeKeyUp]);
294 // Test that command accelerators which destroy the fullscreen window
295 // don't crash when forwarded via the window's responder machinery.
296 TEST_F(RenderWidgetHostViewMacTest, AcceleratorDestroy) {
297 // Use our own RWH since we need to destroy it.
298 MockRenderWidgetHostDelegate delegate;
299 TestBrowserContext browser_context;
300 MockRenderProcessHost* process_host =
301 new MockRenderProcessHost(&browser_context);
302 // Owned by its |cocoa_view()|.
303 RenderWidgetHostImpl* rwh = new RenderWidgetHostImpl(
304 &delegate, process_host, MSG_ROUTING_NONE, false);
305 RenderWidgetHostViewMac* view = new RenderWidgetHostViewMac(rwh, false);
307 view->InitAsFullscreen(rwhv_mac_);
309 WindowedNotificationObserver observer(
310 NOTIFICATION_RENDER_WIDGET_HOST_DESTROYED,
311 Source<RenderWidgetHost>(rwh));
313 // Command-ESC will destroy the view, while the window is still in
314 // |-performKeyEquivalent:|. There are other cases where this can
315 // happen, Command-ESC is the easiest to trigger.
316 [[view->cocoa_view() window] performKeyEquivalent:
317 cocoa_test_event_utils::KeyEventWithKeyCode(
318 53, 27, NSKeyDown, NSCommandKeyMask)];
322 TEST_F(RenderWidgetHostViewMacTest, GetFirstRectForCharacterRangeCaretCase) {
323 const base::string16 kDummyString = base::UTF8ToUTF16("hogehoge");
324 const size_t kDummyOffset = 0;
326 gfx::Rect caret_rect(10, 11, 0, 10);
327 gfx::Range caret_range(0, 0);
328 ViewHostMsg_SelectionBounds_Params params;
331 NSRange actual_range;
332 rwhv_mac_->SelectionChanged(kDummyString, kDummyOffset, caret_range);
333 params.anchor_rect = params.focus_rect = caret_rect;
334 params.anchor_dir = params.focus_dir = blink::WebTextDirectionLeftToRight;
335 rwhv_mac_->SelectionBoundsChanged(params);
336 EXPECT_TRUE(rwhv_mac_->GetCachedFirstRectForCharacterRange(
337 caret_range.ToNSRange(),
340 EXPECT_EQ(caret_rect, gfx::Rect(NSRectToCGRect(rect)));
341 EXPECT_EQ(caret_range, gfx::Range(actual_range));
343 EXPECT_FALSE(rwhv_mac_->GetCachedFirstRectForCharacterRange(
344 gfx::Range(0, 1).ToNSRange(),
347 EXPECT_FALSE(rwhv_mac_->GetCachedFirstRectForCharacterRange(
348 gfx::Range(1, 1).ToNSRange(),
351 EXPECT_FALSE(rwhv_mac_->GetCachedFirstRectForCharacterRange(
352 gfx::Range(2, 3).ToNSRange(),
357 caret_rect = gfx::Rect(20, 11, 0, 10);
358 caret_range = gfx::Range(1, 1);
359 params.anchor_rect = params.focus_rect = caret_rect;
360 rwhv_mac_->SelectionChanged(kDummyString, kDummyOffset, caret_range);
361 rwhv_mac_->SelectionBoundsChanged(params);
362 EXPECT_TRUE(rwhv_mac_->GetCachedFirstRectForCharacterRange(
363 caret_range.ToNSRange(),
366 EXPECT_EQ(caret_rect, gfx::Rect(NSRectToCGRect(rect)));
367 EXPECT_EQ(caret_range, gfx::Range(actual_range));
369 EXPECT_FALSE(rwhv_mac_->GetCachedFirstRectForCharacterRange(
370 gfx::Range(0, 0).ToNSRange(),
373 EXPECT_FALSE(rwhv_mac_->GetCachedFirstRectForCharacterRange(
374 gfx::Range(1, 2).ToNSRange(),
377 EXPECT_FALSE(rwhv_mac_->GetCachedFirstRectForCharacterRange(
378 gfx::Range(2, 3).ToNSRange(),
383 caret_range = gfx::Range(1, 2);
384 rwhv_mac_->SelectionChanged(kDummyString, kDummyOffset, caret_range);
385 params.anchor_rect = caret_rect;
386 params.focus_rect = gfx::Rect(30, 11, 0, 10);
387 rwhv_mac_->SelectionBoundsChanged(params);
388 EXPECT_FALSE(rwhv_mac_->GetCachedFirstRectForCharacterRange(
389 gfx::Range(0, 0).ToNSRange(),
392 EXPECT_FALSE(rwhv_mac_->GetCachedFirstRectForCharacterRange(
393 gfx::Range(0, 1).ToNSRange(),
396 EXPECT_FALSE(rwhv_mac_->GetCachedFirstRectForCharacterRange(
397 gfx::Range(1, 1).ToNSRange(),
400 EXPECT_FALSE(rwhv_mac_->GetCachedFirstRectForCharacterRange(
401 gfx::Range(1, 2).ToNSRange(),
404 EXPECT_FALSE(rwhv_mac_->GetCachedFirstRectForCharacterRange(
405 gfx::Range(2, 2).ToNSRange(),
410 TEST_F(RenderWidgetHostViewMacTest, UpdateCompositionSinglelineCase) {
411 const gfx::Point kOrigin(10, 11);
412 const gfx::Size kBoundsUnit(10, 20);
415 // Make sure not crashing by passing NULL pointer instead of |actual_range|.
416 EXPECT_FALSE(rwhv_mac_->GetCachedFirstRectForCharacterRange(
417 gfx::Range(0, 0).ToNSRange(),
421 // If there are no update from renderer, always returned caret position.
422 NSRange actual_range;
423 EXPECT_FALSE(rwhv_mac_->GetCachedFirstRectForCharacterRange(
424 gfx::Range(0, 0).ToNSRange(),
427 EXPECT_FALSE(rwhv_mac_->GetCachedFirstRectForCharacterRange(
428 gfx::Range(0, 1).ToNSRange(),
431 EXPECT_FALSE(rwhv_mac_->GetCachedFirstRectForCharacterRange(
432 gfx::Range(1, 0).ToNSRange(),
435 EXPECT_FALSE(rwhv_mac_->GetCachedFirstRectForCharacterRange(
436 gfx::Range(1, 1).ToNSRange(),
439 EXPECT_FALSE(rwhv_mac_->GetCachedFirstRectForCharacterRange(
440 gfx::Range(1, 2).ToNSRange(),
444 // If the firstRectForCharacterRange is failed in renderer, empty rect vector
445 // is sent. Make sure this does not crash.
446 rwhv_mac_->ImeCompositionRangeChanged(gfx::Range(10, 12),
447 std::vector<gfx::Rect>());
448 EXPECT_FALSE(rwhv_mac_->GetCachedFirstRectForCharacterRange(
449 gfx::Range(10, 11).ToNSRange(),
453 const int kCompositionLength = 10;
454 std::vector<gfx::Rect> composition_bounds;
455 const int kCompositionStart = 3;
456 const gfx::Range kCompositionRange(kCompositionStart,
457 kCompositionStart + kCompositionLength);
458 GenerateCompositionRectArray(kOrigin,
461 std::vector<size_t>(),
462 &composition_bounds);
463 rwhv_mac_->ImeCompositionRangeChanged(kCompositionRange, composition_bounds);
465 // Out of range requests will return caret position.
466 EXPECT_FALSE(rwhv_mac_->GetCachedFirstRectForCharacterRange(
467 gfx::Range(0, 0).ToNSRange(),
470 EXPECT_FALSE(rwhv_mac_->GetCachedFirstRectForCharacterRange(
471 gfx::Range(1, 1).ToNSRange(),
474 EXPECT_FALSE(rwhv_mac_->GetCachedFirstRectForCharacterRange(
475 gfx::Range(1, 2).ToNSRange(),
478 EXPECT_FALSE(rwhv_mac_->GetCachedFirstRectForCharacterRange(
479 gfx::Range(2, 2).ToNSRange(),
482 EXPECT_FALSE(rwhv_mac_->GetCachedFirstRectForCharacterRange(
483 gfx::Range(13, 14).ToNSRange(),
486 EXPECT_FALSE(rwhv_mac_->GetCachedFirstRectForCharacterRange(
487 gfx::Range(14, 15).ToNSRange(),
491 for (int i = 0; i <= kCompositionLength; ++i) {
492 for (int j = 0; j <= kCompositionLength - i; ++j) {
493 const gfx::Range range(i, i + j);
494 const gfx::Rect expected_rect = GetExpectedRect(kOrigin,
498 const NSRange request_range = gfx::Range(
499 kCompositionStart + range.start(),
500 kCompositionStart + range.end()).ToNSRange();
501 EXPECT_TRUE(rwhv_mac_->GetCachedFirstRectForCharacterRange(
505 EXPECT_EQ(gfx::Range(request_range), gfx::Range(actual_range));
506 EXPECT_EQ(expected_rect, gfx::Rect(NSRectToCGRect(rect)));
508 // Make sure not crashing by passing NULL pointer instead of
510 EXPECT_TRUE(rwhv_mac_->GetCachedFirstRectForCharacterRange(
518 TEST_F(RenderWidgetHostViewMacTest, UpdateCompositionMultilineCase) {
519 const gfx::Point kOrigin(10, 11);
520 const gfx::Size kBoundsUnit(10, 20);
523 const int kCompositionLength = 30;
524 std::vector<gfx::Rect> composition_bounds;
525 const gfx::Range kCompositionRange(0, kCompositionLength);
526 // Set breaking point at 10 and 20.
527 std::vector<size_t> break_points;
528 break_points.push_back(10);
529 break_points.push_back(20);
530 GenerateCompositionRectArray(kOrigin,
534 &composition_bounds);
535 rwhv_mac_->ImeCompositionRangeChanged(kCompositionRange, composition_bounds);
537 // Range doesn't contain line breaking point.
539 range = gfx::Range(5, 8);
540 NSRange actual_range;
541 EXPECT_TRUE(rwhv_mac_->GetCachedFirstRectForCharacterRange(range.ToNSRange(),
544 EXPECT_EQ(range, gfx::Range(actual_range));
546 GetExpectedRect(kOrigin, kBoundsUnit, range, 0),
547 gfx::Rect(NSRectToCGRect(rect)));
548 range = gfx::Range(15, 18);
549 EXPECT_TRUE(rwhv_mac_->GetCachedFirstRectForCharacterRange(range.ToNSRange(),
552 EXPECT_EQ(range, gfx::Range(actual_range));
554 GetExpectedRect(kOrigin, kBoundsUnit, gfx::Range(5, 8), 1),
555 gfx::Rect(NSRectToCGRect(rect)));
556 range = gfx::Range(25, 28);
557 EXPECT_TRUE(rwhv_mac_->GetCachedFirstRectForCharacterRange(range.ToNSRange(),
560 EXPECT_EQ(range, gfx::Range(actual_range));
562 GetExpectedRect(kOrigin, kBoundsUnit, gfx::Range(5, 8), 2),
563 gfx::Rect(NSRectToCGRect(rect)));
565 // Range contains line breaking point.
566 range = gfx::Range(8, 12);
567 EXPECT_TRUE(rwhv_mac_->GetCachedFirstRectForCharacterRange(range.ToNSRange(),
570 EXPECT_EQ(gfx::Range(8, 10), gfx::Range(actual_range));
572 GetExpectedRect(kOrigin, kBoundsUnit, gfx::Range(8, 10), 0),
573 gfx::Rect(NSRectToCGRect(rect)));
574 range = gfx::Range(18, 22);
575 EXPECT_TRUE(rwhv_mac_->GetCachedFirstRectForCharacterRange(range.ToNSRange(),
578 EXPECT_EQ(gfx::Range(18, 20), gfx::Range(actual_range));
580 GetExpectedRect(kOrigin, kBoundsUnit, gfx::Range(8, 10), 1),
581 gfx::Rect(NSRectToCGRect(rect)));
583 // Start point is line breaking point.
584 range = gfx::Range(10, 12);
585 EXPECT_TRUE(rwhv_mac_->GetCachedFirstRectForCharacterRange(range.ToNSRange(),
588 EXPECT_EQ(gfx::Range(10, 12), gfx::Range(actual_range));
590 GetExpectedRect(kOrigin, kBoundsUnit, gfx::Range(0, 2), 1),
591 gfx::Rect(NSRectToCGRect(rect)));
592 range = gfx::Range(20, 22);
593 EXPECT_TRUE(rwhv_mac_->GetCachedFirstRectForCharacterRange(range.ToNSRange(),
596 EXPECT_EQ(gfx::Range(20, 22), gfx::Range(actual_range));
598 GetExpectedRect(kOrigin, kBoundsUnit, gfx::Range(0, 2), 2),
599 gfx::Rect(NSRectToCGRect(rect)));
601 // End point is line breaking point.
602 range = gfx::Range(5, 10);
603 EXPECT_TRUE(rwhv_mac_->GetCachedFirstRectForCharacterRange(range.ToNSRange(),
606 EXPECT_EQ(gfx::Range(5, 10), gfx::Range(actual_range));
608 GetExpectedRect(kOrigin, kBoundsUnit, gfx::Range(5, 10), 0),
609 gfx::Rect(NSRectToCGRect(rect)));
610 range = gfx::Range(15, 20);
611 EXPECT_TRUE(rwhv_mac_->GetCachedFirstRectForCharacterRange(range.ToNSRange(),
614 EXPECT_EQ(gfx::Range(15, 20), gfx::Range(actual_range));
616 GetExpectedRect(kOrigin, kBoundsUnit, gfx::Range(5, 10), 1),
617 gfx::Rect(NSRectToCGRect(rect)));
619 // Start and end point are same line breaking point.
620 range = gfx::Range(10, 10);
621 EXPECT_TRUE(rwhv_mac_->GetCachedFirstRectForCharacterRange(range.ToNSRange(),
624 EXPECT_EQ(gfx::Range(10, 10), gfx::Range(actual_range));
626 GetExpectedRect(kOrigin, kBoundsUnit, gfx::Range(0, 0), 1),
627 gfx::Rect(NSRectToCGRect(rect)));
628 range = gfx::Range(20, 20);
629 EXPECT_TRUE(rwhv_mac_->GetCachedFirstRectForCharacterRange(range.ToNSRange(),
632 EXPECT_EQ(gfx::Range(20, 20), gfx::Range(actual_range));
634 GetExpectedRect(kOrigin, kBoundsUnit, gfx::Range(0, 0), 2),
635 gfx::Rect(NSRectToCGRect(rect)));
637 // Start and end point are different line breaking point.
638 range = gfx::Range(10, 20);
639 EXPECT_TRUE(rwhv_mac_->GetCachedFirstRectForCharacterRange(range.ToNSRange(),
642 EXPECT_EQ(gfx::Range(10, 20), gfx::Range(actual_range));
644 GetExpectedRect(kOrigin, kBoundsUnit, gfx::Range(0, 10), 1),
645 gfx::Rect(NSRectToCGRect(rect)));
648 // Verify that |SetActive()| calls |RenderWidgetHostImpl::Blur()| and
649 // |RenderWidgetHostImp::Focus()|.
650 TEST_F(RenderWidgetHostViewMacTest, BlurAndFocusOnSetActive) {
651 MockRenderWidgetHostDelegate delegate;
652 TestBrowserContext browser_context;
653 MockRenderProcessHost* process_host =
654 new MockRenderProcessHost(&browser_context);
656 // Owned by its |cocoa_view()|.
657 MockRenderWidgetHostImpl* rwh = new MockRenderWidgetHostImpl(
658 &delegate, process_host, MSG_ROUTING_NONE);
659 RenderWidgetHostViewMac* view = new RenderWidgetHostViewMac(rwh, false);
661 base::scoped_nsobject<CocoaTestHelperWindow> window(
662 [[CocoaTestHelperWindow alloc] init]);
663 [[window contentView] addSubview:view->cocoa_view()];
665 EXPECT_CALL(*rwh, Focus());
666 [window makeFirstResponder:view->cocoa_view()];
667 testing::Mock::VerifyAndClearExpectations(rwh);
669 EXPECT_CALL(*rwh, Blur());
670 view->SetActive(false);
671 testing::Mock::VerifyAndClearExpectations(rwh);
673 EXPECT_CALL(*rwh, Focus());
674 view->SetActive(true);
675 testing::Mock::VerifyAndClearExpectations(rwh);
677 // Unsetting first responder should blur.
678 EXPECT_CALL(*rwh, Blur());
679 [window makeFirstResponder:nil];
680 testing::Mock::VerifyAndClearExpectations(rwh);
682 // |SetActive()| shoud not focus if view is not first responder.
683 EXPECT_CALL(*rwh, Focus()).Times(0);
684 view->SetActive(true);
685 testing::Mock::VerifyAndClearExpectations(rwh);
691 TEST_F(RenderWidgetHostViewMacTest, ScrollWheelEndEventDelivery) {
692 // This tests Lion+ functionality, so don't run the test pre-Lion.
693 if (!base::mac::IsOSLionOrLater())
696 // Initialize the view associated with a MockRenderWidgetHostImpl, rather than
697 // the MockRenderProcessHost that is set up by the test harness which mocks
698 // out |OnMessageReceived()|.
699 TestBrowserContext browser_context;
700 MockRenderProcessHost* process_host =
701 new MockRenderProcessHost(&browser_context);
702 MockRenderWidgetHostDelegate delegate;
703 MockRenderWidgetHostImpl* host = new MockRenderWidgetHostImpl(
704 &delegate, process_host, MSG_ROUTING_NONE);
705 RenderWidgetHostViewMac* view = new RenderWidgetHostViewMac(host, false);
707 // Send an initial wheel event with NSEventPhaseBegan to the view.
708 NSEvent* event1 = MockScrollWheelEventWithPhase(@selector(phaseBegan), 0);
709 [view->cocoa_view() scrollWheel:event1];
710 ASSERT_EQ(1U, process_host->sink().message_count());
712 // Send an ACK for the first wheel event, so that the queue will be flushed.
713 InputHostMsg_HandleInputEvent_ACK_Params ack;
714 ack.type = blink::WebInputEvent::MouseWheel;
715 ack.state = INPUT_EVENT_ACK_STATE_CONSUMED;
716 scoped_ptr<IPC::Message> response(
717 new InputHostMsg_HandleInputEvent_ACK(0, ack));
718 host->OnMessageReceived(*response);
720 // Post the NSEventPhaseEnded wheel event to NSApp and check whether the
721 // render view receives it.
722 NSEvent* event2 = MockScrollWheelEventWithPhase(@selector(phaseEnded), 0);
723 [NSApp postEvent:event2 atStart:NO];
724 base::MessageLoop::current()->RunUntilIdle();
725 ASSERT_EQ(2U, process_host->sink().message_count());
731 TEST_F(RenderWidgetHostViewMacTest, IgnoreEmptyUnhandledWheelEvent) {
732 // This tests Lion+ functionality, so don't run the test pre-Lion.
733 if (!base::mac::IsOSLionOrLater())
736 // Initialize the view associated with a MockRenderWidgetHostImpl, rather than
737 // the MockRenderProcessHost that is set up by the test harness which mocks
738 // out |OnMessageReceived()|.
739 TestBrowserContext browser_context;
740 MockRenderProcessHost* process_host =
741 new MockRenderProcessHost(&browser_context);
742 MockRenderWidgetHostDelegate delegate;
743 MockRenderWidgetHostImpl* host = new MockRenderWidgetHostImpl(
744 &delegate, process_host, MSG_ROUTING_NONE);
745 RenderWidgetHostViewMac* view = new RenderWidgetHostViewMac(host, false);
747 // Add a delegate to the view.
748 base::scoped_nsobject<MockRenderWidgetHostViewMacDelegate> view_delegate(
749 [[MockRenderWidgetHostViewMacDelegate alloc] init]);
750 view->SetDelegate(view_delegate.get());
752 // Send an initial wheel event for scrolling by 3 lines.
753 NSEvent* event1 = MockScrollWheelEventWithPhase(@selector(phaseBegan), 3);
754 [view->cocoa_view() scrollWheel:event1];
755 ASSERT_EQ(1U, process_host->sink().message_count());
756 process_host->sink().ClearMessages();
758 // Indicate that the wheel event was unhandled.
759 InputHostMsg_HandleInputEvent_ACK_Params unhandled_ack;
760 unhandled_ack.type = blink::WebInputEvent::MouseWheel;
761 unhandled_ack.state = INPUT_EVENT_ACK_STATE_NOT_CONSUMED;
762 scoped_ptr<IPC::Message> response1(
763 new InputHostMsg_HandleInputEvent_ACK(0, unhandled_ack));
764 host->OnMessageReceived(*response1);
766 // Check that the view delegate got an unhandled wheel event.
767 ASSERT_EQ(YES, view_delegate.get().unhandledWheelEventReceived);
768 view_delegate.get().unhandledWheelEventReceived = NO;
770 // Send another wheel event, this time for scrolling by 0 lines (empty event).
771 NSEvent* event2 = MockScrollWheelEventWithPhase(@selector(phaseChanged), 0);
772 [view->cocoa_view() scrollWheel:event2];
773 ASSERT_EQ(1U, process_host->sink().message_count());
775 // Indicate that the wheel event was also unhandled.
776 scoped_ptr<IPC::Message> response2(
777 new InputHostMsg_HandleInputEvent_ACK(0, unhandled_ack));
778 host->OnMessageReceived(*response2);
780 // Check that the view delegate ignored the empty unhandled wheel event.
781 ASSERT_EQ(NO, view_delegate.get().unhandledWheelEventReceived);
787 // Tests that when view initiated shutdown happens (i.e. RWHView is deleted
788 // before RWH), we clean up properly and don't leak the RWHVGuest.
789 TEST_F(RenderWidgetHostViewMacTest, GuestViewDoesNotLeak) {
790 MockRenderWidgetHostDelegate delegate;
791 TestBrowserContext browser_context;
792 MockRenderProcessHost* process_host =
793 new MockRenderProcessHost(&browser_context);
795 // Owned by its |cocoa_view()|.
796 MockRenderWidgetHostImpl* rwh = new MockRenderWidgetHostImpl(
797 &delegate, process_host, MSG_ROUTING_NONE);
798 RenderWidgetHostViewMac* view = new RenderWidgetHostViewMac(rwh, true);
800 // Add a delegate to the view.
801 base::scoped_nsobject<MockRenderWidgetHostViewMacDelegate> view_delegate(
802 [[MockRenderWidgetHostViewMacDelegate alloc] init]);
803 view->SetDelegate(view_delegate.get());
805 base::WeakPtr<RenderWidgetHostViewBase> guest_rwhv_weak =
806 (new RenderWidgetHostViewGuest(
807 rwh, NULL, view->GetWeakPtr()))->GetWeakPtr();
809 // Remove the cocoa_view() so |view| also goes away before |rwh|.
811 base::scoped_nsobject<RenderWidgetHostViewCocoa> rwhv_cocoa;
812 rwhv_cocoa.reset([view->cocoa_view() retain]);
819 // Let |guest_rwhv_weak| have a chance to delete itself.
820 base::RunLoop run_loop;
821 content::BrowserThread::PostTask(
822 content::BrowserThread::UI, FROM_HERE, run_loop.QuitClosure());
825 ASSERT_FALSE(guest_rwhv_weak.get());
828 // Tests setting background transparency. See also (disabled on Mac)
829 // RenderWidgetHostTest.Background. This test has some additional checks for
831 TEST_F(RenderWidgetHostViewMacTest, Background) {
832 TestBrowserContext browser_context;
833 MockRenderProcessHost* process_host =
834 new MockRenderProcessHost(&browser_context);
835 MockRenderWidgetHostDelegate delegate;
836 MockRenderWidgetHostImpl* host = new MockRenderWidgetHostImpl(
837 &delegate, process_host, MSG_ROUTING_NONE);
838 RenderWidgetHostViewMac* view = new RenderWidgetHostViewMac(host, false);
840 EXPECT_TRUE(view->GetBackgroundOpaque());
841 EXPECT_TRUE([view->cocoa_view() isOpaque]);
843 view->SetBackgroundColor(SK_ColorTRANSPARENT);
844 EXPECT_FALSE(view->GetBackgroundOpaque());
845 EXPECT_FALSE([view->cocoa_view() isOpaque]);
847 const IPC::Message* set_background;
848 set_background = process_host->sink().GetUniqueMessageMatching(
849 ViewMsg_SetBackgroundOpaque::ID);
850 ASSERT_TRUE(set_background);
851 Tuple<bool> sent_background;
852 ViewMsg_SetBackgroundOpaque::Read(set_background, &sent_background);
853 EXPECT_FALSE(get<0>(sent_background));
855 // Try setting it back.
856 process_host->sink().ClearMessages();
857 view->SetBackgroundColor(SK_ColorWHITE);
858 EXPECT_TRUE(view->GetBackgroundOpaque());
859 EXPECT_TRUE([view->cocoa_view() isOpaque]);
860 set_background = process_host->sink().GetUniqueMessageMatching(
861 ViewMsg_SetBackgroundOpaque::ID);
862 ASSERT_TRUE(set_background);
863 ViewMsg_SetBackgroundOpaque::Read(set_background, &sent_background);
864 EXPECT_TRUE(get<0>(sent_background));
869 TEST_F(RenderWidgetHostViewMacTest, PinchThresholding) {
870 // This tests Lion+ functionality, so don't run the test pre-Lion.
871 if (!base::mac::IsOSLionOrLater())
874 // Initialize the view associated with a MockRenderWidgetHostImpl, rather than
875 // the MockRenderProcessHost that is set up by the test harness which mocks
876 // out |OnMessageReceived()|.
877 TestBrowserContext browser_context;
878 MockRenderProcessHost* process_host =
879 new MockRenderProcessHost(&browser_context);
880 MockRenderWidgetHostDelegate delegate;
881 MockRenderWidgetHostImpl* host = new MockRenderWidgetHostImpl(
882 &delegate, process_host, MSG_ROUTING_NONE);
883 RenderWidgetHostViewMac* view = new RenderWidgetHostViewMac(host, false);
885 // We'll use this IPC message to ack events.
886 InputHostMsg_HandleInputEvent_ACK_Params ack;
887 ack.type = blink::WebInputEvent::GesturePinchUpdate;
888 ack.state = INPUT_EVENT_ACK_STATE_CONSUMED;
889 scoped_ptr<IPC::Message> response(
890 new InputHostMsg_HandleInputEvent_ACK(0, ack));
892 // Do a gesture that crosses the threshold.
894 NSEvent* pinchBeginEvent =
895 MockGestureEvent(NSEventTypeBeginGesture, 100.1, 0);
896 NSEvent* pinchUpdateEvents[3] = {
897 MockGestureEvent(NSEventTypeMagnify, 100.2, 0.25),
898 MockGestureEvent(NSEventTypeMagnify, 100.3, 0.25),
899 MockGestureEvent(NSEventTypeMagnify, 100.4, 0.25),
901 NSEvent* pinchEndEvent =
902 MockGestureEvent(NSEventTypeEndGesture, 100.5, 0);
904 // No messages are sent for the pinch begin and the first update event.
905 [view->cocoa_view() beginGestureWithEvent:pinchBeginEvent];
906 [view->cocoa_view() magnifyWithEvent:pinchUpdateEvents[0]];
907 ASSERT_EQ(0U, process_host->sink().message_count());
909 // The second update event crosses the threshold of 0.4, and so a begin
910 // and update are sent.
911 [view->cocoa_view() magnifyWithEvent:pinchUpdateEvents[1]];
912 ASSERT_EQ(2U, process_host->sink().message_count());
913 host->OnMessageReceived(*response);
915 // The third update only causes one event to be sent.
916 [view->cocoa_view() magnifyWithEvent:pinchUpdateEvents[2]];
917 ASSERT_EQ(3U, process_host->sink().message_count());
918 host->OnMessageReceived(*response);
921 [view->cocoa_view() endGestureWithEvent:pinchEndEvent];
922 ASSERT_EQ(4U, process_host->sink().message_count());
924 process_host->sink().ClearMessages();
927 // Do a gesture that doesn't cross the threshold, but happens within 1 second,
928 // so it should be sent to the renderer.
930 NSEvent* pinchBeginEvent =
931 MockGestureEvent(NSEventTypeBeginGesture, 101.0, 0);
932 NSEvent* pinchUpdateEvent =
933 MockGestureEvent(NSEventTypeMagnify, 101.1, 0.25);
934 NSEvent* pinchEndEvent =
935 MockGestureEvent(NSEventTypeEndGesture, 101.2, 0);
937 // No message comes for the begin event.
938 [view->cocoa_view() beginGestureWithEvent:pinchBeginEvent];
939 ASSERT_EQ(0U, process_host->sink().message_count());
941 // Two messages come for the first update event.
942 [view->cocoa_view() magnifyWithEvent:pinchUpdateEvent];
943 ASSERT_EQ(2U, process_host->sink().message_count());
944 host->OnMessageReceived(*response);
946 // The end event sends one message.
947 [view->cocoa_view() endGestureWithEvent:pinchEndEvent];
948 ASSERT_EQ(3U, process_host->sink().message_count());
950 process_host->sink().ClearMessages();
953 // Do a gesture that doesn't cross the threshold and happens more than one
956 NSEvent* pinchBeginEvent =
957 MockGestureEvent(NSEventTypeBeginGesture, 103.0, 0);
958 NSEvent* pinchUpdateEvent =
959 MockGestureEvent(NSEventTypeMagnify, 103.1, 0.25);
960 NSEvent* pinchEndEvent =
961 MockGestureEvent(NSEventTypeEndGesture, 103.2, 0);
963 // No message comes for the begin event.
964 [view->cocoa_view() beginGestureWithEvent:pinchBeginEvent];
965 ASSERT_EQ(0U, process_host->sink().message_count());
967 // Two messages come for the first update event.
968 [view->cocoa_view() magnifyWithEvent:pinchUpdateEvent];
969 ASSERT_EQ(0U, process_host->sink().message_count());
972 [view->cocoa_view() endGestureWithEvent:pinchEndEvent];
973 ASSERT_EQ(0U, process_host->sink().message_count());
975 process_host->sink().ClearMessages();
983 } // namespace content