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;
93 class MockRenderWidgetHostDelegate : public RenderWidgetHostDelegate {
95 MockRenderWidgetHostDelegate() {}
96 virtual ~MockRenderWidgetHostDelegate() {}
99 class MockRenderWidgetHostImpl : public RenderWidgetHostImpl {
101 MockRenderWidgetHostImpl(RenderWidgetHostDelegate* delegate,
102 RenderProcessHost* process,
104 : RenderWidgetHostImpl(delegate, process, routing_id, false) {
107 MOCK_METHOD0(Focus, void());
108 MOCK_METHOD0(Blur, void());
111 // Generates the |length| of composition rectangle vector and save them to
112 // |output|. It starts from |origin| and each rectangle contains |unit_size|.
113 void GenerateCompositionRectArray(const gfx::Point& origin,
114 const gfx::Size& unit_size,
116 const std::vector<size_t>& break_points,
117 std::vector<gfx::Rect>* output) {
121 std::queue<int> break_point_queue;
122 for (size_t i = 0; i < break_points.size(); ++i)
123 break_point_queue.push(break_points[i]);
124 break_point_queue.push(length);
125 size_t next_break_point = break_point_queue.front();
126 break_point_queue.pop();
128 gfx::Rect current_rect(origin, unit_size);
129 for (size_t i = 0; i < length; ++i) {
130 if (i == next_break_point) {
131 current_rect.set_x(origin.x());
132 current_rect.set_y(current_rect.y() + current_rect.height());
133 next_break_point = break_point_queue.front();
134 break_point_queue.pop();
136 output->push_back(current_rect);
137 current_rect.set_x(current_rect.right());
141 gfx::Rect GetExpectedRect(const gfx::Point& origin,
142 const gfx::Size& size,
143 const gfx::Range& range,
146 origin.x() + range.start() * size.width(),
147 origin.y() + line_no * size.height(),
148 range.length() * size.width(),
152 // Returns NSScrollWheel event that mocks -phase. |mockPhaseSelector| should
153 // correspond to a method in |MockPhaseMethods| that returns the desired phase.
154 NSEvent* MockScrollWheelEventWithPhase(SEL mockPhaseSelector, int32_t delta) {
155 CGEventRef cg_event =
156 CGEventCreateScrollWheelEvent(NULL, kCGScrollEventUnitLine, 1, delta, 0);
157 NSEvent* event = [NSEvent eventWithCGEvent:cg_event];
159 method_setImplementation(
160 class_getInstanceMethod([NSEvent class], @selector(phase)),
161 [MockPhaseMethods instanceMethodForSelector:mockPhaseSelector]);
167 class RenderWidgetHostViewMacTest : public RenderViewHostImplTestHarness {
169 RenderWidgetHostViewMacTest() : old_rwhv_(NULL), rwhv_mac_(NULL) {}
171 virtual void SetUp() {
172 RenderViewHostImplTestHarness::SetUp();
174 // TestRenderViewHost's destruction assumes that its view is a
175 // TestRenderWidgetHostView, so store its view and reset it back to the
176 // stored view in |TearDown()|.
177 old_rwhv_ = rvh()->GetView();
179 // Owned by its |cocoa_view()|, i.e. |rwhv_cocoa_|.
180 rwhv_mac_ = static_cast<RenderWidgetHostViewMac*>(
181 RenderWidgetHostView::CreateViewForWidget(rvh()));
182 rwhv_cocoa_.reset([rwhv_mac_->cocoa_view() retain]);
184 virtual void TearDown() {
185 // Make sure the rwhv_mac_ is gone once the superclass's |TearDown()| runs.
188 base::MessageLoop::current()->RunUntilIdle();
191 // See comment in SetUp().
192 test_rvh()->SetView(old_rwhv_);
194 RenderViewHostImplTestHarness::TearDown();
198 // This class isn't derived from PlatformTest.
199 base::mac::ScopedNSAutoreleasePool pool_;
201 RenderWidgetHostView* old_rwhv_;
204 RenderWidgetHostViewMac* rwhv_mac_;
205 base::scoped_nsobject<RenderWidgetHostViewCocoa> rwhv_cocoa_;
208 DISALLOW_COPY_AND_ASSIGN(RenderWidgetHostViewMacTest);
211 TEST_F(RenderWidgetHostViewMacTest, Basic) {
214 TEST_F(RenderWidgetHostViewMacTest, AcceptsFirstResponder) {
215 // The RWHVCocoa should normally accept first responder status.
216 EXPECT_TRUE([rwhv_cocoa_.get() acceptsFirstResponder]);
218 // Unless we tell it not to.
219 rwhv_mac_->SetTakesFocusOnlyOnMouseDown(true);
220 EXPECT_FALSE([rwhv_cocoa_.get() acceptsFirstResponder]);
222 // But we can set things back to the way they were originally.
223 rwhv_mac_->SetTakesFocusOnlyOnMouseDown(false);
224 EXPECT_TRUE([rwhv_cocoa_.get() acceptsFirstResponder]);
227 TEST_F(RenderWidgetHostViewMacTest, TakesFocusOnMouseDown) {
228 base::scoped_nsobject<CocoaTestHelperWindow> window(
229 [[CocoaTestHelperWindow alloc] init]);
230 [[window contentView] addSubview:rwhv_cocoa_.get()];
232 // Even if the RWHVCocoa disallows first responder, clicking on it gives it
234 [window setPretendIsKeyWindow:YES];
235 [window makeFirstResponder:nil];
236 ASSERT_NE(rwhv_cocoa_.get(), [window firstResponder]);
238 rwhv_mac_->SetTakesFocusOnlyOnMouseDown(true);
239 EXPECT_FALSE([rwhv_cocoa_.get() acceptsFirstResponder]);
241 std::pair<NSEvent*, NSEvent*> clicks =
242 cocoa_test_event_utils::MouseClickInView(rwhv_cocoa_.get(), 1);
243 [rwhv_cocoa_.get() mouseDown:clicks.first];
244 EXPECT_EQ(rwhv_cocoa_.get(), [window firstResponder]);
247 TEST_F(RenderWidgetHostViewMacTest, Fullscreen) {
248 rwhv_mac_->InitAsFullscreen(NULL);
249 EXPECT_TRUE(rwhv_mac_->pepper_fullscreen_window());
251 // Break the reference cycle caused by pepper_fullscreen_window() without
252 // an <esc> event. See comment in
253 // release_pepper_fullscreen_window_for_testing().
254 rwhv_mac_->release_pepper_fullscreen_window_for_testing();
257 // Verify that escape key down in fullscreen mode suppressed the keyup event on
259 TEST_F(RenderWidgetHostViewMacTest, FullscreenCloseOnEscape) {
260 // Use our own RWH since we need to destroy it.
261 MockRenderWidgetHostDelegate delegate;
262 TestBrowserContext browser_context;
263 MockRenderProcessHost* process_host =
264 new MockRenderProcessHost(&browser_context);
265 // Owned by its |cocoa_view()|.
266 RenderWidgetHostImpl* rwh = new RenderWidgetHostImpl(
267 &delegate, process_host, MSG_ROUTING_NONE, false);
268 RenderWidgetHostViewMac* view = static_cast<RenderWidgetHostViewMac*>(
269 RenderWidgetHostView::CreateViewForWidget(rwh));
271 view->InitAsFullscreen(rwhv_mac_);
273 WindowedNotificationObserver observer(
274 NOTIFICATION_RENDER_WIDGET_HOST_DESTROYED,
275 Source<RenderWidgetHost>(rwh));
276 EXPECT_FALSE([rwhv_mac_->cocoa_view() suppressNextEscapeKeyUp]);
278 // Escape key down. Should close window and set |suppressNextEscapeKeyUp| on
280 [view->cocoa_view() keyEvent:
281 cocoa_test_event_utils::KeyEventWithKeyCode(53, 27, NSKeyDown, 0)];
283 EXPECT_TRUE([rwhv_mac_->cocoa_view() suppressNextEscapeKeyUp]);
285 // Escape key up on the parent should clear |suppressNextEscapeKeyUp|.
286 [rwhv_mac_->cocoa_view() keyEvent:
287 cocoa_test_event_utils::KeyEventWithKeyCode(53, 27, NSKeyUp, 0)];
288 EXPECT_FALSE([rwhv_mac_->cocoa_view() suppressNextEscapeKeyUp]);
291 // Test that command accelerators which destroy the fullscreen window
292 // don't crash when forwarded via the window's responder machinery.
293 TEST_F(RenderWidgetHostViewMacTest, AcceleratorDestroy) {
294 // Use our own RWH since we need to destroy it.
295 MockRenderWidgetHostDelegate delegate;
296 TestBrowserContext browser_context;
297 MockRenderProcessHost* process_host =
298 new MockRenderProcessHost(&browser_context);
299 // Owned by its |cocoa_view()|.
300 RenderWidgetHostImpl* rwh = new RenderWidgetHostImpl(
301 &delegate, process_host, MSG_ROUTING_NONE, false);
302 RenderWidgetHostViewMac* view = static_cast<RenderWidgetHostViewMac*>(
303 RenderWidgetHostView::CreateViewForWidget(rwh));
305 view->InitAsFullscreen(rwhv_mac_);
307 WindowedNotificationObserver observer(
308 NOTIFICATION_RENDER_WIDGET_HOST_DESTROYED,
309 Source<RenderWidgetHost>(rwh));
311 // Command-ESC will destroy the view, while the window is still in
312 // |-performKeyEquivalent:|. There are other cases where this can
313 // happen, Command-ESC is the easiest to trigger.
314 [[view->cocoa_view() window] performKeyEquivalent:
315 cocoa_test_event_utils::KeyEventWithKeyCode(
316 53, 27, NSKeyDown, NSCommandKeyMask)];
320 TEST_F(RenderWidgetHostViewMacTest, GetFirstRectForCharacterRangeCaretCase) {
321 const base::string16 kDummyString = UTF8ToUTF16("hogehoge");
322 const size_t kDummyOffset = 0;
324 gfx::Rect caret_rect(10, 11, 0, 10);
325 gfx::Range caret_range(0, 0);
326 ViewHostMsg_SelectionBounds_Params params;
329 NSRange actual_range;
330 rwhv_mac_->SelectionChanged(kDummyString, kDummyOffset, caret_range);
331 params.anchor_rect = params.focus_rect = caret_rect;
332 params.anchor_dir = params.focus_dir = blink::WebTextDirectionLeftToRight;
333 rwhv_mac_->SelectionBoundsChanged(params);
334 EXPECT_TRUE(rwhv_mac_->GetCachedFirstRectForCharacterRange(
335 caret_range.ToNSRange(),
338 EXPECT_EQ(caret_rect, gfx::Rect(NSRectToCGRect(rect)));
339 EXPECT_EQ(caret_range, gfx::Range(actual_range));
341 EXPECT_FALSE(rwhv_mac_->GetCachedFirstRectForCharacterRange(
342 gfx::Range(0, 1).ToNSRange(),
345 EXPECT_FALSE(rwhv_mac_->GetCachedFirstRectForCharacterRange(
346 gfx::Range(1, 1).ToNSRange(),
349 EXPECT_FALSE(rwhv_mac_->GetCachedFirstRectForCharacterRange(
350 gfx::Range(2, 3).ToNSRange(),
355 caret_rect = gfx::Rect(20, 11, 0, 10);
356 caret_range = gfx::Range(1, 1);
357 params.anchor_rect = params.focus_rect = caret_rect;
358 rwhv_mac_->SelectionChanged(kDummyString, kDummyOffset, caret_range);
359 rwhv_mac_->SelectionBoundsChanged(params);
360 EXPECT_TRUE(rwhv_mac_->GetCachedFirstRectForCharacterRange(
361 caret_range.ToNSRange(),
364 EXPECT_EQ(caret_rect, gfx::Rect(NSRectToCGRect(rect)));
365 EXPECT_EQ(caret_range, gfx::Range(actual_range));
367 EXPECT_FALSE(rwhv_mac_->GetCachedFirstRectForCharacterRange(
368 gfx::Range(0, 0).ToNSRange(),
371 EXPECT_FALSE(rwhv_mac_->GetCachedFirstRectForCharacterRange(
372 gfx::Range(1, 2).ToNSRange(),
375 EXPECT_FALSE(rwhv_mac_->GetCachedFirstRectForCharacterRange(
376 gfx::Range(2, 3).ToNSRange(),
381 caret_range = gfx::Range(1, 2);
382 rwhv_mac_->SelectionChanged(kDummyString, kDummyOffset, caret_range);
383 params.anchor_rect = caret_rect;
384 params.focus_rect = gfx::Rect(30, 11, 0, 10);
385 rwhv_mac_->SelectionBoundsChanged(params);
386 EXPECT_FALSE(rwhv_mac_->GetCachedFirstRectForCharacterRange(
387 gfx::Range(0, 0).ToNSRange(),
390 EXPECT_FALSE(rwhv_mac_->GetCachedFirstRectForCharacterRange(
391 gfx::Range(0, 1).ToNSRange(),
394 EXPECT_FALSE(rwhv_mac_->GetCachedFirstRectForCharacterRange(
395 gfx::Range(1, 1).ToNSRange(),
398 EXPECT_FALSE(rwhv_mac_->GetCachedFirstRectForCharacterRange(
399 gfx::Range(1, 2).ToNSRange(),
402 EXPECT_FALSE(rwhv_mac_->GetCachedFirstRectForCharacterRange(
403 gfx::Range(2, 2).ToNSRange(),
408 TEST_F(RenderWidgetHostViewMacTest, UpdateCompositionSinglelineCase) {
409 const gfx::Point kOrigin(10, 11);
410 const gfx::Size kBoundsUnit(10, 20);
413 // Make sure not crashing by passing NULL pointer instead of |actual_range|.
414 EXPECT_FALSE(rwhv_mac_->GetCachedFirstRectForCharacterRange(
415 gfx::Range(0, 0).ToNSRange(),
419 // If there are no update from renderer, always returned caret position.
420 NSRange actual_range;
421 EXPECT_FALSE(rwhv_mac_->GetCachedFirstRectForCharacterRange(
422 gfx::Range(0, 0).ToNSRange(),
425 EXPECT_FALSE(rwhv_mac_->GetCachedFirstRectForCharacterRange(
426 gfx::Range(0, 1).ToNSRange(),
429 EXPECT_FALSE(rwhv_mac_->GetCachedFirstRectForCharacterRange(
430 gfx::Range(1, 0).ToNSRange(),
433 EXPECT_FALSE(rwhv_mac_->GetCachedFirstRectForCharacterRange(
434 gfx::Range(1, 1).ToNSRange(),
437 EXPECT_FALSE(rwhv_mac_->GetCachedFirstRectForCharacterRange(
438 gfx::Range(1, 2).ToNSRange(),
442 // If the firstRectForCharacterRange is failed in renderer, empty rect vector
443 // is sent. Make sure this does not crash.
444 rwhv_mac_->ImeCompositionRangeChanged(gfx::Range(10, 12),
445 std::vector<gfx::Rect>());
446 EXPECT_FALSE(rwhv_mac_->GetCachedFirstRectForCharacterRange(
447 gfx::Range(10, 11).ToNSRange(),
451 const int kCompositionLength = 10;
452 std::vector<gfx::Rect> composition_bounds;
453 const int kCompositionStart = 3;
454 const gfx::Range kCompositionRange(kCompositionStart,
455 kCompositionStart + kCompositionLength);
456 GenerateCompositionRectArray(kOrigin,
459 std::vector<size_t>(),
460 &composition_bounds);
461 rwhv_mac_->ImeCompositionRangeChanged(kCompositionRange, composition_bounds);
463 // Out of range requests will return caret position.
464 EXPECT_FALSE(rwhv_mac_->GetCachedFirstRectForCharacterRange(
465 gfx::Range(0, 0).ToNSRange(),
468 EXPECT_FALSE(rwhv_mac_->GetCachedFirstRectForCharacterRange(
469 gfx::Range(1, 1).ToNSRange(),
472 EXPECT_FALSE(rwhv_mac_->GetCachedFirstRectForCharacterRange(
473 gfx::Range(1, 2).ToNSRange(),
476 EXPECT_FALSE(rwhv_mac_->GetCachedFirstRectForCharacterRange(
477 gfx::Range(2, 2).ToNSRange(),
480 EXPECT_FALSE(rwhv_mac_->GetCachedFirstRectForCharacterRange(
481 gfx::Range(13, 14).ToNSRange(),
484 EXPECT_FALSE(rwhv_mac_->GetCachedFirstRectForCharacterRange(
485 gfx::Range(14, 15).ToNSRange(),
489 for (int i = 0; i <= kCompositionLength; ++i) {
490 for (int j = 0; j <= kCompositionLength - i; ++j) {
491 const gfx::Range range(i, i + j);
492 const gfx::Rect expected_rect = GetExpectedRect(kOrigin,
496 const NSRange request_range = gfx::Range(
497 kCompositionStart + range.start(),
498 kCompositionStart + range.end()).ToNSRange();
499 EXPECT_TRUE(rwhv_mac_->GetCachedFirstRectForCharacterRange(
503 EXPECT_EQ(gfx::Range(request_range), gfx::Range(actual_range));
504 EXPECT_EQ(expected_rect, gfx::Rect(NSRectToCGRect(rect)));
506 // Make sure not crashing by passing NULL pointer instead of
508 EXPECT_TRUE(rwhv_mac_->GetCachedFirstRectForCharacterRange(
516 TEST_F(RenderWidgetHostViewMacTest, UpdateCompositionMultilineCase) {
517 const gfx::Point kOrigin(10, 11);
518 const gfx::Size kBoundsUnit(10, 20);
521 const int kCompositionLength = 30;
522 std::vector<gfx::Rect> composition_bounds;
523 const gfx::Range kCompositionRange(0, kCompositionLength);
524 // Set breaking point at 10 and 20.
525 std::vector<size_t> break_points;
526 break_points.push_back(10);
527 break_points.push_back(20);
528 GenerateCompositionRectArray(kOrigin,
532 &composition_bounds);
533 rwhv_mac_->ImeCompositionRangeChanged(kCompositionRange, composition_bounds);
535 // Range doesn't contain line breaking point.
537 range = gfx::Range(5, 8);
538 NSRange actual_range;
539 EXPECT_TRUE(rwhv_mac_->GetCachedFirstRectForCharacterRange(range.ToNSRange(),
542 EXPECT_EQ(range, gfx::Range(actual_range));
544 GetExpectedRect(kOrigin, kBoundsUnit, range, 0),
545 gfx::Rect(NSRectToCGRect(rect)));
546 range = gfx::Range(15, 18);
547 EXPECT_TRUE(rwhv_mac_->GetCachedFirstRectForCharacterRange(range.ToNSRange(),
550 EXPECT_EQ(range, gfx::Range(actual_range));
552 GetExpectedRect(kOrigin, kBoundsUnit, gfx::Range(5, 8), 1),
553 gfx::Rect(NSRectToCGRect(rect)));
554 range = gfx::Range(25, 28);
555 EXPECT_TRUE(rwhv_mac_->GetCachedFirstRectForCharacterRange(range.ToNSRange(),
558 EXPECT_EQ(range, gfx::Range(actual_range));
560 GetExpectedRect(kOrigin, kBoundsUnit, gfx::Range(5, 8), 2),
561 gfx::Rect(NSRectToCGRect(rect)));
563 // Range contains line breaking point.
564 range = gfx::Range(8, 12);
565 EXPECT_TRUE(rwhv_mac_->GetCachedFirstRectForCharacterRange(range.ToNSRange(),
568 EXPECT_EQ(gfx::Range(8, 10), gfx::Range(actual_range));
570 GetExpectedRect(kOrigin, kBoundsUnit, gfx::Range(8, 10), 0),
571 gfx::Rect(NSRectToCGRect(rect)));
572 range = gfx::Range(18, 22);
573 EXPECT_TRUE(rwhv_mac_->GetCachedFirstRectForCharacterRange(range.ToNSRange(),
576 EXPECT_EQ(gfx::Range(18, 20), gfx::Range(actual_range));
578 GetExpectedRect(kOrigin, kBoundsUnit, gfx::Range(8, 10), 1),
579 gfx::Rect(NSRectToCGRect(rect)));
581 // Start point is line breaking point.
582 range = gfx::Range(10, 12);
583 EXPECT_TRUE(rwhv_mac_->GetCachedFirstRectForCharacterRange(range.ToNSRange(),
586 EXPECT_EQ(gfx::Range(10, 12), gfx::Range(actual_range));
588 GetExpectedRect(kOrigin, kBoundsUnit, gfx::Range(0, 2), 1),
589 gfx::Rect(NSRectToCGRect(rect)));
590 range = gfx::Range(20, 22);
591 EXPECT_TRUE(rwhv_mac_->GetCachedFirstRectForCharacterRange(range.ToNSRange(),
594 EXPECT_EQ(gfx::Range(20, 22), gfx::Range(actual_range));
596 GetExpectedRect(kOrigin, kBoundsUnit, gfx::Range(0, 2), 2),
597 gfx::Rect(NSRectToCGRect(rect)));
599 // End point is line breaking point.
600 range = gfx::Range(5, 10);
601 EXPECT_TRUE(rwhv_mac_->GetCachedFirstRectForCharacterRange(range.ToNSRange(),
604 EXPECT_EQ(gfx::Range(5, 10), gfx::Range(actual_range));
606 GetExpectedRect(kOrigin, kBoundsUnit, gfx::Range(5, 10), 0),
607 gfx::Rect(NSRectToCGRect(rect)));
608 range = gfx::Range(15, 20);
609 EXPECT_TRUE(rwhv_mac_->GetCachedFirstRectForCharacterRange(range.ToNSRange(),
612 EXPECT_EQ(gfx::Range(15, 20), gfx::Range(actual_range));
614 GetExpectedRect(kOrigin, kBoundsUnit, gfx::Range(5, 10), 1),
615 gfx::Rect(NSRectToCGRect(rect)));
617 // Start and end point are same line breaking point.
618 range = gfx::Range(10, 10);
619 EXPECT_TRUE(rwhv_mac_->GetCachedFirstRectForCharacterRange(range.ToNSRange(),
622 EXPECT_EQ(gfx::Range(10, 10), gfx::Range(actual_range));
624 GetExpectedRect(kOrigin, kBoundsUnit, gfx::Range(0, 0), 1),
625 gfx::Rect(NSRectToCGRect(rect)));
626 range = gfx::Range(20, 20);
627 EXPECT_TRUE(rwhv_mac_->GetCachedFirstRectForCharacterRange(range.ToNSRange(),
630 EXPECT_EQ(gfx::Range(20, 20), gfx::Range(actual_range));
632 GetExpectedRect(kOrigin, kBoundsUnit, gfx::Range(0, 0), 2),
633 gfx::Rect(NSRectToCGRect(rect)));
635 // Start and end point are different line breaking point.
636 range = gfx::Range(10, 20);
637 EXPECT_TRUE(rwhv_mac_->GetCachedFirstRectForCharacterRange(range.ToNSRange(),
640 EXPECT_EQ(gfx::Range(10, 20), gfx::Range(actual_range));
642 GetExpectedRect(kOrigin, kBoundsUnit, gfx::Range(0, 10), 1),
643 gfx::Rect(NSRectToCGRect(rect)));
646 // Verify that |SetActive()| calls |RenderWidgetHostImpl::Blur()| and
647 // |RenderWidgetHostImp::Focus()|.
648 TEST_F(RenderWidgetHostViewMacTest, BlurAndFocusOnSetActive) {
649 MockRenderWidgetHostDelegate delegate;
650 TestBrowserContext browser_context;
651 MockRenderProcessHost* process_host =
652 new MockRenderProcessHost(&browser_context);
654 // Owned by its |cocoa_view()|.
655 MockRenderWidgetHostImpl* rwh = new MockRenderWidgetHostImpl(
656 &delegate, process_host, MSG_ROUTING_NONE);
657 RenderWidgetHostViewMac* view = static_cast<RenderWidgetHostViewMac*>(
658 RenderWidgetHostView::CreateViewForWidget(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 = static_cast<RenderWidgetHostViewMac*>(
705 RenderWidgetHostView::CreateViewForWidget(host));
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 scoped_ptr<IPC::Message> response(new InputHostMsg_HandleInputEvent_ACK(
714 0, blink::WebInputEvent::MouseWheel, INPUT_EVENT_ACK_STATE_CONSUMED,
716 host->OnMessageReceived(*response);
718 // Post the NSEventPhaseEnded wheel event to NSApp and check whether the
719 // render view receives it.
720 NSEvent* event2 = MockScrollWheelEventWithPhase(@selector(phaseEnded), 0);
721 [NSApp postEvent:event2 atStart:NO];
722 base::MessageLoop::current()->RunUntilIdle();
723 ASSERT_EQ(2U, process_host->sink().message_count());
729 TEST_F(RenderWidgetHostViewMacTest, IgnoreEmptyUnhandledWheelEvent) {
730 // This tests Lion+ functionality, so don't run the test pre-Lion.
731 if (!base::mac::IsOSLionOrLater())
734 // Initialize the view associated with a MockRenderWidgetHostImpl, rather than
735 // the MockRenderProcessHost that is set up by the test harness which mocks
736 // out |OnMessageReceived()|.
737 TestBrowserContext browser_context;
738 MockRenderProcessHost* process_host =
739 new MockRenderProcessHost(&browser_context);
740 MockRenderWidgetHostDelegate delegate;
741 MockRenderWidgetHostImpl* host = new MockRenderWidgetHostImpl(
742 &delegate, process_host, MSG_ROUTING_NONE);
743 RenderWidgetHostViewMac* view = static_cast<RenderWidgetHostViewMac*>(
744 RenderWidgetHostView::CreateViewForWidget(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 scoped_ptr<IPC::Message> response1(new InputHostMsg_HandleInputEvent_ACK(0,
759 blink::WebInputEvent::MouseWheel, INPUT_EVENT_ACK_STATE_NOT_CONSUMED,
761 host->OnMessageReceived(*response1);
763 // Check that the view delegate got an unhandled wheel event.
764 ASSERT_EQ(YES, view_delegate.get().unhandledWheelEventReceived);
765 view_delegate.get().unhandledWheelEventReceived = NO;
767 // Send another wheel event, this time for scrolling by 0 lines (empty event).
768 NSEvent* event2 = MockScrollWheelEventWithPhase(@selector(phaseChanged), 0);
769 [view->cocoa_view() scrollWheel:event2];
770 ASSERT_EQ(1U, process_host->sink().message_count());
772 // Indicate that the wheel event was also unhandled.
773 scoped_ptr<IPC::Message> response2(new InputHostMsg_HandleInputEvent_ACK(0,
774 blink::WebInputEvent::MouseWheel, INPUT_EVENT_ACK_STATE_NOT_CONSUMED,
776 host->OnMessageReceived(*response2);
778 // Check that the view delegate ignored the empty unhandled wheel event.
779 ASSERT_EQ(NO, view_delegate.get().unhandledWheelEventReceived);
785 } // namespace content