Drive: Add BatchableRequest subclass.
[chromium-blink-merge.git] / ui / views / cocoa / bridged_native_widget_unittest.mm
blob3cf3dc2ec9e755f440312d4299d86314fe842bcf
1 // Copyright 2014 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 #import "ui/views/cocoa/bridged_native_widget.h"
7 #import <Cocoa/Cocoa.h>
9 #import "base/mac/foundation_util.h"
10 #import "base/mac/mac_util.h"
11 #import "base/mac/sdk_forward_declarations.h"
12 #include "base/memory/scoped_ptr.h"
13 #include "base/message_loop/message_loop.h"
14 #include "base/strings/sys_string_conversions.h"
15 #include "base/strings/utf_string_conversions.h"
16 #import "testing/gtest_mac.h"
17 #import "ui/gfx/test/ui_cocoa_test_helper.h"
18 #import "ui/views/cocoa/bridged_content_view.h"
19 #import "ui/views/cocoa/native_widget_mac_nswindow.h"
20 #include "ui/views/controls/textfield/textfield.h"
21 #include "ui/views/ime/input_method.h"
22 #include "ui/views/view.h"
23 #include "ui/views/widget/native_widget_mac.h"
24 #include "ui/views/widget/root_view.h"
25 #include "ui/views/widget/widget.h"
26 #include "ui/views/widget/widget_observer.h"
28 using base::ASCIIToUTF16;
29 using base::SysNSStringToUTF8;
30 using base::SysNSStringToUTF16;
31 using base::SysUTF8ToNSString;
33 #define EXPECT_EQ_RANGE(a, b)        \
34   EXPECT_EQ(a.location, b.location); \
35   EXPECT_EQ(a.length, b.length);
37 namespace {
39 // Empty range shortcut for readibility.
40 NSRange EmptyRange() {
41   return NSMakeRange(NSNotFound, 0);
44 }  // namespace
46 // Class to override -[NSWindow toggleFullScreen:] to a no-op. This simulates
47 // NSWindow's behavior when attempting to toggle fullscreen state again, when
48 // the last attempt failed but Cocoa has not yet sent
49 // windowDidFailToEnterFullScreen:.
50 @interface BridgedNativeWidgetTestFullScreenWindow : NativeWidgetMacNSWindow {
51  @private
52   int ignoredToggleFullScreenCount_;
54 @property(readonly, nonatomic) int ignoredToggleFullScreenCount;
55 @end
57 @implementation BridgedNativeWidgetTestFullScreenWindow
59 @synthesize ignoredToggleFullScreenCount = ignoredToggleFullScreenCount_;
61 - (void)toggleFullScreen:(id)sender {
62   ++ignoredToggleFullScreenCount_;
65 @end
67 namespace views {
68 namespace test {
70 // Provides the |parent| argument to construct a BridgedNativeWidget.
71 class MockNativeWidgetMac : public NativeWidgetMac {
72  public:
73   MockNativeWidgetMac(Widget* delegate) : NativeWidgetMac(delegate) {}
75   // Expose a reference, so that it can be reset() independently.
76   scoped_ptr<BridgedNativeWidget>& bridge() {
77     return bridge_;
78   }
80   // internal::NativeWidgetPrivate:
81   void InitNativeWidget(const Widget::InitParams& params) override {
82     ownership_ = params.ownership;
84     // Usually the bridge gets initialized here. It is skipped to run extra
85     // checks in tests, and so that a second window isn't created.
86     delegate()->OnNativeWidgetCreated(true);
88     // To allow events to dispatch to a view, it needs a way to get focus.
89     bridge_->SetFocusManager(GetWidget()->GetFocusManager());
90   }
92   void ReorderNativeViews() override {
93     // Called via Widget::Init to set the content view. No-op in these tests.
94   }
96  private:
97   DISALLOW_COPY_AND_ASSIGN(MockNativeWidgetMac);
100 // Helper test base to construct a BridgedNativeWidget with a valid parent.
101 class BridgedNativeWidgetTestBase : public ui::CocoaTest {
102  public:
103   BridgedNativeWidgetTestBase()
104       : widget_(new Widget),
105         native_widget_mac_(new MockNativeWidgetMac(widget_.get())) {
106   }
108   scoped_ptr<BridgedNativeWidget>& bridge() {
109     return native_widget_mac_->bridge();
110   }
112   // Overridden from testing::Test:
113   void SetUp() override {
114     ui::CocoaTest::SetUp();
116     init_params_.native_widget = native_widget_mac_;
118     // Use a frameless window, otherwise Widget will try to center the window
119     // before the tests covering the Init() flow are ready to do that.
120     init_params_.type = Widget::InitParams::TYPE_WINDOW_FRAMELESS;
122     // To control the lifetime without an actual window that must be closed,
123     // tests in this file need to use WIDGET_OWNS_NATIVE_WIDGET.
124     init_params_.ownership = Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET;
126     // Opacity defaults to "infer" which is usually updated by ViewsDelegate.
127     init_params_.opacity = Widget::InitParams::OPAQUE_WINDOW;
129     native_widget_mac_->GetWidget()->Init(init_params_);
130   }
132  protected:
133   scoped_ptr<Widget> widget_;
134   MockNativeWidgetMac* native_widget_mac_;  // Weak. Owned by |widget_|.
136   // Make the InitParams available to tests to cover initialization codepaths.
137   Widget::InitParams init_params_;
140 class BridgedNativeWidgetTest : public BridgedNativeWidgetTestBase {
141  public:
142   BridgedNativeWidgetTest();
143   ~BridgedNativeWidgetTest() override;
145   // Install a textfield in the view hierarchy and make it the text input
146   // client.
147   void InstallTextField(const std::string& text);
149   // Returns the current text as std::string.
150   std::string GetText();
152   // testing::Test:
153   void SetUp() override;
154   void TearDown() override;
156  protected:
157   scoped_ptr<views::View> view_;
158   scoped_ptr<BridgedNativeWidget> bridge_;
159   BridgedContentView* ns_view_;  // Weak. Owned by bridge_.
160   base::MessageLoopForUI message_loop_;
162  private:
163   DISALLOW_COPY_AND_ASSIGN(BridgedNativeWidgetTest);
166 BridgedNativeWidgetTest::BridgedNativeWidgetTest() {
169 BridgedNativeWidgetTest::~BridgedNativeWidgetTest() {
172 void BridgedNativeWidgetTest::InstallTextField(const std::string& text) {
173   Textfield* textfield = new Textfield();
174   textfield->SetText(ASCIIToUTF16(text));
175   view_->AddChildView(textfield);
177   // Request focus so the InputMethod can dispatch events to the RootView, and
178   // have them delivered to the textfield. Note that focusing a textfield
179   // schedules a task to flash the cursor, so this requires |message_loop_|.
180   textfield->RequestFocus();
182   [ns_view_ setTextInputClient:textfield];
185 std::string BridgedNativeWidgetTest::GetText() {
186   NSRange range = NSMakeRange(0, NSUIntegerMax);
187   NSAttributedString* text =
188       [ns_view_ attributedSubstringForProposedRange:range actualRange:NULL];
189   return SysNSStringToUTF8([text string]);
192 void BridgedNativeWidgetTest::SetUp() {
193   BridgedNativeWidgetTestBase::SetUp();
195   view_.reset(new views::internal::RootView(widget_.get()));
196   base::scoped_nsobject<NSWindow> window([test_window() retain]);
198   // BridgedNativeWidget expects to be initialized with a hidden (deferred)
199   // window.
200   [window orderOut:nil];
201   EXPECT_FALSE([window delegate]);
202   bridge()->Init(window, init_params_);
204   // The delegate should exist before setting the root view.
205   EXPECT_TRUE([window delegate]);
206   bridge()->SetRootView(view_.get());
207   ns_view_ = bridge()->ns_view();
209   // Pretend it has been shown via NativeWidgetMac::Show().
210   [window orderFront:nil];
211   [test_window() makePretendKeyWindowAndSetFirstResponder:bridge()->ns_view()];
214 void BridgedNativeWidgetTest::TearDown() {
215   view_.reset();
216   BridgedNativeWidgetTestBase::TearDown();
219 // The TEST_VIEW macro expects the view it's testing to have a superview. In
220 // these tests, the NSView bridge is a contentView, at the root. These mimic
221 // what TEST_VIEW usually does.
222 TEST_F(BridgedNativeWidgetTest, BridgedNativeWidgetTest_TestViewAddRemove) {
223   base::scoped_nsobject<BridgedContentView> view([bridge()->ns_view() retain]);
224   EXPECT_NSEQ([test_window() contentView], view);
225   EXPECT_NSEQ(test_window(), [view window]);
227   // The superview of a contentView is an NSNextStepFrame.
228   EXPECT_TRUE([view superview]);
229   EXPECT_TRUE([view hostedView]);
231   // Ensure the tracking area to propagate mouseMoved: events to the RootView is
232   // installed.
233   EXPECT_EQ(1u, [[view trackingAreas] count]);
235   // Destroying the C++ bridge should remove references to any C++ objects in
236   // the ObjectiveC object, and remove it from the hierarchy.
237   bridge().reset();
238   EXPECT_FALSE([view hostedView]);
239   EXPECT_FALSE([view superview]);
240   EXPECT_FALSE([view window]);
241   EXPECT_EQ(0u, [[view trackingAreas] count]);
242   EXPECT_FALSE([test_window() contentView]);
243   EXPECT_FALSE([test_window() delegate]);
246 TEST_F(BridgedNativeWidgetTest, BridgedNativeWidgetTest_TestViewDisplay) {
247   [bridge()->ns_view() display];
250 // Test that resizing the window resizes the root view appropriately.
251 TEST_F(BridgedNativeWidgetTest, ViewSizeTracksWindow) {
252   const int kTestNewWidth = 400;
253   const int kTestNewHeight = 300;
255   // |test_window()| is borderless, so these should align.
256   NSSize window_size = [test_window() frame].size;
257   EXPECT_EQ(view_->width(), static_cast<int>(window_size.width));
258   EXPECT_EQ(view_->height(), static_cast<int>(window_size.height));
260   // Make sure a resize actually occurs.
261   EXPECT_NE(kTestNewWidth, view_->width());
262   EXPECT_NE(kTestNewHeight, view_->height());
264   [test_window() setFrame:NSMakeRect(0, 0, kTestNewWidth, kTestNewHeight)
265                   display:NO];
266   EXPECT_EQ(kTestNewWidth, view_->width());
267   EXPECT_EQ(kTestNewHeight, view_->height());
270 TEST_F(BridgedNativeWidgetTest, CreateInputMethodShouldNotReturnNull) {
271   scoped_ptr<views::InputMethod> input_method(bridge()->CreateInputMethod());
272   EXPECT_TRUE(input_method);
275 TEST_F(BridgedNativeWidgetTest, GetHostInputMethodShouldNotReturnNull) {
276   EXPECT_TRUE(bridge()->GetHostInputMethod());
279 // A simpler test harness for testing initialization flows.
280 typedef BridgedNativeWidgetTestBase BridgedNativeWidgetInitTest;
282 // Test that BridgedNativeWidget remains sane if Init() is never called.
283 TEST_F(BridgedNativeWidgetInitTest, InitNotCalled) {
284   EXPECT_FALSE(bridge()->ns_view());
285   EXPECT_FALSE(bridge()->ns_window());
286   bridge().reset();
289 // Test getting complete string using text input protocol.
290 TEST_F(BridgedNativeWidgetTest, TextInput_GetCompleteString) {
291   const std::string kTestString = "foo bar baz";
292   InstallTextField(kTestString);
294   NSRange range = NSMakeRange(0, kTestString.size());
295   NSRange actual_range;
296   NSAttributedString* text =
297       [ns_view_ attributedSubstringForProposedRange:range
298                                         actualRange:&actual_range];
299   EXPECT_EQ(kTestString, SysNSStringToUTF8([text string]));
300   EXPECT_EQ_RANGE(range, actual_range);
303 // Test getting middle substring using text input protocol.
304 TEST_F(BridgedNativeWidgetTest, TextInput_GetMiddleSubstring) {
305   const std::string kTestString = "foo bar baz";
306   InstallTextField(kTestString);
308   NSRange range = NSMakeRange(4, 3);
309   NSRange actual_range;
310   NSAttributedString* text =
311       [ns_view_ attributedSubstringForProposedRange:range
312                                         actualRange:&actual_range];
313   EXPECT_EQ("bar", SysNSStringToUTF8([text string]));
314   EXPECT_EQ_RANGE(range, actual_range);
317 // Test getting ending substring using text input protocol.
318 TEST_F(BridgedNativeWidgetTest, TextInput_GetEndingSubstring) {
319   const std::string kTestString = "foo bar baz";
320   InstallTextField(kTestString);
322   NSRange range = NSMakeRange(8, 100);
323   NSRange actual_range;
324   NSAttributedString* text =
325       [ns_view_ attributedSubstringForProposedRange:range
326                                         actualRange:&actual_range];
327   EXPECT_EQ("baz", SysNSStringToUTF8([text string]));
328   EXPECT_EQ(range.location, actual_range.location);
329   EXPECT_EQ(3U, actual_range.length);
332 // Test getting empty substring using text input protocol.
333 TEST_F(BridgedNativeWidgetTest, TextInput_GetEmptySubstring) {
334   const std::string kTestString = "foo bar baz";
335   InstallTextField(kTestString);
337   NSRange range = EmptyRange();
338   NSRange actual_range;
339   NSAttributedString* text =
340       [ns_view_ attributedSubstringForProposedRange:range
341                                         actualRange:&actual_range];
342   EXPECT_EQ("", SysNSStringToUTF8([text string]));
343   EXPECT_EQ_RANGE(range, actual_range);
346 // Test inserting text using text input protocol.
347 TEST_F(BridgedNativeWidgetTest, TextInput_InsertText) {
348   const std::string kTestString = "foo";
349   InstallTextField(kTestString);
351   [ns_view_ insertText:SysUTF8ToNSString(kTestString)
352       replacementRange:EmptyRange()];
353   gfx::Range range(0, kTestString.size());
354   base::string16 text;
355   EXPECT_TRUE([ns_view_ textInputClient]->GetTextFromRange(range, &text));
356   EXPECT_EQ(ASCIIToUTF16(kTestString), text);
359 // Test replacing text using text input protocol.
360 TEST_F(BridgedNativeWidgetTest, TextInput_ReplaceText) {
361   const std::string kTestString = "foo bar";
362   InstallTextField(kTestString);
364   [ns_view_ insertText:@"baz" replacementRange:NSMakeRange(4, 3)];
365   EXPECT_EQ("foo baz", GetText());
368 // Test IME composition using text input protocol.
369 TEST_F(BridgedNativeWidgetTest, TextInput_Compose) {
370   const std::string kTestString = "foo ";
371   InstallTextField(kTestString);
373   EXPECT_FALSE([ns_view_ hasMarkedText]);
374   EXPECT_EQ_RANGE(EmptyRange(), [ns_view_ markedRange]);
376   // Start composition.
377   NSString* compositionText = @"bar";
378   NSUInteger compositionLength = [compositionText length];
379   [ns_view_ setMarkedText:compositionText
380             selectedRange:NSMakeRange(0, 2)
381          replacementRange:EmptyRange()];
382   EXPECT_TRUE([ns_view_ hasMarkedText]);
383   EXPECT_EQ_RANGE(NSMakeRange(kTestString.size(), compositionLength),
384                   [ns_view_ markedRange]);
385   EXPECT_EQ_RANGE(NSMakeRange(kTestString.size(), 2), [ns_view_ selectedRange]);
387   // Confirm composition.
388   [ns_view_ unmarkText];
389   EXPECT_FALSE([ns_view_ hasMarkedText]);
390   EXPECT_EQ_RANGE(EmptyRange(), [ns_view_ markedRange]);
391   EXPECT_EQ("foo bar", GetText());
392   EXPECT_EQ_RANGE(NSMakeRange(GetText().size(), 0), [ns_view_ selectedRange]);
395 // Test moving the caret left and right using text input protocol.
396 TEST_F(BridgedNativeWidgetTest, TextInput_MoveLeftRight) {
397   InstallTextField("foo");
398   EXPECT_EQ_RANGE(NSMakeRange(3, 0), [ns_view_ selectedRange]);
400   // Move right not allowed, out of range.
401   [ns_view_ doCommandBySelector:@selector(moveRight:)];
402   EXPECT_EQ_RANGE(NSMakeRange(3, 0), [ns_view_ selectedRange]);
404   // Move left.
405   [ns_view_ doCommandBySelector:@selector(moveLeft:)];
406   EXPECT_EQ_RANGE(NSMakeRange(2, 0), [ns_view_ selectedRange]);
408   // Move right.
409   [ns_view_ doCommandBySelector:@selector(moveRight:)];
410   EXPECT_EQ_RANGE(NSMakeRange(3, 0), [ns_view_ selectedRange]);
413 // Test backward delete using text input protocol.
414 TEST_F(BridgedNativeWidgetTest, TextInput_DeleteBackward) {
415   InstallTextField("a");
416   EXPECT_EQ_RANGE(NSMakeRange(1, 0), [ns_view_ selectedRange]);
418   // Delete one character.
419   [ns_view_ doCommandBySelector:@selector(deleteBackward:)];
420   EXPECT_EQ("", GetText());
421   EXPECT_EQ_RANGE(NSMakeRange(0, 0), [ns_view_ selectedRange]);
423   // Try to delete again on an empty string.
424   [ns_view_ doCommandBySelector:@selector(deleteBackward:)];
425   EXPECT_EQ("", GetText());
426   EXPECT_EQ_RANGE(NSMakeRange(0, 0), [ns_view_ selectedRange]);
429 // Test forward delete using text input protocol.
430 TEST_F(BridgedNativeWidgetTest, TextInput_DeleteForward) {
431   InstallTextField("a");
432   EXPECT_EQ_RANGE(NSMakeRange(1, 0), [ns_view_ selectedRange]);
434   // At the end of the string, can't delete forward.
435   [ns_view_ doCommandBySelector:@selector(deleteForward:)];
436   EXPECT_EQ("a", GetText());
437   EXPECT_EQ_RANGE(NSMakeRange(1, 0), [ns_view_ selectedRange]);
439   // Should succeed after moving left first.
440   [ns_view_ doCommandBySelector:@selector(moveLeft:)];
441   [ns_view_ doCommandBySelector:@selector(deleteForward:)];
442   EXPECT_EQ("", GetText());
443   EXPECT_EQ_RANGE(NSMakeRange(0, 0), [ns_view_ selectedRange]);
446 typedef BridgedNativeWidgetTestBase BridgedNativeWidgetSimulateFullscreenTest;
448 // Simulate the notifications that AppKit would send out if a fullscreen
449 // operation begins, and then fails and must abort. This notification sequence
450 // was determined by posting delayed tasks to toggle fullscreen state and then
451 // mashing Ctrl+Left/Right to keep OSX in a transition between Spaces to cause
452 // the fullscreen transition to fail.
453 TEST_F(BridgedNativeWidgetSimulateFullscreenTest, FailToEnterAndExit) {
454   if (base::mac::IsOSSnowLeopard())
455     return;
457   base::scoped_nsobject<NSWindow> owned_window(
458       [[BridgedNativeWidgetTestFullScreenWindow alloc]
459           initWithContentRect:NSMakeRect(50, 50, 400, 300)
460                     styleMask:NSBorderlessWindowMask
461                       backing:NSBackingStoreBuffered
462                         defer:YES]);
463   [owned_window setReleasedWhenClosed:NO];  // Owned by scoped_nsobject.
464   bridge()->Init(owned_window, init_params_);  // Transfers ownership.
466   BridgedNativeWidgetTestFullScreenWindow* window =
467       base::mac::ObjCCastStrict<BridgedNativeWidgetTestFullScreenWindow>(
468           widget_->GetNativeWindow());
469   widget_->Show();
471   NSNotificationCenter* center = [NSNotificationCenter defaultCenter];
473   EXPECT_FALSE(bridge()->target_fullscreen_state());
475   // Simulate an initial toggleFullScreen: (user- or Widget-initiated).
476   [center postNotificationName:NSWindowWillEnterFullScreenNotification
477                         object:window];
479   // On a failure, Cocoa starts by sending an unexpected *exit* fullscreen, and
480   // BridgedNativeWidget will think it's just a delayed transition and try to go
481   // back into fullscreen but get ignored by Cocoa.
482   EXPECT_EQ(0, [window ignoredToggleFullScreenCount]);
483   EXPECT_TRUE(bridge()->target_fullscreen_state());
484   [center postNotificationName:NSWindowDidExitFullScreenNotification
485                         object:window];
486   EXPECT_EQ(1, [window ignoredToggleFullScreenCount]);
487   EXPECT_FALSE(bridge()->target_fullscreen_state());
489   // Cocoa follows up with a failure message sent to the NSWindowDelegate (there
490   // is no equivalent notification for failure). Called via id so that this
491   // compiles on 10.6.
492   id window_delegate = [window delegate];
493   [window_delegate windowDidFailToEnterFullScreen:window];
494   EXPECT_FALSE(bridge()->target_fullscreen_state());
496   // Now perform a successful fullscreen operation.
497   [center postNotificationName:NSWindowWillEnterFullScreenNotification
498                         object:window];
499   EXPECT_TRUE(bridge()->target_fullscreen_state());
500   [center postNotificationName:NSWindowDidEnterFullScreenNotification
501                         object:window];
502   EXPECT_TRUE(bridge()->target_fullscreen_state());
504   // And try to get out.
505   [center postNotificationName:NSWindowWillExitFullScreenNotification
506                         object:window];
507   EXPECT_FALSE(bridge()->target_fullscreen_state());
509   // On a failure, Cocoa sends a failure message, but then just dumps the window
510   // out of fullscreen anyway (in that order).
511   [window_delegate windowDidFailToExitFullScreen:window];
512   EXPECT_FALSE(bridge()->target_fullscreen_state());
513   [center postNotificationName:NSWindowDidExitFullScreenNotification
514                         object:window];
515   EXPECT_EQ(1, [window ignoredToggleFullScreenCount]);  // No change.
516   EXPECT_FALSE(bridge()->target_fullscreen_state());
518   widget_->CloseNow();
521 }  // namespace test
522 }  // namespace views