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/memory/scoped_ptr.h" | 12 #include "base/memory/scoped_ptr.h" |
13 #include "base/message_loop/message_loop.h" | 13 #include "base/message_loop/message_loop.h" |
14 #include "base/strings/sys_string_conversions.h" | 14 #include "base/strings/sys_string_conversions.h" |
15 #include "base/strings/utf_string_conversions.h" | 15 #include "base/strings/utf_string_conversions.h" |
16 #import "testing/gtest_mac.h" | 16 #import "testing/gtest_mac.h" |
17 #import "ui/base/cocoa/window_size_constants.h" | 17 #import "ui/base/cocoa/window_size_constants.h" |
18 #include "ui/base/ime/input_method.h" | 18 #include "ui/base/ime/input_method.h" |
19 #import "ui/gfx/test/ui_cocoa_test_helper.h" | 19 #import "ui/gfx/test/ui_cocoa_test_helper.h" |
| 20 #import "ui/gfx/mac/coordinate_conversion.h" |
20 #import "ui/views/cocoa/bridged_content_view.h" | 21 #import "ui/views/cocoa/bridged_content_view.h" |
21 #import "ui/views/cocoa/native_widget_mac_nswindow.h" | 22 #import "ui/views/cocoa/native_widget_mac_nswindow.h" |
22 #import "ui/views/cocoa/views_nswindow_delegate.h" | 23 #import "ui/views/cocoa/views_nswindow_delegate.h" |
23 #include "ui/views/controls/textfield/textfield.h" | 24 #include "ui/views/controls/textfield/textfield.h" |
24 #include "ui/views/view.h" | 25 #include "ui/views/view.h" |
25 #include "ui/views/widget/native_widget_mac.h" | 26 #include "ui/views/widget/native_widget_mac.h" |
26 #include "ui/views/widget/root_view.h" | 27 #include "ui/views/widget/root_view.h" |
27 #include "ui/views/widget/widget.h" | 28 #include "ui/views/widget/widget.h" |
28 #include "ui/views/widget/widget_observer.h" | 29 #include "ui/views/widget/widget_observer.h" |
29 | 30 |
30 using base::ASCIIToUTF16; | 31 using base::ASCIIToUTF16; |
31 using base::SysNSStringToUTF8; | 32 using base::SysNSStringToUTF8; |
32 using base::SysNSStringToUTF16; | 33 using base::SysNSStringToUTF16; |
33 using base::SysUTF8ToNSString; | 34 using base::SysUTF8ToNSString; |
34 | 35 |
35 #define EXPECT_EQ_RANGE(a, b) \ | 36 #define EXPECT_EQ_RANGE(a, b) \ |
36 EXPECT_EQ(a.location, b.location); \ | 37 EXPECT_EQ(a.location, b.location); \ |
37 EXPECT_EQ(a.length, b.length); | 38 EXPECT_EQ(a.length, b.length); |
38 | 39 |
39 namespace { | 40 namespace { |
40 | 41 |
41 // Empty range shortcut for readibility. | 42 // Empty range shortcut for readibility. |
42 NSRange EmptyRange() { | 43 NSRange EmptyRange() { |
43 return NSMakeRange(NSNotFound, 0); | 44 return NSMakeRange(NSNotFound, 0); |
44 } | 45 } |
45 | 46 |
| 47 // Sets |composition_text| as the composition text with caret placed at |
| 48 // |caret_pos| and updates |caret_range|. |
| 49 void SetCompositionText(ui::TextInputClient* client, |
| 50 const base::string16& composition_text, |
| 51 const int caret_pos, |
| 52 NSRange* caret_range) { |
| 53 ui::CompositionText composition; |
| 54 composition.selection = gfx::Range(caret_pos); |
| 55 composition.text = composition_text; |
| 56 client->SetCompositionText(composition); |
| 57 if (caret_range) |
| 58 *caret_range = NSMakeRange(caret_pos, 0); |
| 59 } |
| 60 |
| 61 // Returns a zero width rectangle corresponding to current caret position. |
| 62 gfx::Rect GetCaretBounds(const ui::TextInputClient* client) { |
| 63 gfx::Rect caret_bounds = client->GetCaretBounds(); |
| 64 caret_bounds.set_width(0); |
| 65 return caret_bounds; |
| 66 } |
| 67 |
| 68 // Returns a zero width rectangle corresponding to caret bounds when it's placed |
| 69 // at |caret_pos| and updates |caret_range|. |
| 70 gfx::Rect GetCaretBoundsForPosition(ui::TextInputClient* client, |
| 71 const base::string16& composition_text, |
| 72 const int caret_pos, |
| 73 NSRange* caret_range) { |
| 74 SetCompositionText(client, composition_text, caret_pos, caret_range); |
| 75 return GetCaretBounds(client); |
| 76 } |
| 77 |
| 78 // Returns the expected boundary rectangle for characters of |composition_text| |
| 79 // within the |query_range|. |
| 80 gfx::Rect GetExpectedBoundsForRange(ui::TextInputClient* client, |
| 81 const base::string16& composition_text, |
| 82 NSRange query_range) { |
| 83 gfx::Rect left_caret = GetCaretBoundsForPosition( |
| 84 client, composition_text, query_range.location, nullptr); |
| 85 gfx::Rect right_caret = GetCaretBoundsForPosition( |
| 86 client, composition_text, query_range.location + query_range.length, |
| 87 nullptr); |
| 88 |
| 89 // The expected bounds correspond to the area between the left and right caret |
| 90 // positions. |
| 91 return gfx::Rect(left_caret.x(), left_caret.y(), |
| 92 right_caret.x() - left_caret.x(), left_caret.height()); |
| 93 } |
| 94 |
46 } // namespace | 95 } // namespace |
47 | 96 |
48 // Class to override -[NSWindow toggleFullScreen:] to a no-op. This simulates | 97 // Class to override -[NSWindow toggleFullScreen:] to a no-op. This simulates |
49 // NSWindow's behavior when attempting to toggle fullscreen state again, when | 98 // NSWindow's behavior when attempting to toggle fullscreen state again, when |
50 // the last attempt failed but Cocoa has not yet sent | 99 // the last attempt failed but Cocoa has not yet sent |
51 // windowDidFailToEnterFullScreen:. | 100 // windowDidFailToEnterFullScreen:. |
52 @interface BridgedNativeWidgetTestFullScreenWindow : NativeWidgetMacNSWindow { | 101 @interface BridgedNativeWidgetTestFullScreenWindow : NativeWidgetMacNSWindow { |
53 @private | 102 @private |
54 int ignoredToggleFullScreenCount_; | 103 int ignoredToggleFullScreenCount_; |
55 } | 104 } |
(...skipping 65 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
121 // before the tests covering the Init() flow are ready to do that. | 170 // before the tests covering the Init() flow are ready to do that. |
122 init_params_.type = Widget::InitParams::TYPE_WINDOW_FRAMELESS; | 171 init_params_.type = Widget::InitParams::TYPE_WINDOW_FRAMELESS; |
123 | 172 |
124 // To control the lifetime without an actual window that must be closed, | 173 // To control the lifetime without an actual window that must be closed, |
125 // tests in this file need to use WIDGET_OWNS_NATIVE_WIDGET. | 174 // tests in this file need to use WIDGET_OWNS_NATIVE_WIDGET. |
126 init_params_.ownership = Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET; | 175 init_params_.ownership = Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET; |
127 | 176 |
128 // Opacity defaults to "infer" which is usually updated by ViewsDelegate. | 177 // Opacity defaults to "infer" which is usually updated by ViewsDelegate. |
129 init_params_.opacity = Widget::InitParams::OPAQUE_WINDOW; | 178 init_params_.opacity = Widget::InitParams::OPAQUE_WINDOW; |
130 | 179 |
| 180 init_params_.bounds = gfx::Rect(100, 100, 100, 100); |
| 181 |
131 native_widget_mac_->GetWidget()->Init(init_params_); | 182 native_widget_mac_->GetWidget()->Init(init_params_); |
132 } | 183 } |
133 | 184 |
134 protected: | 185 protected: |
135 scoped_ptr<Widget> widget_; | 186 scoped_ptr<Widget> widget_; |
136 MockNativeWidgetMac* native_widget_mac_; // Weak. Owned by |widget_|. | 187 MockNativeWidgetMac* native_widget_mac_; // Weak. Owned by |widget_|. |
137 | 188 |
138 // Make the InitParams available to tests to cover initialization codepaths. | 189 // Make the InitParams available to tests to cover initialization codepaths. |
139 Widget::InitParams init_params_; | 190 Widget::InitParams init_params_; |
140 }; | 191 }; |
(...skipping 26 matching lines...) Expand all Loading... |
167 | 218 |
168 BridgedNativeWidgetTest::BridgedNativeWidgetTest() { | 219 BridgedNativeWidgetTest::BridgedNativeWidgetTest() { |
169 } | 220 } |
170 | 221 |
171 BridgedNativeWidgetTest::~BridgedNativeWidgetTest() { | 222 BridgedNativeWidgetTest::~BridgedNativeWidgetTest() { |
172 } | 223 } |
173 | 224 |
174 void BridgedNativeWidgetTest::InstallTextField(const std::string& text) { | 225 void BridgedNativeWidgetTest::InstallTextField(const std::string& text) { |
175 Textfield* textfield = new Textfield(); | 226 Textfield* textfield = new Textfield(); |
176 textfield->SetText(ASCIIToUTF16(text)); | 227 textfield->SetText(ASCIIToUTF16(text)); |
| 228 textfield->SetBoundsRect(init_params_.bounds); |
177 view_->AddChildView(textfield); | 229 view_->AddChildView(textfield); |
178 | 230 |
179 // Request focus so the InputMethod can dispatch events to the RootView, and | 231 // Request focus so the InputMethod can dispatch events to the RootView, and |
180 // have them delivered to the textfield. Note that focusing a textfield | 232 // have them delivered to the textfield. Note that focusing a textfield |
181 // schedules a task to flash the cursor, so this requires |message_loop_|. | 233 // schedules a task to flash the cursor, so this requires |message_loop_|. |
182 textfield->RequestFocus(); | 234 textfield->RequestFocus(); |
183 | 235 |
184 [ns_view_ setTextInputClient:textfield]; | 236 [ns_view_ setTextInputClient:textfield]; |
185 } | 237 } |
186 | 238 |
(...skipping 311 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
498 EXPECT_EQ("a", GetText()); | 550 EXPECT_EQ("a", GetText()); |
499 EXPECT_EQ_RANGE(NSMakeRange(1, 0), [ns_view_ selectedRange]); | 551 EXPECT_EQ_RANGE(NSMakeRange(1, 0), [ns_view_ selectedRange]); |
500 | 552 |
501 // Should succeed after moving left first. | 553 // Should succeed after moving left first. |
502 [ns_view_ doCommandBySelector:@selector(moveLeft:)]; | 554 [ns_view_ doCommandBySelector:@selector(moveLeft:)]; |
503 [ns_view_ doCommandBySelector:@selector(deleteForward:)]; | 555 [ns_view_ doCommandBySelector:@selector(deleteForward:)]; |
504 EXPECT_EQ("", GetText()); | 556 EXPECT_EQ("", GetText()); |
505 EXPECT_EQ_RANGE(NSMakeRange(0, 0), [ns_view_ selectedRange]); | 557 EXPECT_EQ_RANGE(NSMakeRange(0, 0), [ns_view_ selectedRange]); |
506 } | 558 } |
507 | 559 |
| 560 // Test firstRectForCharacterRange:actualRange for cases where query range is |
| 561 // empty or outside composition range. |
| 562 TEST_F(BridgedNativeWidgetTest, TextInput_FirstRectForCharacterRange_Caret) { |
| 563 InstallTextField(""); |
| 564 ui::TextInputClient* client = [ns_view_ textInputClient]; |
| 565 |
| 566 // No composition. Ensure bounds and range corresponding to the current caret |
| 567 // position are returned. |
| 568 // Initially selection range will be [0, 0]. |
| 569 NSRange caret_range = NSMakeRange(0, 0); |
| 570 NSRange query_range = NSMakeRange(1, 1); |
| 571 NSRange actual_range; |
| 572 NSRect rect = [ns_view_ firstRectForCharacterRange:query_range |
| 573 actualRange:&actual_range]; |
| 574 EXPECT_EQ(GetCaretBounds(client), gfx::ScreenRectFromNSRect(rect)); |
| 575 EXPECT_EQ_RANGE(caret_range, actual_range); |
| 576 |
| 577 // Set composition with caret before second character ('e'). |
| 578 const base::string16 test_string = base::ASCIIToUTF16("test_str"); |
| 579 const size_t kTextLength = 8; |
| 580 SetCompositionText(client, test_string, 1, &caret_range); |
| 581 |
| 582 // Test bounds returned for empty ranges within composition range. As per |
| 583 // Apple's documentation for firstRectForCharacterRange:actualRange:, for an |
| 584 // empty query range, the returned rectangle should coincide with the |
| 585 // insertion point and have zero width. However in our implementation, if the |
| 586 // empty query range lies within the composition range, we return a zero width |
| 587 // rectangle corresponding to the query range location. |
| 588 |
| 589 // Test bounds returned for empty range before second character ('e') are same |
| 590 // as caret bounds when placed before second character. |
| 591 query_range = NSMakeRange(1, 0); |
| 592 rect = [ns_view_ firstRectForCharacterRange:query_range |
| 593 actualRange:&actual_range]; |
| 594 EXPECT_EQ(GetCaretBoundsForPosition(client, test_string, 1, &caret_range), |
| 595 gfx::ScreenRectFromNSRect(rect)); |
| 596 EXPECT_EQ_RANGE(query_range, actual_range); |
| 597 |
| 598 // Test bounds returned for empty range after the composition text are same as |
| 599 // caret bounds when placed after the composition text. |
| 600 query_range = NSMakeRange(kTextLength, 0); |
| 601 rect = [ns_view_ firstRectForCharacterRange:query_range |
| 602 actualRange:&actual_range]; |
| 603 EXPECT_NE(GetCaretBoundsForPosition(client, test_string, 1, &caret_range), |
| 604 gfx::ScreenRectFromNSRect(rect)); |
| 605 EXPECT_EQ( |
| 606 GetCaretBoundsForPosition(client, test_string, kTextLength, &caret_range), |
| 607 gfx::ScreenRectFromNSRect(rect)); |
| 608 EXPECT_EQ_RANGE(query_range, actual_range); |
| 609 |
| 610 // Query outside composition range. Ensure bounds and range corresponding to |
| 611 // the current caret position are returned. |
| 612 query_range = NSMakeRange(kTextLength + 1, 0); |
| 613 rect = [ns_view_ firstRectForCharacterRange:query_range |
| 614 actualRange:&actual_range]; |
| 615 EXPECT_EQ(GetCaretBounds(client), gfx::ScreenRectFromNSRect(rect)); |
| 616 EXPECT_EQ_RANGE(caret_range, actual_range); |
| 617 |
| 618 // Make sure not crashing by passing null pointer instead of actualRange. |
| 619 rect = [ns_view_ firstRectForCharacterRange:query_range actualRange:nullptr]; |
| 620 } |
| 621 |
| 622 // Test firstRectForCharacterRange:actualRange for non-empty query ranges within |
| 623 // the composition range. |
| 624 TEST_F(BridgedNativeWidgetTest, TextInput_FirstRectForCharacterRange) { |
| 625 InstallTextField(""); |
| 626 ui::TextInputClient* client = [ns_view_ textInputClient]; |
| 627 |
| 628 const base::string16 kTestString = base::ASCIIToUTF16("test_str"); |
| 629 const size_t kTextLength = 8; |
| 630 SetCompositionText(client, kTestString, 1, nullptr); |
| 631 |
| 632 // Query bounds for the whole composition string. |
| 633 NSRange query_range = NSMakeRange(0, kTextLength); |
| 634 NSRange actual_range; |
| 635 NSRect rect = [ns_view_ firstRectForCharacterRange:query_range |
| 636 actualRange:&actual_range]; |
| 637 EXPECT_EQ(GetExpectedBoundsForRange(client, kTestString, query_range), |
| 638 gfx::ScreenRectFromNSRect(rect)); |
| 639 EXPECT_EQ_RANGE(query_range, actual_range); |
| 640 |
| 641 // Query bounds for the substring "est_". |
| 642 query_range = NSMakeRange(1, 4); |
| 643 rect = [ns_view_ firstRectForCharacterRange:query_range |
| 644 actualRange:&actual_range]; |
| 645 EXPECT_EQ(GetExpectedBoundsForRange(client, kTestString, query_range), |
| 646 gfx::ScreenRectFromNSRect(rect)); |
| 647 EXPECT_EQ_RANGE(query_range, actual_range); |
| 648 } |
| 649 |
508 typedef BridgedNativeWidgetTestBase BridgedNativeWidgetSimulateFullscreenTest; | 650 typedef BridgedNativeWidgetTestBase BridgedNativeWidgetSimulateFullscreenTest; |
509 | 651 |
510 // Simulate the notifications that AppKit would send out if a fullscreen | 652 // Simulate the notifications that AppKit would send out if a fullscreen |
511 // operation begins, and then fails and must abort. This notification sequence | 653 // operation begins, and then fails and must abort. This notification sequence |
512 // was determined by posting delayed tasks to toggle fullscreen state and then | 654 // was determined by posting delayed tasks to toggle fullscreen state and then |
513 // mashing Ctrl+Left/Right to keep OSX in a transition between Spaces to cause | 655 // mashing Ctrl+Left/Right to keep OSX in a transition between Spaces to cause |
514 // the fullscreen transition to fail. | 656 // the fullscreen transition to fail. |
515 TEST_F(BridgedNativeWidgetSimulateFullscreenTest, FailToEnterAndExit) { | 657 TEST_F(BridgedNativeWidgetSimulateFullscreenTest, FailToEnterAndExit) { |
516 if (base::mac::IsOSSnowLeopard()) | 658 if (base::mac::IsOSSnowLeopard()) |
517 return; | 659 return; |
(...skipping 58 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
576 [center postNotificationName:NSWindowDidExitFullScreenNotification | 718 [center postNotificationName:NSWindowDidExitFullScreenNotification |
577 object:window]; | 719 object:window]; |
578 EXPECT_EQ(1, [window ignoredToggleFullScreenCount]); // No change. | 720 EXPECT_EQ(1, [window ignoredToggleFullScreenCount]); // No change. |
579 EXPECT_FALSE(bridge()->target_fullscreen_state()); | 721 EXPECT_FALSE(bridge()->target_fullscreen_state()); |
580 | 722 |
581 widget_->CloseNow(); | 723 widget_->CloseNow(); |
582 } | 724 } |
583 | 725 |
584 } // namespace test | 726 } // namespace test |
585 } // namespace views | 727 } // namespace views |
OLD | NEW |