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