1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #include "content/browser/renderer_host/render_widget_host_view_mac.h"
7 #include "base/mac/mac_util.h"
8 #include "base/mac/scoped_nsautorelease_pool.h"
9 #include "base/mac/sdk_forward_declarations.h"
10 #include "base/strings/utf_string_conversions.h"
11 #include "content/browser/browser_thread_impl.h"
12 #include "content/browser/compositor/test/no_transport_image_transport_factory.h"
13 #include "content/browser/gpu/compositor_util.h"
14 #include "content/browser/renderer_host/render_widget_host_delegate.h"
15 #include "content/common/gpu/gpu_messages.h"
16 #include "content/common/input_messages.h"
17 #include "content/common/view_messages.h"
18 #include "content/public/browser/notification_types.h"
19 #include "content/public/browser/render_widget_host_view_mac_delegate.h"
20 #include "content/public/test/mock_render_process_host.h"
21 #include "content/public/test/test_browser_context.h"
22 #include "content/public/test/test_utils.h"
23 #include "content/test/test_render_view_host.h"
24 #include "testing/gmock/include/gmock/gmock.h"
25 #include "testing/gtest/include/gtest/gtest.h"
26 #include "ui/events/test/cocoa_test_event_utils.h"
27 #import "ui/gfx/test/ui_cocoa_test_helper.h"
29 // Helper class with methods used to mock -[NSEvent phase], used by
30 // |MockScrollWheelEventWithPhase()|.
31 @interface MockPhaseMethods : NSObject {
34 - (NSEventPhase)phaseBegan;
35 - (NSEventPhase)phaseChanged;
36 - (NSEventPhase)phaseEnded;
39 @implementation MockPhaseMethods
41 - (NSEventPhase)phaseBegan {
42 return NSEventPhaseBegan;
44 - (NSEventPhase)phaseChanged {
45 return NSEventPhaseChanged;
47 - (NSEventPhase)phaseEnded {
48 return NSEventPhaseEnded;
53 @interface MockRenderWidgetHostViewMacDelegate
54 : NSObject<RenderWidgetHostViewMacDelegate> {
55 BOOL unhandledWheelEventReceived_;
58 @property(nonatomic) BOOL unhandledWheelEventReceived;
62 @implementation MockRenderWidgetHostViewMacDelegate
64 @synthesize unhandledWheelEventReceived = unhandledWheelEventReceived_;
66 - (void)rendererHandledWheelEvent:(const blink::WebMouseWheelEvent&)event
67 consumed:(BOOL)consumed {
69 unhandledWheelEventReceived_ = true;
71 - (void)touchesBeganWithEvent:(NSEvent*)event {}
72 - (void)touchesMovedWithEvent:(NSEvent*)event {}
73 - (void)touchesCancelledWithEvent:(NSEvent*)event {}
74 - (void)touchesEndedWithEvent:(NSEvent*)event {}
75 - (void)beginGestureWithEvent:(NSEvent*)event {}
76 - (void)endGestureWithEvent:(NSEvent*)event {}
77 - (BOOL)canRubberbandLeft:(NSView*)view {
80 - (BOOL)canRubberbandRight:(NSView*)view {
90 class MockRenderWidgetHostDelegate : public RenderWidgetHostDelegate {
92 MockRenderWidgetHostDelegate() {}
93 virtual ~MockRenderWidgetHostDelegate() {}
96 class MockRenderWidgetHostImpl : public RenderWidgetHostImpl {
98 MockRenderWidgetHostImpl(RenderWidgetHostDelegate* delegate,
99 RenderProcessHost* process,
101 : RenderWidgetHostImpl(delegate, process, routing_id, false) {
104 MOCK_METHOD0(Focus, void());
105 MOCK_METHOD0(Blur, void());
108 // Generates the |length| of composition rectangle vector and save them to
109 // |output|. It starts from |origin| and each rectangle contains |unit_size|.
110 void GenerateCompositionRectArray(const gfx::Point& origin,
111 const gfx::Size& unit_size,
113 const std::vector<size_t>& break_points,
114 std::vector<gfx::Rect>* output) {
118 std::queue<int> break_point_queue;
119 for (size_t i = 0; i < break_points.size(); ++i)
120 break_point_queue.push(break_points[i]);
121 break_point_queue.push(length);
122 size_t next_break_point = break_point_queue.front();
123 break_point_queue.pop();
125 gfx::Rect current_rect(origin, unit_size);
126 for (size_t i = 0; i < length; ++i) {
127 if (i == next_break_point) {
128 current_rect.set_x(origin.x());
129 current_rect.set_y(current_rect.y() + current_rect.height());
130 next_break_point = break_point_queue.front();
131 break_point_queue.pop();
133 output->push_back(current_rect);
134 current_rect.set_x(current_rect.right());
138 gfx::Rect GetExpectedRect(const gfx::Point& origin,
139 const gfx::Size& size,
140 const gfx::Range& range,
143 origin.x() + range.start() * size.width(),
144 origin.y() + line_no * size.height(),
145 range.length() * size.width(),
149 // Returns NSScrollWheel event that mocks -phase. |mockPhaseSelector| should
150 // correspond to a method in |MockPhaseMethods| that returns the desired phase.
151 NSEvent* MockScrollWheelEventWithPhase(SEL mockPhaseSelector, int32_t delta) {
152 CGEventRef cg_event =
153 CGEventCreateScrollWheelEvent(NULL, kCGScrollEventUnitLine, 1, delta, 0);
154 NSEvent* event = [NSEvent eventWithCGEvent:cg_event];
156 method_setImplementation(
157 class_getInstanceMethod([NSEvent class], @selector(phase)),
158 [MockPhaseMethods instanceMethodForSelector:mockPhaseSelector]);
164 class RenderWidgetHostViewMacTest : public RenderViewHostImplTestHarness {
166 RenderWidgetHostViewMacTest() : old_rwhv_(NULL), rwhv_mac_(NULL) {}
168 virtual void SetUp() {
169 RenderViewHostImplTestHarness::SetUp();
170 if (IsDelegatedRendererEnabled()) {
171 ImageTransportFactory::InitializeForUnitTests(
172 scoped_ptr<ImageTransportFactory>(
173 new NoTransportImageTransportFactory));
176 // TestRenderViewHost's destruction assumes that its view is a
177 // TestRenderWidgetHostView, so store its view and reset it back to the
178 // stored view in |TearDown()|.
179 old_rwhv_ = rvh()->GetView();
181 // Owned by its |cocoa_view()|, i.e. |rwhv_cocoa_|.
182 rwhv_mac_ = new RenderWidgetHostViewMac(rvh());
183 rwhv_cocoa_.reset([rwhv_mac_->cocoa_view() retain]);
185 virtual void TearDown() {
186 // Make sure the rwhv_mac_ is gone once the superclass's |TearDown()| runs.
189 base::MessageLoop::current()->RunUntilIdle();
192 // See comment in SetUp().
193 test_rvh()->SetView(static_cast<RenderWidgetHostViewBase*>(old_rwhv_));
195 if (IsDelegatedRendererEnabled())
196 ImageTransportFactory::Terminate();
197 RenderViewHostImplTestHarness::TearDown();
201 // This class isn't derived from PlatformTest.
202 base::mac::ScopedNSAutoreleasePool pool_;
204 RenderWidgetHostView* old_rwhv_;
207 RenderWidgetHostViewMac* rwhv_mac_;
208 base::scoped_nsobject<RenderWidgetHostViewCocoa> rwhv_cocoa_;
211 DISALLOW_COPY_AND_ASSIGN(RenderWidgetHostViewMacTest);
214 TEST_F(RenderWidgetHostViewMacTest, Basic) {
217 TEST_F(RenderWidgetHostViewMacTest, AcceptsFirstResponder) {
218 // The RWHVCocoa should normally accept first responder status.
219 EXPECT_TRUE([rwhv_cocoa_.get() acceptsFirstResponder]);
221 // Unless we tell it not to.
222 rwhv_mac_->SetTakesFocusOnlyOnMouseDown(true);
223 EXPECT_FALSE([rwhv_cocoa_.get() acceptsFirstResponder]);
225 // But we can set things back to the way they were originally.
226 rwhv_mac_->SetTakesFocusOnlyOnMouseDown(false);
227 EXPECT_TRUE([rwhv_cocoa_.get() acceptsFirstResponder]);
230 TEST_F(RenderWidgetHostViewMacTest, TakesFocusOnMouseDown) {
231 base::scoped_nsobject<CocoaTestHelperWindow> window(
232 [[CocoaTestHelperWindow alloc] init]);
233 [[window contentView] addSubview:rwhv_cocoa_.get()];
235 // Even if the RWHVCocoa disallows first responder, clicking on it gives it
237 [window setPretendIsKeyWindow:YES];
238 [window makeFirstResponder:nil];
239 ASSERT_NE(rwhv_cocoa_.get(), [window firstResponder]);
241 rwhv_mac_->SetTakesFocusOnlyOnMouseDown(true);
242 EXPECT_FALSE([rwhv_cocoa_.get() acceptsFirstResponder]);
244 std::pair<NSEvent*, NSEvent*> clicks =
245 cocoa_test_event_utils::MouseClickInView(rwhv_cocoa_.get(), 1);
246 [rwhv_cocoa_.get() mouseDown:clicks.first];
247 EXPECT_EQ(rwhv_cocoa_.get(), [window firstResponder]);
250 TEST_F(RenderWidgetHostViewMacTest, Fullscreen) {
251 rwhv_mac_->InitAsFullscreen(NULL);
252 EXPECT_TRUE(rwhv_mac_->pepper_fullscreen_window());
254 // Break the reference cycle caused by pepper_fullscreen_window() without
255 // an <esc> event. See comment in
256 // release_pepper_fullscreen_window_for_testing().
257 rwhv_mac_->release_pepper_fullscreen_window_for_testing();
260 // Verify that escape key down in fullscreen mode suppressed the keyup event on
262 TEST_F(RenderWidgetHostViewMacTest, FullscreenCloseOnEscape) {
263 // Use our own RWH since we need to destroy it.
264 MockRenderWidgetHostDelegate delegate;
265 TestBrowserContext browser_context;
266 MockRenderProcessHost* process_host =
267 new MockRenderProcessHost(&browser_context);
268 // Owned by its |cocoa_view()|.
269 RenderWidgetHostImpl* rwh = new RenderWidgetHostImpl(
270 &delegate, process_host, MSG_ROUTING_NONE, false);
271 RenderWidgetHostViewMac* view = new RenderWidgetHostViewMac(rwh);
273 view->InitAsFullscreen(rwhv_mac_);
275 WindowedNotificationObserver observer(
276 NOTIFICATION_RENDER_WIDGET_HOST_DESTROYED,
277 Source<RenderWidgetHost>(rwh));
278 EXPECT_FALSE([rwhv_mac_->cocoa_view() suppressNextEscapeKeyUp]);
280 // Escape key down. Should close window and set |suppressNextEscapeKeyUp| on
282 [view->cocoa_view() keyEvent:
283 cocoa_test_event_utils::KeyEventWithKeyCode(53, 27, NSKeyDown, 0)];
285 EXPECT_TRUE([rwhv_mac_->cocoa_view() suppressNextEscapeKeyUp]);
287 // Escape key up on the parent should clear |suppressNextEscapeKeyUp|.
288 [rwhv_mac_->cocoa_view() keyEvent:
289 cocoa_test_event_utils::KeyEventWithKeyCode(53, 27, NSKeyUp, 0)];
290 EXPECT_FALSE([rwhv_mac_->cocoa_view() suppressNextEscapeKeyUp]);
293 // Test that command accelerators which destroy the fullscreen window
294 // don't crash when forwarded via the window's responder machinery.
295 TEST_F(RenderWidgetHostViewMacTest, AcceleratorDestroy) {
296 // Use our own RWH since we need to destroy it.
297 MockRenderWidgetHostDelegate delegate;
298 TestBrowserContext browser_context;
299 MockRenderProcessHost* process_host =
300 new MockRenderProcessHost(&browser_context);
301 // Owned by its |cocoa_view()|.
302 RenderWidgetHostImpl* rwh = new RenderWidgetHostImpl(
303 &delegate, process_host, MSG_ROUTING_NONE, false);
304 RenderWidgetHostViewMac* view = new RenderWidgetHostViewMac(rwh);
306 view->InitAsFullscreen(rwhv_mac_);
308 WindowedNotificationObserver observer(
309 NOTIFICATION_RENDER_WIDGET_HOST_DESTROYED,
310 Source<RenderWidgetHost>(rwh));
312 // Command-ESC will destroy the view, while the window is still in
313 // |-performKeyEquivalent:|. There are other cases where this can
314 // happen, Command-ESC is the easiest to trigger.
315 [[view->cocoa_view() window] performKeyEquivalent:
316 cocoa_test_event_utils::KeyEventWithKeyCode(
317 53, 27, NSKeyDown, NSCommandKeyMask)];
321 TEST_F(RenderWidgetHostViewMacTest, GetFirstRectForCharacterRangeCaretCase) {
322 const base::string16 kDummyString = base::UTF8ToUTF16("hogehoge");
323 const size_t kDummyOffset = 0;
325 gfx::Rect caret_rect(10, 11, 0, 10);
326 gfx::Range caret_range(0, 0);
327 ViewHostMsg_SelectionBounds_Params params;
330 NSRange actual_range;
331 rwhv_mac_->SelectionChanged(kDummyString, kDummyOffset, caret_range);
332 params.anchor_rect = params.focus_rect = caret_rect;
333 params.anchor_dir = params.focus_dir = blink::WebTextDirectionLeftToRight;
334 rwhv_mac_->SelectionBoundsChanged(params);
335 EXPECT_TRUE(rwhv_mac_->GetCachedFirstRectForCharacterRange(
336 caret_range.ToNSRange(),
339 EXPECT_EQ(caret_rect, gfx::Rect(NSRectToCGRect(rect)));
340 EXPECT_EQ(caret_range, gfx::Range(actual_range));
342 EXPECT_FALSE(rwhv_mac_->GetCachedFirstRectForCharacterRange(
343 gfx::Range(0, 1).ToNSRange(),
346 EXPECT_FALSE(rwhv_mac_->GetCachedFirstRectForCharacterRange(
347 gfx::Range(1, 1).ToNSRange(),
350 EXPECT_FALSE(rwhv_mac_->GetCachedFirstRectForCharacterRange(
351 gfx::Range(2, 3).ToNSRange(),
356 caret_rect = gfx::Rect(20, 11, 0, 10);
357 caret_range = gfx::Range(1, 1);
358 params.anchor_rect = params.focus_rect = caret_rect;
359 rwhv_mac_->SelectionChanged(kDummyString, kDummyOffset, caret_range);
360 rwhv_mac_->SelectionBoundsChanged(params);
361 EXPECT_TRUE(rwhv_mac_->GetCachedFirstRectForCharacterRange(
362 caret_range.ToNSRange(),
365 EXPECT_EQ(caret_rect, gfx::Rect(NSRectToCGRect(rect)));
366 EXPECT_EQ(caret_range, gfx::Range(actual_range));
368 EXPECT_FALSE(rwhv_mac_->GetCachedFirstRectForCharacterRange(
369 gfx::Range(0, 0).ToNSRange(),
372 EXPECT_FALSE(rwhv_mac_->GetCachedFirstRectForCharacterRange(
373 gfx::Range(1, 2).ToNSRange(),
376 EXPECT_FALSE(rwhv_mac_->GetCachedFirstRectForCharacterRange(
377 gfx::Range(2, 3).ToNSRange(),
382 caret_range = gfx::Range(1, 2);
383 rwhv_mac_->SelectionChanged(kDummyString, kDummyOffset, caret_range);
384 params.anchor_rect = caret_rect;
385 params.focus_rect = gfx::Rect(30, 11, 0, 10);
386 rwhv_mac_->SelectionBoundsChanged(params);
387 EXPECT_FALSE(rwhv_mac_->GetCachedFirstRectForCharacterRange(
388 gfx::Range(0, 0).ToNSRange(),
391 EXPECT_FALSE(rwhv_mac_->GetCachedFirstRectForCharacterRange(
392 gfx::Range(0, 1).ToNSRange(),
395 EXPECT_FALSE(rwhv_mac_->GetCachedFirstRectForCharacterRange(
396 gfx::Range(1, 1).ToNSRange(),
399 EXPECT_FALSE(rwhv_mac_->GetCachedFirstRectForCharacterRange(
400 gfx::Range(1, 2).ToNSRange(),
403 EXPECT_FALSE(rwhv_mac_->GetCachedFirstRectForCharacterRange(
404 gfx::Range(2, 2).ToNSRange(),
409 TEST_F(RenderWidgetHostViewMacTest, UpdateCompositionSinglelineCase) {
410 const gfx::Point kOrigin(10, 11);
411 const gfx::Size kBoundsUnit(10, 20);
414 // Make sure not crashing by passing NULL pointer instead of |actual_range|.
415 EXPECT_FALSE(rwhv_mac_->GetCachedFirstRectForCharacterRange(
416 gfx::Range(0, 0).ToNSRange(),
420 // If there are no update from renderer, always returned caret position.
421 NSRange actual_range;
422 EXPECT_FALSE(rwhv_mac_->GetCachedFirstRectForCharacterRange(
423 gfx::Range(0, 0).ToNSRange(),
426 EXPECT_FALSE(rwhv_mac_->GetCachedFirstRectForCharacterRange(
427 gfx::Range(0, 1).ToNSRange(),
430 EXPECT_FALSE(rwhv_mac_->GetCachedFirstRectForCharacterRange(
431 gfx::Range(1, 0).ToNSRange(),
434 EXPECT_FALSE(rwhv_mac_->GetCachedFirstRectForCharacterRange(
435 gfx::Range(1, 1).ToNSRange(),
438 EXPECT_FALSE(rwhv_mac_->GetCachedFirstRectForCharacterRange(
439 gfx::Range(1, 2).ToNSRange(),
443 // If the firstRectForCharacterRange is failed in renderer, empty rect vector
444 // is sent. Make sure this does not crash.
445 rwhv_mac_->ImeCompositionRangeChanged(gfx::Range(10, 12),
446 std::vector<gfx::Rect>());
447 EXPECT_FALSE(rwhv_mac_->GetCachedFirstRectForCharacterRange(
448 gfx::Range(10, 11).ToNSRange(),
452 const int kCompositionLength = 10;
453 std::vector<gfx::Rect> composition_bounds;
454 const int kCompositionStart = 3;
455 const gfx::Range kCompositionRange(kCompositionStart,
456 kCompositionStart + kCompositionLength);
457 GenerateCompositionRectArray(kOrigin,
460 std::vector<size_t>(),
461 &composition_bounds);
462 rwhv_mac_->ImeCompositionRangeChanged(kCompositionRange, composition_bounds);
464 // Out of range requests will return caret position.
465 EXPECT_FALSE(rwhv_mac_->GetCachedFirstRectForCharacterRange(
466 gfx::Range(0, 0).ToNSRange(),
469 EXPECT_FALSE(rwhv_mac_->GetCachedFirstRectForCharacterRange(
470 gfx::Range(1, 1).ToNSRange(),
473 EXPECT_FALSE(rwhv_mac_->GetCachedFirstRectForCharacterRange(
474 gfx::Range(1, 2).ToNSRange(),
477 EXPECT_FALSE(rwhv_mac_->GetCachedFirstRectForCharacterRange(
478 gfx::Range(2, 2).ToNSRange(),
481 EXPECT_FALSE(rwhv_mac_->GetCachedFirstRectForCharacterRange(
482 gfx::Range(13, 14).ToNSRange(),
485 EXPECT_FALSE(rwhv_mac_->GetCachedFirstRectForCharacterRange(
486 gfx::Range(14, 15).ToNSRange(),
490 for (int i = 0; i <= kCompositionLength; ++i) {
491 for (int j = 0; j <= kCompositionLength - i; ++j) {
492 const gfx::Range range(i, i + j);
493 const gfx::Rect expected_rect = GetExpectedRect(kOrigin,
497 const NSRange request_range = gfx::Range(
498 kCompositionStart + range.start(),
499 kCompositionStart + range.end()).ToNSRange();
500 EXPECT_TRUE(rwhv_mac_->GetCachedFirstRectForCharacterRange(
504 EXPECT_EQ(gfx::Range(request_range), gfx::Range(actual_range));
505 EXPECT_EQ(expected_rect, gfx::Rect(NSRectToCGRect(rect)));
507 // Make sure not crashing by passing NULL pointer instead of
509 EXPECT_TRUE(rwhv_mac_->GetCachedFirstRectForCharacterRange(
517 TEST_F(RenderWidgetHostViewMacTest, UpdateCompositionMultilineCase) {
518 const gfx::Point kOrigin(10, 11);
519 const gfx::Size kBoundsUnit(10, 20);
522 const int kCompositionLength = 30;
523 std::vector<gfx::Rect> composition_bounds;
524 const gfx::Range kCompositionRange(0, kCompositionLength);
525 // Set breaking point at 10 and 20.
526 std::vector<size_t> break_points;
527 break_points.push_back(10);
528 break_points.push_back(20);
529 GenerateCompositionRectArray(kOrigin,
533 &composition_bounds);
534 rwhv_mac_->ImeCompositionRangeChanged(kCompositionRange, composition_bounds);
536 // Range doesn't contain line breaking point.
538 range = gfx::Range(5, 8);
539 NSRange actual_range;
540 EXPECT_TRUE(rwhv_mac_->GetCachedFirstRectForCharacterRange(range.ToNSRange(),
543 EXPECT_EQ(range, gfx::Range(actual_range));
545 GetExpectedRect(kOrigin, kBoundsUnit, range, 0),
546 gfx::Rect(NSRectToCGRect(rect)));
547 range = gfx::Range(15, 18);
548 EXPECT_TRUE(rwhv_mac_->GetCachedFirstRectForCharacterRange(range.ToNSRange(),
551 EXPECT_EQ(range, gfx::Range(actual_range));
553 GetExpectedRect(kOrigin, kBoundsUnit, gfx::Range(5, 8), 1),
554 gfx::Rect(NSRectToCGRect(rect)));
555 range = gfx::Range(25, 28);
556 EXPECT_TRUE(rwhv_mac_->GetCachedFirstRectForCharacterRange(range.ToNSRange(),
559 EXPECT_EQ(range, gfx::Range(actual_range));
561 GetExpectedRect(kOrigin, kBoundsUnit, gfx::Range(5, 8), 2),
562 gfx::Rect(NSRectToCGRect(rect)));
564 // Range contains line breaking point.
565 range = gfx::Range(8, 12);
566 EXPECT_TRUE(rwhv_mac_->GetCachedFirstRectForCharacterRange(range.ToNSRange(),
569 EXPECT_EQ(gfx::Range(8, 10), gfx::Range(actual_range));
571 GetExpectedRect(kOrigin, kBoundsUnit, gfx::Range(8, 10), 0),
572 gfx::Rect(NSRectToCGRect(rect)));
573 range = gfx::Range(18, 22);
574 EXPECT_TRUE(rwhv_mac_->GetCachedFirstRectForCharacterRange(range.ToNSRange(),
577 EXPECT_EQ(gfx::Range(18, 20), gfx::Range(actual_range));
579 GetExpectedRect(kOrigin, kBoundsUnit, gfx::Range(8, 10), 1),
580 gfx::Rect(NSRectToCGRect(rect)));
582 // Start point is line breaking point.
583 range = gfx::Range(10, 12);
584 EXPECT_TRUE(rwhv_mac_->GetCachedFirstRectForCharacterRange(range.ToNSRange(),
587 EXPECT_EQ(gfx::Range(10, 12), gfx::Range(actual_range));
589 GetExpectedRect(kOrigin, kBoundsUnit, gfx::Range(0, 2), 1),
590 gfx::Rect(NSRectToCGRect(rect)));
591 range = gfx::Range(20, 22);
592 EXPECT_TRUE(rwhv_mac_->GetCachedFirstRectForCharacterRange(range.ToNSRange(),
595 EXPECT_EQ(gfx::Range(20, 22), gfx::Range(actual_range));
597 GetExpectedRect(kOrigin, kBoundsUnit, gfx::Range(0, 2), 2),
598 gfx::Rect(NSRectToCGRect(rect)));
600 // End point is line breaking point.
601 range = gfx::Range(5, 10);
602 EXPECT_TRUE(rwhv_mac_->GetCachedFirstRectForCharacterRange(range.ToNSRange(),
605 EXPECT_EQ(gfx::Range(5, 10), gfx::Range(actual_range));
607 GetExpectedRect(kOrigin, kBoundsUnit, gfx::Range(5, 10), 0),
608 gfx::Rect(NSRectToCGRect(rect)));
609 range = gfx::Range(15, 20);
610 EXPECT_TRUE(rwhv_mac_->GetCachedFirstRectForCharacterRange(range.ToNSRange(),
613 EXPECT_EQ(gfx::Range(15, 20), gfx::Range(actual_range));
615 GetExpectedRect(kOrigin, kBoundsUnit, gfx::Range(5, 10), 1),
616 gfx::Rect(NSRectToCGRect(rect)));
618 // Start and end point are same line breaking point.
619 range = gfx::Range(10, 10);
620 EXPECT_TRUE(rwhv_mac_->GetCachedFirstRectForCharacterRange(range.ToNSRange(),
623 EXPECT_EQ(gfx::Range(10, 10), gfx::Range(actual_range));
625 GetExpectedRect(kOrigin, kBoundsUnit, gfx::Range(0, 0), 1),
626 gfx::Rect(NSRectToCGRect(rect)));
627 range = gfx::Range(20, 20);
628 EXPECT_TRUE(rwhv_mac_->GetCachedFirstRectForCharacterRange(range.ToNSRange(),
631 EXPECT_EQ(gfx::Range(20, 20), gfx::Range(actual_range));
633 GetExpectedRect(kOrigin, kBoundsUnit, gfx::Range(0, 0), 2),
634 gfx::Rect(NSRectToCGRect(rect)));
636 // Start and end point are different line breaking point.
637 range = gfx::Range(10, 20);
638 EXPECT_TRUE(rwhv_mac_->GetCachedFirstRectForCharacterRange(range.ToNSRange(),
641 EXPECT_EQ(gfx::Range(10, 20), gfx::Range(actual_range));
643 GetExpectedRect(kOrigin, kBoundsUnit, gfx::Range(0, 10), 1),
644 gfx::Rect(NSRectToCGRect(rect)));
647 // Verify that |SetActive()| calls |RenderWidgetHostImpl::Blur()| and
648 // |RenderWidgetHostImp::Focus()|.
649 TEST_F(RenderWidgetHostViewMacTest, BlurAndFocusOnSetActive) {
650 MockRenderWidgetHostDelegate delegate;
651 TestBrowserContext browser_context;
652 MockRenderProcessHost* process_host =
653 new MockRenderProcessHost(&browser_context);
655 // Owned by its |cocoa_view()|.
656 MockRenderWidgetHostImpl* rwh = new MockRenderWidgetHostImpl(
657 &delegate, process_host, MSG_ROUTING_NONE);
658 RenderWidgetHostViewMac* view = new RenderWidgetHostViewMac(rwh);
660 base::scoped_nsobject<CocoaTestHelperWindow> window(
661 [[CocoaTestHelperWindow alloc] init]);
662 [[window contentView] addSubview:view->cocoa_view()];
664 EXPECT_CALL(*rwh, Focus());
665 [window makeFirstResponder:view->cocoa_view()];
666 testing::Mock::VerifyAndClearExpectations(rwh);
668 EXPECT_CALL(*rwh, Blur());
669 view->SetActive(false);
670 testing::Mock::VerifyAndClearExpectations(rwh);
672 EXPECT_CALL(*rwh, Focus());
673 view->SetActive(true);
674 testing::Mock::VerifyAndClearExpectations(rwh);
676 // Unsetting first responder should blur.
677 EXPECT_CALL(*rwh, Blur());
678 [window makeFirstResponder:nil];
679 testing::Mock::VerifyAndClearExpectations(rwh);
681 // |SetActive()| shoud not focus if view is not first responder.
682 EXPECT_CALL(*rwh, Focus()).Times(0);
683 view->SetActive(true);
684 testing::Mock::VerifyAndClearExpectations(rwh);
690 TEST_F(RenderWidgetHostViewMacTest, ScrollWheelEndEventDelivery) {
691 // This tests Lion+ functionality, so don't run the test pre-Lion.
692 if (!base::mac::IsOSLionOrLater())
695 // Initialize the view associated with a MockRenderWidgetHostImpl, rather than
696 // the MockRenderProcessHost that is set up by the test harness which mocks
697 // out |OnMessageReceived()|.
698 TestBrowserContext browser_context;
699 MockRenderProcessHost* process_host =
700 new MockRenderProcessHost(&browser_context);
701 MockRenderWidgetHostDelegate delegate;
702 MockRenderWidgetHostImpl* host = new MockRenderWidgetHostImpl(
703 &delegate, process_host, MSG_ROUTING_NONE);
704 RenderWidgetHostViewMac* view = new RenderWidgetHostViewMac(host);
706 // Send an initial wheel event with NSEventPhaseBegan to the view.
707 NSEvent* event1 = MockScrollWheelEventWithPhase(@selector(phaseBegan), 0);
708 [view->cocoa_view() scrollWheel:event1];
709 ASSERT_EQ(1U, process_host->sink().message_count());
711 // Send an ACK for the first wheel event, so that the queue will be flushed.
712 InputHostMsg_HandleInputEvent_ACK_Params ack;
713 ack.type = blink::WebInputEvent::MouseWheel;
714 ack.state = INPUT_EVENT_ACK_STATE_CONSUMED;
715 scoped_ptr<IPC::Message> response(
716 new InputHostMsg_HandleInputEvent_ACK(0, ack));
717 host->OnMessageReceived(*response);
719 // Post the NSEventPhaseEnded wheel event to NSApp and check whether the
720 // render view receives it.
721 NSEvent* event2 = MockScrollWheelEventWithPhase(@selector(phaseEnded), 0);
722 [NSApp postEvent:event2 atStart:NO];
723 base::MessageLoop::current()->RunUntilIdle();
724 ASSERT_EQ(2U, process_host->sink().message_count());
730 TEST_F(RenderWidgetHostViewMacTest, IgnoreEmptyUnhandledWheelEvent) {
731 // This tests Lion+ functionality, so don't run the test pre-Lion.
732 if (!base::mac::IsOSLionOrLater())
735 // Initialize the view associated with a MockRenderWidgetHostImpl, rather than
736 // the MockRenderProcessHost that is set up by the test harness which mocks
737 // out |OnMessageReceived()|.
738 TestBrowserContext browser_context;
739 MockRenderProcessHost* process_host =
740 new MockRenderProcessHost(&browser_context);
741 MockRenderWidgetHostDelegate delegate;
742 MockRenderWidgetHostImpl* host = new MockRenderWidgetHostImpl(
743 &delegate, process_host, MSG_ROUTING_NONE);
744 RenderWidgetHostViewMac* view = new RenderWidgetHostViewMac(host);
746 // Add a delegate to the view.
747 base::scoped_nsobject<MockRenderWidgetHostViewMacDelegate> view_delegate(
748 [[MockRenderWidgetHostViewMacDelegate alloc] init]);
749 view->SetDelegate(view_delegate.get());
751 // Send an initial wheel event for scrolling by 3 lines.
752 NSEvent* event1 = MockScrollWheelEventWithPhase(@selector(phaseBegan), 3);
753 [view->cocoa_view() scrollWheel:event1];
754 ASSERT_EQ(1U, process_host->sink().message_count());
755 process_host->sink().ClearMessages();
757 // Indicate that the wheel event was unhandled.
758 InputHostMsg_HandleInputEvent_ACK_Params unhandled_ack;
759 unhandled_ack.type = blink::WebInputEvent::MouseWheel;
760 unhandled_ack.state = INPUT_EVENT_ACK_STATE_NOT_CONSUMED;
761 scoped_ptr<IPC::Message> response1(
762 new InputHostMsg_HandleInputEvent_ACK(0, unhandled_ack));
763 host->OnMessageReceived(*response1);
765 // Check that the view delegate got an unhandled wheel event.
766 ASSERT_EQ(YES, view_delegate.get().unhandledWheelEventReceived);
767 view_delegate.get().unhandledWheelEventReceived = NO;
769 // Send another wheel event, this time for scrolling by 0 lines (empty event).
770 NSEvent* event2 = MockScrollWheelEventWithPhase(@selector(phaseChanged), 0);
771 [view->cocoa_view() scrollWheel:event2];
772 ASSERT_EQ(1U, process_host->sink().message_count());
774 // Indicate that the wheel event was also unhandled.
775 scoped_ptr<IPC::Message> response2(
776 new InputHostMsg_HandleInputEvent_ACK(0, unhandled_ack));
777 host->OnMessageReceived(*response2);
779 // Check that the view delegate ignored the empty unhandled wheel event.
780 ASSERT_EQ(NO, view_delegate.get().unhandledWheelEventReceived);
786 } // namespace content