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 |