| 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 #include <memory> | 9 #include <memory> |
| 10 | 10 |
| 11 #import "base/mac/foundation_util.h" | 11 #import "base/mac/foundation_util.h" |
| 12 #import "base/mac/mac_util.h" | 12 #import "base/mac/mac_util.h" |
| 13 #import "base/mac/sdk_forward_declarations.h" | 13 #import "base/mac/sdk_forward_declarations.h" |
| 14 #include "base/macros.h" | 14 #include "base/macros.h" |
| 15 #include "base/message_loop/message_loop.h" | 15 #include "base/message_loop/message_loop.h" |
| 16 #include "base/strings/stringprintf.h" |
| 16 #include "base/strings/sys_string_conversions.h" | 17 #include "base/strings/sys_string_conversions.h" |
| 17 #include "base/strings/utf_string_conversions.h" | 18 #include "base/strings/utf_string_conversions.h" |
| 18 #import "testing/gtest_mac.h" | 19 #import "testing/gtest_mac.h" |
| 19 #import "ui/base/cocoa/window_size_constants.h" | 20 #import "ui/base/cocoa/window_size_constants.h" |
| 20 #include "ui/base/ime/input_method.h" | 21 #include "ui/base/ime/input_method.h" |
| 21 #import "ui/gfx/mac/coordinate_conversion.h" | 22 #import "ui/gfx/mac/coordinate_conversion.h" |
| 22 #import "ui/gfx/test/ui_cocoa_test_helper.h" | 23 #import "ui/gfx/test/ui_cocoa_test_helper.h" |
| 23 #import "ui/views/cocoa/bridged_content_view.h" | 24 #import "ui/views/cocoa/bridged_content_view.h" |
| 24 #import "ui/views/cocoa/native_widget_mac_nswindow.h" | 25 #import "ui/views/cocoa/native_widget_mac_nswindow.h" |
| 25 #import "ui/views/cocoa/views_nswindow_delegate.h" | 26 #import "ui/views/cocoa/views_nswindow_delegate.h" |
| (...skipping 179 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 205 Widget::InitParams init_params_; | 206 Widget::InitParams init_params_; |
| 206 }; | 207 }; |
| 207 | 208 |
| 208 class BridgedNativeWidgetTest : public BridgedNativeWidgetTestBase { | 209 class BridgedNativeWidgetTest : public BridgedNativeWidgetTestBase { |
| 209 public: | 210 public: |
| 210 BridgedNativeWidgetTest(); | 211 BridgedNativeWidgetTest(); |
| 211 ~BridgedNativeWidgetTest() override; | 212 ~BridgedNativeWidgetTest() override; |
| 212 | 213 |
| 213 // Install a textfield with input type |text_input_type| in the view hierarchy | 214 // Install a textfield with input type |text_input_type| in the view hierarchy |
| 214 // and make it the text input client. Also initializes |dummy_text_view_|. | 215 // and make it the text input client. Also initializes |dummy_text_view_|. |
| 215 void InstallTextField(const std::string& text, | 216 void InstallTextField(const base::string16& text, |
| 216 ui::TextInputType text_input_type); | 217 ui::TextInputType text_input_type); |
| 217 | 218 |
| 218 // Install a textfield with input type ui::TEXT_INPUT_TYPE_TEXT in the view | 219 // Install a textfield with input type ui::TEXT_INPUT_TYPE_TEXT in the view |
| 219 // hierarchy and make it the text input client. Also initializes | 220 // hierarchy and make it the text input client. Also initializes |
| 220 // |dummy_text_view_|. | 221 // |dummy_text_view_|. |
| 222 void InstallTextField(const base::string16& text); |
| 223 |
| 221 void InstallTextField(const std::string& text); | 224 void InstallTextField(const std::string& text); |
| 222 | 225 |
| 223 // Returns the actual current text for |ns_view_|. | 226 // Returns the actual current text for |ns_view_|. |
| 224 NSString* GetActualText(); | 227 NSString* GetActualText(); |
| 225 | 228 |
| 226 // Returns the expected current text from |dummy_text_view_|. | 229 // Returns the expected current text from |dummy_text_view_|. |
| 227 NSString* GetExpectedText(); | 230 NSString* GetExpectedText(); |
| 228 | 231 |
| 229 // Returns the actual selection range for |ns_view_|. | 232 // Returns the actual selection range for |ns_view_|. |
| 230 NSRange GetActualSelectionRange(); | 233 NSRange GetActualSelectionRange(); |
| 231 | 234 |
| 232 // Returns the expected selection range from |dummy_text_view_|. | 235 // Returns the expected selection range from |dummy_text_view_|. |
| 233 NSRange GetExpectedSelectionRange(); | 236 NSRange GetExpectedSelectionRange(); |
| 234 | 237 |
| 235 // Set the selection range for the installed textfield and |dummy_text_view_|. | 238 // Set the selection range for the installed textfield and |dummy_text_view_|. |
| 236 void SetSelectionRange(NSRange range); | 239 void SetSelectionRange(NSRange range); |
| 237 | 240 |
| 238 // Perform command |sel| on |ns_view_| and |dummy_text_view_|. | 241 // Perform command |sel| on |ns_view_| and |dummy_text_view_|. |
| 239 void PerformCommand(SEL sel); | 242 void PerformCommand(SEL sel); |
| 240 | 243 |
| 244 // Make selection from |start| to |end| on installed textfield and |
| 245 // |dummy_text_view_|. If |start| > |end|, extend selection to left from |
| 246 // |start|. |
| 247 void MakeSelection(int start, int end); |
| 248 |
| 241 // testing::Test: | 249 // testing::Test: |
| 242 void SetUp() override; | 250 void SetUp() override; |
| 243 void TearDown() override; | 251 void TearDown() override; |
| 244 | 252 |
| 245 protected: | 253 protected: |
| 246 // Test delete to beginning of line or paragraph based on |sel|. |sel| can be | 254 // Test delete to beginning of line or paragraph based on |sel|. |sel| can be |
| 247 // either deleteToBeginningOfLine: or deleteToBeginningOfParagraph:. | 255 // either deleteToBeginningOfLine: or deleteToBeginningOfParagraph:. |
| 248 void TestDeleteBeginning(SEL sel); | 256 void TestDeleteBeginning(SEL sel); |
| 249 | 257 |
| 250 // Test delete to end of line or paragraph based on |sel|. |sel| can be | 258 // Test delete to end of line or paragraph based on |sel|. |sel| can be |
| (...skipping 13 matching lines...) Expand all Loading... |
| 264 private: | 272 private: |
| 265 DISALLOW_COPY_AND_ASSIGN(BridgedNativeWidgetTest); | 273 DISALLOW_COPY_AND_ASSIGN(BridgedNativeWidgetTest); |
| 266 }; | 274 }; |
| 267 | 275 |
| 268 BridgedNativeWidgetTest::BridgedNativeWidgetTest() { | 276 BridgedNativeWidgetTest::BridgedNativeWidgetTest() { |
| 269 } | 277 } |
| 270 | 278 |
| 271 BridgedNativeWidgetTest::~BridgedNativeWidgetTest() { | 279 BridgedNativeWidgetTest::~BridgedNativeWidgetTest() { |
| 272 } | 280 } |
| 273 | 281 |
| 282 void BridgedNativeWidgetTest::InstallTextField(const base::string16& text) { |
| 283 InstallTextField(text, ui::TEXT_INPUT_TYPE_TEXT); |
| 284 } |
| 285 |
| 286 void BridgedNativeWidgetTest::InstallTextField(const std::string& text) { |
| 287 InstallTextField(base::ASCIIToUTF16(text), ui::TEXT_INPUT_TYPE_TEXT); |
| 288 } |
| 289 |
| 274 void BridgedNativeWidgetTest::InstallTextField( | 290 void BridgedNativeWidgetTest::InstallTextField( |
| 275 const std::string& text, | 291 const base::string16& text, |
| 276 ui::TextInputType text_input_type) { | 292 ui::TextInputType text_input_type) { |
| 277 Textfield* textfield = new Textfield(); | 293 Textfield* textfield = new Textfield(); |
| 278 textfield->SetText(ASCIIToUTF16(text)); | 294 textfield->SetText(text); |
| 279 textfield->SetTextInputType(text_input_type); | 295 textfield->SetTextInputType(text_input_type); |
| 280 view_->RemoveAllChildViews(true); | 296 view_->RemoveAllChildViews(true); |
| 281 view_->AddChildView(textfield); | 297 view_->AddChildView(textfield); |
| 282 textfield->SetBoundsRect(init_params_.bounds); | 298 textfield->SetBoundsRect(init_params_.bounds); |
| 283 | 299 |
| 284 // Request focus so the InputMethod can dispatch events to the RootView, and | 300 // Request focus so the InputMethod can dispatch events to the RootView, and |
| 285 // have them delivered to the textfield. Note that focusing a textfield | 301 // have them delivered to the textfield. Note that focusing a textfield |
| 286 // schedules a task to flash the cursor, so this requires |message_loop_|. | 302 // schedules a task to flash the cursor, so this requires |message_loop_|. |
| 287 textfield->RequestFocus(); | 303 textfield->RequestFocus(); |
| 288 | 304 |
| 289 [ns_view_ setTextInputClient:textfield]; | 305 [ns_view_ setTextInputClient:textfield]; |
| 290 | 306 |
| 291 // Initialize the dummy text view. | 307 // Initialize the dummy text view. |
| 292 dummy_text_view_.reset([[NSTextView alloc] initWithFrame:NSZeroRect]); | 308 dummy_text_view_.reset([[NSTextView alloc] initWithFrame:NSZeroRect]); |
| 293 [dummy_text_view_ setString:SysUTF8ToNSString(text)]; | 309 [dummy_text_view_ setString:SysUTF16ToNSString(text)]; |
| 294 } | |
| 295 | |
| 296 void BridgedNativeWidgetTest::InstallTextField(const std::string& text) { | |
| 297 InstallTextField(text, ui::TEXT_INPUT_TYPE_TEXT); | |
| 298 } | 310 } |
| 299 | 311 |
| 300 NSString* BridgedNativeWidgetTest::GetActualText() { | 312 NSString* BridgedNativeWidgetTest::GetActualText() { |
| 301 NSRange range = NSMakeRange(0, NSUIntegerMax); | 313 NSRange range = NSMakeRange(0, NSUIntegerMax); |
| 302 return [[ns_view_ attributedSubstringForProposedRange:range | 314 return [[ns_view_ attributedSubstringForProposedRange:range |
| 303 actualRange:nullptr] string]; | 315 actualRange:nullptr] string]; |
| 304 } | 316 } |
| 305 | 317 |
| 306 NSString* BridgedNativeWidgetTest::GetExpectedText() { | 318 NSString* BridgedNativeWidgetTest::GetExpectedText() { |
| 307 NSRange range = NSMakeRange(0, NSUIntegerMax); | 319 NSRange range = NSMakeRange(0, NSUIntegerMax); |
| (...skipping 15 matching lines...) Expand all Loading... |
| 323 client->SetSelectionRange(gfx::Range(range)); | 335 client->SetSelectionRange(gfx::Range(range)); |
| 324 | 336 |
| 325 [dummy_text_view_ setSelectedRange:range]; | 337 [dummy_text_view_ setSelectedRange:range]; |
| 326 } | 338 } |
| 327 | 339 |
| 328 void BridgedNativeWidgetTest::PerformCommand(SEL sel) { | 340 void BridgedNativeWidgetTest::PerformCommand(SEL sel) { |
| 329 [ns_view_ doCommandBySelector:sel]; | 341 [ns_view_ doCommandBySelector:sel]; |
| 330 [dummy_text_view_ doCommandBySelector:sel]; | 342 [dummy_text_view_ doCommandBySelector:sel]; |
| 331 } | 343 } |
| 332 | 344 |
| 345 void BridgedNativeWidgetTest::MakeSelection(int start, int end) { |
| 346 ui::TextInputClient* client = [ns_view_ textInputClient]; |
| 347 client->SetSelectionRange(gfx::Range(start, end)); |
| 348 |
| 349 // NSTextView does not support specifying the selection "direction" i.e. the |
| 350 // leading edge of selection. Hence we extend the selection from |start| to |
| 351 // |end|. |
| 352 [dummy_text_view_ setSelectedRange:NSMakeRange(start, 0)]; |
| 353 SEL sel = start > end ? @selector(moveBackwardAndModifySelection:) |
| 354 : @selector(moveForwardAndModifySelection:); |
| 355 size_t delta = std::abs(end - start); |
| 356 |
| 357 for (size_t i = 0; i < delta; i++) |
| 358 [dummy_text_view_ doCommandBySelector:sel]; |
| 359 } |
| 360 |
| 333 void BridgedNativeWidgetTest::SetUp() { | 361 void BridgedNativeWidgetTest::SetUp() { |
| 334 BridgedNativeWidgetTestBase::SetUp(); | 362 BridgedNativeWidgetTestBase::SetUp(); |
| 335 | 363 |
| 336 view_.reset(new views::internal::RootView(widget_.get())); | 364 view_.reset(new views::internal::RootView(widget_.get())); |
| 337 base::scoped_nsobject<NSWindow> window([test_window() retain]); | 365 base::scoped_nsobject<NSWindow> window([test_window() retain]); |
| 338 | 366 |
| 339 // BridgedNativeWidget expects to be initialized with a hidden (deferred) | 367 // BridgedNativeWidget expects to be initialized with a hidden (deferred) |
| 340 // window. | 368 // window. |
| 341 [window orderOut:nil]; | 369 [window orderOut:nil]; |
| 342 EXPECT_FALSE([window delegate]); | 370 EXPECT_FALSE([window delegate]); |
| (...skipping 200 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 543 PerformInit(); | 571 PerformInit(); |
| 544 EXPECT_TRUE([window_ hasShadow]); // Preserves shadow. | 572 EXPECT_TRUE([window_ hasShadow]); // Preserves shadow. |
| 545 | 573 |
| 546 window_.reset(); | 574 window_.reset(); |
| 547 widget_.reset(); | 575 widget_.reset(); |
| 548 } | 576 } |
| 549 | 577 |
| 550 // Ensure a nil NSTextInputContext is returned when the ui::TextInputClient is | 578 // Ensure a nil NSTextInputContext is returned when the ui::TextInputClient is |
| 551 // not editable, a password field, or unset. | 579 // not editable, a password field, or unset. |
| 552 TEST_F(BridgedNativeWidgetTest, InputContext) { | 580 TEST_F(BridgedNativeWidgetTest, InputContext) { |
| 553 const std::string test_string = "test_str"; | 581 const base::string16 test_string = base::ASCIIToUTF16("test_str"); |
| 554 InstallTextField(test_string, ui::TEXT_INPUT_TYPE_PASSWORD); | 582 InstallTextField(test_string, ui::TEXT_INPUT_TYPE_PASSWORD); |
| 555 EXPECT_FALSE([ns_view_ inputContext]); | 583 EXPECT_FALSE([ns_view_ inputContext]); |
| 556 InstallTextField(test_string, ui::TEXT_INPUT_TYPE_TEXT); | 584 InstallTextField(test_string, ui::TEXT_INPUT_TYPE_TEXT); |
| 557 EXPECT_TRUE([ns_view_ inputContext]); | 585 EXPECT_TRUE([ns_view_ inputContext]); |
| 558 [ns_view_ setTextInputClient:nil]; | 586 [ns_view_ setTextInputClient:nil]; |
| 559 EXPECT_FALSE([ns_view_ inputContext]); | 587 EXPECT_FALSE([ns_view_ inputContext]); |
| 560 InstallTextField(test_string, ui::TEXT_INPUT_TYPE_NONE); | 588 InstallTextField(test_string, ui::TEXT_INPUT_TYPE_NONE); |
| 561 EXPECT_FALSE([ns_view_ inputContext]); | 589 EXPECT_FALSE([ns_view_ inputContext]); |
| 562 } | 590 } |
| 563 | 591 |
| (...skipping 302 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 866 } | 894 } |
| 867 | 895 |
| 868 TEST_F(BridgedNativeWidgetTest, TextInput_DeleteToBeginningOfParagraph) { | 896 TEST_F(BridgedNativeWidgetTest, TextInput_DeleteToBeginningOfParagraph) { |
| 869 TestDeleteBeginning(@selector(deleteToBeginningOfParagraph:)); | 897 TestDeleteBeginning(@selector(deleteToBeginningOfParagraph:)); |
| 870 } | 898 } |
| 871 | 899 |
| 872 TEST_F(BridgedNativeWidgetTest, TextInput_DeleteToEndOfParagraph) { | 900 TEST_F(BridgedNativeWidgetTest, TextInput_DeleteToEndOfParagraph) { |
| 873 TestDeleteEnd(@selector(deleteToEndOfParagraph:)); | 901 TestDeleteEnd(@selector(deleteToEndOfParagraph:)); |
| 874 } | 902 } |
| 875 | 903 |
| 904 TEST_F(BridgedNativeWidgetTest, EditingCommands) { |
| 905 NSArray* selectors = @[ |
| 906 @"moveForward:", @"moveRight:", @"moveBackward:", @"moveLeft:", @"moveUp:", |
| 907 @"moveDown:", @"moveWordForward:", @"moveWordBackward:", |
| 908 @"moveToBeginningOfLine:", @"moveToEndOfLine:", |
| 909 @"moveToBeginningOfParagraph:", @"moveToEndOfParagraph:", |
| 910 @"moveToEndOfDocument:", @"moveToBeginningOfDocument:", @"pageDown:", |
| 911 @"pageUp:", @"moveBackwardAndModifySelection:", |
| 912 @"moveForwardAndModifySelection:", |
| 913 // @"moveWordForwardAndModifySelection:", |
| 914 // @"moveWordBackwardAndModifySelection:", |
| 915 @"moveUpAndModifySelection:", @"moveDownAndModifySelection:", |
| 916 // @"moveToBeginningOfLineAndModifySelection:", |
| 917 // @"moveToEndOfLineAndModifySelection:", |
| 918 // @"moveToBeginningOfParagraphAndModifySelection:", |
| 919 // @"moveToEndOfParagraphAndModifySelection:", |
| 920 // @"moveToEndOfDocumentAndModifySelection:", |
| 921 // @"moveToBeginningOfDocumentAndModifySelection:", |
| 922 @"pageDownAndModifySelection:", @"pageUpAndModifySelection:", |
| 923 // @"moveParagraphForwardAndModifySelection:", |
| 924 // @"moveParagraphBackwardAndModifySelection:", |
| 925 @"moveWordRight:", @"moveWordLeft:", @"moveRightAndModifySelection:", |
| 926 @"moveLeftAndModifySelection:", |
| 927 // @"moveWordRightAndModifySelection:", |
| 928 // @"moveWordLeftAndModifySelection:", |
| 929 @"moveToLeftEndOfLine:", @"moveToRightEndOfLine:", |
| 930 // @"moveToLeftEndOfLineAndModifySelection:", |
| 931 // @"moveToRightEndOfLineAndModifySelection:", |
| 932 @"deleteForward:", @"deleteBackward:", @"deleteWordForward:", |
| 933 @"deleteWordBackward:", @"deleteToBeginningOfLine:", @"deleteToEndOfLine:", |
| 934 @"deleteToBeginningOfParagraph:", @"deleteToEndOfParagraph:", |
| 935 @"cancelOperation:" |
| 936 ]; |
| 937 |
| 938 struct { |
| 939 const std::wstring test_string; |
| 940 bool rtl; |
| 941 } cases[] = |
| 942 {{L"abc def", false}, |
| 943 {L"\x0634\x0632\x0630 \x064A\x062B\x0628", true}}; |
| 944 |
| 945 for (auto test_case : cases) { |
| 946 for (NSString* selector_string in selectors) { |
| 947 SEL sel = NSSelectorFromString(selector_string); |
| 948 const int len = test_case.test_string.length(); |
| 949 for (int i = 0; i <= len; i++) { |
| 950 for (int j = 0; j <= len; j++) { |
| 951 SCOPED_TRACE(base::StringPrintf( |
| 952 "Testing range [%d-%d] for case %s and selector %s\n", i, j, |
| 953 base::WideToUTF8(test_case.test_string).c_str(), |
| 954 base::SysNSStringToUTF8(selector_string).c_str())); |
| 955 |
| 956 InstallTextField(base::WideToUTF16(test_case.test_string)); |
| 957 MakeSelection(i, j); |
| 958 EXPECT_EQ_RANGE_3(NSMakeRange(std::min(i, j), std::abs(i - j)), |
| 959 GetExpectedSelectionRange(), |
| 960 GetActualSelectionRange()); |
| 961 |
| 962 PerformCommand(sel); |
| 963 EXPECT_NSEQ(GetExpectedText(), GetActualText()); |
| 964 |
| 965 // This is another case where NSTextView behaves a bit weirdly. Turns |
| 966 // out for RTL text, move[Left/Right]AndModifySelection and |
| 967 // move[Forward/Backward]AndModifySelection do not share selection |
| 968 // "direction". For eg say current rtl text is "abc|". On doing |
| 969 // moveForwardAndModifySelection: twice, text becomes "a|bc|". Now if |
| 970 // we do moveLeftAndModifySelection, text should become "|abc|" but |
| 971 // instead it becomes "a|b|c". Blink also behaves similarly to our |
| 972 // implementation in this case. |
| 973 if (!(test_case.rtl && |
| 974 ([selector_string |
| 975 isEqualToString:@"moveRightAndModifySelection:"] || |
| 976 [selector_string |
| 977 isEqualToString:@"moveLeftAndModifySelection:"]))) { |
| 978 EXPECT_EQ_RANGE(GetExpectedSelectionRange(), |
| 979 GetActualSelectionRange()); |
| 980 } |
| 981 } |
| 982 } |
| 983 } |
| 984 } |
| 985 } |
| 986 |
| 876 // Test firstRectForCharacterRange:actualRange for cases where query range is | 987 // Test firstRectForCharacterRange:actualRange for cases where query range is |
| 877 // empty or outside composition range. | 988 // empty or outside composition range. |
| 878 TEST_F(BridgedNativeWidgetTest, TextInput_FirstRectForCharacterRange_Caret) { | 989 TEST_F(BridgedNativeWidgetTest, TextInput_FirstRectForCharacterRange_Caret) { |
| 879 InstallTextField(""); | 990 InstallTextField(""); |
| 880 ui::TextInputClient* client = [ns_view_ textInputClient]; | 991 ui::TextInputClient* client = [ns_view_ textInputClient]; |
| 881 | 992 |
| 882 // No composition. Ensure bounds and range corresponding to the current caret | 993 // No composition. Ensure bounds and range corresponding to the current caret |
| 883 // position are returned. | 994 // position are returned. |
| 884 // Initially selection range will be [0, 0]. | 995 // Initially selection range will be [0, 0]. |
| 885 NSRange caret_range = NSMakeRange(0, 0); | 996 NSRange caret_range = NSMakeRange(0, 0); |
| (...skipping 144 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 1030 [center postNotificationName:NSWindowDidExitFullScreenNotification | 1141 [center postNotificationName:NSWindowDidExitFullScreenNotification |
| 1031 object:window]; | 1142 object:window]; |
| 1032 EXPECT_EQ(1, [window ignoredToggleFullScreenCount]); // No change. | 1143 EXPECT_EQ(1, [window ignoredToggleFullScreenCount]); // No change. |
| 1033 EXPECT_FALSE(bridge()->target_fullscreen_state()); | 1144 EXPECT_FALSE(bridge()->target_fullscreen_state()); |
| 1034 | 1145 |
| 1035 widget_->CloseNow(); | 1146 widget_->CloseNow(); |
| 1036 } | 1147 } |
| 1037 | 1148 |
| 1038 } // namespace test | 1149 } // namespace test |
| 1039 } // namespace views | 1150 } // namespace views |
| OLD | NEW |