DevTools: cut host and port from webSocketDebuggerUrl in addition to ws:// prefix
[chromium-blink-merge.git] / content / browser / renderer_host / render_widget_host_view_mac_unittest.mm
blobceef25ed902db50bcadaef883bc5f694c1150adb
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 {}
122 class MockRenderWidgetHostImpl : public RenderWidgetHostImpl {
123  public:
124   MockRenderWidgetHostImpl(RenderWidgetHostDelegate* delegate,
125                            RenderProcessHost* process,
126                            int routing_id)
127       : RenderWidgetHostImpl(delegate, process, routing_id, false) {
128   }
130   MOCK_METHOD0(Focus, void());
131   MOCK_METHOD0(Blur, void());
134 // Generates the |length| of composition rectangle vector and save them to
135 // |output|. It starts from |origin| and each rectangle contains |unit_size|.
136 void GenerateCompositionRectArray(const gfx::Point& origin,
137                                   const gfx::Size& unit_size,
138                                   size_t length,
139                                   const std::vector<size_t>& break_points,
140                                   std::vector<gfx::Rect>* output) {
141   DCHECK(output);
142   output->clear();
144   std::queue<int> break_point_queue;
145   for (size_t i = 0; i < break_points.size(); ++i)
146     break_point_queue.push(break_points[i]);
147   break_point_queue.push(length);
148   size_t next_break_point = break_point_queue.front();
149   break_point_queue.pop();
151   gfx::Rect current_rect(origin, unit_size);
152   for (size_t i = 0; i < length; ++i) {
153     if (i == next_break_point) {
154       current_rect.set_x(origin.x());
155       current_rect.set_y(current_rect.y() + current_rect.height());
156       next_break_point = break_point_queue.front();
157       break_point_queue.pop();
158     }
159     output->push_back(current_rect);
160     current_rect.set_x(current_rect.right());
161   }
164 gfx::Rect GetExpectedRect(const gfx::Point& origin,
165                           const gfx::Size& size,
166                           const gfx::Range& range,
167                           int line_no) {
168   return gfx::Rect(
169       origin.x() + range.start() * size.width(),
170       origin.y() + line_no * size.height(),
171       range.length() * size.width(),
172       size.height());
175 // Returns NSScrollWheel event that mocks -phase. |mockPhaseSelector| should
176 // correspond to a method in |MockPhaseMethods| that returns the desired phase.
177 NSEvent* MockScrollWheelEventWithPhase(SEL mockPhaseSelector, int32_t delta) {
178   CGEventRef cg_event =
179       CGEventCreateScrollWheelEvent(NULL, kCGScrollEventUnitLine, 1, delta, 0);
180   NSEvent* event = [NSEvent eventWithCGEvent:cg_event];
181   CFRelease(cg_event);
182   method_setImplementation(
183       class_getInstanceMethod([NSEvent class], @selector(phase)),
184       [MockPhaseMethods instanceMethodForSelector:mockPhaseSelector]);
185   return event;
188 }  // namespace
190 class RenderWidgetHostViewMacTest : public RenderViewHostImplTestHarness {
191  public:
192   RenderWidgetHostViewMacTest() : old_rwhv_(NULL), rwhv_mac_(NULL) {}
194   void SetUp() override {
195     RenderViewHostImplTestHarness::SetUp();
196     if (IsDelegatedRendererEnabled()) {
197       ImageTransportFactory::InitializeForUnitTests(
198           scoped_ptr<ImageTransportFactory>(
199               new NoTransportImageTransportFactory));
200     }
202     // TestRenderViewHost's destruction assumes that its view is a
203     // TestRenderWidgetHostView, so store its view and reset it back to the
204     // stored view in |TearDown()|.
205     old_rwhv_ = rvh()->GetView();
207     // Owned by its |cocoa_view()|, i.e. |rwhv_cocoa_|.
208     rwhv_mac_ = new RenderWidgetHostViewMac(rvh(), false);
209     rwhv_cocoa_.reset([rwhv_mac_->cocoa_view() retain]);
210   }
211   void TearDown() override {
212     // Make sure the rwhv_mac_ is gone once the superclass's |TearDown()| runs.
213     rwhv_cocoa_.reset();
214     RecycleAndWait();
216     // See comment in SetUp().
217     test_rvh()->SetView(static_cast<RenderWidgetHostViewBase*>(old_rwhv_));
219     if (IsDelegatedRendererEnabled())
220       ImageTransportFactory::Terminate();
221     RenderViewHostImplTestHarness::TearDown();
222   }
224   void RecycleAndWait() {
225     pool_.Recycle();
226     base::MessageLoop::current()->RunUntilIdle();
227     pool_.Recycle();
228   }
229  protected:
230  private:
231   // This class isn't derived from PlatformTest.
232   base::mac::ScopedNSAutoreleasePool pool_;
234   RenderWidgetHostView* old_rwhv_;
236  protected:
237   RenderWidgetHostViewMac* rwhv_mac_;
238   base::scoped_nsobject<RenderWidgetHostViewCocoa> rwhv_cocoa_;
240  private:
241   DISALLOW_COPY_AND_ASSIGN(RenderWidgetHostViewMacTest);
244 TEST_F(RenderWidgetHostViewMacTest, Basic) {
247 TEST_F(RenderWidgetHostViewMacTest, AcceptsFirstResponder) {
248   // The RWHVCocoa should normally accept first responder status.
249   EXPECT_TRUE([rwhv_cocoa_.get() acceptsFirstResponder]);
252 TEST_F(RenderWidgetHostViewMacTest, Fullscreen) {
253   rwhv_mac_->InitAsFullscreen(NULL);
254   EXPECT_TRUE(rwhv_mac_->pepper_fullscreen_window());
256   // Break the reference cycle caused by pepper_fullscreen_window() without
257   // an <esc> event. See comment in
258   // release_pepper_fullscreen_window_for_testing().
259   rwhv_mac_->release_pepper_fullscreen_window_for_testing();
262 // Verify that escape key down in fullscreen mode suppressed the keyup event on
263 // the parent.
264 TEST_F(RenderWidgetHostViewMacTest, FullscreenCloseOnEscape) {
265   // Use our own RWH since we need to destroy it.
266   MockRenderWidgetHostDelegate delegate;
267   TestBrowserContext browser_context;
268   MockRenderProcessHost* process_host =
269       new MockRenderProcessHost(&browser_context);
270   // Owned by its |cocoa_view()|.
271   RenderWidgetHostImpl* rwh = new RenderWidgetHostImpl(
272       &delegate, process_host, MSG_ROUTING_NONE, false);
273   RenderWidgetHostViewMac* view = new RenderWidgetHostViewMac(rwh, false);
275   view->InitAsFullscreen(rwhv_mac_);
277   WindowedNotificationObserver observer(
278       NOTIFICATION_RENDER_WIDGET_HOST_DESTROYED,
279       Source<RenderWidgetHost>(rwh));
280   EXPECT_FALSE([rwhv_mac_->cocoa_view() suppressNextEscapeKeyUp]);
282   // Escape key down. Should close window and set |suppressNextEscapeKeyUp| on
283   // the parent.
284   [view->cocoa_view() keyEvent:
285       cocoa_test_event_utils::KeyEventWithKeyCode(53, 27, NSKeyDown, 0)];
286   observer.Wait();
287   EXPECT_TRUE([rwhv_mac_->cocoa_view() suppressNextEscapeKeyUp]);
289   // Escape key up on the parent should clear |suppressNextEscapeKeyUp|.
290   [rwhv_mac_->cocoa_view() keyEvent:
291       cocoa_test_event_utils::KeyEventWithKeyCode(53, 27, NSKeyUp, 0)];
292   EXPECT_FALSE([rwhv_mac_->cocoa_view() suppressNextEscapeKeyUp]);
295 // Test that command accelerators which destroy the fullscreen window
296 // don't crash when forwarded via the window's responder machinery.
297 TEST_F(RenderWidgetHostViewMacTest, AcceleratorDestroy) {
298   // Use our own RWH since we need to destroy it.
299   MockRenderWidgetHostDelegate delegate;
300   TestBrowserContext browser_context;
301   MockRenderProcessHost* process_host =
302       new MockRenderProcessHost(&browser_context);
303   // Owned by its |cocoa_view()|.
304   RenderWidgetHostImpl* rwh = new RenderWidgetHostImpl(
305       &delegate, process_host, MSG_ROUTING_NONE, false);
306   RenderWidgetHostViewMac* view = new RenderWidgetHostViewMac(rwh, false);
308   view->InitAsFullscreen(rwhv_mac_);
310   WindowedNotificationObserver observer(
311       NOTIFICATION_RENDER_WIDGET_HOST_DESTROYED,
312       Source<RenderWidgetHost>(rwh));
314   // Command-ESC will destroy the view, while the window is still in
315   // |-performKeyEquivalent:|.  There are other cases where this can
316   // happen, Command-ESC is the easiest to trigger.
317   [[view->cocoa_view() window] performKeyEquivalent:
318       cocoa_test_event_utils::KeyEventWithKeyCode(
319           53, 27, NSKeyDown, NSCommandKeyMask)];
320   observer.Wait();
323 TEST_F(RenderWidgetHostViewMacTest, GetFirstRectForCharacterRangeCaretCase) {
324   const base::string16 kDummyString = base::UTF8ToUTF16("hogehoge");
325   const size_t kDummyOffset = 0;
327   gfx::Rect caret_rect(10, 11, 0, 10);
328   gfx::Range caret_range(0, 0);
329   ViewHostMsg_SelectionBounds_Params params;
331   NSRect rect;
332   NSRange actual_range;
333   rwhv_mac_->SelectionChanged(kDummyString, kDummyOffset, caret_range);
334   params.anchor_rect = params.focus_rect = caret_rect;
335   params.anchor_dir = params.focus_dir = blink::WebTextDirectionLeftToRight;
336   rwhv_mac_->SelectionBoundsChanged(params);
337   EXPECT_TRUE(rwhv_mac_->GetCachedFirstRectForCharacterRange(
338         caret_range.ToNSRange(),
339         &rect,
340         &actual_range));
341   EXPECT_EQ(caret_rect, gfx::Rect(NSRectToCGRect(rect)));
342   EXPECT_EQ(caret_range, gfx::Range(actual_range));
344   EXPECT_FALSE(rwhv_mac_->GetCachedFirstRectForCharacterRange(
345         gfx::Range(0, 1).ToNSRange(),
346         &rect,
347         &actual_range));
348   EXPECT_FALSE(rwhv_mac_->GetCachedFirstRectForCharacterRange(
349         gfx::Range(1, 1).ToNSRange(),
350         &rect,
351         &actual_range));
352   EXPECT_FALSE(rwhv_mac_->GetCachedFirstRectForCharacterRange(
353         gfx::Range(2, 3).ToNSRange(),
354         &rect,
355         &actual_range));
357   // Caret moved.
358   caret_rect = gfx::Rect(20, 11, 0, 10);
359   caret_range = gfx::Range(1, 1);
360   params.anchor_rect = params.focus_rect = caret_rect;
361   rwhv_mac_->SelectionChanged(kDummyString, kDummyOffset, caret_range);
362   rwhv_mac_->SelectionBoundsChanged(params);
363   EXPECT_TRUE(rwhv_mac_->GetCachedFirstRectForCharacterRange(
364         caret_range.ToNSRange(),
365         &rect,
366         &actual_range));
367   EXPECT_EQ(caret_rect, gfx::Rect(NSRectToCGRect(rect)));
368   EXPECT_EQ(caret_range, gfx::Range(actual_range));
370   EXPECT_FALSE(rwhv_mac_->GetCachedFirstRectForCharacterRange(
371         gfx::Range(0, 0).ToNSRange(),
372         &rect,
373         &actual_range));
374   EXPECT_FALSE(rwhv_mac_->GetCachedFirstRectForCharacterRange(
375         gfx::Range(1, 2).ToNSRange(),
376         &rect,
377         &actual_range));
378   EXPECT_FALSE(rwhv_mac_->GetCachedFirstRectForCharacterRange(
379         gfx::Range(2, 3).ToNSRange(),
380         &rect,
381         &actual_range));
383   // No caret.
384   caret_range = gfx::Range(1, 2);
385   rwhv_mac_->SelectionChanged(kDummyString, kDummyOffset, caret_range);
386   params.anchor_rect = caret_rect;
387   params.focus_rect = gfx::Rect(30, 11, 0, 10);
388   rwhv_mac_->SelectionBoundsChanged(params);
389   EXPECT_FALSE(rwhv_mac_->GetCachedFirstRectForCharacterRange(
390         gfx::Range(0, 0).ToNSRange(),
391         &rect,
392         &actual_range));
393   EXPECT_FALSE(rwhv_mac_->GetCachedFirstRectForCharacterRange(
394         gfx::Range(0, 1).ToNSRange(),
395         &rect,
396         &actual_range));
397   EXPECT_FALSE(rwhv_mac_->GetCachedFirstRectForCharacterRange(
398         gfx::Range(1, 1).ToNSRange(),
399         &rect,
400         &actual_range));
401   EXPECT_FALSE(rwhv_mac_->GetCachedFirstRectForCharacterRange(
402         gfx::Range(1, 2).ToNSRange(),
403         &rect,
404         &actual_range));
405   EXPECT_FALSE(rwhv_mac_->GetCachedFirstRectForCharacterRange(
406         gfx::Range(2, 2).ToNSRange(),
407         &rect,
408         &actual_range));
411 TEST_F(RenderWidgetHostViewMacTest, UpdateCompositionSinglelineCase) {
412   const gfx::Point kOrigin(10, 11);
413   const gfx::Size kBoundsUnit(10, 20);
415   NSRect rect;
416   // Make sure not crashing by passing NULL pointer instead of |actual_range|.
417   EXPECT_FALSE(rwhv_mac_->GetCachedFirstRectForCharacterRange(
418       gfx::Range(0, 0).ToNSRange(),
419       &rect,
420       NULL));
422   // If there are no update from renderer, always returned caret position.
423   NSRange actual_range;
424   EXPECT_FALSE(rwhv_mac_->GetCachedFirstRectForCharacterRange(
425       gfx::Range(0, 0).ToNSRange(),
426       &rect,
427       &actual_range));
428   EXPECT_FALSE(rwhv_mac_->GetCachedFirstRectForCharacterRange(
429       gfx::Range(0, 1).ToNSRange(),
430       &rect,
431       &actual_range));
432   EXPECT_FALSE(rwhv_mac_->GetCachedFirstRectForCharacterRange(
433       gfx::Range(1, 0).ToNSRange(),
434       &rect,
435       &actual_range));
436   EXPECT_FALSE(rwhv_mac_->GetCachedFirstRectForCharacterRange(
437       gfx::Range(1, 1).ToNSRange(),
438       &rect,
439       &actual_range));
440   EXPECT_FALSE(rwhv_mac_->GetCachedFirstRectForCharacterRange(
441       gfx::Range(1, 2).ToNSRange(),
442       &rect,
443       &actual_range));
445   // If the firstRectForCharacterRange is failed in renderer, empty rect vector
446   // is sent. Make sure this does not crash.
447   rwhv_mac_->ImeCompositionRangeChanged(gfx::Range(10, 12),
448                                         std::vector<gfx::Rect>());
449   EXPECT_FALSE(rwhv_mac_->GetCachedFirstRectForCharacterRange(
450       gfx::Range(10, 11).ToNSRange(),
451       &rect,
452       NULL));
454   const int kCompositionLength = 10;
455   std::vector<gfx::Rect> composition_bounds;
456   const int kCompositionStart = 3;
457   const gfx::Range kCompositionRange(kCompositionStart,
458                                     kCompositionStart + kCompositionLength);
459   GenerateCompositionRectArray(kOrigin,
460                                kBoundsUnit,
461                                kCompositionLength,
462                                std::vector<size_t>(),
463                                &composition_bounds);
464   rwhv_mac_->ImeCompositionRangeChanged(kCompositionRange, composition_bounds);
466   // Out of range requests will return caret position.
467   EXPECT_FALSE(rwhv_mac_->GetCachedFirstRectForCharacterRange(
468       gfx::Range(0, 0).ToNSRange(),
469       &rect,
470       &actual_range));
471   EXPECT_FALSE(rwhv_mac_->GetCachedFirstRectForCharacterRange(
472       gfx::Range(1, 1).ToNSRange(),
473       &rect,
474       &actual_range));
475   EXPECT_FALSE(rwhv_mac_->GetCachedFirstRectForCharacterRange(
476       gfx::Range(1, 2).ToNSRange(),
477       &rect,
478       &actual_range));
479   EXPECT_FALSE(rwhv_mac_->GetCachedFirstRectForCharacterRange(
480       gfx::Range(2, 2).ToNSRange(),
481       &rect,
482       &actual_range));
483   EXPECT_FALSE(rwhv_mac_->GetCachedFirstRectForCharacterRange(
484       gfx::Range(13, 14).ToNSRange(),
485       &rect,
486       &actual_range));
487   EXPECT_FALSE(rwhv_mac_->GetCachedFirstRectForCharacterRange(
488       gfx::Range(14, 15).ToNSRange(),
489       &rect,
490       &actual_range));
492   for (int i = 0; i <= kCompositionLength; ++i) {
493     for (int j = 0; j <= kCompositionLength - i; ++j) {
494       const gfx::Range range(i, i + j);
495       const gfx::Rect expected_rect = GetExpectedRect(kOrigin,
496                                                       kBoundsUnit,
497                                                       range,
498                                                       0);
499       const NSRange request_range = gfx::Range(
500           kCompositionStart + range.start(),
501           kCompositionStart + range.end()).ToNSRange();
502       EXPECT_TRUE(rwhv_mac_->GetCachedFirstRectForCharacterRange(
503             request_range,
504             &rect,
505             &actual_range));
506       EXPECT_EQ(gfx::Range(request_range), gfx::Range(actual_range));
507       EXPECT_EQ(expected_rect, gfx::Rect(NSRectToCGRect(rect)));
509       // Make sure not crashing by passing NULL pointer instead of
510       // |actual_range|.
511       EXPECT_TRUE(rwhv_mac_->GetCachedFirstRectForCharacterRange(
512             request_range,
513             &rect,
514             NULL));
515     }
516   }
519 TEST_F(RenderWidgetHostViewMacTest, UpdateCompositionMultilineCase) {
520   const gfx::Point kOrigin(10, 11);
521   const gfx::Size kBoundsUnit(10, 20);
522   NSRect rect;
524   const int kCompositionLength = 30;
525   std::vector<gfx::Rect> composition_bounds;
526   const gfx::Range kCompositionRange(0, kCompositionLength);
527   // Set breaking point at 10 and 20.
528   std::vector<size_t> break_points;
529   break_points.push_back(10);
530   break_points.push_back(20);
531   GenerateCompositionRectArray(kOrigin,
532                                kBoundsUnit,
533                                kCompositionLength,
534                                break_points,
535                                &composition_bounds);
536   rwhv_mac_->ImeCompositionRangeChanged(kCompositionRange, composition_bounds);
538   // Range doesn't contain line breaking point.
539   gfx::Range range;
540   range = gfx::Range(5, 8);
541   NSRange actual_range;
542   EXPECT_TRUE(rwhv_mac_->GetCachedFirstRectForCharacterRange(range.ToNSRange(),
543                                                              &rect,
544                                                              &actual_range));
545   EXPECT_EQ(range, gfx::Range(actual_range));
546   EXPECT_EQ(
547       GetExpectedRect(kOrigin, kBoundsUnit, range, 0),
548       gfx::Rect(NSRectToCGRect(rect)));
549   range = gfx::Range(15, 18);
550   EXPECT_TRUE(rwhv_mac_->GetCachedFirstRectForCharacterRange(range.ToNSRange(),
551                                                              &rect,
552                                                              &actual_range));
553   EXPECT_EQ(range, gfx::Range(actual_range));
554   EXPECT_EQ(
555       GetExpectedRect(kOrigin, kBoundsUnit, gfx::Range(5, 8), 1),
556       gfx::Rect(NSRectToCGRect(rect)));
557   range = gfx::Range(25, 28);
558   EXPECT_TRUE(rwhv_mac_->GetCachedFirstRectForCharacterRange(range.ToNSRange(),
559                                                              &rect,
560                                                              &actual_range));
561   EXPECT_EQ(range, gfx::Range(actual_range));
562   EXPECT_EQ(
563       GetExpectedRect(kOrigin, kBoundsUnit, gfx::Range(5, 8), 2),
564       gfx::Rect(NSRectToCGRect(rect)));
566   // Range contains line breaking point.
567   range = gfx::Range(8, 12);
568   EXPECT_TRUE(rwhv_mac_->GetCachedFirstRectForCharacterRange(range.ToNSRange(),
569                                                              &rect,
570                                                              &actual_range));
571   EXPECT_EQ(gfx::Range(8, 10), gfx::Range(actual_range));
572   EXPECT_EQ(
573       GetExpectedRect(kOrigin, kBoundsUnit, gfx::Range(8, 10), 0),
574       gfx::Rect(NSRectToCGRect(rect)));
575   range = gfx::Range(18, 22);
576   EXPECT_TRUE(rwhv_mac_->GetCachedFirstRectForCharacterRange(range.ToNSRange(),
577                                                              &rect,
578                                                              &actual_range));
579   EXPECT_EQ(gfx::Range(18, 20), gfx::Range(actual_range));
580   EXPECT_EQ(
581       GetExpectedRect(kOrigin, kBoundsUnit, gfx::Range(8, 10), 1),
582       gfx::Rect(NSRectToCGRect(rect)));
584   // Start point is line breaking point.
585   range = gfx::Range(10, 12);
586   EXPECT_TRUE(rwhv_mac_->GetCachedFirstRectForCharacterRange(range.ToNSRange(),
587                                                              &rect,
588                                                              &actual_range));
589   EXPECT_EQ(gfx::Range(10, 12), gfx::Range(actual_range));
590   EXPECT_EQ(
591       GetExpectedRect(kOrigin, kBoundsUnit, gfx::Range(0, 2), 1),
592       gfx::Rect(NSRectToCGRect(rect)));
593   range = gfx::Range(20, 22);
594   EXPECT_TRUE(rwhv_mac_->GetCachedFirstRectForCharacterRange(range.ToNSRange(),
595                                                              &rect,
596                                                              &actual_range));
597   EXPECT_EQ(gfx::Range(20, 22), gfx::Range(actual_range));
598   EXPECT_EQ(
599       GetExpectedRect(kOrigin, kBoundsUnit, gfx::Range(0, 2), 2),
600       gfx::Rect(NSRectToCGRect(rect)));
602   // End point is line breaking point.
603   range = gfx::Range(5, 10);
604   EXPECT_TRUE(rwhv_mac_->GetCachedFirstRectForCharacterRange(range.ToNSRange(),
605                                                              &rect,
606                                                              &actual_range));
607   EXPECT_EQ(gfx::Range(5, 10), gfx::Range(actual_range));
608   EXPECT_EQ(
609       GetExpectedRect(kOrigin, kBoundsUnit, gfx::Range(5, 10), 0),
610       gfx::Rect(NSRectToCGRect(rect)));
611   range = gfx::Range(15, 20);
612   EXPECT_TRUE(rwhv_mac_->GetCachedFirstRectForCharacterRange(range.ToNSRange(),
613                                                              &rect,
614                                                              &actual_range));
615   EXPECT_EQ(gfx::Range(15, 20), gfx::Range(actual_range));
616   EXPECT_EQ(
617       GetExpectedRect(kOrigin, kBoundsUnit, gfx::Range(5, 10), 1),
618       gfx::Rect(NSRectToCGRect(rect)));
620   // Start and end point are same line breaking point.
621   range = gfx::Range(10, 10);
622   EXPECT_TRUE(rwhv_mac_->GetCachedFirstRectForCharacterRange(range.ToNSRange(),
623                                                              &rect,
624                                                              &actual_range));
625   EXPECT_EQ(gfx::Range(10, 10), gfx::Range(actual_range));
626   EXPECT_EQ(
627       GetExpectedRect(kOrigin, kBoundsUnit, gfx::Range(0, 0), 1),
628       gfx::Rect(NSRectToCGRect(rect)));
629   range = gfx::Range(20, 20);
630   EXPECT_TRUE(rwhv_mac_->GetCachedFirstRectForCharacterRange(range.ToNSRange(),
631                                                              &rect,
632                                                              &actual_range));
633   EXPECT_EQ(gfx::Range(20, 20), gfx::Range(actual_range));
634   EXPECT_EQ(
635       GetExpectedRect(kOrigin, kBoundsUnit, gfx::Range(0, 0), 2),
636       gfx::Rect(NSRectToCGRect(rect)));
638   // Start and end point are different line breaking point.
639   range = gfx::Range(10, 20);
640   EXPECT_TRUE(rwhv_mac_->GetCachedFirstRectForCharacterRange(range.ToNSRange(),
641                                                              &rect,
642                                                              &actual_range));
643   EXPECT_EQ(gfx::Range(10, 20), gfx::Range(actual_range));
644   EXPECT_EQ(
645       GetExpectedRect(kOrigin, kBoundsUnit, gfx::Range(0, 10), 1),
646       gfx::Rect(NSRectToCGRect(rect)));
649 // Verify that |SetActive()| calls |RenderWidgetHostImpl::Blur()| and
650 // |RenderWidgetHostImp::Focus()|.
651 TEST_F(RenderWidgetHostViewMacTest, BlurAndFocusOnSetActive) {
652   MockRenderWidgetHostDelegate delegate;
653   TestBrowserContext browser_context;
654   MockRenderProcessHost* process_host =
655       new MockRenderProcessHost(&browser_context);
657   // Owned by its |cocoa_view()|.
658   MockRenderWidgetHostImpl* rwh = new MockRenderWidgetHostImpl(
659       &delegate, process_host, MSG_ROUTING_NONE);
660   RenderWidgetHostViewMac* view = new RenderWidgetHostViewMac(rwh, false);
662   base::scoped_nsobject<CocoaTestHelperWindow> window(
663       [[CocoaTestHelperWindow alloc] init]);
664   [[window contentView] addSubview:view->cocoa_view()];
666   EXPECT_CALL(*rwh, Focus());
667   [window makeFirstResponder:view->cocoa_view()];
668   testing::Mock::VerifyAndClearExpectations(rwh);
670   EXPECT_CALL(*rwh, Blur());
671   view->SetActive(false);
672   testing::Mock::VerifyAndClearExpectations(rwh);
674   EXPECT_CALL(*rwh, Focus());
675   view->SetActive(true);
676   testing::Mock::VerifyAndClearExpectations(rwh);
678   // Unsetting first responder should blur.
679   EXPECT_CALL(*rwh, Blur());
680   [window makeFirstResponder:nil];
681   testing::Mock::VerifyAndClearExpectations(rwh);
683   // |SetActive()| shoud not focus if view is not first responder.
684   EXPECT_CALL(*rwh, Focus()).Times(0);
685   view->SetActive(true);
686   testing::Mock::VerifyAndClearExpectations(rwh);
688   // Clean up.
689   rwh->Shutdown();
692 TEST_F(RenderWidgetHostViewMacTest, ScrollWheelEndEventDelivery) {
693   // This tests Lion+ functionality, so don't run the test pre-Lion.
694   if (!base::mac::IsOSLionOrLater())
695     return;
697   // Initialize the view associated with a MockRenderWidgetHostImpl, rather than
698   // the MockRenderProcessHost that is set up by the test harness which mocks
699   // out |OnMessageReceived()|.
700   TestBrowserContext browser_context;
701   MockRenderProcessHost* process_host =
702       new MockRenderProcessHost(&browser_context);
703   process_host->Init();
704   MockRenderWidgetHostDelegate delegate;
705   MockRenderWidgetHostImpl* host = new MockRenderWidgetHostImpl(
706       &delegate, process_host, MSG_ROUTING_NONE);
707   RenderWidgetHostViewMac* view = new RenderWidgetHostViewMac(host, false);
709   // Send an initial wheel event with NSEventPhaseBegan to the view.
710   NSEvent* event1 = MockScrollWheelEventWithPhase(@selector(phaseBegan), 0);
711   [view->cocoa_view() scrollWheel:event1];
712   ASSERT_EQ(1U, process_host->sink().message_count());
714   // Send an ACK for the first wheel event, so that the queue will be flushed.
715   InputEventAck ack(blink::WebInputEvent::MouseWheel,
716                     INPUT_EVENT_ACK_STATE_CONSUMED);
717   scoped_ptr<IPC::Message> response(
718       new InputHostMsg_HandleInputEvent_ACK(0, ack));
719   host->OnMessageReceived(*response);
721   // Post the NSEventPhaseEnded wheel event to NSApp and check whether the
722   // render view receives it.
723   NSEvent* event2 = MockScrollWheelEventWithPhase(@selector(phaseEnded), 0);
724   [NSApp postEvent:event2 atStart:NO];
725   base::MessageLoop::current()->RunUntilIdle();
726   ASSERT_EQ(2U, process_host->sink().message_count());
728   // Clean up.
729   host->Shutdown();
732 TEST_F(RenderWidgetHostViewMacTest, IgnoreEmptyUnhandledWheelEvent) {
733   // This tests Lion+ functionality, so don't run the test pre-Lion.
734   if (!base::mac::IsOSLionOrLater())
735     return;
737   // Initialize the view associated with a MockRenderWidgetHostImpl, rather than
738   // the MockRenderProcessHost that is set up by the test harness which mocks
739   // out |OnMessageReceived()|.
740   TestBrowserContext browser_context;
741   MockRenderProcessHost* process_host =
742       new MockRenderProcessHost(&browser_context);
743   process_host->Init();
744   MockRenderWidgetHostDelegate delegate;
745   MockRenderWidgetHostImpl* host = new MockRenderWidgetHostImpl(
746       &delegate, process_host, MSG_ROUTING_NONE);
747   RenderWidgetHostViewMac* view = new RenderWidgetHostViewMac(host, false);
749   // Add a delegate to the view.
750   base::scoped_nsobject<MockRenderWidgetHostViewMacDelegate> view_delegate(
751       [[MockRenderWidgetHostViewMacDelegate alloc] init]);
752   view->SetDelegate(view_delegate.get());
754   // Send an initial wheel event for scrolling by 3 lines.
755   NSEvent* event1 = MockScrollWheelEventWithPhase(@selector(phaseBegan), 3);
756   [view->cocoa_view() scrollWheel:event1];
757   ASSERT_EQ(1U, process_host->sink().message_count());
758   process_host->sink().ClearMessages();
760   // Indicate that the wheel event was unhandled.
761   InputEventAck unhandled_ack(blink::WebInputEvent::MouseWheel,
762                               INPUT_EVENT_ACK_STATE_NOT_CONSUMED);
763   scoped_ptr<IPC::Message> response1(
764       new InputHostMsg_HandleInputEvent_ACK(0, unhandled_ack));
765   host->OnMessageReceived(*response1);
767   // Check that the view delegate got an unhandled wheel event.
768   ASSERT_EQ(YES, view_delegate.get().unhandledWheelEventReceived);
769   view_delegate.get().unhandledWheelEventReceived = NO;
771   // Send another wheel event, this time for scrolling by 0 lines (empty event).
772   NSEvent* event2 = MockScrollWheelEventWithPhase(@selector(phaseChanged), 0);
773   [view->cocoa_view() scrollWheel:event2];
774   ASSERT_EQ(1U, process_host->sink().message_count());
776   // Indicate that the wheel event was also unhandled.
777   scoped_ptr<IPC::Message> response2(
778       new InputHostMsg_HandleInputEvent_ACK(0, unhandled_ack));
779   host->OnMessageReceived(*response2);
781   // Check that the view delegate ignored the empty unhandled wheel event.
782   ASSERT_EQ(NO, view_delegate.get().unhandledWheelEventReceived);
784   // Clean up.
785   host->Shutdown();
788 // Tests that when view initiated shutdown happens (i.e. RWHView is deleted
789 // before RWH), we clean up properly and don't leak the RWHVGuest.
790 TEST_F(RenderWidgetHostViewMacTest, GuestViewDoesNotLeak) {
791   MockRenderWidgetHostDelegate delegate;
792   TestBrowserContext browser_context;
793   MockRenderProcessHost* process_host =
794       new MockRenderProcessHost(&browser_context);
796   // Owned by its |cocoa_view()|.
797   MockRenderWidgetHostImpl* rwh = new MockRenderWidgetHostImpl(
798       &delegate, process_host, MSG_ROUTING_NONE);
799   RenderWidgetHostViewMac* view = new RenderWidgetHostViewMac(rwh, true);
801   // Add a delegate to the view.
802   base::scoped_nsobject<MockRenderWidgetHostViewMacDelegate> view_delegate(
803       [[MockRenderWidgetHostViewMacDelegate alloc] init]);
804   view->SetDelegate(view_delegate.get());
806   base::WeakPtr<RenderWidgetHostViewBase> guest_rwhv_weak =
807       (new RenderWidgetHostViewGuest(
808            rwh, NULL, view->GetWeakPtr()))->GetWeakPtr();
810   // Remove the cocoa_view() so |view| also goes away before |rwh|.
811   {
812     base::scoped_nsobject<RenderWidgetHostViewCocoa> rwhv_cocoa;
813     rwhv_cocoa.reset([view->cocoa_view() retain]);
814   }
815   RecycleAndWait();
817   // Clean up.
818   rwh->Shutdown();
820   // Let |guest_rwhv_weak| have a chance to delete itself.
821   base::RunLoop run_loop;
822   content::BrowserThread::PostTask(
823       content::BrowserThread::UI, FROM_HERE, run_loop.QuitClosure());
824   run_loop.Run();
826   ASSERT_FALSE(guest_rwhv_weak.get());
829 // Tests setting background transparency. See also (disabled on Mac)
830 // RenderWidgetHostTest.Background. This test has some additional checks for
831 // Mac.
832 TEST_F(RenderWidgetHostViewMacTest, Background) {
833   TestBrowserContext browser_context;
834   MockRenderProcessHost* process_host =
835       new MockRenderProcessHost(&browser_context);
836   MockRenderWidgetHostDelegate delegate;
837   MockRenderWidgetHostImpl* host = new MockRenderWidgetHostImpl(
838       &delegate, process_host, MSG_ROUTING_NONE);
839   RenderWidgetHostViewMac* view = new RenderWidgetHostViewMac(host, false);
841   EXPECT_TRUE(view->GetBackgroundOpaque());
842   EXPECT_TRUE([view->cocoa_view() isOpaque]);
844   view->SetBackgroundColor(SK_ColorTRANSPARENT);
845   EXPECT_FALSE(view->GetBackgroundOpaque());
846   EXPECT_FALSE([view->cocoa_view() isOpaque]);
848   const IPC::Message* set_background;
849   set_background = process_host->sink().GetUniqueMessageMatching(
850       ViewMsg_SetBackgroundOpaque::ID);
851   ASSERT_TRUE(set_background);
852   base::Tuple<bool> sent_background;
853   ViewMsg_SetBackgroundOpaque::Read(set_background, &sent_background);
854   EXPECT_FALSE(base::get<0>(sent_background));
856   // Try setting it back.
857   process_host->sink().ClearMessages();
858   view->SetBackgroundColor(SK_ColorWHITE);
859   EXPECT_TRUE(view->GetBackgroundOpaque());
860   EXPECT_TRUE([view->cocoa_view() isOpaque]);
861   set_background = process_host->sink().GetUniqueMessageMatching(
862       ViewMsg_SetBackgroundOpaque::ID);
863   ASSERT_TRUE(set_background);
864   ViewMsg_SetBackgroundOpaque::Read(set_background, &sent_background);
865   EXPECT_TRUE(base::get<0>(sent_background));
867   host->Shutdown();
870 class RenderWidgetHostViewMacPinchTest : public RenderWidgetHostViewMacTest {
871  public:
872   RenderWidgetHostViewMacPinchTest() : process_host_(NULL) {}
874   bool ZoomDisabledForPinchUpdateMessage() {
875     const IPC::Message* message = NULL;
876     // The first message may be a PinchBegin. Go for the second message if
877     // there are two.
878     switch (process_host_->sink().message_count()) {
879       case 1:
880         message = process_host_->sink().GetMessageAt(0);
881         break;
882       case 2:
883         message = process_host_->sink().GetMessageAt(1);
884         break;
885       default:
886         NOTREACHED();
887         break;
888     }
889     DCHECK(message);
890     base::Tuple<IPC::WebInputEventPointer, ui::LatencyInfo, bool> data;
891     InputMsg_HandleInputEvent::Read(message, &data);
892     IPC::WebInputEventPointer ipc_event = base::get<0>(data);
893     const blink::WebGestureEvent* gesture_event =
894         static_cast<const blink::WebGestureEvent*>(ipc_event);
895     return gesture_event->data.pinchUpdate.zoomDisabled;
896   }
898   MockRenderProcessHost* process_host_;
901 TEST_F(RenderWidgetHostViewMacPinchTest, PinchThresholding) {
902   // This tests Lion+ functionality, so don't run the test pre-Lion.
903   if (!base::mac::IsOSLionOrLater())
904     return;
906   // Initialize the view associated with a MockRenderWidgetHostImpl, rather than
907   // the MockRenderProcessHost that is set up by the test harness which mocks
908   // out |OnMessageReceived()|.
909   TestBrowserContext browser_context;
910   process_host_ = new MockRenderProcessHost(&browser_context);
911   process_host_->Init();
912   MockRenderWidgetHostDelegate delegate;
913   MockRenderWidgetHostImpl* host = new MockRenderWidgetHostImpl(
914       &delegate, process_host_, MSG_ROUTING_NONE);
915   RenderWidgetHostViewMac* view = new RenderWidgetHostViewMac(host, false);
917   // We'll use this IPC message to ack events.
918   InputEventAck ack(blink::WebInputEvent::GesturePinchUpdate,
919                     INPUT_EVENT_ACK_STATE_CONSUMED);
920   scoped_ptr<IPC::Message> response(
921       new InputHostMsg_HandleInputEvent_ACK(0, ack));
923   // Do a gesture that crosses the threshold.
924   {
925     NSEvent* pinchBeginEvent =
926         MockGestureEvent(NSEventTypeBeginGesture, 0);
927     NSEvent* pinchUpdateEvents[3] = {
928         MockGestureEvent(NSEventTypeMagnify, 0.25),
929         MockGestureEvent(NSEventTypeMagnify, 0.25),
930         MockGestureEvent(NSEventTypeMagnify, 0.25),
931     };
932     NSEvent* pinchEndEvent =
933         MockGestureEvent(NSEventTypeEndGesture, 0);
935     [view->cocoa_view() beginGestureWithEvent:pinchBeginEvent];
936     EXPECT_EQ(0U, process_host_->sink().message_count());
938     // No zoom is sent for the first update event.
939     [view->cocoa_view() magnifyWithEvent:pinchUpdateEvents[0]];
940     host->OnMessageReceived(*response);
941     EXPECT_EQ(2U, process_host_->sink().message_count());
942     EXPECT_TRUE(ZoomDisabledForPinchUpdateMessage());
943     process_host_->sink().ClearMessages();
945     // The second update event crosses the threshold of 0.4, and so zoom is no
946     // longer disabled.
947     [view->cocoa_view() magnifyWithEvent:pinchUpdateEvents[1]];
948     EXPECT_FALSE(ZoomDisabledForPinchUpdateMessage());
949     host->OnMessageReceived(*response);
950     EXPECT_EQ(1U, process_host_->sink().message_count());
951     process_host_->sink().ClearMessages();
953     // The third update still has zoom enabled.
954     [view->cocoa_view() magnifyWithEvent:pinchUpdateEvents[2]];
955     EXPECT_FALSE(ZoomDisabledForPinchUpdateMessage());
956     host->OnMessageReceived(*response);
957     EXPECT_EQ(1U, process_host_->sink().message_count());
958     process_host_->sink().ClearMessages();
960     [view->cocoa_view() endGestureWithEvent:pinchEndEvent];
961     EXPECT_EQ(1U, process_host_->sink().message_count());
962     process_host_->sink().ClearMessages();
963   }
965   // Do a gesture that doesn't cross the threshold, but happens when we're not
966   // at page scale factor one, so it should be sent to the renderer.
967   {
968     NSEvent* pinchBeginEvent = MockGestureEvent(NSEventTypeBeginGesture, 0);
969     NSEvent* pinchUpdateEvent = MockGestureEvent(NSEventTypeMagnify, 0.25);
970     NSEvent* pinchEndEvent = MockGestureEvent(NSEventTypeEndGesture, 0);
972     view->page_at_minimum_scale_ = false;
974     [view->cocoa_view() beginGestureWithEvent:pinchBeginEvent];
975     EXPECT_EQ(0U, process_host_->sink().message_count());
977     // Expect that a zoom happen because the time threshold has not passed.
978     [view->cocoa_view() magnifyWithEvent:pinchUpdateEvent];
979     EXPECT_FALSE(ZoomDisabledForPinchUpdateMessage());
980     host->OnMessageReceived(*response);
981     EXPECT_EQ(2U, process_host_->sink().message_count());
982     process_host_->sink().ClearMessages();
984     [view->cocoa_view() endGestureWithEvent:pinchEndEvent];
985     EXPECT_EQ(1U, process_host_->sink().message_count());
986     process_host_->sink().ClearMessages();
987   }
989   // Do a gesture again, after the page scale is no longer at one, and ensure
990   // that it is thresholded again.
991   {
992     NSEvent* pinchBeginEvent = MockGestureEvent(NSEventTypeBeginGesture, 0);
993     NSEvent* pinchUpdateEvent = MockGestureEvent(NSEventTypeMagnify, 0.25);
994     NSEvent* pinchEndEvent = MockGestureEvent(NSEventTypeEndGesture, 0);
996     view->page_at_minimum_scale_ = true;
998     [view->cocoa_view() beginGestureWithEvent:pinchBeginEvent];
999     EXPECT_EQ(0U, process_host_->sink().message_count());
1001     // Get back to zoom one right after the begin event. This should still keep
1002     // the thresholding in place (it is latched at the begin event).
1003     view->page_at_minimum_scale_ = false;
1005     // Expect that zoom be disabled because the time threshold has passed.
1006     [view->cocoa_view() magnifyWithEvent:pinchUpdateEvent];
1007     EXPECT_EQ(2U, process_host_->sink().message_count());
1008     EXPECT_TRUE(ZoomDisabledForPinchUpdateMessage());
1009     host->OnMessageReceived(*response);
1010     process_host_->sink().ClearMessages();
1012     [view->cocoa_view() endGestureWithEvent:pinchEndEvent];
1013     EXPECT_EQ(1U, process_host_->sink().message_count());
1014     process_host_->sink().ClearMessages();
1015   }
1017   // Clean up.
1018   host->Shutdown();
1022 }  // namespace content