OLD | NEW |
1 // Copyright 2014 The Chromium Authors. All rights reserved. | 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 | 2 // Use of this source code is governed by a BSD-style license that can be |
3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
4 | 4 |
5 #import "ui/views/cocoa/bridged_native_widget.h" | 5 #import "ui/views/cocoa/bridged_native_widget.h" |
6 | 6 |
7 #import <Cocoa/Cocoa.h> | 7 #import <Cocoa/Cocoa.h> |
8 | 8 |
9 #import "base/mac/foundation_util.h" | 9 #import "base/mac/foundation_util.h" |
10 #import "base/mac/mac_util.h" | 10 #import "base/mac/mac_util.h" |
11 #import "base/mac/sdk_forward_declarations.h" | 11 #import "base/mac/sdk_forward_declarations.h" |
12 #include "base/macros.h" | 12 #include "base/macros.h" |
13 #include "base/memory/scoped_ptr.h" | 13 #include "base/memory/scoped_ptr.h" |
14 #include "base/message_loop/message_loop.h" | 14 #include "base/message_loop/message_loop.h" |
15 #include "base/strings/sys_string_conversions.h" | 15 #include "base/strings/sys_string_conversions.h" |
16 #include "base/strings/utf_string_conversions.h" | 16 #include "base/strings/utf_string_conversions.h" |
17 #import "testing/gtest_mac.h" | 17 #import "testing/gtest_mac.h" |
18 #import "ui/base/cocoa/window_size_constants.h" | 18 #import "ui/base/cocoa/window_size_constants.h" |
19 #include "ui/base/ime/input_method.h" | 19 #include "ui/base/ime/input_method.h" |
20 #import "ui/gfx/test/ui_cocoa_test_helper.h" | 20 #import "ui/gfx/test/ui_cocoa_test_helper.h" |
| 21 #import "ui/gfx/mac/coordinate_conversion.h" |
21 #import "ui/views/cocoa/bridged_content_view.h" | 22 #import "ui/views/cocoa/bridged_content_view.h" |
22 #import "ui/views/cocoa/native_widget_mac_nswindow.h" | 23 #import "ui/views/cocoa/native_widget_mac_nswindow.h" |
23 #import "ui/views/cocoa/views_nswindow_delegate.h" | 24 #import "ui/views/cocoa/views_nswindow_delegate.h" |
24 #include "ui/views/controls/textfield/textfield.h" | 25 #include "ui/views/controls/textfield/textfield.h" |
25 #include "ui/views/view.h" | 26 #include "ui/views/view.h" |
26 #include "ui/views/widget/native_widget_mac.h" | 27 #include "ui/views/widget/native_widget_mac.h" |
27 #include "ui/views/widget/root_view.h" | 28 #include "ui/views/widget/root_view.h" |
28 #include "ui/views/widget/widget.h" | 29 #include "ui/views/widget/widget.h" |
29 #include "ui/views/widget/widget_observer.h" | 30 #include "ui/views/widget/widget_observer.h" |
30 | 31 |
31 using base::ASCIIToUTF16; | 32 using base::ASCIIToUTF16; |
32 using base::SysNSStringToUTF8; | 33 using base::SysNSStringToUTF8; |
33 using base::SysNSStringToUTF16; | 34 using base::SysNSStringToUTF16; |
34 using base::SysUTF8ToNSString; | 35 using base::SysUTF8ToNSString; |
35 | 36 |
36 #define EXPECT_EQ_RANGE(a, b) \ | 37 #define EXPECT_EQ_RANGE(a, b) \ |
37 EXPECT_EQ(a.location, b.location); \ | 38 EXPECT_EQ(a.location, b.location); \ |
38 EXPECT_EQ(a.length, b.length); | 39 EXPECT_EQ(a.length, b.length); |
39 | 40 |
40 namespace { | 41 namespace { |
41 | 42 |
42 // Empty range shortcut for readibility. | 43 // Empty range shortcut for readibility. |
43 NSRange EmptyRange() { | 44 NSRange EmptyRange() { |
44 return NSMakeRange(NSNotFound, 0); | 45 return NSMakeRange(NSNotFound, 0); |
45 } | 46 } |
46 | 47 |
| 48 // Sets |composition_text| as the composition text with caret placed at |
| 49 // |caret_pos| and updates |caret_range|. |
| 50 void SetCompositionText(ui::TextInputClient* client, |
| 51 const base::string16& composition_text, |
| 52 const int caret_pos, |
| 53 NSRange* caret_range) { |
| 54 ui::CompositionText composition; |
| 55 composition.selection = gfx::Range(caret_pos); |
| 56 composition.text = composition_text; |
| 57 client->SetCompositionText(composition); |
| 58 if (caret_range) |
| 59 *caret_range = NSMakeRange(caret_pos, 0); |
| 60 } |
| 61 |
| 62 // Returns a zero width rectangle corresponding to current caret position. |
| 63 gfx::Rect GetCaretBounds(const ui::TextInputClient* client) { |
| 64 gfx::Rect caret_bounds = client->GetCaretBounds(); |
| 65 caret_bounds.set_width(0); |
| 66 return caret_bounds; |
| 67 } |
| 68 |
| 69 // Returns a zero width rectangle corresponding to caret bounds when it's placed |
| 70 // at |caret_pos| and updates |caret_range|. |
| 71 gfx::Rect GetCaretBoundsForPosition(ui::TextInputClient* client, |
| 72 const base::string16& composition_text, |
| 73 const int caret_pos, |
| 74 NSRange* caret_range) { |
| 75 SetCompositionText(client, composition_text, caret_pos, caret_range); |
| 76 return GetCaretBounds(client); |
| 77 } |
| 78 |
| 79 // Returns the expected boundary rectangle for characters of |composition_text| |
| 80 // within the |query_range|. |
| 81 gfx::Rect GetExpectedBoundsForRange(ui::TextInputClient* client, |
| 82 const base::string16& composition_text, |
| 83 NSRange query_range) { |
| 84 gfx::Rect left_caret = GetCaretBoundsForPosition( |
| 85 client, composition_text, query_range.location, nullptr); |
| 86 gfx::Rect right_caret = GetCaretBoundsForPosition( |
| 87 client, composition_text, query_range.location + query_range.length, |
| 88 nullptr); |
| 89 |
| 90 // The expected bounds correspond to the area between the left and right caret |
| 91 // positions. |
| 92 return gfx::Rect(left_caret.x(), left_caret.y(), |
| 93 right_caret.x() - left_caret.x(), left_caret.height()); |
| 94 } |
| 95 |
47 } // namespace | 96 } // namespace |
48 | 97 |
49 // Class to override -[NSWindow toggleFullScreen:] to a no-op. This simulates | 98 // Class to override -[NSWindow toggleFullScreen:] to a no-op. This simulates |
50 // NSWindow's behavior when attempting to toggle fullscreen state again, when | 99 // NSWindow's behavior when attempting to toggle fullscreen state again, when |
51 // the last attempt failed but Cocoa has not yet sent | 100 // the last attempt failed but Cocoa has not yet sent |
52 // windowDidFailToEnterFullScreen:. | 101 // windowDidFailToEnterFullScreen:. |
53 @interface BridgedNativeWidgetTestFullScreenWindow : NativeWidgetMacNSWindow { | 102 @interface BridgedNativeWidgetTestFullScreenWindow : NativeWidgetMacNSWindow { |
54 @private | 103 @private |
55 int ignoredToggleFullScreenCount_; | 104 int ignoredToggleFullScreenCount_; |
56 } | 105 } |
(...skipping 65 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
122 // before the tests covering the Init() flow are ready to do that. | 171 // before the tests covering the Init() flow are ready to do that. |
123 init_params_.type = Widget::InitParams::TYPE_WINDOW_FRAMELESS; | 172 init_params_.type = Widget::InitParams::TYPE_WINDOW_FRAMELESS; |
124 | 173 |
125 // To control the lifetime without an actual window that must be closed, | 174 // To control the lifetime without an actual window that must be closed, |
126 // tests in this file need to use WIDGET_OWNS_NATIVE_WIDGET. | 175 // tests in this file need to use WIDGET_OWNS_NATIVE_WIDGET. |
127 init_params_.ownership = Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET; | 176 init_params_.ownership = Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET; |
128 | 177 |
129 // Opacity defaults to "infer" which is usually updated by ViewsDelegate. | 178 // Opacity defaults to "infer" which is usually updated by ViewsDelegate. |
130 init_params_.opacity = Widget::InitParams::OPAQUE_WINDOW; | 179 init_params_.opacity = Widget::InitParams::OPAQUE_WINDOW; |
131 | 180 |
| 181 init_params_.bounds = gfx::Rect(100, 100, 100, 100); |
| 182 |
132 native_widget_mac_->GetWidget()->Init(init_params_); | 183 native_widget_mac_->GetWidget()->Init(init_params_); |
133 } | 184 } |
134 | 185 |
135 protected: | 186 protected: |
136 scoped_ptr<Widget> widget_; | 187 scoped_ptr<Widget> widget_; |
137 MockNativeWidgetMac* native_widget_mac_; // Weak. Owned by |widget_|. | 188 MockNativeWidgetMac* native_widget_mac_; // Weak. Owned by |widget_|. |
138 | 189 |
139 // Make the InitParams available to tests to cover initialization codepaths. | 190 // Make the InitParams available to tests to cover initialization codepaths. |
140 Widget::InitParams init_params_; | 191 Widget::InitParams init_params_; |
141 }; | 192 }; |
(...skipping 36 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
178 } | 229 } |
179 | 230 |
180 void BridgedNativeWidgetTest::InstallTextField( | 231 void BridgedNativeWidgetTest::InstallTextField( |
181 const std::string& text, | 232 const std::string& text, |
182 ui::TextInputType text_input_type) { | 233 ui::TextInputType text_input_type) { |
183 Textfield* textfield = new Textfield(); | 234 Textfield* textfield = new Textfield(); |
184 textfield->SetText(ASCIIToUTF16(text)); | 235 textfield->SetText(ASCIIToUTF16(text)); |
185 textfield->SetTextInputType(text_input_type); | 236 textfield->SetTextInputType(text_input_type); |
186 view_->RemoveAllChildViews(true); | 237 view_->RemoveAllChildViews(true); |
187 view_->AddChildView(textfield); | 238 view_->AddChildView(textfield); |
| 239 textfield->SetBoundsRect(init_params_.bounds); |
188 | 240 |
189 // Request focus so the InputMethod can dispatch events to the RootView, and | 241 // Request focus so the InputMethod can dispatch events to the RootView, and |
190 // have them delivered to the textfield. Note that focusing a textfield | 242 // have them delivered to the textfield. Note that focusing a textfield |
191 // schedules a task to flash the cursor, so this requires |message_loop_|. | 243 // schedules a task to flash the cursor, so this requires |message_loop_|. |
192 textfield->RequestFocus(); | 244 textfield->RequestFocus(); |
193 | 245 |
194 [ns_view_ setTextInputClient:textfield]; | 246 [ns_view_ setTextInputClient:textfield]; |
195 } | 247 } |
196 | 248 |
197 void BridgedNativeWidgetTest::InstallTextField(const std::string& text) { | 249 void BridgedNativeWidgetTest::InstallTextField(const std::string& text) { |
(...skipping 328 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
526 EXPECT_EQ("a", GetText()); | 578 EXPECT_EQ("a", GetText()); |
527 EXPECT_EQ_RANGE(NSMakeRange(1, 0), [ns_view_ selectedRange]); | 579 EXPECT_EQ_RANGE(NSMakeRange(1, 0), [ns_view_ selectedRange]); |
528 | 580 |
529 // Should succeed after moving left first. | 581 // Should succeed after moving left first. |
530 [ns_view_ doCommandBySelector:@selector(moveLeft:)]; | 582 [ns_view_ doCommandBySelector:@selector(moveLeft:)]; |
531 [ns_view_ doCommandBySelector:@selector(deleteForward:)]; | 583 [ns_view_ doCommandBySelector:@selector(deleteForward:)]; |
532 EXPECT_EQ("", GetText()); | 584 EXPECT_EQ("", GetText()); |
533 EXPECT_EQ_RANGE(NSMakeRange(0, 0), [ns_view_ selectedRange]); | 585 EXPECT_EQ_RANGE(NSMakeRange(0, 0), [ns_view_ selectedRange]); |
534 } | 586 } |
535 | 587 |
| 588 // Test firstRectForCharacterRange:actualRange for cases where query range is |
| 589 // empty or outside composition range. |
| 590 TEST_F(BridgedNativeWidgetTest, TextInput_FirstRectForCharacterRange_Caret) { |
| 591 InstallTextField(""); |
| 592 ui::TextInputClient* client = [ns_view_ textInputClient]; |
| 593 |
| 594 // No composition. Ensure bounds and range corresponding to the current caret |
| 595 // position are returned. |
| 596 // Initially selection range will be [0, 0]. |
| 597 NSRange caret_range = NSMakeRange(0, 0); |
| 598 NSRange query_range = NSMakeRange(1, 1); |
| 599 NSRange actual_range; |
| 600 NSRect rect = [ns_view_ firstRectForCharacterRange:query_range |
| 601 actualRange:&actual_range]; |
| 602 EXPECT_EQ(GetCaretBounds(client), gfx::ScreenRectFromNSRect(rect)); |
| 603 EXPECT_EQ_RANGE(caret_range, actual_range); |
| 604 |
| 605 // Set composition with caret before second character ('e'). |
| 606 const base::string16 test_string = base::ASCIIToUTF16("test_str"); |
| 607 const size_t kTextLength = 8; |
| 608 SetCompositionText(client, test_string, 1, &caret_range); |
| 609 |
| 610 // Test bounds returned for empty ranges within composition range. As per |
| 611 // Apple's documentation for firstRectForCharacterRange:actualRange:, for an |
| 612 // empty query range, the returned rectangle should coincide with the |
| 613 // insertion point and have zero width. However in our implementation, if the |
| 614 // empty query range lies within the composition range, we return a zero width |
| 615 // rectangle corresponding to the query range location. |
| 616 |
| 617 // Test bounds returned for empty range before second character ('e') are same |
| 618 // as caret bounds when placed before second character. |
| 619 query_range = NSMakeRange(1, 0); |
| 620 rect = [ns_view_ firstRectForCharacterRange:query_range |
| 621 actualRange:&actual_range]; |
| 622 EXPECT_EQ(GetCaretBoundsForPosition(client, test_string, 1, &caret_range), |
| 623 gfx::ScreenRectFromNSRect(rect)); |
| 624 EXPECT_EQ_RANGE(query_range, actual_range); |
| 625 |
| 626 // Test bounds returned for empty range after the composition text are same as |
| 627 // caret bounds when placed after the composition text. |
| 628 query_range = NSMakeRange(kTextLength, 0); |
| 629 rect = [ns_view_ firstRectForCharacterRange:query_range |
| 630 actualRange:&actual_range]; |
| 631 EXPECT_NE(GetCaretBoundsForPosition(client, test_string, 1, &caret_range), |
| 632 gfx::ScreenRectFromNSRect(rect)); |
| 633 EXPECT_EQ( |
| 634 GetCaretBoundsForPosition(client, test_string, kTextLength, &caret_range), |
| 635 gfx::ScreenRectFromNSRect(rect)); |
| 636 EXPECT_EQ_RANGE(query_range, actual_range); |
| 637 |
| 638 // Query outside composition range. Ensure bounds and range corresponding to |
| 639 // the current caret position are returned. |
| 640 query_range = NSMakeRange(kTextLength + 1, 0); |
| 641 rect = [ns_view_ firstRectForCharacterRange:query_range |
| 642 actualRange:&actual_range]; |
| 643 EXPECT_EQ(GetCaretBounds(client), gfx::ScreenRectFromNSRect(rect)); |
| 644 EXPECT_EQ_RANGE(caret_range, actual_range); |
| 645 |
| 646 // Make sure not crashing by passing null pointer instead of actualRange. |
| 647 rect = [ns_view_ firstRectForCharacterRange:query_range actualRange:nullptr]; |
| 648 } |
| 649 |
| 650 // Test firstRectForCharacterRange:actualRange for non-empty query ranges within |
| 651 // the composition range. |
| 652 TEST_F(BridgedNativeWidgetTest, TextInput_FirstRectForCharacterRange) { |
| 653 InstallTextField(""); |
| 654 ui::TextInputClient* client = [ns_view_ textInputClient]; |
| 655 |
| 656 const base::string16 kTestString = base::ASCIIToUTF16("test_str"); |
| 657 const size_t kTextLength = 8; |
| 658 SetCompositionText(client, kTestString, 1, nullptr); |
| 659 |
| 660 // Query bounds for the whole composition string. |
| 661 NSRange query_range = NSMakeRange(0, kTextLength); |
| 662 NSRange actual_range; |
| 663 NSRect rect = [ns_view_ firstRectForCharacterRange:query_range |
| 664 actualRange:&actual_range]; |
| 665 EXPECT_EQ(GetExpectedBoundsForRange(client, kTestString, query_range), |
| 666 gfx::ScreenRectFromNSRect(rect)); |
| 667 EXPECT_EQ_RANGE(query_range, actual_range); |
| 668 |
| 669 // Query bounds for the substring "est_". |
| 670 query_range = NSMakeRange(1, 4); |
| 671 rect = [ns_view_ firstRectForCharacterRange:query_range |
| 672 actualRange:&actual_range]; |
| 673 EXPECT_EQ(GetExpectedBoundsForRange(client, kTestString, query_range), |
| 674 gfx::ScreenRectFromNSRect(rect)); |
| 675 EXPECT_EQ_RANGE(query_range, actual_range); |
| 676 } |
| 677 |
536 typedef BridgedNativeWidgetTestBase BridgedNativeWidgetSimulateFullscreenTest; | 678 typedef BridgedNativeWidgetTestBase BridgedNativeWidgetSimulateFullscreenTest; |
537 | 679 |
538 // Simulate the notifications that AppKit would send out if a fullscreen | 680 // Simulate the notifications that AppKit would send out if a fullscreen |
539 // operation begins, and then fails and must abort. This notification sequence | 681 // operation begins, and then fails and must abort. This notification sequence |
540 // was determined by posting delayed tasks to toggle fullscreen state and then | 682 // was determined by posting delayed tasks to toggle fullscreen state and then |
541 // mashing Ctrl+Left/Right to keep OSX in a transition between Spaces to cause | 683 // mashing Ctrl+Left/Right to keep OSX in a transition between Spaces to cause |
542 // the fullscreen transition to fail. | 684 // the fullscreen transition to fail. |
543 TEST_F(BridgedNativeWidgetSimulateFullscreenTest, FailToEnterAndExit) { | 685 TEST_F(BridgedNativeWidgetSimulateFullscreenTest, FailToEnterAndExit) { |
544 if (base::mac::IsOSSnowLeopard()) | 686 if (base::mac::IsOSSnowLeopard()) |
545 return; | 687 return; |
(...skipping 58 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
604 [center postNotificationName:NSWindowDidExitFullScreenNotification | 746 [center postNotificationName:NSWindowDidExitFullScreenNotification |
605 object:window]; | 747 object:window]; |
606 EXPECT_EQ(1, [window ignoredToggleFullScreenCount]); // No change. | 748 EXPECT_EQ(1, [window ignoredToggleFullScreenCount]); // No change. |
607 EXPECT_FALSE(bridge()->target_fullscreen_state()); | 749 EXPECT_FALSE(bridge()->target_fullscreen_state()); |
608 | 750 |
609 widget_->CloseNow(); | 751 widget_->CloseNow(); |
610 } | 752 } |
611 | 753 |
612 } // namespace test | 754 } // namespace test |
613 } // namespace views | 755 } // namespace views |
OLD | NEW |