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. 4 5#include "content/browser/renderer_host/render_widget_host_view_mac.h" 6 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" 28 29// Helper class with methods used to mock -[NSEvent phase], used by 30// |MockScrollWheelEventWithPhase()|. 31@interface MockPhaseMethods : NSObject { 32} 33 34- (NSEventPhase)phaseBegan; 35- (NSEventPhase)phaseChanged; 36- (NSEventPhase)phaseEnded; 37@end 38 39@implementation MockPhaseMethods 40 41- (NSEventPhase)phaseBegan { 42 return NSEventPhaseBegan; 43} 44- (NSEventPhase)phaseChanged { 45 return NSEventPhaseChanged; 46} 47- (NSEventPhase)phaseEnded { 48 return NSEventPhaseEnded; 49} 50 51@end 52 53@interface MockRenderWidgetHostViewMacDelegate 54 : NSObject<RenderWidgetHostViewMacDelegate> { 55 BOOL unhandledWheelEventReceived_; 56} 57 58@property(nonatomic) BOOL unhandledWheelEventReceived; 59 60@end 61 62@implementation MockRenderWidgetHostViewMacDelegate 63 64@synthesize unhandledWheelEventReceived = unhandledWheelEventReceived_; 65 66- (void)rendererHandledWheelEvent:(const blink::WebMouseWheelEvent&)event 67 consumed:(BOOL)consumed { 68 if (!consumed) 69 unhandledWheelEventReceived_ = true; 70} 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 { 78 return true; 79} 80- (BOOL)canRubberbandRight:(NSView*)view { 81 return true; 82} 83 84@end 85 86namespace content { 87 88namespace { 89 90class MockRenderWidgetHostDelegate : public RenderWidgetHostDelegate { 91 public: 92 MockRenderWidgetHostDelegate() {} 93 virtual ~MockRenderWidgetHostDelegate() {} 94}; 95 96class MockRenderWidgetHostImpl : public RenderWidgetHostImpl { 97 public: 98 MockRenderWidgetHostImpl(RenderWidgetHostDelegate* delegate, 99 RenderProcessHost* process, 100 int routing_id) 101 : RenderWidgetHostImpl(delegate, process, routing_id, false) { 102 } 103 104 MOCK_METHOD0(Focus, void()); 105 MOCK_METHOD0(Blur, void()); 106}; 107 108// Generates the |length| of composition rectangle vector and save them to 109// |output|. It starts from |origin| and each rectangle contains |unit_size|. 110void GenerateCompositionRectArray(const gfx::Point& origin, 111 const gfx::Size& unit_size, 112 size_t length, 113 const std::vector<size_t>& break_points, 114 std::vector<gfx::Rect>* output) { 115 DCHECK(output); 116 output->clear(); 117 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(); 124 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(); 132 } 133 output->push_back(current_rect); 134 current_rect.set_x(current_rect.right()); 135 } 136} 137 138gfx::Rect GetExpectedRect(const gfx::Point& origin, 139 const gfx::Size& size, 140 const gfx::Range& range, 141 int line_no) { 142 return gfx::Rect( 143 origin.x() + range.start() * size.width(), 144 origin.y() + line_no * size.height(), 145 range.length() * size.width(), 146 size.height()); 147} 148 149// Returns NSScrollWheel event that mocks -phase. |mockPhaseSelector| should 150// correspond to a method in |MockPhaseMethods| that returns the desired phase. 151NSEvent* MockScrollWheelEventWithPhase(SEL mockPhaseSelector, int32_t delta) { 152 CGEventRef cg_event = 153 CGEventCreateScrollWheelEvent(NULL, kCGScrollEventUnitLine, 1, delta, 0); 154 NSEvent* event = [NSEvent eventWithCGEvent:cg_event]; 155 CFRelease(cg_event); 156 method_setImplementation( 157 class_getInstanceMethod([NSEvent class], @selector(phase)), 158 [MockPhaseMethods instanceMethodForSelector:mockPhaseSelector]); 159 return event; 160} 161 162} // namespace 163 164class RenderWidgetHostViewMacTest : public RenderViewHostImplTestHarness { 165 public: 166 RenderWidgetHostViewMacTest() : old_rwhv_(NULL), rwhv_mac_(NULL) {} 167 168 virtual void SetUp() { 169 RenderViewHostImplTestHarness::SetUp(); 170 if (IsDelegatedRendererEnabled()) { 171 ImageTransportFactory::InitializeForUnitTests( 172 scoped_ptr<ImageTransportFactory>( 173 new NoTransportImageTransportFactory)); 174 } 175 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(); 180 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]); 184 } 185 virtual void TearDown() { 186 // Make sure the rwhv_mac_ is gone once the superclass's |TearDown()| runs. 187 rwhv_cocoa_.reset(); 188 pool_.Recycle(); 189 base::MessageLoop::current()->RunUntilIdle(); 190 pool_.Recycle(); 191 192 // See comment in SetUp(). 193 test_rvh()->SetView(static_cast<RenderWidgetHostViewBase*>(old_rwhv_)); 194 195 if (IsDelegatedRendererEnabled()) 196 ImageTransportFactory::Terminate(); 197 RenderViewHostImplTestHarness::TearDown(); 198 } 199 protected: 200 private: 201 // This class isn't derived from PlatformTest. 202 base::mac::ScopedNSAutoreleasePool pool_; 203 204 RenderWidgetHostView* old_rwhv_; 205 206 protected: 207 RenderWidgetHostViewMac* rwhv_mac_; 208 base::scoped_nsobject<RenderWidgetHostViewCocoa> rwhv_cocoa_; 209 210 private: 211 DISALLOW_COPY_AND_ASSIGN(RenderWidgetHostViewMacTest); 212}; 213 214TEST_F(RenderWidgetHostViewMacTest, Basic) { 215} 216 217TEST_F(RenderWidgetHostViewMacTest, AcceptsFirstResponder) { 218 // The RWHVCocoa should normally accept first responder status. 219 EXPECT_TRUE([rwhv_cocoa_.get() acceptsFirstResponder]); 220 221 // Unless we tell it not to. 222 rwhv_mac_->SetTakesFocusOnlyOnMouseDown(true); 223 EXPECT_FALSE([rwhv_cocoa_.get() acceptsFirstResponder]); 224 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]); 228} 229 230TEST_F(RenderWidgetHostViewMacTest, TakesFocusOnMouseDown) { 231 base::scoped_nsobject<CocoaTestHelperWindow> window( 232 [[CocoaTestHelperWindow alloc] init]); 233 [[window contentView] addSubview:rwhv_cocoa_.get()]; 234 235 // Even if the RWHVCocoa disallows first responder, clicking on it gives it 236 // focus. 237 [window setPretendIsKeyWindow:YES]; 238 [window makeFirstResponder:nil]; 239 ASSERT_NE(rwhv_cocoa_.get(), [window firstResponder]); 240 241 rwhv_mac_->SetTakesFocusOnlyOnMouseDown(true); 242 EXPECT_FALSE([rwhv_cocoa_.get() acceptsFirstResponder]); 243 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]); 248} 249 250TEST_F(RenderWidgetHostViewMacTest, Fullscreen) { 251 rwhv_mac_->InitAsFullscreen(NULL); 252 EXPECT_TRUE(rwhv_mac_->pepper_fullscreen_window()); 253 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(); 258} 259 260// Verify that escape key down in fullscreen mode suppressed the keyup event on 261// the parent. 262TEST_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); 272 273 view->InitAsFullscreen(rwhv_mac_); 274 275 WindowedNotificationObserver observer( 276 NOTIFICATION_RENDER_WIDGET_HOST_DESTROYED, 277 Source<RenderWidgetHost>(rwh)); 278 EXPECT_FALSE([rwhv_mac_->cocoa_view() suppressNextEscapeKeyUp]); 279 280 // Escape key down. Should close window and set |suppressNextEscapeKeyUp| on 281 // the parent. 282 [view->cocoa_view() keyEvent: 283 cocoa_test_event_utils::KeyEventWithKeyCode(53, 27, NSKeyDown, 0)]; 284 observer.Wait(); 285 EXPECT_TRUE([rwhv_mac_->cocoa_view() suppressNextEscapeKeyUp]); 286 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]); 291} 292 293// Test that command accelerators which destroy the fullscreen window 294// don't crash when forwarded via the window's responder machinery. 295TEST_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); 305 306 view->InitAsFullscreen(rwhv_mac_); 307 308 WindowedNotificationObserver observer( 309 NOTIFICATION_RENDER_WIDGET_HOST_DESTROYED, 310 Source<RenderWidgetHost>(rwh)); 311 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)]; 318 observer.Wait(); 319} 320 321TEST_F(RenderWidgetHostViewMacTest, GetFirstRectForCharacterRangeCaretCase) { 322 const base::string16 kDummyString = base::UTF8ToUTF16("hogehoge"); 323 const size_t kDummyOffset = 0; 324 325 gfx::Rect caret_rect(10, 11, 0, 10); 326 gfx::Range caret_range(0, 0); 327 ViewHostMsg_SelectionBounds_Params params; 328 329 NSRect rect; 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(), 337 &rect, 338 &actual_range)); 339 EXPECT_EQ(caret_rect, gfx::Rect(NSRectToCGRect(rect))); 340 EXPECT_EQ(caret_range, gfx::Range(actual_range)); 341 342 EXPECT_FALSE(rwhv_mac_->GetCachedFirstRectForCharacterRange( 343 gfx::Range(0, 1).ToNSRange(), 344 &rect, 345 &actual_range)); 346 EXPECT_FALSE(rwhv_mac_->GetCachedFirstRectForCharacterRange( 347 gfx::Range(1, 1).ToNSRange(), 348 &rect, 349 &actual_range)); 350 EXPECT_FALSE(rwhv_mac_->GetCachedFirstRectForCharacterRange( 351 gfx::Range(2, 3).ToNSRange(), 352 &rect, 353 &actual_range)); 354 355 // Caret moved. 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(), 363 &rect, 364 &actual_range)); 365 EXPECT_EQ(caret_rect, gfx::Rect(NSRectToCGRect(rect))); 366 EXPECT_EQ(caret_range, gfx::Range(actual_range)); 367 368 EXPECT_FALSE(rwhv_mac_->GetCachedFirstRectForCharacterRange( 369 gfx::Range(0, 0).ToNSRange(), 370 &rect, 371 &actual_range)); 372 EXPECT_FALSE(rwhv_mac_->GetCachedFirstRectForCharacterRange( 373 gfx::Range(1, 2).ToNSRange(), 374 &rect, 375 &actual_range)); 376 EXPECT_FALSE(rwhv_mac_->GetCachedFirstRectForCharacterRange( 377 gfx::Range(2, 3).ToNSRange(), 378 &rect, 379 &actual_range)); 380 381 // No caret. 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(), 389 &rect, 390 &actual_range)); 391 EXPECT_FALSE(rwhv_mac_->GetCachedFirstRectForCharacterRange( 392 gfx::Range(0, 1).ToNSRange(), 393 &rect, 394 &actual_range)); 395 EXPECT_FALSE(rwhv_mac_->GetCachedFirstRectForCharacterRange( 396 gfx::Range(1, 1).ToNSRange(), 397 &rect, 398 &actual_range)); 399 EXPECT_FALSE(rwhv_mac_->GetCachedFirstRectForCharacterRange( 400 gfx::Range(1, 2).ToNSRange(), 401 &rect, 402 &actual_range)); 403 EXPECT_FALSE(rwhv_mac_->GetCachedFirstRectForCharacterRange( 404 gfx::Range(2, 2).ToNSRange(), 405 &rect, 406 &actual_range)); 407} 408 409TEST_F(RenderWidgetHostViewMacTest, UpdateCompositionSinglelineCase) { 410 const gfx::Point kOrigin(10, 11); 411 const gfx::Size kBoundsUnit(10, 20); 412 413 NSRect rect; 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(), 417 &rect, 418 NULL)); 419 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(), 424 &rect, 425 &actual_range)); 426 EXPECT_FALSE(rwhv_mac_->GetCachedFirstRectForCharacterRange( 427 gfx::Range(0, 1).ToNSRange(), 428 &rect, 429 &actual_range)); 430 EXPECT_FALSE(rwhv_mac_->GetCachedFirstRectForCharacterRange( 431 gfx::Range(1, 0).ToNSRange(), 432 &rect, 433 &actual_range)); 434 EXPECT_FALSE(rwhv_mac_->GetCachedFirstRectForCharacterRange( 435 gfx::Range(1, 1).ToNSRange(), 436 &rect, 437 &actual_range)); 438 EXPECT_FALSE(rwhv_mac_->GetCachedFirstRectForCharacterRange( 439 gfx::Range(1, 2).ToNSRange(), 440 &rect, 441 &actual_range)); 442 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(), 449 &rect, 450 NULL)); 451 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, 458 kBoundsUnit, 459 kCompositionLength, 460 std::vector<size_t>(), 461 &composition_bounds); 462 rwhv_mac_->ImeCompositionRangeChanged(kCompositionRange, composition_bounds); 463 464 // Out of range requests will return caret position. 465 EXPECT_FALSE(rwhv_mac_->GetCachedFirstRectForCharacterRange( 466 gfx::Range(0, 0).ToNSRange(), 467 &rect, 468 &actual_range)); 469 EXPECT_FALSE(rwhv_mac_->GetCachedFirstRectForCharacterRange( 470 gfx::Range(1, 1).ToNSRange(), 471 &rect, 472 &actual_range)); 473 EXPECT_FALSE(rwhv_mac_->GetCachedFirstRectForCharacterRange( 474 gfx::Range(1, 2).ToNSRange(), 475 &rect, 476 &actual_range)); 477 EXPECT_FALSE(rwhv_mac_->GetCachedFirstRectForCharacterRange( 478 gfx::Range(2, 2).ToNSRange(), 479 &rect, 480 &actual_range)); 481 EXPECT_FALSE(rwhv_mac_->GetCachedFirstRectForCharacterRange( 482 gfx::Range(13, 14).ToNSRange(), 483 &rect, 484 &actual_range)); 485 EXPECT_FALSE(rwhv_mac_->GetCachedFirstRectForCharacterRange( 486 gfx::Range(14, 15).ToNSRange(), 487 &rect, 488 &actual_range)); 489 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, 494 kBoundsUnit, 495 range, 496 0); 497 const NSRange request_range = gfx::Range( 498 kCompositionStart + range.start(), 499 kCompositionStart + range.end()).ToNSRange(); 500 EXPECT_TRUE(rwhv_mac_->GetCachedFirstRectForCharacterRange( 501 request_range, 502 &rect, 503 &actual_range)); 504 EXPECT_EQ(gfx::Range(request_range), gfx::Range(actual_range)); 505 EXPECT_EQ(expected_rect, gfx::Rect(NSRectToCGRect(rect))); 506 507 // Make sure not crashing by passing NULL pointer instead of 508 // |actual_range|. 509 EXPECT_TRUE(rwhv_mac_->GetCachedFirstRectForCharacterRange( 510 request_range, 511 &rect, 512 NULL)); 513 } 514 } 515} 516 517TEST_F(RenderWidgetHostViewMacTest, UpdateCompositionMultilineCase) { 518 const gfx::Point kOrigin(10, 11); 519 const gfx::Size kBoundsUnit(10, 20); 520 NSRect rect; 521 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, 530 kBoundsUnit, 531 kCompositionLength, 532 break_points, 533 &composition_bounds); 534 rwhv_mac_->ImeCompositionRangeChanged(kCompositionRange, composition_bounds); 535 536 // Range doesn't contain line breaking point. 537 gfx::Range range; 538 range = gfx::Range(5, 8); 539 NSRange actual_range; 540 EXPECT_TRUE(rwhv_mac_->GetCachedFirstRectForCharacterRange(range.ToNSRange(), 541 &rect, 542 &actual_range)); 543 EXPECT_EQ(range, gfx::Range(actual_range)); 544 EXPECT_EQ( 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(), 549 &rect, 550 &actual_range)); 551 EXPECT_EQ(range, gfx::Range(actual_range)); 552 EXPECT_EQ( 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(), 557 &rect, 558 &actual_range)); 559 EXPECT_EQ(range, gfx::Range(actual_range)); 560 EXPECT_EQ( 561 GetExpectedRect(kOrigin, kBoundsUnit, gfx::Range(5, 8), 2), 562 gfx::Rect(NSRectToCGRect(rect))); 563 564 // Range contains line breaking point. 565 range = gfx::Range(8, 12); 566 EXPECT_TRUE(rwhv_mac_->GetCachedFirstRectForCharacterRange(range.ToNSRange(), 567 &rect, 568 &actual_range)); 569 EXPECT_EQ(gfx::Range(8, 10), gfx::Range(actual_range)); 570 EXPECT_EQ( 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(), 575 &rect, 576 &actual_range)); 577 EXPECT_EQ(gfx::Range(18, 20), gfx::Range(actual_range)); 578 EXPECT_EQ( 579 GetExpectedRect(kOrigin, kBoundsUnit, gfx::Range(8, 10), 1), 580 gfx::Rect(NSRectToCGRect(rect))); 581 582 // Start point is line breaking point. 583 range = gfx::Range(10, 12); 584 EXPECT_TRUE(rwhv_mac_->GetCachedFirstRectForCharacterRange(range.ToNSRange(), 585 &rect, 586 &actual_range)); 587 EXPECT_EQ(gfx::Range(10, 12), gfx::Range(actual_range)); 588 EXPECT_EQ( 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(), 593 &rect, 594 &actual_range)); 595 EXPECT_EQ(gfx::Range(20, 22), gfx::Range(actual_range)); 596 EXPECT_EQ( 597 GetExpectedRect(kOrigin, kBoundsUnit, gfx::Range(0, 2), 2), 598 gfx::Rect(NSRectToCGRect(rect))); 599 600 // End point is line breaking point. 601 range = gfx::Range(5, 10); 602 EXPECT_TRUE(rwhv_mac_->GetCachedFirstRectForCharacterRange(range.ToNSRange(), 603 &rect, 604 &actual_range)); 605 EXPECT_EQ(gfx::Range(5, 10), gfx::Range(actual_range)); 606 EXPECT_EQ( 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(), 611 &rect, 612 &actual_range)); 613 EXPECT_EQ(gfx::Range(15, 20), gfx::Range(actual_range)); 614 EXPECT_EQ( 615 GetExpectedRect(kOrigin, kBoundsUnit, gfx::Range(5, 10), 1), 616 gfx::Rect(NSRectToCGRect(rect))); 617 618 // Start and end point are same line breaking point. 619 range = gfx::Range(10, 10); 620 EXPECT_TRUE(rwhv_mac_->GetCachedFirstRectForCharacterRange(range.ToNSRange(), 621 &rect, 622 &actual_range)); 623 EXPECT_EQ(gfx::Range(10, 10), gfx::Range(actual_range)); 624 EXPECT_EQ( 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(), 629 &rect, 630 &actual_range)); 631 EXPECT_EQ(gfx::Range(20, 20), gfx::Range(actual_range)); 632 EXPECT_EQ( 633 GetExpectedRect(kOrigin, kBoundsUnit, gfx::Range(0, 0), 2), 634 gfx::Rect(NSRectToCGRect(rect))); 635 636 // Start and end point are different line breaking point. 637 range = gfx::Range(10, 20); 638 EXPECT_TRUE(rwhv_mac_->GetCachedFirstRectForCharacterRange(range.ToNSRange(), 639 &rect, 640 &actual_range)); 641 EXPECT_EQ(gfx::Range(10, 20), gfx::Range(actual_range)); 642 EXPECT_EQ( 643 GetExpectedRect(kOrigin, kBoundsUnit, gfx::Range(0, 10), 1), 644 gfx::Rect(NSRectToCGRect(rect))); 645} 646 647// Verify that |SetActive()| calls |RenderWidgetHostImpl::Blur()| and 648// |RenderWidgetHostImp::Focus()|. 649TEST_F(RenderWidgetHostViewMacTest, BlurAndFocusOnSetActive) { 650 MockRenderWidgetHostDelegate delegate; 651 TestBrowserContext browser_context; 652 MockRenderProcessHost* process_host = 653 new MockRenderProcessHost(&browser_context); 654 655 // Owned by its |cocoa_view()|. 656 MockRenderWidgetHostImpl* rwh = new MockRenderWidgetHostImpl( 657 &delegate, process_host, MSG_ROUTING_NONE); 658 RenderWidgetHostViewMac* view = new RenderWidgetHostViewMac(rwh); 659 660 base::scoped_nsobject<CocoaTestHelperWindow> window( 661 [[CocoaTestHelperWindow alloc] init]); 662 [[window contentView] addSubview:view->cocoa_view()]; 663 664 EXPECT_CALL(*rwh, Focus()); 665 [window makeFirstResponder:view->cocoa_view()]; 666 testing::Mock::VerifyAndClearExpectations(rwh); 667 668 EXPECT_CALL(*rwh, Blur()); 669 view->SetActive(false); 670 testing::Mock::VerifyAndClearExpectations(rwh); 671 672 EXPECT_CALL(*rwh, Focus()); 673 view->SetActive(true); 674 testing::Mock::VerifyAndClearExpectations(rwh); 675 676 // Unsetting first responder should blur. 677 EXPECT_CALL(*rwh, Blur()); 678 [window makeFirstResponder:nil]; 679 testing::Mock::VerifyAndClearExpectations(rwh); 680 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); 685 686 // Clean up. 687 rwh->Shutdown(); 688} 689 690TEST_F(RenderWidgetHostViewMacTest, ScrollWheelEndEventDelivery) { 691 // This tests Lion+ functionality, so don't run the test pre-Lion. 692 if (!base::mac::IsOSLionOrLater()) 693 return; 694 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); 705 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()); 710 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); 718 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()); 725 726 // Clean up. 727 host->Shutdown(); 728} 729 730TEST_F(RenderWidgetHostViewMacTest, IgnoreEmptyUnhandledWheelEvent) { 731 // This tests Lion+ functionality, so don't run the test pre-Lion. 732 if (!base::mac::IsOSLionOrLater()) 733 return; 734 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); 745 746 // Add a delegate to the view. 747 base::scoped_nsobject<MockRenderWidgetHostViewMacDelegate> view_delegate( 748 [[MockRenderWidgetHostViewMacDelegate alloc] init]); 749 view->SetDelegate(view_delegate.get()); 750 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(); 756 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); 764 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; 768 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()); 773 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); 778 779 // Check that the view delegate ignored the empty unhandled wheel event. 780 ASSERT_EQ(NO, view_delegate.get().unhandledWheelEventReceived); 781 782 // Clean up. 783 host->Shutdown(); 784} 785 786} // namespace content 787