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