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/strings/utf_string_conversions.h"
10 #include "content/browser/browser_thread_impl.h"
11 #include "content/browser/renderer_host/render_widget_host_delegate.h"
12 #include "content/common/gpu/gpu_messages.h"
13 #include "content/common/input_messages.h"
14 #include "content/common/view_messages.h"
15 #include "content/public/browser/notification_types.h"
16 #include "content/public/browser/render_widget_host_view_mac_delegate.h"
17 #include "content/public/test/mock_render_process_host.h"
18 #include "content/public/test/test_browser_context.h"
19 #include "content/public/test/test_utils.h"
20 #include "content/test/test_render_view_host.h"
21 #include "testing/gmock/include/gmock/gmock.h"
22 #include "testing/gtest/include/gtest/gtest.h"
23 #include "ui/base/test/cocoa_test_event_utils.h"
24 #import "ui/base/test/ui_cocoa_test_helper.h"
26 // Declare things that are part of the 10.7 SDK.
27 #if !defined(MAC_OS_X_VERSION_10_7) || \
28 MAC_OS_X_VERSION_MAX_ALLOWED < MAC_OS_X_VERSION_10_7
30 NSEventPhaseNone = 0, // event not associated with a phase.
31 NSEventPhaseBegan = 0x1 << 0,
32 NSEventPhaseStationary = 0x1 << 1,
33 NSEventPhaseChanged = 0x1 << 2,
34 NSEventPhaseEnded = 0x1 << 3,
35 NSEventPhaseCancelled = 0x1 << 4,
37 typedef NSUInteger NSEventPhase;
39 @interface NSEvent (LionAPI)
40 - (NSEventPhase)phase;
45 // Helper class with methods used to mock -[NSEvent phase], used by
46 // |MockScrollWheelEventWithPhase()|.
47 @interface MockPhaseMethods : NSObject {
50 - (NSEventPhase)phaseBegan;
51 - (NSEventPhase)phaseChanged;
52 - (NSEventPhase)phaseEnded;
55 @implementation MockPhaseMethods
57 - (NSEventPhase)phaseBegan {
58 return NSEventPhaseBegan;
60 - (NSEventPhase)phaseChanged {
61 return NSEventPhaseChanged;
63 - (NSEventPhase)phaseEnded {
64 return NSEventPhaseEnded;
69 @interface MockRenderWidgetHostViewMacDelegate
70 : NSObject<RenderWidgetHostViewMacDelegate> {
71 BOOL unhandledWheelEventReceived_;
74 @property(nonatomic) BOOL unhandledWheelEventReceived;
76 - (void)gotUnhandledWheelEvent;
79 @implementation MockRenderWidgetHostViewMacDelegate
81 @synthesize unhandledWheelEventReceived = unhandledWheelEventReceived_;
83 - (void)gotUnhandledWheelEvent {
84 unhandledWheelEventReceived_ = true;
86 - (void)touchesBeganWithEvent:(NSEvent*)event{}
87 - (void)touchesMovedWithEvent:(NSEvent*)event{}
88 - (void)touchesCancelledWithEvent:(NSEvent*)event{}
89 - (void)touchesEndedWithEvent:(NSEvent*)event{}
90 - (void)beginGestureWithEvent:(NSEvent*)event{}
91 - (void)endGestureWithEvent:(NSEvent*)event{}
99 class MockRenderWidgetHostDelegate : public RenderWidgetHostDelegate {
101 MockRenderWidgetHostDelegate() {}
102 virtual ~MockRenderWidgetHostDelegate() {}
105 class MockRenderWidgetHostImpl : public RenderWidgetHostImpl {
107 MockRenderWidgetHostImpl(RenderWidgetHostDelegate* delegate,
108 RenderProcessHost* process,
110 : RenderWidgetHostImpl(delegate, process, routing_id, false) {
113 MOCK_METHOD0(Focus, void());
114 MOCK_METHOD0(Blur, void());
117 // Generates the |length| of composition rectangle vector and save them to
118 // |output|. It starts from |origin| and each rectangle contains |unit_size|.
119 void GenerateCompositionRectArray(const gfx::Point& origin,
120 const gfx::Size& unit_size,
122 const std::vector<size_t>& break_points,
123 std::vector<gfx::Rect>* output) {
127 std::queue<int> break_point_queue;
128 for (size_t i = 0; i < break_points.size(); ++i)
129 break_point_queue.push(break_points[i]);
130 break_point_queue.push(length);
131 size_t next_break_point = break_point_queue.front();
132 break_point_queue.pop();
134 gfx::Rect current_rect(origin, unit_size);
135 for (size_t i = 0; i < length; ++i) {
136 if (i == next_break_point) {
137 current_rect.set_x(origin.x());
138 current_rect.set_y(current_rect.y() + current_rect.height());
139 next_break_point = break_point_queue.front();
140 break_point_queue.pop();
142 output->push_back(current_rect);
143 current_rect.set_x(current_rect.right());
147 gfx::Rect GetExpectedRect(const gfx::Point& origin,
148 const gfx::Size& size,
149 const gfx::Range& range,
152 origin.x() + range.start() * size.width(),
153 origin.y() + line_no * size.height(),
154 range.length() * size.width(),
158 // Returns NSScrollWheel event that mocks -phase. |mockPhaseSelector| should
159 // correspond to a method in |MockPhaseMethods| that returns the desired phase.
160 NSEvent* MockScrollWheelEventWithPhase(SEL mockPhaseSelector, int32_t delta) {
161 CGEventRef cg_event =
162 CGEventCreateScrollWheelEvent(NULL, kCGScrollEventUnitLine, 1, delta, 0);
163 NSEvent* event = [NSEvent eventWithCGEvent:cg_event];
165 method_setImplementation(
166 class_getInstanceMethod([NSEvent class], @selector(phase)),
167 [MockPhaseMethods instanceMethodForSelector:mockPhaseSelector]);
173 class RenderWidgetHostViewMacTest : public RenderViewHostImplTestHarness {
175 RenderWidgetHostViewMacTest() : old_rwhv_(NULL), rwhv_mac_(NULL) {}
177 virtual void SetUp() {
178 RenderViewHostImplTestHarness::SetUp();
180 // TestRenderViewHost's destruction assumes that its view is a
181 // TestRenderWidgetHostView, so store its view and reset it back to the
182 // stored view in |TearDown()|.
183 old_rwhv_ = rvh()->GetView();
185 // Owned by its |cocoa_view()|, i.e. |rwhv_cocoa_|.
186 rwhv_mac_ = static_cast<RenderWidgetHostViewMac*>(
187 RenderWidgetHostView::CreateViewForWidget(rvh()));
188 rwhv_cocoa_.reset([rwhv_mac_->cocoa_view() retain]);
190 virtual void TearDown() {
191 // Make sure the rwhv_mac_ is gone once the superclass's |TearDown()| runs.
194 base::MessageLoop::current()->RunUntilIdle();
197 // See comment in SetUp().
198 test_rvh()->SetView(old_rwhv_);
200 RenderViewHostImplTestHarness::TearDown();
204 // This class isn't derived from PlatformTest.
205 base::mac::ScopedNSAutoreleasePool pool_;
207 RenderWidgetHostView* old_rwhv_;
210 RenderWidgetHostViewMac* rwhv_mac_;
211 base::scoped_nsobject<RenderWidgetHostViewCocoa> rwhv_cocoa_;
214 DISALLOW_COPY_AND_ASSIGN(RenderWidgetHostViewMacTest);
217 TEST_F(RenderWidgetHostViewMacTest, Basic) {
220 TEST_F(RenderWidgetHostViewMacTest, AcceptsFirstResponder) {
221 // The RWHVCocoa should normally accept first responder status.
222 EXPECT_TRUE([rwhv_cocoa_.get() acceptsFirstResponder]);
224 // Unless we tell it not to.
225 rwhv_mac_->SetTakesFocusOnlyOnMouseDown(true);
226 EXPECT_FALSE([rwhv_cocoa_.get() acceptsFirstResponder]);
228 // But we can set things back to the way they were originally.
229 rwhv_mac_->SetTakesFocusOnlyOnMouseDown(false);
230 EXPECT_TRUE([rwhv_cocoa_.get() acceptsFirstResponder]);
233 TEST_F(RenderWidgetHostViewMacTest, TakesFocusOnMouseDown) {
234 base::scoped_nsobject<CocoaTestHelperWindow> window(
235 [[CocoaTestHelperWindow alloc] init]);
236 [[window contentView] addSubview:rwhv_cocoa_.get()];
238 // Even if the RWHVCocoa disallows first responder, clicking on it gives it
240 [window setPretendIsKeyWindow:YES];
241 [window makeFirstResponder:nil];
242 ASSERT_NE(rwhv_cocoa_.get(), [window firstResponder]);
244 rwhv_mac_->SetTakesFocusOnlyOnMouseDown(true);
245 EXPECT_FALSE([rwhv_cocoa_.get() acceptsFirstResponder]);
247 std::pair<NSEvent*, NSEvent*> clicks =
248 cocoa_test_event_utils::MouseClickInView(rwhv_cocoa_.get(), 1);
249 [rwhv_cocoa_.get() mouseDown:clicks.first];
250 EXPECT_EQ(rwhv_cocoa_.get(), [window firstResponder]);
253 TEST_F(RenderWidgetHostViewMacTest, Fullscreen) {
254 rwhv_mac_->InitAsFullscreen(NULL);
255 EXPECT_TRUE(rwhv_mac_->pepper_fullscreen_window());
257 // Break the reference cycle caused by pepper_fullscreen_window() without
258 // an <esc> event. See comment in
259 // release_pepper_fullscreen_window_for_testing().
260 rwhv_mac_->release_pepper_fullscreen_window_for_testing();
263 // Verify that escape key down in fullscreen mode suppressed the keyup event on
265 TEST_F(RenderWidgetHostViewMacTest, FullscreenCloseOnEscape) {
266 // Use our own RWH since we need to destroy it.
267 MockRenderWidgetHostDelegate delegate;
268 TestBrowserContext browser_context;
269 MockRenderProcessHost* process_host =
270 new MockRenderProcessHost(&browser_context);
271 // Owned by its |cocoa_view()|.
272 RenderWidgetHostImpl* rwh = new RenderWidgetHostImpl(
273 &delegate, process_host, MSG_ROUTING_NONE, false);
274 RenderWidgetHostViewMac* view = static_cast<RenderWidgetHostViewMac*>(
275 RenderWidgetHostView::CreateViewForWidget(rwh));
277 view->InitAsFullscreen(rwhv_mac_);
279 WindowedNotificationObserver observer(
280 NOTIFICATION_RENDER_WIDGET_HOST_DESTROYED,
281 Source<RenderWidgetHost>(rwh));
282 EXPECT_FALSE([rwhv_mac_->cocoa_view() suppressNextEscapeKeyUp]);
284 // Escape key down. Should close window and set |suppressNextEscapeKeyUp| on
286 [view->cocoa_view() keyEvent:
287 cocoa_test_event_utils::KeyEventWithKeyCode(53, 27, NSKeyDown, 0)];
289 EXPECT_TRUE([rwhv_mac_->cocoa_view() suppressNextEscapeKeyUp]);
291 // Escape key up on the parent should clear |suppressNextEscapeKeyUp|.
292 [rwhv_mac_->cocoa_view() keyEvent:
293 cocoa_test_event_utils::KeyEventWithKeyCode(53, 27, NSKeyUp, 0)];
294 EXPECT_FALSE([rwhv_mac_->cocoa_view() suppressNextEscapeKeyUp]);
297 // Test that command accelerators which destroy the fullscreen window
298 // don't crash when forwarded via the window's responder machinery.
299 TEST_F(RenderWidgetHostViewMacTest, AcceleratorDestroy) {
300 // Use our own RWH since we need to destroy it.
301 MockRenderWidgetHostDelegate delegate;
302 TestBrowserContext browser_context;
303 MockRenderProcessHost* process_host =
304 new MockRenderProcessHost(&browser_context);
305 // Owned by its |cocoa_view()|.
306 RenderWidgetHostImpl* rwh = new RenderWidgetHostImpl(
307 &delegate, process_host, MSG_ROUTING_NONE, false);
308 RenderWidgetHostViewMac* view = static_cast<RenderWidgetHostViewMac*>(
309 RenderWidgetHostView::CreateViewForWidget(rwh));
311 view->InitAsFullscreen(rwhv_mac_);
313 WindowedNotificationObserver observer(
314 NOTIFICATION_RENDER_WIDGET_HOST_DESTROYED,
315 Source<RenderWidgetHost>(rwh));
317 // Command-ESC will destroy the view, while the window is still in
318 // |-performKeyEquivalent:|. There are other cases where this can
319 // happen, Command-ESC is the easiest to trigger.
320 [[view->cocoa_view() window] performKeyEquivalent:
321 cocoa_test_event_utils::KeyEventWithKeyCode(
322 53, 27, NSKeyDown, NSCommandKeyMask)];
326 TEST_F(RenderWidgetHostViewMacTest, GetFirstRectForCharacterRangeCaretCase) {
327 const base::string16 kDummyString = base::UTF8ToUTF16("hogehoge");
328 const size_t kDummyOffset = 0;
330 gfx::Rect caret_rect(10, 11, 0, 10);
331 gfx::Range caret_range(0, 0);
332 ViewHostMsg_SelectionBounds_Params params;
335 NSRange actual_range;
336 rwhv_mac_->SelectionChanged(kDummyString, kDummyOffset, caret_range);
337 params.anchor_rect = params.focus_rect = caret_rect;
338 params.anchor_dir = params.focus_dir = blink::WebTextDirectionLeftToRight;
339 rwhv_mac_->SelectionBoundsChanged(params);
340 EXPECT_TRUE(rwhv_mac_->GetCachedFirstRectForCharacterRange(
341 caret_range.ToNSRange(),
344 EXPECT_EQ(caret_rect, gfx::Rect(NSRectToCGRect(rect)));
345 EXPECT_EQ(caret_range, gfx::Range(actual_range));
347 EXPECT_FALSE(rwhv_mac_->GetCachedFirstRectForCharacterRange(
348 gfx::Range(0, 1).ToNSRange(),
351 EXPECT_FALSE(rwhv_mac_->GetCachedFirstRectForCharacterRange(
352 gfx::Range(1, 1).ToNSRange(),
355 EXPECT_FALSE(rwhv_mac_->GetCachedFirstRectForCharacterRange(
356 gfx::Range(2, 3).ToNSRange(),
361 caret_rect = gfx::Rect(20, 11, 0, 10);
362 caret_range = gfx::Range(1, 1);
363 params.anchor_rect = params.focus_rect = caret_rect;
364 rwhv_mac_->SelectionChanged(kDummyString, kDummyOffset, caret_range);
365 rwhv_mac_->SelectionBoundsChanged(params);
366 EXPECT_TRUE(rwhv_mac_->GetCachedFirstRectForCharacterRange(
367 caret_range.ToNSRange(),
370 EXPECT_EQ(caret_rect, gfx::Rect(NSRectToCGRect(rect)));
371 EXPECT_EQ(caret_range, gfx::Range(actual_range));
373 EXPECT_FALSE(rwhv_mac_->GetCachedFirstRectForCharacterRange(
374 gfx::Range(0, 0).ToNSRange(),
377 EXPECT_FALSE(rwhv_mac_->GetCachedFirstRectForCharacterRange(
378 gfx::Range(1, 2).ToNSRange(),
381 EXPECT_FALSE(rwhv_mac_->GetCachedFirstRectForCharacterRange(
382 gfx::Range(2, 3).ToNSRange(),
387 caret_range = gfx::Range(1, 2);
388 rwhv_mac_->SelectionChanged(kDummyString, kDummyOffset, caret_range);
389 params.anchor_rect = caret_rect;
390 params.focus_rect = gfx::Rect(30, 11, 0, 10);
391 rwhv_mac_->SelectionBoundsChanged(params);
392 EXPECT_FALSE(rwhv_mac_->GetCachedFirstRectForCharacterRange(
393 gfx::Range(0, 0).ToNSRange(),
396 EXPECT_FALSE(rwhv_mac_->GetCachedFirstRectForCharacterRange(
397 gfx::Range(0, 1).ToNSRange(),
400 EXPECT_FALSE(rwhv_mac_->GetCachedFirstRectForCharacterRange(
401 gfx::Range(1, 1).ToNSRange(),
404 EXPECT_FALSE(rwhv_mac_->GetCachedFirstRectForCharacterRange(
405 gfx::Range(1, 2).ToNSRange(),
408 EXPECT_FALSE(rwhv_mac_->GetCachedFirstRectForCharacterRange(
409 gfx::Range(2, 2).ToNSRange(),
414 TEST_F(RenderWidgetHostViewMacTest, UpdateCompositionSinglelineCase) {
415 const gfx::Point kOrigin(10, 11);
416 const gfx::Size kBoundsUnit(10, 20);
419 // Make sure not crashing by passing NULL pointer instead of |actual_range|.
420 EXPECT_FALSE(rwhv_mac_->GetCachedFirstRectForCharacterRange(
421 gfx::Range(0, 0).ToNSRange(),
425 // If there are no update from renderer, always returned caret position.
426 NSRange actual_range;
427 EXPECT_FALSE(rwhv_mac_->GetCachedFirstRectForCharacterRange(
428 gfx::Range(0, 0).ToNSRange(),
431 EXPECT_FALSE(rwhv_mac_->GetCachedFirstRectForCharacterRange(
432 gfx::Range(0, 1).ToNSRange(),
435 EXPECT_FALSE(rwhv_mac_->GetCachedFirstRectForCharacterRange(
436 gfx::Range(1, 0).ToNSRange(),
439 EXPECT_FALSE(rwhv_mac_->GetCachedFirstRectForCharacterRange(
440 gfx::Range(1, 1).ToNSRange(),
443 EXPECT_FALSE(rwhv_mac_->GetCachedFirstRectForCharacterRange(
444 gfx::Range(1, 2).ToNSRange(),
448 // If the firstRectForCharacterRange is failed in renderer, empty rect vector
449 // is sent. Make sure this does not crash.
450 rwhv_mac_->ImeCompositionRangeChanged(gfx::Range(10, 12),
451 std::vector<gfx::Rect>());
452 EXPECT_FALSE(rwhv_mac_->GetCachedFirstRectForCharacterRange(
453 gfx::Range(10, 11).ToNSRange(),
457 const int kCompositionLength = 10;
458 std::vector<gfx::Rect> composition_bounds;
459 const int kCompositionStart = 3;
460 const gfx::Range kCompositionRange(kCompositionStart,
461 kCompositionStart + kCompositionLength);
462 GenerateCompositionRectArray(kOrigin,
465 std::vector<size_t>(),
466 &composition_bounds);
467 rwhv_mac_->ImeCompositionRangeChanged(kCompositionRange, composition_bounds);
469 // Out of range requests will return caret position.
470 EXPECT_FALSE(rwhv_mac_->GetCachedFirstRectForCharacterRange(
471 gfx::Range(0, 0).ToNSRange(),
474 EXPECT_FALSE(rwhv_mac_->GetCachedFirstRectForCharacterRange(
475 gfx::Range(1, 1).ToNSRange(),
478 EXPECT_FALSE(rwhv_mac_->GetCachedFirstRectForCharacterRange(
479 gfx::Range(1, 2).ToNSRange(),
482 EXPECT_FALSE(rwhv_mac_->GetCachedFirstRectForCharacterRange(
483 gfx::Range(2, 2).ToNSRange(),
486 EXPECT_FALSE(rwhv_mac_->GetCachedFirstRectForCharacterRange(
487 gfx::Range(13, 14).ToNSRange(),
490 EXPECT_FALSE(rwhv_mac_->GetCachedFirstRectForCharacterRange(
491 gfx::Range(14, 15).ToNSRange(),
495 for (int i = 0; i <= kCompositionLength; ++i) {
496 for (int j = 0; j <= kCompositionLength - i; ++j) {
497 const gfx::Range range(i, i + j);
498 const gfx::Rect expected_rect = GetExpectedRect(kOrigin,
502 const NSRange request_range = gfx::Range(
503 kCompositionStart + range.start(),
504 kCompositionStart + range.end()).ToNSRange();
505 EXPECT_TRUE(rwhv_mac_->GetCachedFirstRectForCharacterRange(
509 EXPECT_EQ(gfx::Range(request_range), gfx::Range(actual_range));
510 EXPECT_EQ(expected_rect, gfx::Rect(NSRectToCGRect(rect)));
512 // Make sure not crashing by passing NULL pointer instead of
514 EXPECT_TRUE(rwhv_mac_->GetCachedFirstRectForCharacterRange(
522 TEST_F(RenderWidgetHostViewMacTest, UpdateCompositionMultilineCase) {
523 const gfx::Point kOrigin(10, 11);
524 const gfx::Size kBoundsUnit(10, 20);
527 const int kCompositionLength = 30;
528 std::vector<gfx::Rect> composition_bounds;
529 const gfx::Range kCompositionRange(0, kCompositionLength);
530 // Set breaking point at 10 and 20.
531 std::vector<size_t> break_points;
532 break_points.push_back(10);
533 break_points.push_back(20);
534 GenerateCompositionRectArray(kOrigin,
538 &composition_bounds);
539 rwhv_mac_->ImeCompositionRangeChanged(kCompositionRange, composition_bounds);
541 // Range doesn't contain line breaking point.
543 range = gfx::Range(5, 8);
544 NSRange actual_range;
545 EXPECT_TRUE(rwhv_mac_->GetCachedFirstRectForCharacterRange(range.ToNSRange(),
548 EXPECT_EQ(range, gfx::Range(actual_range));
550 GetExpectedRect(kOrigin, kBoundsUnit, range, 0),
551 gfx::Rect(NSRectToCGRect(rect)));
552 range = gfx::Range(15, 18);
553 EXPECT_TRUE(rwhv_mac_->GetCachedFirstRectForCharacterRange(range.ToNSRange(),
556 EXPECT_EQ(range, gfx::Range(actual_range));
558 GetExpectedRect(kOrigin, kBoundsUnit, gfx::Range(5, 8), 1),
559 gfx::Rect(NSRectToCGRect(rect)));
560 range = gfx::Range(25, 28);
561 EXPECT_TRUE(rwhv_mac_->GetCachedFirstRectForCharacterRange(range.ToNSRange(),
564 EXPECT_EQ(range, gfx::Range(actual_range));
566 GetExpectedRect(kOrigin, kBoundsUnit, gfx::Range(5, 8), 2),
567 gfx::Rect(NSRectToCGRect(rect)));
569 // Range contains line breaking point.
570 range = gfx::Range(8, 12);
571 EXPECT_TRUE(rwhv_mac_->GetCachedFirstRectForCharacterRange(range.ToNSRange(),
574 EXPECT_EQ(gfx::Range(8, 10), gfx::Range(actual_range));
576 GetExpectedRect(kOrigin, kBoundsUnit, gfx::Range(8, 10), 0),
577 gfx::Rect(NSRectToCGRect(rect)));
578 range = gfx::Range(18, 22);
579 EXPECT_TRUE(rwhv_mac_->GetCachedFirstRectForCharacterRange(range.ToNSRange(),
582 EXPECT_EQ(gfx::Range(18, 20), gfx::Range(actual_range));
584 GetExpectedRect(kOrigin, kBoundsUnit, gfx::Range(8, 10), 1),
585 gfx::Rect(NSRectToCGRect(rect)));
587 // Start point is line breaking point.
588 range = gfx::Range(10, 12);
589 EXPECT_TRUE(rwhv_mac_->GetCachedFirstRectForCharacterRange(range.ToNSRange(),
592 EXPECT_EQ(gfx::Range(10, 12), gfx::Range(actual_range));
594 GetExpectedRect(kOrigin, kBoundsUnit, gfx::Range(0, 2), 1),
595 gfx::Rect(NSRectToCGRect(rect)));
596 range = gfx::Range(20, 22);
597 EXPECT_TRUE(rwhv_mac_->GetCachedFirstRectForCharacterRange(range.ToNSRange(),
600 EXPECT_EQ(gfx::Range(20, 22), gfx::Range(actual_range));
602 GetExpectedRect(kOrigin, kBoundsUnit, gfx::Range(0, 2), 2),
603 gfx::Rect(NSRectToCGRect(rect)));
605 // End point is line breaking point.
606 range = gfx::Range(5, 10);
607 EXPECT_TRUE(rwhv_mac_->GetCachedFirstRectForCharacterRange(range.ToNSRange(),
610 EXPECT_EQ(gfx::Range(5, 10), gfx::Range(actual_range));
612 GetExpectedRect(kOrigin, kBoundsUnit, gfx::Range(5, 10), 0),
613 gfx::Rect(NSRectToCGRect(rect)));
614 range = gfx::Range(15, 20);
615 EXPECT_TRUE(rwhv_mac_->GetCachedFirstRectForCharacterRange(range.ToNSRange(),
618 EXPECT_EQ(gfx::Range(15, 20), gfx::Range(actual_range));
620 GetExpectedRect(kOrigin, kBoundsUnit, gfx::Range(5, 10), 1),
621 gfx::Rect(NSRectToCGRect(rect)));
623 // Start and end point are same line breaking point.
624 range = gfx::Range(10, 10);
625 EXPECT_TRUE(rwhv_mac_->GetCachedFirstRectForCharacterRange(range.ToNSRange(),
628 EXPECT_EQ(gfx::Range(10, 10), gfx::Range(actual_range));
630 GetExpectedRect(kOrigin, kBoundsUnit, gfx::Range(0, 0), 1),
631 gfx::Rect(NSRectToCGRect(rect)));
632 range = gfx::Range(20, 20);
633 EXPECT_TRUE(rwhv_mac_->GetCachedFirstRectForCharacterRange(range.ToNSRange(),
636 EXPECT_EQ(gfx::Range(20, 20), gfx::Range(actual_range));
638 GetExpectedRect(kOrigin, kBoundsUnit, gfx::Range(0, 0), 2),
639 gfx::Rect(NSRectToCGRect(rect)));
641 // Start and end point are different line breaking point.
642 range = gfx::Range(10, 20);
643 EXPECT_TRUE(rwhv_mac_->GetCachedFirstRectForCharacterRange(range.ToNSRange(),
646 EXPECT_EQ(gfx::Range(10, 20), gfx::Range(actual_range));
648 GetExpectedRect(kOrigin, kBoundsUnit, gfx::Range(0, 10), 1),
649 gfx::Rect(NSRectToCGRect(rect)));
652 // Verify that |SetActive()| calls |RenderWidgetHostImpl::Blur()| and
653 // |RenderWidgetHostImp::Focus()|.
654 TEST_F(RenderWidgetHostViewMacTest, BlurAndFocusOnSetActive) {
655 MockRenderWidgetHostDelegate delegate;
656 TestBrowserContext browser_context;
657 MockRenderProcessHost* process_host =
658 new MockRenderProcessHost(&browser_context);
660 // Owned by its |cocoa_view()|.
661 MockRenderWidgetHostImpl* rwh = new MockRenderWidgetHostImpl(
662 &delegate, process_host, MSG_ROUTING_NONE);
663 RenderWidgetHostViewMac* view = static_cast<RenderWidgetHostViewMac*>(
664 RenderWidgetHostView::CreateViewForWidget(rwh));
666 base::scoped_nsobject<CocoaTestHelperWindow> window(
667 [[CocoaTestHelperWindow alloc] init]);
668 [[window contentView] addSubview:view->cocoa_view()];
670 EXPECT_CALL(*rwh, Focus());
671 [window makeFirstResponder:view->cocoa_view()];
672 testing::Mock::VerifyAndClearExpectations(rwh);
674 EXPECT_CALL(*rwh, Blur());
675 view->SetActive(false);
676 testing::Mock::VerifyAndClearExpectations(rwh);
678 EXPECT_CALL(*rwh, Focus());
679 view->SetActive(true);
680 testing::Mock::VerifyAndClearExpectations(rwh);
682 // Unsetting first responder should blur.
683 EXPECT_CALL(*rwh, Blur());
684 [window makeFirstResponder:nil];
685 testing::Mock::VerifyAndClearExpectations(rwh);
687 // |SetActive()| shoud not focus if view is not first responder.
688 EXPECT_CALL(*rwh, Focus()).Times(0);
689 view->SetActive(true);
690 testing::Mock::VerifyAndClearExpectations(rwh);
696 TEST_F(RenderWidgetHostViewMacTest, ScrollWheelEndEventDelivery) {
697 // This tests Lion+ functionality, so don't run the test pre-Lion.
698 if (!base::mac::IsOSLionOrLater())
701 // Initialize the view associated with a MockRenderWidgetHostImpl, rather than
702 // the MockRenderProcessHost that is set up by the test harness which mocks
703 // out |OnMessageReceived()|.
704 TestBrowserContext browser_context;
705 MockRenderProcessHost* process_host =
706 new MockRenderProcessHost(&browser_context);
707 MockRenderWidgetHostDelegate delegate;
708 MockRenderWidgetHostImpl* host = new MockRenderWidgetHostImpl(
709 &delegate, process_host, MSG_ROUTING_NONE);
710 RenderWidgetHostViewMac* view = static_cast<RenderWidgetHostViewMac*>(
711 RenderWidgetHostView::CreateViewForWidget(host));
713 // Send an initial wheel event with NSEventPhaseBegan to the view.
714 NSEvent* event1 = MockScrollWheelEventWithPhase(@selector(phaseBegan), 0);
715 [view->cocoa_view() scrollWheel:event1];
716 ASSERT_EQ(1U, process_host->sink().message_count());
718 // Send an ACK for the first wheel event, so that the queue will be flushed.
719 scoped_ptr<IPC::Message> response(new InputHostMsg_HandleInputEvent_ACK(
720 0, blink::WebInputEvent::MouseWheel, INPUT_EVENT_ACK_STATE_CONSUMED,
722 host->OnMessageReceived(*response);
724 // Post the NSEventPhaseEnded wheel event to NSApp and check whether the
725 // render view receives it.
726 NSEvent* event2 = MockScrollWheelEventWithPhase(@selector(phaseEnded), 0);
727 [NSApp postEvent:event2 atStart:NO];
728 base::MessageLoop::current()->RunUntilIdle();
729 ASSERT_EQ(2U, process_host->sink().message_count());
735 TEST_F(RenderWidgetHostViewMacTest, IgnoreEmptyUnhandledWheelEvent) {
736 // This tests Lion+ functionality, so don't run the test pre-Lion.
737 if (!base::mac::IsOSLionOrLater())
740 // Initialize the view associated with a MockRenderWidgetHostImpl, rather than
741 // the MockRenderProcessHost that is set up by the test harness which mocks
742 // out |OnMessageReceived()|.
743 TestBrowserContext browser_context;
744 MockRenderProcessHost* process_host =
745 new MockRenderProcessHost(&browser_context);
746 MockRenderWidgetHostDelegate delegate;
747 MockRenderWidgetHostImpl* host = new MockRenderWidgetHostImpl(
748 &delegate, process_host, MSG_ROUTING_NONE);
749 RenderWidgetHostViewMac* view = static_cast<RenderWidgetHostViewMac*>(
750 RenderWidgetHostView::CreateViewForWidget(host));
752 // Add a delegate to the view.
753 base::scoped_nsobject<MockRenderWidgetHostViewMacDelegate> view_delegate(
754 [[MockRenderWidgetHostViewMacDelegate alloc] init]);
755 view->SetDelegate(view_delegate.get());
757 // Send an initial wheel event for scrolling by 3 lines.
758 NSEvent* event1 = MockScrollWheelEventWithPhase(@selector(phaseBegan), 3);
759 [view->cocoa_view() scrollWheel:event1];
760 ASSERT_EQ(1U, process_host->sink().message_count());
761 process_host->sink().ClearMessages();
763 // Indicate that the wheel event was unhandled.
764 scoped_ptr<IPC::Message> response1(new InputHostMsg_HandleInputEvent_ACK(0,
765 blink::WebInputEvent::MouseWheel, INPUT_EVENT_ACK_STATE_NOT_CONSUMED,
767 host->OnMessageReceived(*response1);
769 // Check that the view delegate got an unhandled wheel event.
770 ASSERT_EQ(YES, view_delegate.get().unhandledWheelEventReceived);
771 view_delegate.get().unhandledWheelEventReceived = NO;
773 // Send another wheel event, this time for scrolling by 0 lines (empty event).
774 NSEvent* event2 = MockScrollWheelEventWithPhase(@selector(phaseChanged), 0);
775 [view->cocoa_view() scrollWheel:event2];
776 ASSERT_EQ(1U, process_host->sink().message_count());
778 // Indicate that the wheel event was also unhandled.
779 scoped_ptr<IPC::Message> response2(new InputHostMsg_HandleInputEvent_ACK(0,
780 blink::WebInputEvent::MouseWheel, INPUT_EVENT_ACK_STATE_NOT_CONSUMED,
782 host->OnMessageReceived(*response2);
784 // Check that the view delegate ignored the empty unhandled wheel event.
785 ASSERT_EQ(NO, view_delegate.get().unhandledWheelEventReceived);
791 } // namespace content