Clean up check for dependency_info.
[chromium-blink-merge.git] / content / browser / renderer_host / render_widget_host_view_mac_unittest.mm
blobceedad6ef350a9e0ea8e5bab3db3cc9014a07aa9
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 <Cocoa/Cocoa.h>
9 #include "base/mac/mac_util.h"
10 #include "base/mac/scoped_nsautorelease_pool.h"
11 #include "base/mac/sdk_forward_declarations.h"
12 #include "base/strings/utf_string_conversions.h"
13 #include "content/browser/browser_thread_impl.h"
14 #include "content/browser/compositor/test/no_transport_image_transport_factory.h"
15 #include "content/browser/frame_host/render_widget_host_view_guest.h"
16 #include "content/browser/gpu/compositor_util.h"
17 #include "content/browser/gpu/gpu_surface_tracker.h"
18 #include "content/browser/renderer_host/render_widget_host_delegate.h"
19 #include "content/common/gpu/gpu_messages.h"
20 #include "content/common/input_messages.h"
21 #include "content/common/view_messages.h"
22 #include "content/public/browser/notification_types.h"
23 #include "content/public/browser/render_widget_host_view_mac_delegate.h"
24 #include "content/public/test/mock_render_process_host.h"
25 #include "content/public/test/test_browser_context.h"
26 #include "content/public/test/test_utils.h"
27 #include "content/test/test_render_view_host.h"
28 #include "testing/gmock/include/gmock/gmock.h"
29 #include "testing/gtest/include/gtest/gtest.h"
30 #import "third_party/ocmock/OCMock/OCMock.h"
31 #import "third_party/ocmock/ocmock_extensions.h"
32 #include "ui/events/test/cocoa_test_event_utils.h"
33 #import "ui/gfx/test/ui_cocoa_test_helper.h"
35 // Helper class with methods used to mock -[NSEvent phase], used by
36 // |MockScrollWheelEventWithPhase()|.
37 @interface MockPhaseMethods : NSObject {
40 - (NSEventPhase)phaseBegan;
41 - (NSEventPhase)phaseChanged;
42 - (NSEventPhase)phaseEnded;
43 @end
45 @implementation MockPhaseMethods
47 - (NSEventPhase)phaseBegan {
48   return NSEventPhaseBegan;
50 - (NSEventPhase)phaseChanged {
51   return NSEventPhaseChanged;
53 - (NSEventPhase)phaseEnded {
54   return NSEventPhaseEnded;
57 @end
59 @interface MockRenderWidgetHostViewMacDelegate
60     : NSObject<RenderWidgetHostViewMacDelegate> {
61   BOOL unhandledWheelEventReceived_;
64 @property(nonatomic) BOOL unhandledWheelEventReceived;
66 @end
68 @implementation MockRenderWidgetHostViewMacDelegate
70 @synthesize unhandledWheelEventReceived = unhandledWheelEventReceived_;
72 - (void)rendererHandledWheelEvent:(const blink::WebMouseWheelEvent&)event
73                          consumed:(BOOL)consumed {
74   if (!consumed)
75     unhandledWheelEventReceived_ = true;
77 - (void)touchesBeganWithEvent:(NSEvent*)event {}
78 - (void)touchesMovedWithEvent:(NSEvent*)event {}
79 - (void)touchesCancelledWithEvent:(NSEvent*)event {}
80 - (void)touchesEndedWithEvent:(NSEvent*)event {}
81 - (void)beginGestureWithEvent:(NSEvent*)event {}
82 - (void)endGestureWithEvent:(NSEvent*)event {}
83 - (BOOL)canRubberbandLeft:(NSView*)view {
84   return true;
86 - (BOOL)canRubberbandRight:(NSView*)view {
87   return true;
90 @end
92 namespace content {
94 namespace {
96 id MockGestureEvent(NSEventType type, double magnification) {
97   id event = [OCMockObject mockForClass:[NSEvent class]];
98   NSPoint locationInWindow = NSMakePoint(0, 0);
99   CGFloat deltaX = 0;
100   CGFloat deltaY = 0;
101   NSTimeInterval timestamp = 1;
102   NSUInteger modifierFlags = 0;
104   [(NSEvent*)[[event stub] andReturnValue:OCMOCK_VALUE(type)] type];
105   [(NSEvent*)[[event stub]
106       andReturnValue:OCMOCK_VALUE(locationInWindow)] locationInWindow];
107   [(NSEvent*)[[event stub] andReturnValue:OCMOCK_VALUE(deltaX)] deltaX];
108   [(NSEvent*)[[event stub] andReturnValue:OCMOCK_VALUE(deltaY)] deltaY];
109   [(NSEvent*)[[event stub] andReturnValue:OCMOCK_VALUE(timestamp)] timestamp];
110   [(NSEvent*)[[event stub]
111       andReturnValue:OCMOCK_VALUE(modifierFlags)] modifierFlags];
112   [(NSEvent*)[[event stub]
113       andReturnValue:OCMOCK_VALUE(magnification)] magnification];
114   return event;
117 class MockRenderWidgetHostDelegate : public RenderWidgetHostDelegate {
118  public:
119   MockRenderWidgetHostDelegate() {}
120   ~MockRenderWidgetHostDelegate() override {}
122  private:
123   void Cut() override {}
124   void Copy() override {}
125   void Paste() override {}
126   void SelectAll() override {}
129 class MockRenderWidgetHostImpl : public RenderWidgetHostImpl {
130  public:
131   MockRenderWidgetHostImpl(RenderWidgetHostDelegate* delegate,
132                            RenderProcessHost* process,
133                            int32 routing_id,
134                            int32 surface_id)
135       : RenderWidgetHostImpl(delegate, process, routing_id, surface_id, false) {
136   }
138   MOCK_METHOD0(Focus, void());
139   MOCK_METHOD0(Blur, void());
142 // Generates the |length| of composition rectangle vector and save them to
143 // |output|. It starts from |origin| and each rectangle contains |unit_size|.
144 void GenerateCompositionRectArray(const gfx::Point& origin,
145                                   const gfx::Size& unit_size,
146                                   size_t length,
147                                   const std::vector<size_t>& break_points,
148                                   std::vector<gfx::Rect>* output) {
149   DCHECK(output);
150   output->clear();
152   std::queue<int> break_point_queue;
153   for (size_t i = 0; i < break_points.size(); ++i)
154     break_point_queue.push(break_points[i]);
155   break_point_queue.push(length);
156   size_t next_break_point = break_point_queue.front();
157   break_point_queue.pop();
159   gfx::Rect current_rect(origin, unit_size);
160   for (size_t i = 0; i < length; ++i) {
161     if (i == next_break_point) {
162       current_rect.set_x(origin.x());
163       current_rect.set_y(current_rect.y() + current_rect.height());
164       next_break_point = break_point_queue.front();
165       break_point_queue.pop();
166     }
167     output->push_back(current_rect);
168     current_rect.set_x(current_rect.right());
169   }
172 gfx::Rect GetExpectedRect(const gfx::Point& origin,
173                           const gfx::Size& size,
174                           const gfx::Range& range,
175                           int line_no) {
176   return gfx::Rect(
177       origin.x() + range.start() * size.width(),
178       origin.y() + line_no * size.height(),
179       range.length() * size.width(),
180       size.height());
183 // Returns NSScrollWheel event that mocks -phase. |mockPhaseSelector| should
184 // correspond to a method in |MockPhaseMethods| that returns the desired phase.
185 NSEvent* MockScrollWheelEventWithPhase(SEL mockPhaseSelector, int32_t delta) {
186   CGEventRef cg_event =
187       CGEventCreateScrollWheelEvent(NULL, kCGScrollEventUnitLine, 1, delta, 0);
188   NSEvent* event = [NSEvent eventWithCGEvent:cg_event];
189   CFRelease(cg_event);
190   method_setImplementation(
191       class_getInstanceMethod([NSEvent class], @selector(phase)),
192       [MockPhaseMethods instanceMethodForSelector:mockPhaseSelector]);
193   return event;
196 }  // namespace
198 class RenderWidgetHostViewMacTest : public RenderViewHostImplTestHarness {
199  public:
200   RenderWidgetHostViewMacTest() : old_rwhv_(NULL), rwhv_mac_(NULL) {}
202   void SetUp() override {
203     RenderViewHostImplTestHarness::SetUp();
204     if (IsDelegatedRendererEnabled()) {
205       ImageTransportFactory::InitializeForUnitTests(
206           scoped_ptr<ImageTransportFactory>(
207               new NoTransportImageTransportFactory));
208     }
210     // TestRenderViewHost's destruction assumes that its view is a
211     // TestRenderWidgetHostView, so store its view and reset it back to the
212     // stored view in |TearDown()|.
213     old_rwhv_ = rvh()->GetView();
215     // Owned by its |cocoa_view()|, i.e. |rwhv_cocoa_|.
216     rwhv_mac_ = new RenderWidgetHostViewMac(rvh(), false);
217     rwhv_cocoa_.reset([rwhv_mac_->cocoa_view() retain]);
218   }
219   void TearDown() override {
220     // Make sure the rwhv_mac_ is gone once the superclass's |TearDown()| runs.
221     rwhv_cocoa_.reset();
222     RecycleAndWait();
224     // See comment in SetUp().
225     test_rvh()->SetView(static_cast<RenderWidgetHostViewBase*>(old_rwhv_));
227     if (IsDelegatedRendererEnabled())
228       ImageTransportFactory::Terminate();
229     RenderViewHostImplTestHarness::TearDown();
230   }
232   void RecycleAndWait() {
233     pool_.Recycle();
234     base::MessageLoop::current()->RunUntilIdle();
235     pool_.Recycle();
236   }
237  protected:
238  private:
239   // This class isn't derived from PlatformTest.
240   base::mac::ScopedNSAutoreleasePool pool_;
242   RenderWidgetHostView* old_rwhv_;
244  protected:
245   RenderWidgetHostViewMac* rwhv_mac_;
246   base::scoped_nsobject<RenderWidgetHostViewCocoa> rwhv_cocoa_;
248  private:
249   DISALLOW_COPY_AND_ASSIGN(RenderWidgetHostViewMacTest);
252 TEST_F(RenderWidgetHostViewMacTest, Basic) {
255 TEST_F(RenderWidgetHostViewMacTest, AcceptsFirstResponder) {
256   // The RWHVCocoa should normally accept first responder status.
257   EXPECT_TRUE([rwhv_cocoa_.get() acceptsFirstResponder]);
260 TEST_F(RenderWidgetHostViewMacTest, Fullscreen) {
261   rwhv_mac_->InitAsFullscreen(NULL);
262   EXPECT_TRUE(rwhv_mac_->pepper_fullscreen_window());
264   // Break the reference cycle caused by pepper_fullscreen_window() without
265   // an <esc> event. See comment in
266   // release_pepper_fullscreen_window_for_testing().
267   rwhv_mac_->release_pepper_fullscreen_window_for_testing();
270 // Verify that escape key down in fullscreen mode suppressed the keyup event on
271 // the parent.
272 TEST_F(RenderWidgetHostViewMacTest, FullscreenCloseOnEscape) {
273   // Use our own RWH since we need to destroy it.
274   MockRenderWidgetHostDelegate delegate;
275   TestBrowserContext browser_context;
276   MockRenderProcessHost* process_host =
277       new MockRenderProcessHost(&browser_context);
278   int32 routing_id = process_host->GetNextRoutingID();
279   int32 surface_id = GpuSurfaceTracker::Get()->AddSurfaceForRenderer(
280       process_host->GetID(), routing_id);
281   // Owned by its |cocoa_view()|.
282   RenderWidgetHostImpl* rwh = new RenderWidgetHostImpl(
283       &delegate, process_host, routing_id, surface_id, false);
284   RenderWidgetHostViewMac* view = new RenderWidgetHostViewMac(rwh, false);
286   view->InitAsFullscreen(rwhv_mac_);
288   WindowedNotificationObserver observer(
289       NOTIFICATION_RENDER_WIDGET_HOST_DESTROYED,
290       Source<RenderWidgetHost>(rwh));
291   EXPECT_FALSE([rwhv_mac_->cocoa_view() suppressNextEscapeKeyUp]);
293   // Escape key down. Should close window and set |suppressNextEscapeKeyUp| on
294   // the parent.
295   [view->cocoa_view() keyEvent:
296       cocoa_test_event_utils::KeyEventWithKeyCode(53, 27, NSKeyDown, 0)];
297   observer.Wait();
298   EXPECT_TRUE([rwhv_mac_->cocoa_view() suppressNextEscapeKeyUp]);
300   // Escape key up on the parent should clear |suppressNextEscapeKeyUp|.
301   [rwhv_mac_->cocoa_view() keyEvent:
302       cocoa_test_event_utils::KeyEventWithKeyCode(53, 27, NSKeyUp, 0)];
303   EXPECT_FALSE([rwhv_mac_->cocoa_view() suppressNextEscapeKeyUp]);
306 // Test that command accelerators which destroy the fullscreen window
307 // don't crash when forwarded via the window's responder machinery.
308 TEST_F(RenderWidgetHostViewMacTest, AcceleratorDestroy) {
309   // Use our own RWH since we need to destroy it.
310   MockRenderWidgetHostDelegate delegate;
311   TestBrowserContext browser_context;
312   MockRenderProcessHost* process_host =
313       new MockRenderProcessHost(&browser_context);
314   int32 routing_id = process_host->GetNextRoutingID();
315   int32 surface_id = GpuSurfaceTracker::Get()->AddSurfaceForRenderer(
316       process_host->GetID(), routing_id);
317   // Owned by its |cocoa_view()|.
318   RenderWidgetHostImpl* rwh = new RenderWidgetHostImpl(
319       &delegate, process_host, routing_id, surface_id, false);
320   RenderWidgetHostViewMac* view = new RenderWidgetHostViewMac(rwh, false);
322   view->InitAsFullscreen(rwhv_mac_);
324   WindowedNotificationObserver observer(
325       NOTIFICATION_RENDER_WIDGET_HOST_DESTROYED,
326       Source<RenderWidgetHost>(rwh));
328   // Command-ESC will destroy the view, while the window is still in
329   // |-performKeyEquivalent:|.  There are other cases where this can
330   // happen, Command-ESC is the easiest to trigger.
331   [[view->cocoa_view() window] performKeyEquivalent:
332       cocoa_test_event_utils::KeyEventWithKeyCode(
333           53, 27, NSKeyDown, NSCommandKeyMask)];
334   observer.Wait();
337 TEST_F(RenderWidgetHostViewMacTest, GetFirstRectForCharacterRangeCaretCase) {
338   const base::string16 kDummyString = base::UTF8ToUTF16("hogehoge");
339   const size_t kDummyOffset = 0;
341   gfx::Rect caret_rect(10, 11, 0, 10);
342   gfx::Range caret_range(0, 0);
343   ViewHostMsg_SelectionBounds_Params params;
345   NSRect rect;
346   NSRange actual_range;
347   rwhv_mac_->SelectionChanged(kDummyString, kDummyOffset, caret_range);
348   params.anchor_rect = params.focus_rect = caret_rect;
349   params.anchor_dir = params.focus_dir = blink::WebTextDirectionLeftToRight;
350   rwhv_mac_->SelectionBoundsChanged(params);
351   EXPECT_TRUE(rwhv_mac_->GetCachedFirstRectForCharacterRange(
352         caret_range.ToNSRange(),
353         &rect,
354         &actual_range));
355   EXPECT_EQ(caret_rect, gfx::Rect(NSRectToCGRect(rect)));
356   EXPECT_EQ(caret_range, gfx::Range(actual_range));
358   EXPECT_FALSE(rwhv_mac_->GetCachedFirstRectForCharacterRange(
359         gfx::Range(0, 1).ToNSRange(),
360         &rect,
361         &actual_range));
362   EXPECT_FALSE(rwhv_mac_->GetCachedFirstRectForCharacterRange(
363         gfx::Range(1, 1).ToNSRange(),
364         &rect,
365         &actual_range));
366   EXPECT_FALSE(rwhv_mac_->GetCachedFirstRectForCharacterRange(
367         gfx::Range(2, 3).ToNSRange(),
368         &rect,
369         &actual_range));
371   // Caret moved.
372   caret_rect = gfx::Rect(20, 11, 0, 10);
373   caret_range = gfx::Range(1, 1);
374   params.anchor_rect = params.focus_rect = caret_rect;
375   rwhv_mac_->SelectionChanged(kDummyString, kDummyOffset, caret_range);
376   rwhv_mac_->SelectionBoundsChanged(params);
377   EXPECT_TRUE(rwhv_mac_->GetCachedFirstRectForCharacterRange(
378         caret_range.ToNSRange(),
379         &rect,
380         &actual_range));
381   EXPECT_EQ(caret_rect, gfx::Rect(NSRectToCGRect(rect)));
382   EXPECT_EQ(caret_range, gfx::Range(actual_range));
384   EXPECT_FALSE(rwhv_mac_->GetCachedFirstRectForCharacterRange(
385         gfx::Range(0, 0).ToNSRange(),
386         &rect,
387         &actual_range));
388   EXPECT_FALSE(rwhv_mac_->GetCachedFirstRectForCharacterRange(
389         gfx::Range(1, 2).ToNSRange(),
390         &rect,
391         &actual_range));
392   EXPECT_FALSE(rwhv_mac_->GetCachedFirstRectForCharacterRange(
393         gfx::Range(2, 3).ToNSRange(),
394         &rect,
395         &actual_range));
397   // No caret.
398   caret_range = gfx::Range(1, 2);
399   rwhv_mac_->SelectionChanged(kDummyString, kDummyOffset, caret_range);
400   params.anchor_rect = caret_rect;
401   params.focus_rect = gfx::Rect(30, 11, 0, 10);
402   rwhv_mac_->SelectionBoundsChanged(params);
403   EXPECT_FALSE(rwhv_mac_->GetCachedFirstRectForCharacterRange(
404         gfx::Range(0, 0).ToNSRange(),
405         &rect,
406         &actual_range));
407   EXPECT_FALSE(rwhv_mac_->GetCachedFirstRectForCharacterRange(
408         gfx::Range(0, 1).ToNSRange(),
409         &rect,
410         &actual_range));
411   EXPECT_FALSE(rwhv_mac_->GetCachedFirstRectForCharacterRange(
412         gfx::Range(1, 1).ToNSRange(),
413         &rect,
414         &actual_range));
415   EXPECT_FALSE(rwhv_mac_->GetCachedFirstRectForCharacterRange(
416         gfx::Range(1, 2).ToNSRange(),
417         &rect,
418         &actual_range));
419   EXPECT_FALSE(rwhv_mac_->GetCachedFirstRectForCharacterRange(
420         gfx::Range(2, 2).ToNSRange(),
421         &rect,
422         &actual_range));
425 TEST_F(RenderWidgetHostViewMacTest, UpdateCompositionSinglelineCase) {
426   const gfx::Point kOrigin(10, 11);
427   const gfx::Size kBoundsUnit(10, 20);
429   NSRect rect;
430   // Make sure not crashing by passing NULL pointer instead of |actual_range|.
431   EXPECT_FALSE(rwhv_mac_->GetCachedFirstRectForCharacterRange(
432       gfx::Range(0, 0).ToNSRange(),
433       &rect,
434       NULL));
436   // If there are no update from renderer, always returned caret position.
437   NSRange actual_range;
438   EXPECT_FALSE(rwhv_mac_->GetCachedFirstRectForCharacterRange(
439       gfx::Range(0, 0).ToNSRange(),
440       &rect,
441       &actual_range));
442   EXPECT_FALSE(rwhv_mac_->GetCachedFirstRectForCharacterRange(
443       gfx::Range(0, 1).ToNSRange(),
444       &rect,
445       &actual_range));
446   EXPECT_FALSE(rwhv_mac_->GetCachedFirstRectForCharacterRange(
447       gfx::Range(1, 0).ToNSRange(),
448       &rect,
449       &actual_range));
450   EXPECT_FALSE(rwhv_mac_->GetCachedFirstRectForCharacterRange(
451       gfx::Range(1, 1).ToNSRange(),
452       &rect,
453       &actual_range));
454   EXPECT_FALSE(rwhv_mac_->GetCachedFirstRectForCharacterRange(
455       gfx::Range(1, 2).ToNSRange(),
456       &rect,
457       &actual_range));
459   // If the firstRectForCharacterRange is failed in renderer, empty rect vector
460   // is sent. Make sure this does not crash.
461   rwhv_mac_->ImeCompositionRangeChanged(gfx::Range(10, 12),
462                                         std::vector<gfx::Rect>());
463   EXPECT_FALSE(rwhv_mac_->GetCachedFirstRectForCharacterRange(
464       gfx::Range(10, 11).ToNSRange(),
465       &rect,
466       NULL));
468   const int kCompositionLength = 10;
469   std::vector<gfx::Rect> composition_bounds;
470   const int kCompositionStart = 3;
471   const gfx::Range kCompositionRange(kCompositionStart,
472                                     kCompositionStart + kCompositionLength);
473   GenerateCompositionRectArray(kOrigin,
474                                kBoundsUnit,
475                                kCompositionLength,
476                                std::vector<size_t>(),
477                                &composition_bounds);
478   rwhv_mac_->ImeCompositionRangeChanged(kCompositionRange, composition_bounds);
480   // Out of range requests will return caret position.
481   EXPECT_FALSE(rwhv_mac_->GetCachedFirstRectForCharacterRange(
482       gfx::Range(0, 0).ToNSRange(),
483       &rect,
484       &actual_range));
485   EXPECT_FALSE(rwhv_mac_->GetCachedFirstRectForCharacterRange(
486       gfx::Range(1, 1).ToNSRange(),
487       &rect,
488       &actual_range));
489   EXPECT_FALSE(rwhv_mac_->GetCachedFirstRectForCharacterRange(
490       gfx::Range(1, 2).ToNSRange(),
491       &rect,
492       &actual_range));
493   EXPECT_FALSE(rwhv_mac_->GetCachedFirstRectForCharacterRange(
494       gfx::Range(2, 2).ToNSRange(),
495       &rect,
496       &actual_range));
497   EXPECT_FALSE(rwhv_mac_->GetCachedFirstRectForCharacterRange(
498       gfx::Range(13, 14).ToNSRange(),
499       &rect,
500       &actual_range));
501   EXPECT_FALSE(rwhv_mac_->GetCachedFirstRectForCharacterRange(
502       gfx::Range(14, 15).ToNSRange(),
503       &rect,
504       &actual_range));
506   for (int i = 0; i <= kCompositionLength; ++i) {
507     for (int j = 0; j <= kCompositionLength - i; ++j) {
508       const gfx::Range range(i, i + j);
509       const gfx::Rect expected_rect = GetExpectedRect(kOrigin,
510                                                       kBoundsUnit,
511                                                       range,
512                                                       0);
513       const NSRange request_range = gfx::Range(
514           kCompositionStart + range.start(),
515           kCompositionStart + range.end()).ToNSRange();
516       EXPECT_TRUE(rwhv_mac_->GetCachedFirstRectForCharacterRange(
517             request_range,
518             &rect,
519             &actual_range));
520       EXPECT_EQ(gfx::Range(request_range), gfx::Range(actual_range));
521       EXPECT_EQ(expected_rect, gfx::Rect(NSRectToCGRect(rect)));
523       // Make sure not crashing by passing NULL pointer instead of
524       // |actual_range|.
525       EXPECT_TRUE(rwhv_mac_->GetCachedFirstRectForCharacterRange(
526             request_range,
527             &rect,
528             NULL));
529     }
530   }
533 TEST_F(RenderWidgetHostViewMacTest, UpdateCompositionMultilineCase) {
534   const gfx::Point kOrigin(10, 11);
535   const gfx::Size kBoundsUnit(10, 20);
536   NSRect rect;
538   const int kCompositionLength = 30;
539   std::vector<gfx::Rect> composition_bounds;
540   const gfx::Range kCompositionRange(0, kCompositionLength);
541   // Set breaking point at 10 and 20.
542   std::vector<size_t> break_points;
543   break_points.push_back(10);
544   break_points.push_back(20);
545   GenerateCompositionRectArray(kOrigin,
546                                kBoundsUnit,
547                                kCompositionLength,
548                                break_points,
549                                &composition_bounds);
550   rwhv_mac_->ImeCompositionRangeChanged(kCompositionRange, composition_bounds);
552   // Range doesn't contain line breaking point.
553   gfx::Range range;
554   range = gfx::Range(5, 8);
555   NSRange actual_range;
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, range, 0),
562       gfx::Rect(NSRectToCGRect(rect)));
563   range = gfx::Range(15, 18);
564   EXPECT_TRUE(rwhv_mac_->GetCachedFirstRectForCharacterRange(range.ToNSRange(),
565                                                              &rect,
566                                                              &actual_range));
567   EXPECT_EQ(range, gfx::Range(actual_range));
568   EXPECT_EQ(
569       GetExpectedRect(kOrigin, kBoundsUnit, gfx::Range(5, 8), 1),
570       gfx::Rect(NSRectToCGRect(rect)));
571   range = gfx::Range(25, 28);
572   EXPECT_TRUE(rwhv_mac_->GetCachedFirstRectForCharacterRange(range.ToNSRange(),
573                                                              &rect,
574                                                              &actual_range));
575   EXPECT_EQ(range, gfx::Range(actual_range));
576   EXPECT_EQ(
577       GetExpectedRect(kOrigin, kBoundsUnit, gfx::Range(5, 8), 2),
578       gfx::Rect(NSRectToCGRect(rect)));
580   // Range contains line breaking point.
581   range = gfx::Range(8, 12);
582   EXPECT_TRUE(rwhv_mac_->GetCachedFirstRectForCharacterRange(range.ToNSRange(),
583                                                              &rect,
584                                                              &actual_range));
585   EXPECT_EQ(gfx::Range(8, 10), gfx::Range(actual_range));
586   EXPECT_EQ(
587       GetExpectedRect(kOrigin, kBoundsUnit, gfx::Range(8, 10), 0),
588       gfx::Rect(NSRectToCGRect(rect)));
589   range = gfx::Range(18, 22);
590   EXPECT_TRUE(rwhv_mac_->GetCachedFirstRectForCharacterRange(range.ToNSRange(),
591                                                              &rect,
592                                                              &actual_range));
593   EXPECT_EQ(gfx::Range(18, 20), gfx::Range(actual_range));
594   EXPECT_EQ(
595       GetExpectedRect(kOrigin, kBoundsUnit, gfx::Range(8, 10), 1),
596       gfx::Rect(NSRectToCGRect(rect)));
598   // Start point is line breaking point.
599   range = gfx::Range(10, 12);
600   EXPECT_TRUE(rwhv_mac_->GetCachedFirstRectForCharacterRange(range.ToNSRange(),
601                                                              &rect,
602                                                              &actual_range));
603   EXPECT_EQ(gfx::Range(10, 12), gfx::Range(actual_range));
604   EXPECT_EQ(
605       GetExpectedRect(kOrigin, kBoundsUnit, gfx::Range(0, 2), 1),
606       gfx::Rect(NSRectToCGRect(rect)));
607   range = gfx::Range(20, 22);
608   EXPECT_TRUE(rwhv_mac_->GetCachedFirstRectForCharacterRange(range.ToNSRange(),
609                                                              &rect,
610                                                              &actual_range));
611   EXPECT_EQ(gfx::Range(20, 22), gfx::Range(actual_range));
612   EXPECT_EQ(
613       GetExpectedRect(kOrigin, kBoundsUnit, gfx::Range(0, 2), 2),
614       gfx::Rect(NSRectToCGRect(rect)));
616   // End point is line breaking point.
617   range = gfx::Range(5, 10);
618   EXPECT_TRUE(rwhv_mac_->GetCachedFirstRectForCharacterRange(range.ToNSRange(),
619                                                              &rect,
620                                                              &actual_range));
621   EXPECT_EQ(gfx::Range(5, 10), gfx::Range(actual_range));
622   EXPECT_EQ(
623       GetExpectedRect(kOrigin, kBoundsUnit, gfx::Range(5, 10), 0),
624       gfx::Rect(NSRectToCGRect(rect)));
625   range = gfx::Range(15, 20);
626   EXPECT_TRUE(rwhv_mac_->GetCachedFirstRectForCharacterRange(range.ToNSRange(),
627                                                              &rect,
628                                                              &actual_range));
629   EXPECT_EQ(gfx::Range(15, 20), gfx::Range(actual_range));
630   EXPECT_EQ(
631       GetExpectedRect(kOrigin, kBoundsUnit, gfx::Range(5, 10), 1),
632       gfx::Rect(NSRectToCGRect(rect)));
634   // Start and end point are same line breaking point.
635   range = gfx::Range(10, 10);
636   EXPECT_TRUE(rwhv_mac_->GetCachedFirstRectForCharacterRange(range.ToNSRange(),
637                                                              &rect,
638                                                              &actual_range));
639   EXPECT_EQ(gfx::Range(10, 10), gfx::Range(actual_range));
640   EXPECT_EQ(
641       GetExpectedRect(kOrigin, kBoundsUnit, gfx::Range(0, 0), 1),
642       gfx::Rect(NSRectToCGRect(rect)));
643   range = gfx::Range(20, 20);
644   EXPECT_TRUE(rwhv_mac_->GetCachedFirstRectForCharacterRange(range.ToNSRange(),
645                                                              &rect,
646                                                              &actual_range));
647   EXPECT_EQ(gfx::Range(20, 20), gfx::Range(actual_range));
648   EXPECT_EQ(
649       GetExpectedRect(kOrigin, kBoundsUnit, gfx::Range(0, 0), 2),
650       gfx::Rect(NSRectToCGRect(rect)));
652   // Start and end point are different line breaking point.
653   range = gfx::Range(10, 20);
654   EXPECT_TRUE(rwhv_mac_->GetCachedFirstRectForCharacterRange(range.ToNSRange(),
655                                                              &rect,
656                                                              &actual_range));
657   EXPECT_EQ(gfx::Range(10, 20), gfx::Range(actual_range));
658   EXPECT_EQ(
659       GetExpectedRect(kOrigin, kBoundsUnit, gfx::Range(0, 10), 1),
660       gfx::Rect(NSRectToCGRect(rect)));
663 // Verify that |SetActive()| calls |RenderWidgetHostImpl::Blur()| and
664 // |RenderWidgetHostImp::Focus()|.
665 TEST_F(RenderWidgetHostViewMacTest, BlurAndFocusOnSetActive) {
666   MockRenderWidgetHostDelegate delegate;
667   TestBrowserContext browser_context;
668   MockRenderProcessHost* process_host =
669       new MockRenderProcessHost(&browser_context);
671   // Owned by its |cocoa_view()|.
672   int32 routing_id = process_host->GetNextRoutingID();
673   int32 surface_id = GpuSurfaceTracker::Get()->AddSurfaceForRenderer(
674       process_host->GetID(), routing_id);
675   MockRenderWidgetHostImpl* rwh = new MockRenderWidgetHostImpl(
676       &delegate, process_host, routing_id, surface_id);
677   RenderWidgetHostViewMac* view = new RenderWidgetHostViewMac(rwh, false);
679   base::scoped_nsobject<CocoaTestHelperWindow> window(
680       [[CocoaTestHelperWindow alloc] init]);
681   [[window contentView] addSubview:view->cocoa_view()];
683   EXPECT_CALL(*rwh, Focus());
684   [window makeFirstResponder:view->cocoa_view()];
685   testing::Mock::VerifyAndClearExpectations(rwh);
687   EXPECT_CALL(*rwh, Blur());
688   view->SetActive(false);
689   testing::Mock::VerifyAndClearExpectations(rwh);
691   EXPECT_CALL(*rwh, Focus());
692   view->SetActive(true);
693   testing::Mock::VerifyAndClearExpectations(rwh);
695   // Unsetting first responder should blur.
696   EXPECT_CALL(*rwh, Blur());
697   [window makeFirstResponder:nil];
698   testing::Mock::VerifyAndClearExpectations(rwh);
700   // |SetActive()| shoud not focus if view is not first responder.
701   EXPECT_CALL(*rwh, Focus()).Times(0);
702   view->SetActive(true);
703   testing::Mock::VerifyAndClearExpectations(rwh);
705   // Clean up.
706   rwh->Shutdown();
709 TEST_F(RenderWidgetHostViewMacTest, ScrollWheelEndEventDelivery) {
710   // This tests Lion+ functionality, so don't run the test pre-Lion.
711   if (!base::mac::IsOSLionOrLater())
712     return;
714   // Initialize the view associated with a MockRenderWidgetHostImpl, rather than
715   // the MockRenderProcessHost that is set up by the test harness which mocks
716   // out |OnMessageReceived()|.
717   TestBrowserContext browser_context;
718   MockRenderProcessHost* process_host =
719       new MockRenderProcessHost(&browser_context);
720   process_host->Init();
721   MockRenderWidgetHostDelegate delegate;
722   int32 routing_id = process_host->GetNextRoutingID();
723   int32 surface_id = GpuSurfaceTracker::Get()->AddSurfaceForRenderer(
724       process_host->GetID(), routing_id);
725   MockRenderWidgetHostImpl* host = new MockRenderWidgetHostImpl(
726       &delegate, process_host, routing_id, surface_id);
727   RenderWidgetHostViewMac* view = new RenderWidgetHostViewMac(host, false);
729   // Send an initial wheel event with NSEventPhaseBegan to the view.
730   NSEvent* event1 = MockScrollWheelEventWithPhase(@selector(phaseBegan), 0);
731   [view->cocoa_view() scrollWheel:event1];
732   ASSERT_EQ(1U, process_host->sink().message_count());
734   // Send an ACK for the first wheel event, so that the queue will be flushed.
735   InputEventAck ack(blink::WebInputEvent::MouseWheel,
736                     INPUT_EVENT_ACK_STATE_CONSUMED);
737   scoped_ptr<IPC::Message> response(
738       new InputHostMsg_HandleInputEvent_ACK(0, ack));
739   host->OnMessageReceived(*response);
741   // Post the NSEventPhaseEnded wheel event to NSApp and check whether the
742   // render view receives it.
743   NSEvent* event2 = MockScrollWheelEventWithPhase(@selector(phaseEnded), 0);
744   [NSApp postEvent:event2 atStart:NO];
745   base::MessageLoop::current()->RunUntilIdle();
746   ASSERT_EQ(2U, process_host->sink().message_count());
748   // Clean up.
749   host->Shutdown();
752 TEST_F(RenderWidgetHostViewMacTest, IgnoreEmptyUnhandledWheelEvent) {
753   // This tests Lion+ functionality, so don't run the test pre-Lion.
754   if (!base::mac::IsOSLionOrLater())
755     return;
757   // Initialize the view associated with a MockRenderWidgetHostImpl, rather than
758   // the MockRenderProcessHost that is set up by the test harness which mocks
759   // out |OnMessageReceived()|.
760   TestBrowserContext browser_context;
761   MockRenderProcessHost* process_host =
762       new MockRenderProcessHost(&browser_context);
763   process_host->Init();
764   MockRenderWidgetHostDelegate delegate;
765   int32 routing_id = process_host->GetNextRoutingID();
766   int32 surface_id = GpuSurfaceTracker::Get()->AddSurfaceForRenderer(
767       process_host->GetID(), routing_id);
768   MockRenderWidgetHostImpl* host = new MockRenderWidgetHostImpl(
769       &delegate, process_host, routing_id, surface_id);
770   RenderWidgetHostViewMac* view = new RenderWidgetHostViewMac(host, false);
772   // Add a delegate to the view.
773   base::scoped_nsobject<MockRenderWidgetHostViewMacDelegate> view_delegate(
774       [[MockRenderWidgetHostViewMacDelegate alloc] init]);
775   view->SetDelegate(view_delegate.get());
777   // Send an initial wheel event for scrolling by 3 lines.
778   NSEvent* event1 = MockScrollWheelEventWithPhase(@selector(phaseBegan), 3);
779   [view->cocoa_view() scrollWheel:event1];
780   ASSERT_EQ(1U, process_host->sink().message_count());
781   process_host->sink().ClearMessages();
783   // Indicate that the wheel event was unhandled.
784   InputEventAck unhandled_ack(blink::WebInputEvent::MouseWheel,
785                               INPUT_EVENT_ACK_STATE_NOT_CONSUMED);
786   scoped_ptr<IPC::Message> response1(
787       new InputHostMsg_HandleInputEvent_ACK(0, unhandled_ack));
788   host->OnMessageReceived(*response1);
790   // Check that the view delegate got an unhandled wheel event.
791   ASSERT_EQ(YES, view_delegate.get().unhandledWheelEventReceived);
792   view_delegate.get().unhandledWheelEventReceived = NO;
794   // Send another wheel event, this time for scrolling by 0 lines (empty event).
795   NSEvent* event2 = MockScrollWheelEventWithPhase(@selector(phaseChanged), 0);
796   [view->cocoa_view() scrollWheel:event2];
797   ASSERT_EQ(1U, process_host->sink().message_count());
799   // Indicate that the wheel event was also unhandled.
800   scoped_ptr<IPC::Message> response2(
801       new InputHostMsg_HandleInputEvent_ACK(0, unhandled_ack));
802   host->OnMessageReceived(*response2);
804   // Check that the view delegate ignored the empty unhandled wheel event.
805   ASSERT_EQ(NO, view_delegate.get().unhandledWheelEventReceived);
807   // Clean up.
808   host->Shutdown();
811 // Tests that when view initiated shutdown happens (i.e. RWHView is deleted
812 // before RWH), we clean up properly and don't leak the RWHVGuest.
813 TEST_F(RenderWidgetHostViewMacTest, GuestViewDoesNotLeak) {
814   MockRenderWidgetHostDelegate delegate;
815   TestBrowserContext browser_context;
816   MockRenderProcessHost* process_host =
817       new MockRenderProcessHost(&browser_context);
818   int32 routing_id = process_host->GetNextRoutingID();
819   int32 surface_id = GpuSurfaceTracker::Get()->AddSurfaceForRenderer(
820       process_host->GetID(), routing_id);
822   // Owned by its |cocoa_view()|.
823   MockRenderWidgetHostImpl* rwh = new MockRenderWidgetHostImpl(
824       &delegate, process_host, routing_id, surface_id);
825   RenderWidgetHostViewMac* view = new RenderWidgetHostViewMac(rwh, true);
827   // Add a delegate to the view.
828   base::scoped_nsobject<MockRenderWidgetHostViewMacDelegate> view_delegate(
829       [[MockRenderWidgetHostViewMacDelegate alloc] init]);
830   view->SetDelegate(view_delegate.get());
832   base::WeakPtr<RenderWidgetHostViewBase> guest_rwhv_weak =
833       (new RenderWidgetHostViewGuest(
834            rwh, NULL, view->GetWeakPtr()))->GetWeakPtr();
836   // Remove the cocoa_view() so |view| also goes away before |rwh|.
837   {
838     base::scoped_nsobject<RenderWidgetHostViewCocoa> rwhv_cocoa;
839     rwhv_cocoa.reset([view->cocoa_view() retain]);
840   }
841   RecycleAndWait();
843   // Clean up.
844   rwh->Shutdown();
846   // Let |guest_rwhv_weak| have a chance to delete itself.
847   base::RunLoop run_loop;
848   content::BrowserThread::PostTask(
849       content::BrowserThread::UI, FROM_HERE, run_loop.QuitClosure());
850   run_loop.Run();
852   ASSERT_FALSE(guest_rwhv_weak.get());
855 // Tests setting background transparency. See also (disabled on Mac)
856 // RenderWidgetHostTest.Background. This test has some additional checks for
857 // Mac.
858 TEST_F(RenderWidgetHostViewMacTest, Background) {
859   TestBrowserContext browser_context;
860   MockRenderProcessHost* process_host =
861       new MockRenderProcessHost(&browser_context);
862   MockRenderWidgetHostDelegate delegate;
863   int32 routing_id = process_host->GetNextRoutingID();
864   int32 surface_id = GpuSurfaceTracker::Get()->AddSurfaceForRenderer(
865       process_host->GetID(), routing_id);
866   MockRenderWidgetHostImpl* host = new MockRenderWidgetHostImpl(
867       &delegate, process_host, routing_id, surface_id);
868   RenderWidgetHostViewMac* view = new RenderWidgetHostViewMac(host, false);
870   EXPECT_TRUE(view->GetBackgroundOpaque());
871   EXPECT_TRUE([view->cocoa_view() isOpaque]);
873   view->SetBackgroundColor(SK_ColorTRANSPARENT);
874   EXPECT_FALSE(view->GetBackgroundOpaque());
875   EXPECT_FALSE([view->cocoa_view() isOpaque]);
877   const IPC::Message* set_background;
878   set_background = process_host->sink().GetUniqueMessageMatching(
879       ViewMsg_SetBackgroundOpaque::ID);
880   ASSERT_TRUE(set_background);
881   base::Tuple<bool> sent_background;
882   ViewMsg_SetBackgroundOpaque::Read(set_background, &sent_background);
883   EXPECT_FALSE(base::get<0>(sent_background));
885   // Try setting it back.
886   process_host->sink().ClearMessages();
887   view->SetBackgroundColor(SK_ColorWHITE);
888   EXPECT_TRUE(view->GetBackgroundOpaque());
889   EXPECT_TRUE([view->cocoa_view() isOpaque]);
890   set_background = process_host->sink().GetUniqueMessageMatching(
891       ViewMsg_SetBackgroundOpaque::ID);
892   ASSERT_TRUE(set_background);
893   ViewMsg_SetBackgroundOpaque::Read(set_background, &sent_background);
894   EXPECT_TRUE(base::get<0>(sent_background));
896   host->Shutdown();
899 class RenderWidgetHostViewMacPinchTest : public RenderWidgetHostViewMacTest {
900  public:
901   RenderWidgetHostViewMacPinchTest() : process_host_(NULL) {}
903   bool ZoomDisabledForPinchUpdateMessage() {
904     const IPC::Message* message = NULL;
905     // The first message may be a PinchBegin. Go for the second message if
906     // there are two.
907     switch (process_host_->sink().message_count()) {
908       case 1:
909         message = process_host_->sink().GetMessageAt(0);
910         break;
911       case 2:
912         message = process_host_->sink().GetMessageAt(1);
913         break;
914       default:
915         NOTREACHED();
916         break;
917     }
918     DCHECK(message);
919     base::Tuple<IPC::WebInputEventPointer, ui::LatencyInfo, bool> data;
920     InputMsg_HandleInputEvent::Read(message, &data);
921     IPC::WebInputEventPointer ipc_event = base::get<0>(data);
922     const blink::WebGestureEvent* gesture_event =
923         static_cast<const blink::WebGestureEvent*>(ipc_event);
924     return gesture_event->data.pinchUpdate.zoomDisabled;
925   }
927   MockRenderProcessHost* process_host_;
930 TEST_F(RenderWidgetHostViewMacPinchTest, PinchThresholding) {
931   // This tests Lion+ functionality, so don't run the test pre-Lion.
932   if (!base::mac::IsOSLionOrLater())
933     return;
935   // Initialize the view associated with a MockRenderWidgetHostImpl, rather than
936   // the MockRenderProcessHost that is set up by the test harness which mocks
937   // out |OnMessageReceived()|.
938   TestBrowserContext browser_context;
939   process_host_ = new MockRenderProcessHost(&browser_context);
940   process_host_->Init();
941   MockRenderWidgetHostDelegate delegate;
942   int32 routing_id = process_host_->GetNextRoutingID();
943   int32 surface_id = GpuSurfaceTracker::Get()->AddSurfaceForRenderer(
944       process_host_->GetID(), routing_id);
945   MockRenderWidgetHostImpl* host = new MockRenderWidgetHostImpl(
946       &delegate, process_host_, routing_id, surface_id);
947   RenderWidgetHostViewMac* view = new RenderWidgetHostViewMac(host, false);
949   // We'll use this IPC message to ack events.
950   InputEventAck ack(blink::WebInputEvent::GesturePinchUpdate,
951                     INPUT_EVENT_ACK_STATE_CONSUMED);
952   scoped_ptr<IPC::Message> response(
953       new InputHostMsg_HandleInputEvent_ACK(0, ack));
955   // Do a gesture that crosses the threshold.
956   {
957     NSEvent* pinchBeginEvent =
958         MockGestureEvent(NSEventTypeBeginGesture, 0);
959     NSEvent* pinchUpdateEvents[3] = {
960         MockGestureEvent(NSEventTypeMagnify, 0.25),
961         MockGestureEvent(NSEventTypeMagnify, 0.25),
962         MockGestureEvent(NSEventTypeMagnify, 0.25),
963     };
964     NSEvent* pinchEndEvent =
965         MockGestureEvent(NSEventTypeEndGesture, 0);
967     [view->cocoa_view() beginGestureWithEvent:pinchBeginEvent];
968     EXPECT_EQ(0U, process_host_->sink().message_count());
970     // No zoom is sent for the first update event.
971     [view->cocoa_view() magnifyWithEvent:pinchUpdateEvents[0]];
972     host->OnMessageReceived(*response);
973     EXPECT_EQ(2U, process_host_->sink().message_count());
974     EXPECT_TRUE(ZoomDisabledForPinchUpdateMessage());
975     process_host_->sink().ClearMessages();
977     // The second update event crosses the threshold of 0.4, and so zoom is no
978     // longer disabled.
979     [view->cocoa_view() magnifyWithEvent:pinchUpdateEvents[1]];
980     EXPECT_FALSE(ZoomDisabledForPinchUpdateMessage());
981     host->OnMessageReceived(*response);
982     EXPECT_EQ(1U, process_host_->sink().message_count());
983     process_host_->sink().ClearMessages();
985     // The third update still has zoom enabled.
986     [view->cocoa_view() magnifyWithEvent:pinchUpdateEvents[2]];
987     EXPECT_FALSE(ZoomDisabledForPinchUpdateMessage());
988     host->OnMessageReceived(*response);
989     EXPECT_EQ(1U, process_host_->sink().message_count());
990     process_host_->sink().ClearMessages();
992     [view->cocoa_view() endGestureWithEvent:pinchEndEvent];
993     EXPECT_EQ(1U, process_host_->sink().message_count());
994     process_host_->sink().ClearMessages();
995   }
997   // Do a gesture that doesn't cross the threshold, but happens when we're not
998   // at page scale factor one, so it should be sent to the renderer.
999   {
1000     NSEvent* pinchBeginEvent = MockGestureEvent(NSEventTypeBeginGesture, 0);
1001     NSEvent* pinchUpdateEvent = MockGestureEvent(NSEventTypeMagnify, 0.25);
1002     NSEvent* pinchEndEvent = MockGestureEvent(NSEventTypeEndGesture, 0);
1004     view->page_at_minimum_scale_ = false;
1006     [view->cocoa_view() beginGestureWithEvent:pinchBeginEvent];
1007     EXPECT_EQ(0U, process_host_->sink().message_count());
1009     // Expect that a zoom happen because the time threshold has not passed.
1010     [view->cocoa_view() magnifyWithEvent:pinchUpdateEvent];
1011     EXPECT_FALSE(ZoomDisabledForPinchUpdateMessage());
1012     host->OnMessageReceived(*response);
1013     EXPECT_EQ(2U, process_host_->sink().message_count());
1014     process_host_->sink().ClearMessages();
1016     [view->cocoa_view() endGestureWithEvent:pinchEndEvent];
1017     EXPECT_EQ(1U, process_host_->sink().message_count());
1018     process_host_->sink().ClearMessages();
1019   }
1021   // Do a gesture again, after the page scale is no longer at one, and ensure
1022   // that it is thresholded again.
1023   {
1024     NSEvent* pinchBeginEvent = MockGestureEvent(NSEventTypeBeginGesture, 0);
1025     NSEvent* pinchUpdateEvent = MockGestureEvent(NSEventTypeMagnify, 0.25);
1026     NSEvent* pinchEndEvent = MockGestureEvent(NSEventTypeEndGesture, 0);
1028     view->page_at_minimum_scale_ = true;
1030     [view->cocoa_view() beginGestureWithEvent:pinchBeginEvent];
1031     EXPECT_EQ(0U, process_host_->sink().message_count());
1033     // Get back to zoom one right after the begin event. This should still keep
1034     // the thresholding in place (it is latched at the begin event).
1035     view->page_at_minimum_scale_ = false;
1037     // Expect that zoom be disabled because the time threshold has passed.
1038     [view->cocoa_view() magnifyWithEvent:pinchUpdateEvent];
1039     EXPECT_EQ(2U, process_host_->sink().message_count());
1040     EXPECT_TRUE(ZoomDisabledForPinchUpdateMessage());
1041     host->OnMessageReceived(*response);
1042     process_host_->sink().ClearMessages();
1044     [view->cocoa_view() endGestureWithEvent:pinchEndEvent];
1045     EXPECT_EQ(1U, process_host_->sink().message_count());
1046     process_host_->sink().ClearMessages();
1047   }
1049   // Clean up.
1050   host->Shutdown();
1054 }  // namespace content