1 // Copyright 2018 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "chrome/browser/ui/views/toolbar/browser_actions_container.h"
6
7 #include <stddef.h>
8
9 #include "chrome/browser/extensions/api/extension_action/extension_action_api.h"
10 #include "chrome/browser/extensions/extension_context_menu_model.h"
11 #include "chrome/browser/ui/browser_window.h"
12 #include "chrome/browser/ui/extensions/extension_action_test_helper.h"
13 #include "chrome/browser/ui/toolbar/browser_actions_bar_browsertest.h"
14 #include "chrome/browser/ui/toolbar/toolbar_action_view_controller.h"
15 #include "chrome/browser/ui/toolbar/toolbar_actions_model.h"
16 #include "chrome/browser/ui/views/extensions/browser_action_drag_data.h"
17 #include "chrome/browser/ui/views/frame/browser_view.h"
18 #include "chrome/browser/ui/views/toolbar/toolbar_action_view.h"
19 #include "chrome/browser/ui/views/toolbar/toolbar_view.h"
20 #include "content/public/test/browser_test.h"
21 #include "extensions/browser/extension_prefs.h"
22 #include "extensions/browser/extension_registry.h"
23 #include "extensions/common/extension.h"
24 #include "ui/base/dragdrop/drop_target_event.h"
25 #include "ui/base/dragdrop/os_exchange_data.h"
26 #include "ui/gfx/geometry/point.h"
27 #include "ui/views/controls/resize_area.h"
28 #include "ui/views/test/test_views.h"
29 #include "ui/views/view.h"
30
31 // TODO(devlin): Continue moving any tests that should be platform independent
32 // from this file to the crossplatform tests in
33 // chrome/browser/ui/toolbar/browser_actions_bar_browsertest.cc.
34
35 // Test that dragging browser actions works, and that dragging a browser action
36 // from the overflow menu results in it "popping" out (growing the container
37 // size by 1), rather than just reordering the extensions.
38
IN_PROC_BROWSER_TEST_F(BrowserActionsBarBrowserTest,DragBrowserActions)39 IN_PROC_BROWSER_TEST_F(BrowserActionsBarBrowserTest, DragBrowserActions) {
40 LoadExtensions();
41
42 // Sanity check: All extensions showing; order is A B C.
43 RunScheduledLayouts();
44 EXPECT_EQ(3, browser_actions_bar()->VisibleBrowserActions());
45 EXPECT_EQ(3, browser_actions_bar()->NumberOfBrowserActions());
46 EXPECT_EQ(extension_a()->id(), browser_actions_bar()->GetExtensionId(0));
47 EXPECT_EQ(extension_b()->id(), browser_actions_bar()->GetExtensionId(1));
48 EXPECT_EQ(extension_c()->id(), browser_actions_bar()->GetExtensionId(2));
49
50 BrowserActionsContainer* container =
51 BrowserView::GetBrowserViewForBrowser(browser())
52 ->toolbar()->browser_actions();
53
54 // The order of the child views should be the same.
55 const auto& children = container->children();
56 EXPECT_EQ(container->GetViewForId(extension_a()->id()), children[0]);
57 EXPECT_EQ(container->GetViewForId(extension_b()->id()), children[1]);
58 EXPECT_EQ(container->GetViewForId(extension_c()->id()), children[2]);
59
60 // Simulate a drag and drop to the right.
61 ui::OSExchangeData drop_data;
62 // Drag extension A from index 0...
63 BrowserActionDragData browser_action_drag_data(extension_a()->id(), 0u);
64 browser_action_drag_data.Write(profile(), &drop_data);
65 ToolbarActionView* view = container->GetViewForId(extension_b()->id());
66 // ...to the right of extension B.
67 gfx::PointF location(view->x() + view->width(), view->y());
68 ui::DropTargetEvent target_event(
69 drop_data, location, location, ui::DragDropTypes::DRAG_MOVE);
70
71 // Drag and drop.
72 container->OnDragUpdated(target_event);
73 container->OnPerformDrop(target_event);
74
75 // The order should now be B A C, since A was dragged to the right of B.
76 EXPECT_EQ(extension_b()->id(), browser_actions_bar()->GetExtensionId(0));
77 EXPECT_EQ(extension_a()->id(), browser_actions_bar()->GetExtensionId(1));
78 EXPECT_EQ(extension_c()->id(), browser_actions_bar()->GetExtensionId(2));
79 EXPECT_EQ(container->GetViewForId(extension_b()->id()), children[0]);
80 EXPECT_EQ(container->GetViewForId(extension_a()->id()), children[1]);
81 EXPECT_EQ(container->GetViewForId(extension_c()->id()), children[2]);
82
83 const extensions::ExtensionSet& extension_set =
84 extensions::ExtensionRegistry::Get(profile())->enabled_extensions();
85 const std::vector<ToolbarActionsModel::ActionId>& toolbar_action_ids =
86 toolbar_model()->action_ids();
87
88 // This order should be reflected in the underlying model.
89 EXPECT_EQ(extension_b(), extension_set.GetByID(toolbar_action_ids[0]));
90 EXPECT_EQ(extension_a(), extension_set.GetByID(toolbar_action_ids[1]));
91 EXPECT_EQ(extension_c(), extension_set.GetByID(toolbar_action_ids[2]));
92
93 // Simulate a drag and drop to the left.
94 ui::OSExchangeData drop_data2;
95 // Drag extension A from index 1...
96 BrowserActionDragData browser_action_drag_data2(extension_a()->id(), 1u);
97 browser_action_drag_data2.Write(profile(), &drop_data2);
98 // ...to the left of extension B (which is now at index 0).
99 location = gfx::PointF(view->x(), view->y());
100 ui::DropTargetEvent target_event2(
101 drop_data2, location, location, ui::DragDropTypes::DRAG_MOVE);
102
103 // Drag and drop.
104 container->OnDragUpdated(target_event2);
105 container->OnPerformDrop(target_event2);
106
107 // Order should be restored to A B C.
108 EXPECT_EQ(extension_a()->id(), browser_actions_bar()->GetExtensionId(0));
109 EXPECT_EQ(extension_b()->id(), browser_actions_bar()->GetExtensionId(1));
110 EXPECT_EQ(extension_c()->id(), browser_actions_bar()->GetExtensionId(2));
111 EXPECT_EQ(container->GetViewForId(extension_a()->id()), children[0]);
112 EXPECT_EQ(container->GetViewForId(extension_b()->id()), children[1]);
113 EXPECT_EQ(container->GetViewForId(extension_c()->id()), children[2]);
114
115 // Shrink the size of the container so we have an overflow menu.
116 toolbar_model()->SetVisibleIconCount(2u);
117 RunScheduledLayouts();
118 EXPECT_EQ(2u, container->VisibleBrowserActions());
119
120 // Simulate a drag and drop from the overflow menu.
121 ui::OSExchangeData drop_data3;
122 // Drag extension C from index 2 (in the overflow menu)...
123 BrowserActionDragData browser_action_drag_data3(extension_c()->id(), 2u);
124 browser_action_drag_data3.Write(profile(), &drop_data3);
125 // ...to the left of extension B (which is back in index 1 on the main bar).
126 location = gfx::PointF(view->x(), view->y());
127 ui::DropTargetEvent target_event3(
128 drop_data3, location, location, ui::DragDropTypes::DRAG_MOVE);
129
130 // Drag and drop.
131 container->OnDragUpdated(target_event3);
132 container->OnPerformDrop(target_event3);
133
134 // The order should have changed *and* the container should have grown to
135 // accommodate extension C. The new order should be A C B, and all three
136 // extensions should be visible, with no overflow menu.
137 EXPECT_EQ(extension_a()->id(), browser_actions_bar()->GetExtensionId(0));
138 EXPECT_EQ(extension_c()->id(), browser_actions_bar()->GetExtensionId(1));
139 EXPECT_EQ(extension_b()->id(), browser_actions_bar()->GetExtensionId(2));
140 RunScheduledLayouts();
141 EXPECT_EQ(3u, container->VisibleBrowserActions());
142 EXPECT_TRUE(toolbar_model()->all_icons_visible());
143 }
144
145 // Test that changes performed in one container affect containers in other
146 // windows so that it is consistent.
IN_PROC_BROWSER_TEST_F(BrowserActionsBarBrowserTest,MultipleWindows)147 IN_PROC_BROWSER_TEST_F(BrowserActionsBarBrowserTest, MultipleWindows) {
148 LoadExtensions();
149
150 BrowserActionsContainer* first_container =
151 BrowserView::GetBrowserViewForBrowser(browser())
152 ->toolbar()
153 ->browser_actions();
154
155 // Create a second browser.
156 Browser* second_browser =
157 Browser::Create(Browser::CreateParams(profile(), true));
158 BrowserActionsContainer* second_container =
159 BrowserView::GetBrowserViewForBrowser(second_browser)
160 ->toolbar()
161 ->browser_actions();
162
163 // Both containers should have the same order and visible actions, which
164 // is right now A B C.
165 RunScheduledLayouts();
166 EXPECT_EQ(3u, first_container->VisibleBrowserActions());
167 EXPECT_EQ(3u, second_container->VisibleBrowserActions());
168 EXPECT_EQ(extension_a()->id(), first_container->GetIdAt(0u));
169 EXPECT_EQ(extension_a()->id(), second_container->GetIdAt(0u));
170 EXPECT_EQ(extension_b()->id(), first_container->GetIdAt(1u));
171 EXPECT_EQ(extension_b()->id(), second_container->GetIdAt(1u));
172 EXPECT_EQ(extension_c()->id(), first_container->GetIdAt(2u));
173 EXPECT_EQ(extension_c()->id(), second_container->GetIdAt(2u));
174
175 // Simulate a drag and drop to the right.
176 ui::OSExchangeData drop_data;
177 // Drag extension A from index 0...
178 BrowserActionDragData browser_action_drag_data(extension_a()->id(), 0u);
179 browser_action_drag_data.Write(profile(), &drop_data);
180 ToolbarActionView* view = first_container->GetViewForId(extension_b()->id());
181 // ...to the right of extension B.
182 gfx::PointF location(view->x() + view->width(), view->y());
183 ui::DropTargetEvent target_event(
184 drop_data, location, location, ui::DragDropTypes::DRAG_MOVE);
185
186 // Drag and drop.
187 first_container->toolbar_actions_bar()->OnDragStarted(0u);
188 first_container->OnDragUpdated(target_event);
189 RunScheduledLayouts();
190
191 // Semi-random placement for a regression test for crbug.com/539744.
192 first_container->OnPerformDrop(target_event);
193 RunScheduledLayouts();
194
195 // The new order, B A C, should be reflected in *both* containers, even
196 // though the drag only happened in the first one.
197 EXPECT_EQ(extension_b()->id(), first_container->GetIdAt(0u));
198 EXPECT_EQ(extension_b()->id(), second_container->GetIdAt(0u));
199 EXPECT_EQ(extension_a()->id(), first_container->GetIdAt(1u));
200 EXPECT_EQ(extension_a()->id(), second_container->GetIdAt(1u));
201 EXPECT_EQ(extension_c()->id(), first_container->GetIdAt(2u));
202 EXPECT_EQ(extension_c()->id(), second_container->GetIdAt(2u));
203
204 // Next, simulate a resize by shrinking the container.
205 first_container->OnResize(1, true);
206 // The first and second container should each have resized.
207 RunScheduledLayouts();
208 EXPECT_EQ(2u, first_container->VisibleBrowserActions());
209 EXPECT_EQ(2u, second_container->VisibleBrowserActions());
210 }
211
212 // Test that the BrowserActionsContainer responds correctly when the underlying
213 // model enters highlight mode, and that browser actions are undraggable in
214 // highlight mode. (Highlight mode itself it tested more thoroughly in the
215 // ToolbarActionsModel browsertests).
IN_PROC_BROWSER_TEST_F(BrowserActionsBarBrowserTest,HighlightMode)216 IN_PROC_BROWSER_TEST_F(BrowserActionsBarBrowserTest, HighlightMode) {
217 LoadExtensions();
218
219 BrowserActionsContainer* const container =
220 BrowserView::GetBrowserViewForBrowser(browser())
221 ->toolbar_button_provider()
222 ->GetBrowserActionsContainer();
223 auto container_can_be_resized = [container]() {
224 // The container can only be resized if we can start a drag for the view.
225 EXPECT_GE(container->num_toolbar_actions(), 1u);
226 ToolbarActionView* action_view = container->GetToolbarActionViewAt(0);
227 gfx::Point point(action_view->x(), action_view->y());
228 return container->CanStartDragForView(action_view, point, point);
229 };
230
231 RunScheduledLayouts();
232 EXPECT_EQ(3, browser_actions_bar()->VisibleBrowserActions());
233 EXPECT_EQ(3, browser_actions_bar()->NumberOfBrowserActions());
234 EXPECT_TRUE(container_can_be_resized());
235
236 std::vector<std::string> action_ids;
237 action_ids.push_back(extension_a()->id());
238 action_ids.push_back(extension_b()->id());
239 toolbar_model()->HighlightActions(action_ids,
240 ToolbarActionsModel::HIGHLIGHT_WARNING);
241
242 // Only two browser actions should be visible.
243 RunScheduledLayouts();
244 EXPECT_EQ(2, browser_actions_bar()->VisibleBrowserActions());
245 EXPECT_EQ(2, browser_actions_bar()->NumberOfBrowserActions());
246
247 // We shouldn't be able to drag in highlight mode.
248 EXPECT_FALSE(container_can_be_resized());
249
250 // We should go back to normal after leaving highlight mode.
251 toolbar_model()->StopHighlighting();
252 RunScheduledLayouts();
253 EXPECT_EQ(3, browser_actions_bar()->VisibleBrowserActions());
254 EXPECT_EQ(3, browser_actions_bar()->NumberOfBrowserActions());
255 EXPECT_TRUE(container_can_be_resized());
256 }
257
258 namespace {
259
260 // Wraps an existing browser actions container (BAC) delegate and forwards all
261 // calls to it, except for reporting the max width available to the BAC, which
262 // it intercepts. Injected into a live BAC for testing.
263 class ForwardingDelegate : public BrowserActionsContainer::Delegate {
264 public:
265 explicit ForwardingDelegate(BrowserActionsContainer::Delegate* forward_to);
266 ~ForwardingDelegate() override = default;
267
forward_to()268 BrowserActionsContainer::Delegate* forward_to() { return forward_to_; }
set_max_browser_actions_width(const base::Optional<int> & max_browser_actions_width)269 void set_max_browser_actions_width(
270 const base::Optional<int>& max_browser_actions_width) {
271 max_browser_actions_width_ = max_browser_actions_width;
272 }
273
274 protected:
275 // BrowserActionsContainer::Delegate:
276 views::LabelButton* GetOverflowReferenceView() override;
277 std::unique_ptr<ToolbarActionsBar> CreateToolbarActionsBar(
278 ToolbarActionsBarDelegate* delegate,
279 Browser* browser,
280 ToolbarActionsBar* main_bar) const override;
281 base::Optional<int> GetMaxBrowserActionsWidth() const override;
282
283 private:
284 BrowserActionsContainer::Delegate* const forward_to_;
285 base::Optional<int> max_browser_actions_width_;
286 };
287
ForwardingDelegate(BrowserActionsContainer::Delegate * forward_to)288 ForwardingDelegate::ForwardingDelegate(
289 BrowserActionsContainer::Delegate* forward_to)
290 : forward_to_(forward_to),
291 max_browser_actions_width_(forward_to->GetMaxBrowserActionsWidth()) {}
292
GetOverflowReferenceView()293 views::LabelButton* ForwardingDelegate::GetOverflowReferenceView() {
294 return forward_to_->GetOverflowReferenceView();
295 }
296
CreateToolbarActionsBar(ToolbarActionsBarDelegate * delegate,Browser * browser,ToolbarActionsBar * main_bar) const297 std::unique_ptr<ToolbarActionsBar> ForwardingDelegate::CreateToolbarActionsBar(
298 ToolbarActionsBarDelegate* delegate,
299 Browser* browser,
300 ToolbarActionsBar* main_bar) const {
301 return forward_to_->CreateToolbarActionsBar(delegate, browser, main_bar);
302 }
303
GetMaxBrowserActionsWidth() const304 base::Optional<int> ForwardingDelegate::GetMaxBrowserActionsWidth() const {
305 return max_browser_actions_width_;
306 }
307
308 } // namespace
309
310 // Contains (mostly regression) tests that rely on direct access to the browser
311 // action container's internal members. This test fixture is a friend of
312 // BrowserActionContainer.
313 class BrowserActionsContainerBrowserTest : public BrowserActionsBarBrowserTest {
314 public:
315 BrowserActionsContainerBrowserTest() = default;
316 BrowserActionsContainerBrowserTest(
317 const BrowserActionsContainerBrowserTest&) = delete;
318 BrowserActionsContainerBrowserTest& operator=(
319 const BrowserActionsContainerBrowserTest&) = delete;
320 ~BrowserActionsContainerBrowserTest() override = default;
321
test_delegate()322 ForwardingDelegate* test_delegate() { return test_delegate_.get(); }
323
324 views::ResizeArea* GetResizeArea();
325 void UpdateResizeArea();
326 int GetMinimumSize();
327 int GetMaximumSize();
328
329 protected:
330 // BrowserActionsBarBrowserTest:
331 void SetUpOnMainThread() override;
332 void TearDownOnMainThread() override;
333
334 private:
335 BrowserActionsContainer* GetContainer();
336
337 std::unique_ptr<ForwardingDelegate> test_delegate_;
338 };
339
GetResizeArea()340 views::ResizeArea* BrowserActionsContainerBrowserTest::GetResizeArea() {
341 return GetContainer()->resize_area_;
342 }
343
UpdateResizeArea()344 void BrowserActionsContainerBrowserTest::UpdateResizeArea() {
345 return GetContainer()->UpdateResizeArea();
346 }
347
GetMinimumSize()348 int BrowserActionsContainerBrowserTest::GetMinimumSize() {
349 return GetContainer()->GetWidthForIconCount(1);
350 }
351
GetMaximumSize()352 int BrowserActionsContainerBrowserTest::GetMaximumSize() {
353 return GetContainer()->GetWidthWithAllActionsVisible();
354 }
355
SetUpOnMainThread()356 void BrowserActionsContainerBrowserTest::SetUpOnMainThread() {
357 BrowserActionsBarBrowserTest::SetUpOnMainThread();
358 BrowserActionsContainer* const container = GetContainer();
359 // Create and inject a test delegate. We need to do const-fu because in the
360 // production code the delegate is (rightly) a const pointer.
361 test_delegate_ = std::make_unique<ForwardingDelegate>(container->delegate_);
362 *const_cast<BrowserActionsContainer::Delegate**>(&container->delegate_) =
363 test_delegate_.get();
364 LoadExtensions();
365 }
366
TearDownOnMainThread()367 void BrowserActionsContainerBrowserTest::TearDownOnMainThread() {
368 if (test_delegate_) {
369 BrowserActionsContainer* const container = GetContainer();
370 // De-inject the test delegate. We need to do const-fu because in the
371 // production code the delegate is (rightly) a const pointer.
372 *const_cast<BrowserActionsContainer::Delegate**>(&container->delegate_) =
373 test_delegate_->forward_to();
374 test_delegate_.reset();
375 }
376 BrowserActionsBarBrowserTest::TearDownOnMainThread();
377 }
378
GetContainer()379 BrowserActionsContainer* BrowserActionsContainerBrowserTest::GetContainer() {
380 return BrowserView::GetBrowserViewForBrowser(browser())
381 ->toolbar()
382 ->browser_actions();
383 }
384
IN_PROC_BROWSER_TEST_F(BrowserActionsContainerBrowserTest,CanResize_InHighlightMode)385 IN_PROC_BROWSER_TEST_F(BrowserActionsContainerBrowserTest,
386 CanResize_InHighlightMode) {
387 views::ResizeArea* const resize_area = GetResizeArea();
388 UpdateResizeArea();
389
390 // Resize area should be enabled by default.
391 EXPECT_TRUE(resize_area->GetEnabled());
392
393 std::vector<std::string> action_ids;
394 action_ids.push_back(extension_a()->id());
395 action_ids.push_back(extension_b()->id());
396 toolbar_model()->HighlightActions(action_ids,
397 ToolbarActionsModel::HIGHLIGHT_WARNING);
398
399 UpdateResizeArea();
400
401 // Resize area is disabled in highlight mode.
402 EXPECT_FALSE(resize_area->GetEnabled());
403 }
404
IN_PROC_BROWSER_TEST_F(BrowserActionsContainerBrowserTest,CanResize_AtMinimumWidth)405 IN_PROC_BROWSER_TEST_F(BrowserActionsContainerBrowserTest,
406 CanResize_AtMinimumWidth) {
407 views::ResizeArea* const resize_area = GetResizeArea();
408 UpdateResizeArea();
409
410 // Resize area should be enabled by default.
411 EXPECT_TRUE(resize_area->GetEnabled());
412
413 // Resize area should be enabled when there is enough space for one icon.
414 const int required_space = GetMinimumSize();
415 test_delegate()->set_max_browser_actions_width(required_space);
416 UpdateResizeArea();
417 EXPECT_TRUE(resize_area->GetEnabled());
418 }
419
IN_PROC_BROWSER_TEST_F(BrowserActionsContainerBrowserTest,CanResize_AboveMaximumWidth)420 IN_PROC_BROWSER_TEST_F(BrowserActionsContainerBrowserTest,
421 CanResize_AboveMaximumWidth) {
422 views::ResizeArea* const resize_area = GetResizeArea();
423 UpdateResizeArea();
424
425 // Resize area should be enabled by default.
426 EXPECT_TRUE(resize_area->GetEnabled());
427
428 // Resize area should be enabled when there is more than the maximum space
429 // requested.
430 const int max_space = GetMaximumSize();
431 test_delegate()->set_max_browser_actions_width(max_space + 1);
432 UpdateResizeArea();
433 EXPECT_TRUE(resize_area->GetEnabled());
434
435 // Resize area should remain enabled when the space shrinks to the minimum
436 // required.
437 const int required_space = GetMinimumSize();
438 test_delegate()->set_max_browser_actions_width(required_space);
439 UpdateResizeArea();
440 EXPECT_TRUE(resize_area->GetEnabled());
441 }
442
IN_PROC_BROWSER_TEST_F(BrowserActionsContainerBrowserTest,CannotResize_AtZeroWidth)443 IN_PROC_BROWSER_TEST_F(BrowserActionsContainerBrowserTest,
444 CannotResize_AtZeroWidth) {
445 views::ResizeArea* const resize_area = GetResizeArea();
446 UpdateResizeArea();
447
448 // Resize area should be enabled by default.
449 EXPECT_TRUE(resize_area->GetEnabled());
450
451 // Resize area should be disabled when there is zero space available.
452 test_delegate()->set_max_browser_actions_width(0);
453 UpdateResizeArea();
454 EXPECT_FALSE(resize_area->GetEnabled());
455
456 // Resize area should be re-enabled when there is enough space.
457 const int required_space = GetMinimumSize();
458 test_delegate()->set_max_browser_actions_width(required_space);
459 UpdateResizeArea();
460 EXPECT_TRUE(resize_area->GetEnabled());
461 }
462
IN_PROC_BROWSER_TEST_F(BrowserActionsContainerBrowserTest,CannotResize_BelowMinimumWidth)463 IN_PROC_BROWSER_TEST_F(BrowserActionsContainerBrowserTest,
464 CannotResize_BelowMinimumWidth) {
465 views::ResizeArea* const resize_area = GetResizeArea();
466 UpdateResizeArea();
467
468 // Resize area should be enabled by default.
469 EXPECT_TRUE(resize_area->GetEnabled());
470
471 // Resize area should be disabled when there is less than the minimum space
472 // for one icon.
473 const int required_space = GetMinimumSize();
474 test_delegate()->set_max_browser_actions_width(required_space - 1);
475 UpdateResizeArea();
476 EXPECT_FALSE(resize_area->GetEnabled());
477
478 // Resize area should be re-enabled when there is enough space.
479 test_delegate()->set_max_browser_actions_width(required_space);
480 UpdateResizeArea();
481 EXPECT_TRUE(resize_area->GetEnabled());
482 }
483
484 // Test the behavior of the overflow container for Extension Actions.
485 class BrowserActionsContainerOverflowTest
486 : public BrowserActionsBarBrowserTest {
487 public:
BrowserActionsContainerOverflowTest()488 BrowserActionsContainerOverflowTest() : main_bar_(nullptr),
489 overflow_bar_(nullptr) {
490 }
491 BrowserActionsContainerOverflowTest(
492 const BrowserActionsContainerOverflowTest&) = delete;
493 BrowserActionsContainerOverflowTest& operator=(
494 const BrowserActionsContainerOverflowTest&) = delete;
495 ~BrowserActionsContainerOverflowTest() override = default;
496
497 protected:
498 // Icon visibility is updated on layout, but layouts happen
499 // asynchronously in the production browser. Force any pending layout
500 // to happen immediately.
501 void UpdateUi();
502
503 // Returns true if the order of the ToolbarActionViews in |main_bar_|
504 // and |overflow_bar_| match.
505 bool ViewOrdersMatch();
506
507 // Returns Success if the visible count matches |expected_visible|. This means
508 // that the number of visible browser actions in |main_bar_| is
509 // |expected_visible| and shows the first icons, and that the overflow bar
510 // shows all (and only) the remainder.
511 testing::AssertionResult VerifyVisibleCount(size_t expected_visible)
512 WARN_UNUSED_RESULT;
513
514 // Accessors.
main_bar()515 BrowserActionsContainer* main_bar() { return main_bar_; }
overflow_bar()516 BrowserActionsContainer* overflow_bar() { return overflow_bar_; }
517
518 private:
519 void SetUpOnMainThread() override;
520 void TearDownOnMainThread() override;
521
522 // The main BrowserActionsContainer (owned by the browser view).
523 BrowserActionsContainer* main_bar_;
524
525 // A parent view for the overflow menu.
526 std::unique_ptr<views::View> overflow_parent_;
527
528 // The overflow BrowserActionsContainer. We manufacture this so that we don't
529 // have to open the app menu.
530 // Owned by the |overflow_parent_|.
531 BrowserActionsContainer* overflow_bar_;
532 };
533
SetUpOnMainThread()534 void BrowserActionsContainerOverflowTest::SetUpOnMainThread() {
535 BrowserActionsBarBrowserTest::SetUpOnMainThread();
536 main_bar_ = BrowserView::GetBrowserViewForBrowser(browser())
537 ->toolbar()->browser_actions();
538 overflow_parent_ = std::make_unique<views::ResizeAwareParentView>();
539 overflow_bar_ =
540 overflow_parent_->AddChildView(std::make_unique<BrowserActionsContainer>(
541 browser(), main_bar_,
542 BrowserView::GetBrowserViewForBrowser(browser())->toolbar(), true));
543 }
544
TearDownOnMainThread()545 void BrowserActionsContainerOverflowTest::TearDownOnMainThread() {
546 overflow_parent_.reset();
547 BrowserActionsBarBrowserTest::TearDownOnMainThread();
548 }
549
UpdateUi()550 void BrowserActionsContainerOverflowTest::UpdateUi() {
551 // The overflow bar seems to layout correctly only if the main bar is
552 // laid out first. This happens in practice but we must force it in
553 // the test.
554 RunScheduledLayouts();
555 overflow_bar_->Layout();
556 }
557
ViewOrdersMatch()558 bool BrowserActionsContainerOverflowTest::ViewOrdersMatch() {
559 if (main_bar_->num_toolbar_actions() !=
560 overflow_bar_->num_toolbar_actions())
561 return false;
562 for (size_t i = 0; i < main_bar_->num_toolbar_actions(); ++i) {
563 if (main_bar_->GetIdAt(i) != overflow_bar_->GetIdAt(i))
564 return false;
565 }
566 return true;
567 }
568
569 testing::AssertionResult
VerifyVisibleCount(size_t expected_visible)570 BrowserActionsContainerOverflowTest::VerifyVisibleCount(
571 size_t expected_visible) {
572 // Views order should always match (as it is based directly off the model).
573 if (!ViewOrdersMatch())
574 return testing::AssertionFailure() << "View orders don't match";
575
576 // Loop through and check each browser action for proper visibility (which
577 // implicitly also guarantees that the proper number are visible).
578 for (size_t i = 0; i < overflow_bar_->num_toolbar_actions(); ++i) {
579 bool visible = i < expected_visible;
580 if (main_bar_->GetToolbarActionViewAt(i)->GetVisible() != visible) {
581 return testing::AssertionFailure() << "Index " << i <<
582 " has improper visibility in main: " << !visible;
583 }
584 if (overflow_bar_->GetToolbarActionViewAt(i)->GetVisible() == visible) {
585 return testing::AssertionFailure() << "Index " << i <<
586 " has improper visibility in overflow: " << visible;
587 }
588 }
589 return testing::AssertionSuccess();
590 }
591
592 // Test the basic functionality of the BrowserActionsContainer in overflow mode.
IN_PROC_BROWSER_TEST_F(BrowserActionsContainerOverflowTest,TestBasicActionOverflow)593 IN_PROC_BROWSER_TEST_F(BrowserActionsContainerOverflowTest,
594 TestBasicActionOverflow) {
595 LoadExtensions();
596
597 UpdateUi();
598
599 // All actions are showing, and are in the installation order.
600 EXPECT_TRUE(toolbar_model()->all_icons_visible());
601 EXPECT_EQ(3u, toolbar_model()->visible_icon_count());
602 ASSERT_EQ(3u, main_bar()->num_toolbar_actions());
603 EXPECT_EQ(extension_a()->id(), main_bar()->GetIdAt(0u));
604 EXPECT_EQ(extension_b()->id(), main_bar()->GetIdAt(1u));
605 EXPECT_EQ(extension_c()->id(), main_bar()->GetIdAt(2u));
606 EXPECT_TRUE(VerifyVisibleCount(3u));
607
608 // Reduce the visible count to 2. Order should be unchanged (A B C), but
609 // only A and B should be visible on the main bar.
610 toolbar_model()->SetVisibleIconCount(2u);
611 UpdateUi();
612 EXPECT_EQ(extension_a()->id(), main_bar()->GetIdAt(0u));
613 EXPECT_EQ(extension_b()->id(), main_bar()->GetIdAt(1u));
614 EXPECT_EQ(extension_c()->id(), main_bar()->GetIdAt(2u));
615 EXPECT_TRUE(VerifyVisibleCount(2u));
616
617 // Move extension C to the first position. Order should now be C A B, with
618 // C and A visible in the main bar.
619 toolbar_model()->MoveActionIcon(extension_c()->id(), 0);
620 UpdateUi();
621 EXPECT_EQ(extension_c()->id(), main_bar()->GetIdAt(0u));
622 EXPECT_EQ(extension_a()->id(), main_bar()->GetIdAt(1u));
623 EXPECT_EQ(extension_b()->id(), main_bar()->GetIdAt(2u));
624 EXPECT_TRUE(VerifyVisibleCount(2u));
625
626 // Hide action A via a context menu. This results in it being sent to
627 // overflow, and reducing the visible size to 1, so the order should be C A B,
628 // with only C visible in the main bar.
629 ui::MenuModel* menu_model = main_bar()
630 ->GetToolbarActionViewAt(1)
631 ->view_controller()
632 ->GetContextMenu();
633 extensions::ExtensionContextMenuModel* extension_menu =
634 static_cast<extensions::ExtensionContextMenuModel*>(menu_model);
635 extension_menu->ExecuteCommand(
636 extensions::ExtensionContextMenuModel::TOGGLE_VISIBILITY, 0);
637 UpdateUi();
638 EXPECT_EQ(extension_c()->id(), main_bar()->GetIdAt(0u));
639 EXPECT_EQ(extension_a()->id(), main_bar()->GetIdAt(1u));
640 EXPECT_EQ(extension_b()->id(), main_bar()->GetIdAt(2u));
641 EXPECT_TRUE(VerifyVisibleCount(1u));
642 }
643
644 // Test drag and drop between the overflow container and the main container.
IN_PROC_BROWSER_TEST_F(BrowserActionsContainerOverflowTest,TestOverflowDragging)645 IN_PROC_BROWSER_TEST_F(BrowserActionsContainerOverflowTest,
646 TestOverflowDragging) {
647 LoadExtensions();
648
649 // Start with one extension in overflow.
650 toolbar_model()->SetVisibleIconCount(2u);
651 UpdateUi();
652
653 // Verify starting state is A B [C].
654 ASSERT_EQ(3u, main_bar()->num_toolbar_actions());
655 EXPECT_EQ(extension_a()->id(), main_bar()->GetIdAt(0u));
656 EXPECT_EQ(extension_b()->id(), main_bar()->GetIdAt(1u));
657 EXPECT_EQ(extension_c()->id(), main_bar()->GetIdAt(2u));
658 EXPECT_TRUE(VerifyVisibleCount(2u));
659
660 // Drag extension A (on the main bar) to the left of extension C (in
661 // overflow).
662 ui::OSExchangeData drop_data;
663 BrowserActionDragData browser_action_drag_data(extension_a()->id(), 0u);
664 browser_action_drag_data.Write(profile(), &drop_data);
665 ToolbarActionView* view = overflow_bar()->GetViewForId(extension_c()->id());
666 gfx::PointF location(view->x(), view->y());
667 ui::DropTargetEvent target_event(
668 drop_data, location, location, ui::DragDropTypes::DRAG_MOVE);
669
670 overflow_bar()->OnDragUpdated(target_event);
671 overflow_bar()->OnPerformDrop(target_event);
672 UpdateUi();
673
674 // Order should now be B [A C].
675 EXPECT_EQ(extension_b()->id(), main_bar()->GetIdAt(0u));
676 EXPECT_EQ(extension_a()->id(), main_bar()->GetIdAt(1u));
677 EXPECT_EQ(extension_c()->id(), main_bar()->GetIdAt(2u));
678 EXPECT_TRUE(VerifyVisibleCount(1u));
679
680 // Drag extension A back from overflow to the main bar.
681 ui::OSExchangeData drop_data2;
682 BrowserActionDragData browser_action_drag_data2(extension_a()->id(), 1u);
683 browser_action_drag_data2.Write(profile(), &drop_data2);
684 view = main_bar()->GetViewForId(extension_b()->id());
685 location = gfx::PointF(view->x(), view->y());
686 ui::DropTargetEvent target_event2(
687 drop_data2, location, location, ui::DragDropTypes::DRAG_MOVE);
688
689 main_bar()->OnDragUpdated(target_event2);
690 main_bar()->OnPerformDrop(target_event2);
691
692 // Order should be A B [C] again.
693 UpdateUi();
694 EXPECT_EQ(extension_a()->id(), main_bar()->GetIdAt(0u));
695 EXPECT_EQ(extension_b()->id(), main_bar()->GetIdAt(1u));
696 EXPECT_EQ(extension_c()->id(), main_bar()->GetIdAt(2u));
697 EXPECT_TRUE(VerifyVisibleCount(2u));
698
699 // Drag extension C from overflow to the main bar (before extension B).
700 ui::OSExchangeData drop_data3;
701 BrowserActionDragData browser_action_drag_data3(extension_c()->id(), 2u);
702 browser_action_drag_data3.Write(profile(), &drop_data3);
703 location = gfx::PointF(view->x(), view->y());
704 ui::DropTargetEvent target_event3(
705 drop_data3, location, location, ui::DragDropTypes::DRAG_MOVE);
706
707 main_bar()->OnDragUpdated(target_event3);
708 main_bar()->OnPerformDrop(target_event3);
709
710 // Order should be A C B, and there should be no extensions in overflow.
711 UpdateUi();
712 EXPECT_EQ(extension_a()->id(), main_bar()->GetIdAt(0u));
713 EXPECT_EQ(extension_c()->id(), main_bar()->GetIdAt(1u));
714 EXPECT_EQ(extension_b()->id(), main_bar()->GetIdAt(2u));
715 EXPECT_TRUE(VerifyVisibleCount(3u));
716 }
717