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