| 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/controls/menu/menu_runner_impl_cocoa.h" | 5 #import "ui/views/controls/menu/menu_runner_impl_cocoa.h" |
| 6 | 6 |
| 7 #import <Cocoa/Cocoa.h> | 7 #import <Cocoa/Cocoa.h> |
| 8 | 8 |
| 9 #include "base/macros.h" | 9 #include "base/macros.h" |
| 10 #include "base/run_loop.h" | 10 #include "base/run_loop.h" |
| (...skipping 96 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 107 runner_ = NULL; | 107 runner_ = NULL; |
| 108 } | 108 } |
| 109 | 109 |
| 110 parent_->CloseNow(); | 110 parent_->CloseNow(); |
| 111 ViewsTestBase::TearDown(); | 111 ViewsTestBase::TearDown(); |
| 112 } | 112 } |
| 113 | 113 |
| 114 int IsAsync() const { return GetParam() == MenuType::VIEWS; } | 114 int IsAsync() const { return GetParam() == MenuType::VIEWS; } |
| 115 | 115 |
| 116 // Runs the menu after registering |callback| as the menu open callback. | 116 // Runs the menu after registering |callback| as the menu open callback. |
| 117 MenuRunner::RunResult RunMenu(const base::Closure& callback) { | 117 void RunMenu(const base::Closure& callback) { |
| 118 if (IsAsync()) { | 118 if (IsAsync()) { |
| 119 // Cancelling an async menu under MenuController::OpenMenuImpl() (which | 119 // Cancelling an async menu under MenuController::OpenMenuImpl() (which |
| 120 // invokes WillShowMenu()) will cause a UAF when that same function tries | 120 // invokes WillShowMenu()) will cause a UAF when that same function tries |
| 121 // to show the menu. So post a task instead. | 121 // to show the menu. So post a task instead. |
| 122 base::ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE, callback); | 122 base::ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE, callback); |
| 123 } else { | 123 } else { |
| 124 menu_->set_menu_open_callback( | 124 menu_->set_menu_open_callback( |
| 125 base::Bind(&MenuRunnerCocoaTest::RunMenuWrapperCallback, | 125 base::Bind(&MenuRunnerCocoaTest::RunMenuWrapperCallback, |
| 126 base::Unretained(this), callback)); | 126 base::Unretained(this), callback)); |
| 127 } | 127 } |
| 128 | 128 |
| 129 // Always pass ASYNC, even though native menus will be sync. | 129 runner_->RunMenuAt(parent_, nullptr, gfx::Rect(), MENU_ANCHOR_TOPLEFT, |
| 130 int run_types = MenuRunner::CONTEXT_MENU | MenuRunner::ASYNC; | 130 MenuRunner::CONTEXT_MENU); |
| 131 return MaybeRunAsync(runner_->RunMenuAt(parent_, nullptr, gfx::Rect(), | 131 MaybeRunAsync(); |
| 132 MENU_ANCHOR_TOPLEFT, run_types)); | |
| 133 } | 132 } |
| 134 | 133 |
| 135 // Runs then cancels a combobox menu and captures the frame of the anchoring | 134 // Runs then cancels a combobox menu and captures the frame of the anchoring |
| 136 // view. | 135 // view. |
| 137 MenuRunner::RunResult RunMenuAt(const gfx::Rect& anchor) { | 136 void RunMenuAt(const gfx::Rect& anchor) { |
| 138 last_anchor_frame_ = NSZeroRect; | 137 last_anchor_frame_ = NSZeroRect; |
| 139 | 138 |
| 140 // Should be one child (the compositor layer) before showing, and it should | 139 // Should be one child (the compositor layer) before showing, and it should |
| 141 // go up by one (the anchor view) while the menu is shown. | 140 // go up by one (the anchor view) while the menu is shown. |
| 142 EXPECT_EQ(1u, [[parent_->GetNativeView() subviews] count]); | 141 EXPECT_EQ(1u, [[parent_->GetNativeView() subviews] count]); |
| 143 | 142 |
| 144 base::Closure callback = | 143 base::Closure callback = |
| 145 base::Bind(&MenuRunnerCocoaTest::ComboboxRunMenuAtCallback, | 144 base::Bind(&MenuRunnerCocoaTest::ComboboxRunMenuAtCallback, |
| 146 base::Unretained(this)); | 145 base::Unretained(this)); |
| 147 if (IsAsync()) | 146 if (IsAsync()) |
| 148 base::ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE, callback); | 147 base::ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE, callback); |
| 149 else | 148 else |
| 150 menu_->set_menu_open_callback(callback); | 149 menu_->set_menu_open_callback(callback); |
| 151 | 150 |
| 152 MenuRunner::RunResult result = MaybeRunAsync( | 151 runner_->RunMenuAt(parent_, nullptr, anchor, MENU_ANCHOR_TOPLEFT, |
| 153 runner_->RunMenuAt(parent_, nullptr, anchor, MENU_ANCHOR_TOPLEFT, | 152 MenuRunner::COMBOBOX); |
| 154 MenuRunner::COMBOBOX | MenuRunner::ASYNC)); | 153 MaybeRunAsync(); |
| 155 | 154 |
| 156 // Ensure the anchor view is removed. | 155 // Ensure the anchor view is removed. |
| 157 EXPECT_EQ(1u, [[parent_->GetNativeView() subviews] count]); | 156 EXPECT_EQ(1u, [[parent_->GetNativeView() subviews] count]); |
| 158 return result; | |
| 159 } | 157 } |
| 160 | 158 |
| 161 void MenuCancelCallback() { | 159 void MenuCancelCallback() { |
| 162 runner_->Cancel(); | 160 runner_->Cancel(); |
| 163 if (IsAsync()) { | 161 if (IsAsync()) { |
| 164 // Async menus report their cancellation immediately. | 162 // Async menus report their cancellation immediately. |
| 165 EXPECT_FALSE(runner_->IsRunning()); | 163 EXPECT_FALSE(runner_->IsRunning()); |
| 166 } else { | 164 } else { |
| 167 // For a synchronous menu, MenuRunner::IsRunning() should return true | 165 // For a synchronous menu, MenuRunner::IsRunning() should return true |
| 168 // immediately after MenuRunner::Cancel() since the menu message loop has | 166 // immediately after MenuRunner::Cancel() since the menu message loop has |
| (...skipping 37 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 206 ASSERT_EQ(2u, [subviews count]); | 204 ASSERT_EQ(2u, [subviews count]); |
| 207 last_anchor_frame_ = [[subviews objectAtIndex:1] frame]; | 205 last_anchor_frame_ = [[subviews objectAtIndex:1] frame]; |
| 208 } else { | 206 } else { |
| 209 EXPECT_EQ(1u, [subviews count]); | 207 EXPECT_EQ(1u, [subviews count]); |
| 210 } | 208 } |
| 211 runner_->Cancel(); | 209 runner_->Cancel(); |
| 212 } | 210 } |
| 213 | 211 |
| 214 // Run a nested message loop so that async and sync menus can be tested the | 212 // Run a nested message loop so that async and sync menus can be tested the |
| 215 // same way. | 213 // same way. |
| 216 MenuRunner::RunResult MaybeRunAsync(MenuRunner::RunResult run_result) { | 214 void MaybeRunAsync() { |
| 217 if (!IsAsync()) | 215 if (!IsAsync()) |
| 218 return run_result; | 216 return; |
| 219 | 217 |
| 220 // Async menus should always return NORMAL_EXIT. | |
| 221 EXPECT_EQ(MenuRunner::NORMAL_EXIT, run_result); | |
| 222 base::RunLoop run_loop; | 218 base::RunLoop run_loop; |
| 223 quit_closure_ = run_loop.QuitClosure(); | 219 quit_closure_ = run_loop.QuitClosure(); |
| 224 base::ThreadTaskRunnerHandle::Get()->PostDelayedTask( | 220 base::ThreadTaskRunnerHandle::Get()->PostDelayedTask( |
| 225 FROM_HERE, quit_closure_, TestTimeouts::action_timeout()); | 221 FROM_HERE, quit_closure_, TestTimeouts::action_timeout()); |
| 226 run_loop.Run(); | 222 run_loop.Run(); |
| 227 | 223 |
| 228 // |quit_closure_| should be run by QuitAsyncRunLoop(), not the timeout. | 224 // |quit_closure_| should be run by QuitAsyncRunLoop(), not the timeout. |
| 229 EXPECT_TRUE(quit_closure_.is_null()); | 225 EXPECT_TRUE(quit_closure_.is_null()); |
| 230 return run_result; | |
| 231 } | 226 } |
| 232 | 227 |
| 233 void QuitAsyncRunLoop() { | 228 void QuitAsyncRunLoop() { |
| 234 if (!IsAsync()) { | 229 if (!IsAsync()) { |
| 235 EXPECT_TRUE(quit_closure_.is_null()); | 230 EXPECT_TRUE(quit_closure_.is_null()); |
| 236 return; | 231 return; |
| 237 } | 232 } |
| 238 ASSERT_FALSE(quit_closure_.is_null()); | 233 ASSERT_FALSE(quit_closure_.is_null()); |
| 239 quit_closure_.Run(); | 234 quit_closure_.Run(); |
| 240 quit_closure_.Reset(); | 235 quit_closure_.Reset(); |
| 241 } | 236 } |
| 242 | 237 |
| 243 void MenuCloseCallback() { | 238 void MenuCloseCallback() { |
| 244 ++menu_close_count_; | 239 ++menu_close_count_; |
| 245 QuitAsyncRunLoop(); | 240 QuitAsyncRunLoop(); |
| 246 } | 241 } |
| 247 | 242 |
| 248 base::Closure quit_closure_; | 243 base::Closure quit_closure_; |
| 249 | 244 |
| 250 DISALLOW_COPY_AND_ASSIGN(MenuRunnerCocoaTest); | 245 DISALLOW_COPY_AND_ASSIGN(MenuRunnerCocoaTest); |
| 251 }; | 246 }; |
| 252 | 247 |
| 253 TEST_P(MenuRunnerCocoaTest, RunMenuAndCancel) { | 248 TEST_P(MenuRunnerCocoaTest, RunMenuAndCancel) { |
| 254 base::TimeTicks min_time = ui::EventTimeForNow(); | 249 base::TimeTicks min_time = ui::EventTimeForNow(); |
| 255 | 250 |
| 256 MenuRunner::RunResult result = RunMenu(base::Bind( | 251 RunMenu(base::Bind(&MenuRunnerCocoaTest::MenuCancelCallback, |
| 257 &MenuRunnerCocoaTest::MenuCancelCallback, base::Unretained(this))); | 252 base::Unretained(this))); |
| 258 | 253 |
| 259 EXPECT_EQ(1, menu_close_count_); | 254 EXPECT_EQ(1, menu_close_count_); |
| 260 EXPECT_EQ(MenuRunner::NORMAL_EXIT, result); | |
| 261 EXPECT_FALSE(runner_->IsRunning()); | 255 EXPECT_FALSE(runner_->IsRunning()); |
| 262 | 256 |
| 263 if (GetParam() == MenuType::VIEWS) { | 257 if (GetParam() == MenuType::VIEWS) { |
| 264 // MenuItemView's MenuRunnerImpl gets the closing time from MenuController:: | 258 // MenuItemView's MenuRunnerImpl gets the closing time from MenuController:: |
| 265 // closing_event_time(). This is is reset on show, but only updated when an | 259 // closing_event_time(). This is is reset on show, but only updated when an |
| 266 // event closes the menu -- not a cancellation. | 260 // event closes the menu -- not a cancellation. |
| 267 EXPECT_EQ(runner_->GetClosingEventTime(), base::TimeTicks()); | 261 EXPECT_EQ(runner_->GetClosingEventTime(), base::TimeTicks()); |
| 268 } else { | 262 } else { |
| 269 EXPECT_GE(runner_->GetClosingEventTime(), min_time); | 263 EXPECT_GE(runner_->GetClosingEventTime(), min_time); |
| 270 } | 264 } |
| 271 EXPECT_LE(runner_->GetClosingEventTime(), ui::EventTimeForNow()); | 265 EXPECT_LE(runner_->GetClosingEventTime(), ui::EventTimeForNow()); |
| 272 | 266 |
| 273 // Cancel again. | 267 // Cancel again. |
| 274 runner_->Cancel(); | 268 runner_->Cancel(); |
| 275 EXPECT_FALSE(runner_->IsRunning()); | 269 EXPECT_FALSE(runner_->IsRunning()); |
| 276 EXPECT_EQ(1, menu_close_count_); | 270 EXPECT_EQ(1, menu_close_count_); |
| 277 } | 271 } |
| 278 | 272 |
| 279 TEST_P(MenuRunnerCocoaTest, RunMenuAndDelete) { | 273 TEST_P(MenuRunnerCocoaTest, RunMenuAndDelete) { |
| 280 MenuRunner::RunResult result = RunMenu(base::Bind( | 274 RunMenu(base::Bind(&MenuRunnerCocoaTest::MenuDeleteCallback, |
| 281 &MenuRunnerCocoaTest::MenuDeleteCallback, base::Unretained(this))); | 275 base::Unretained(this))); |
| 282 // Note the close callback is NOT invoked for deleted menus. | 276 // Note the close callback is NOT invoked for deleted menus. |
| 283 EXPECT_EQ(0, menu_close_count_); | 277 EXPECT_EQ(0, menu_close_count_); |
| 284 | |
| 285 // Async menus always return NORMAL from RunMenuAt(). | |
| 286 if (GetParam() == MenuType::VIEWS) | |
| 287 EXPECT_EQ(MenuRunner::NORMAL_EXIT, result); | |
| 288 else | |
| 289 EXPECT_EQ(MenuRunner::MENU_DELETED, result); | |
| 290 } | 278 } |
| 291 | 279 |
| 292 // Ensure a menu can be safely released immediately after a call to Cancel() in | 280 // Ensure a menu can be safely released immediately after a call to Cancel() in |
| 293 // the same run loop iteration. | 281 // the same run loop iteration. |
| 294 TEST_P(MenuRunnerCocoaTest, DestroyAfterCanceling) { | 282 TEST_P(MenuRunnerCocoaTest, DestroyAfterCanceling) { |
| 295 MenuRunner::RunResult result = | 283 RunMenu(base::Bind(&MenuRunnerCocoaTest::MenuCancelAndDeleteCallback, |
| 296 RunMenu(base::Bind(&MenuRunnerCocoaTest::MenuCancelAndDeleteCallback, | 284 base::Unretained(this))); |
| 297 base::Unretained(this))); | |
| 298 | 285 |
| 299 if (IsAsync()) { | 286 if (IsAsync()) { |
| 300 EXPECT_EQ(MenuRunner::NORMAL_EXIT, result); | |
| 301 EXPECT_EQ(1, menu_close_count_); | 287 EXPECT_EQ(1, menu_close_count_); |
| 302 } else { | 288 } else { |
| 303 EXPECT_EQ(MenuRunner::MENU_DELETED, result); | |
| 304 // For a synchronous menu, the deletion happens before the cancel can be | 289 // For a synchronous menu, the deletion happens before the cancel can be |
| 305 // processed, so the close callback will not be invoked. | 290 // processed, so the close callback will not be invoked. |
| 306 EXPECT_EQ(0, menu_close_count_); | 291 EXPECT_EQ(0, menu_close_count_); |
| 307 } | 292 } |
| 308 } | 293 } |
| 309 | 294 |
| 310 TEST_P(MenuRunnerCocoaTest, RunMenuTwice) { | 295 TEST_P(MenuRunnerCocoaTest, RunMenuTwice) { |
| 311 for (int i = 0; i < 2; ++i) { | 296 for (int i = 0; i < 2; ++i) { |
| 312 MenuRunner::RunResult result = RunMenu(base::Bind( | 297 RunMenu(base::Bind(&MenuRunnerCocoaTest::MenuCancelCallback, |
| 313 &MenuRunnerCocoaTest::MenuCancelCallback, base::Unretained(this))); | 298 base::Unretained(this))); |
| 314 EXPECT_EQ(MenuRunner::NORMAL_EXIT, result); | |
| 315 EXPECT_FALSE(runner_->IsRunning()); | 299 EXPECT_FALSE(runner_->IsRunning()); |
| 316 EXPECT_EQ(i + 1, menu_close_count_); | 300 EXPECT_EQ(i + 1, menu_close_count_); |
| 317 } | 301 } |
| 318 } | 302 } |
| 319 | 303 |
| 320 TEST_P(MenuRunnerCocoaTest, CancelWithoutRunning) { | 304 TEST_P(MenuRunnerCocoaTest, CancelWithoutRunning) { |
| 321 runner_->Cancel(); | 305 runner_->Cancel(); |
| 322 EXPECT_FALSE(runner_->IsRunning()); | 306 EXPECT_FALSE(runner_->IsRunning()); |
| 323 EXPECT_EQ(base::TimeTicks(), runner_->GetClosingEventTime()); | 307 EXPECT_EQ(base::TimeTicks(), runner_->GetClosingEventTime()); |
| 324 EXPECT_EQ(0, menu_close_count_); | 308 EXPECT_EQ(0, menu_close_count_); |
| (...skipping 53 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 378 EXPECT_EQ(combobox_rect.right(), last_anchor_frame_.origin.x); | 362 EXPECT_EQ(combobox_rect.right(), last_anchor_frame_.origin.x); |
| 379 } | 363 } |
| 380 | 364 |
| 381 INSTANTIATE_TEST_CASE_P(, | 365 INSTANTIATE_TEST_CASE_P(, |
| 382 MenuRunnerCocoaTest, | 366 MenuRunnerCocoaTest, |
| 383 ::testing::Values(MenuType::NATIVE, MenuType::VIEWS), | 367 ::testing::Values(MenuType::NATIVE, MenuType::VIEWS), |
| 384 &MenuTypeToString); | 368 &MenuTypeToString); |
| 385 | 369 |
| 386 } // namespace test | 370 } // namespace test |
| 387 } // namespace views | 371 } // namespace views |
| OLD | NEW |