1 // Copyright (c) 2020 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/webui/tab_strip/tab_strip_ui_handler.h"
6 
7 #include <memory>
8 
9 #include "base/macros.h"
10 #include "base/optional.h"
11 #include "base/strings/utf_string_conversions.h"
12 #include "base/values.h"
13 #include "chrome/browser/extensions/extension_tab_util.h"
14 #include "chrome/browser/ui/tabs/tab_group_model.h"
15 #include "chrome/browser/ui/webui/tab_strip/tab_strip_ui_embedder.h"
16 #include "chrome/browser/ui/webui/tab_strip/tab_strip_ui_layout.h"
17 #include "chrome/test/base/browser_with_test_window_test.h"
18 #include "chrome/test/base/testing_profile_manager.h"
19 #include "components/tab_groups/tab_group_color.h"
20 #include "components/tab_groups/tab_group_id.h"
21 #include "components/tab_groups/tab_group_visual_data.h"
22 #include "content/public/browser/web_ui.h"
23 #include "content/public/test/test_web_ui.h"
24 #include "content/public/test/web_contents_tester.h"
25 #include "ui/base/accelerators/accelerator.h"
26 #include "ui/base/theme_provider.h"
27 #include "ui/gfx/color_utils.h"
28 #include "ui/gfx/geometry/point.h"
29 
30 namespace {
31 
32 class TestTabStripUIHandler : public TabStripUIHandler {
33  public:
TestTabStripUIHandler(content::WebUI * web_ui,Browser * browser,TabStripUIEmbedder * embedder)34   explicit TestTabStripUIHandler(content::WebUI* web_ui,
35                                  Browser* browser,
36                                  TabStripUIEmbedder* embedder)
37       : TabStripUIHandler(browser, embedder) {
38     set_web_ui(web_ui);
39   }
40 };
41 
42 class StubTabStripUIEmbedder : public TabStripUIEmbedder {
43  public:
GetAcceleratorProvider() const44   const ui::AcceleratorProvider* GetAcceleratorProvider() const override {
45     return nullptr;
46   }
CloseContainer()47   void CloseContainer() override {}
ShowContextMenuAtPoint(gfx::Point point,std::unique_ptr<ui::MenuModel> menu_model)48   void ShowContextMenuAtPoint(
49       gfx::Point point,
50       std::unique_ptr<ui::MenuModel> menu_model) override {}
ShowEditDialogForGroupAtPoint(gfx::Point point,gfx::Rect rect,tab_groups::TabGroupId group_id)51   void ShowEditDialogForGroupAtPoint(gfx::Point point,
52                                      gfx::Rect rect,
53                                      tab_groups::TabGroupId group_id) override {
54   }
GetLayout()55   TabStripUILayout GetLayout() override { return TabStripUILayout(); }
GetColor(int id) const56   SkColor GetColor(int id) const override { return SK_ColorWHITE; }
57 };
58 
59 }  // namespace
60 
61 class TabStripUIHandlerTest : public BrowserWithTestWindowTest {
62  public:
63   TabStripUIHandlerTest() = default;
64 
SetUp()65   void SetUp() override {
66     BrowserWithTestWindowTest::SetUp();
67     handler_ = std::make_unique<TestTabStripUIHandler>(web_ui(), browser(),
68                                                        &stub_embedder_);
69     handler()->AllowJavascriptForTesting();
70     web_ui()->ClearTrackedCalls();
71   }
72 
handler()73   TabStripUIHandler* handler() { return handler_.get(); }
web_ui()74   content::TestWebUI* web_ui() { return &web_ui_; }
75 
ExpectVisualDataDictionary(const tab_groups::TabGroupVisualData visual_data,const base::DictionaryValue * visual_data_dict)76   void ExpectVisualDataDictionary(
77       const tab_groups::TabGroupVisualData visual_data,
78       const base::DictionaryValue* visual_data_dict) {
79     std::string group_title;
80     ASSERT_TRUE(visual_data_dict->GetString("title", &group_title));
81     EXPECT_EQ(base::UTF16ToASCII(visual_data.title()), group_title);
82 
83     std::string group_color;
84     ASSERT_TRUE(visual_data_dict->GetString("color", &group_color));
85     EXPECT_EQ(color_utils::SkColorToRgbString(SK_ColorWHITE), group_color);
86   }
87 
88  private:
89   StubTabStripUIEmbedder stub_embedder_;
90   content::TestWebUI web_ui_;
91   std::unique_ptr<TestTabStripUIHandler> handler_;
92 };
93 
TEST_F(TabStripUIHandlerTest,GroupClosedEvent)94 TEST_F(TabStripUIHandlerTest, GroupClosedEvent) {
95   AddTab(browser(), GURL("http://foo"));
96   tab_groups::TabGroupId expected_group_id =
97       browser()->tab_strip_model()->AddToNewGroup({0});
98   browser()->tab_strip_model()->RemoveFromGroup({0});
99 
100   const content::TestWebUI::CallData& call_data = *web_ui()->call_data().back();
101   EXPECT_EQ("cr.webUIListenerCallback", call_data.function_name());
102   EXPECT_EQ("tab-group-closed", call_data.arg1()->GetString());
103   EXPECT_EQ(expected_group_id.ToString(), call_data.arg2()->GetString());
104 }
105 
TEST_F(TabStripUIHandlerTest,GroupStateChangedEvents)106 TEST_F(TabStripUIHandlerTest, GroupStateChangedEvents) {
107   AddTab(browser(), GURL("http://foo/1"));
108   AddTab(browser(), GURL("http://foo/2"));
109 
110   // Add one of the tabs to a group to test for a tab-group-state-changed event.
111   tab_groups::TabGroupId expected_group_id =
112       browser()->tab_strip_model()->AddToNewGroup({0, 1});
113   int expected_tab_id = extensions::ExtensionTabUtil::GetTabId(
114       browser()->tab_strip_model()->GetWebContentsAt(1));
115 
116   const content::TestWebUI::CallData& grouped_call_data =
117       *web_ui()->call_data().back();
118   EXPECT_EQ("cr.webUIListenerCallback", grouped_call_data.function_name());
119   EXPECT_EQ("tab-group-state-changed", grouped_call_data.arg1()->GetString());
120   EXPECT_EQ(expected_tab_id, grouped_call_data.arg2()->GetInt());
121   EXPECT_EQ(1, grouped_call_data.arg3()->GetInt());
122   EXPECT_EQ(expected_group_id.ToString(),
123             grouped_call_data.arg4()->GetString());
124 
125   // Remove the tab from the group to test for a tab-group-state-changed event.
126   browser()->tab_strip_model()->RemoveFromGroup({1});
127   const content::TestWebUI::CallData& ungrouped_call_data =
128       *web_ui()->call_data().back();
129   EXPECT_EQ("cr.webUIListenerCallback", ungrouped_call_data.function_name());
130   EXPECT_EQ("tab-group-state-changed", ungrouped_call_data.arg1()->GetString());
131   EXPECT_EQ(expected_tab_id, ungrouped_call_data.arg2()->GetInt());
132   EXPECT_EQ(1, ungrouped_call_data.arg3()->GetInt());
133   EXPECT_EQ(nullptr, ungrouped_call_data.arg4());
134 }
135 
TEST_F(TabStripUIHandlerTest,GroupMovedEvents)136 TEST_F(TabStripUIHandlerTest, GroupMovedEvents) {
137   // Create a tab group and a few other tabs to allow the group to move.
138   AddTab(browser(), GURL("http://foo/1"));
139   AddTab(browser(), GURL("http://foo/2"));
140   AddTab(browser(), GURL("http://foo/3"));
141   AddTab(browser(), GURL("http://foo/4"));
142   tab_groups::TabGroupId expected_group_id =
143       browser()->tab_strip_model()->AddToNewGroup({0, 1});
144 
145   // Select all the tabs in the group.
146   ui::ListSelectionModel selection;
147   selection.AddIndexToSelection(0);
148   selection.AddIndexToSelection(1);
149   selection.set_active(0);
150   browser()->tab_strip_model()->SetSelectionFromModel(selection);
151 
152   web_ui()->ClearTrackedCalls();
153 
154   // Move the selected tabs to later in the tab strip. This should result in
155   // a single event that is fired to indicate the entire group has moved.
156   int expected_index = 2;
157   browser()->tab_strip_model()->MoveSelectedTabsTo(expected_index);
158 
159   EXPECT_EQ(1U, web_ui()->call_data().size());
160 
161   const content::TestWebUI::CallData& call_data = *web_ui()->call_data().back();
162   EXPECT_EQ("cr.webUIListenerCallback", call_data.function_name());
163   EXPECT_EQ("tab-group-moved", call_data.arg1()->GetString());
164   EXPECT_EQ(expected_group_id.ToString(), call_data.arg2()->GetString());
165   EXPECT_EQ(expected_index, call_data.arg3()->GetInt());
166 
167   web_ui()->ClearTrackedCalls();
168 
169   // Move the selected tabs to earlier in the tab strip. This should also
170   // result in a single event that is fired to indicate the entire group has
171   // moved.
172   expected_index = 1;
173   browser()->tab_strip_model()->MoveSelectedTabsTo(expected_index);
174 
175   EXPECT_EQ(1U, web_ui()->call_data().size());
176 
177   const content::TestWebUI::CallData& call_data2 =
178       *web_ui()->call_data().back();
179   EXPECT_EQ("cr.webUIListenerCallback", call_data2.function_name());
180   EXPECT_EQ("tab-group-moved", call_data2.arg1()->GetString());
181   EXPECT_EQ(expected_group_id.ToString(), call_data2.arg2()->GetString());
182   EXPECT_EQ(expected_index, call_data2.arg3()->GetInt());
183 }
184 
TEST_F(TabStripUIHandlerTest,GetGroupVisualData)185 TEST_F(TabStripUIHandlerTest, GetGroupVisualData) {
186   AddTab(browser(), GURL("http://foo/1"));
187   AddTab(browser(), GURL("http://foo/2"));
188   tab_groups::TabGroupId group1 =
189       browser()->tab_strip_model()->AddToNewGroup({0});
190   const tab_groups::TabGroupVisualData group1_visuals(
191       base::ASCIIToUTF16("Group 1"), tab_groups::TabGroupColorId::kGreen);
192   browser()
193       ->tab_strip_model()
194       ->group_model()
195       ->GetTabGroup(group1)
196       ->SetVisualData(group1_visuals);
197   tab_groups::TabGroupId group2 =
198       browser()->tab_strip_model()->AddToNewGroup({1});
199   const tab_groups::TabGroupVisualData group2_visuals(
200       base::ASCIIToUTF16("Group 2"), tab_groups::TabGroupColorId::kCyan);
201   browser()
202       ->tab_strip_model()
203       ->group_model()
204       ->GetTabGroup(group2)
205       ->SetVisualData(group2_visuals);
206 
207   base::ListValue args;
208   args.AppendString("callback-id");
209   handler()->HandleGetGroupVisualData(&args);
210 
211   const content::TestWebUI::CallData& call_data = *web_ui()->call_data().back();
212   EXPECT_EQ("cr.webUIResponse", call_data.function_name());
213   EXPECT_EQ("callback-id", call_data.arg1()->GetString());
214   EXPECT_TRUE(call_data.arg2()->GetBool());
215 
216   const base::DictionaryValue* returned_data;
217   ASSERT_TRUE(call_data.arg3()->GetAsDictionary(&returned_data));
218 
219   const base::DictionaryValue* group1_dict;
220   ASSERT_TRUE(returned_data->GetDictionary(group1.ToString(), &group1_dict));
221   ExpectVisualDataDictionary(group1_visuals, group1_dict);
222 
223   const base::DictionaryValue* group2_dict;
224   ASSERT_TRUE(returned_data->GetDictionary(group2.ToString(), &group2_dict));
225   ExpectVisualDataDictionary(group2_visuals, group2_dict);
226 }
227 
TEST_F(TabStripUIHandlerTest,GroupVisualDataChangedEvent)228 TEST_F(TabStripUIHandlerTest, GroupVisualDataChangedEvent) {
229   AddTab(browser(), GURL("http://foo"));
230   tab_groups::TabGroupId expected_group_id =
231       browser()->tab_strip_model()->AddToNewGroup({0});
232   const tab_groups::TabGroupVisualData new_visual_data(
233       base::ASCIIToUTF16("My new title"), tab_groups::TabGroupColorId::kGreen);
234   browser()
235       ->tab_strip_model()
236       ->group_model()
237       ->GetTabGroup(expected_group_id)
238       ->SetVisualData(new_visual_data);
239 
240   const content::TestWebUI::CallData& call_data = *web_ui()->call_data().back();
241   EXPECT_EQ("cr.webUIListenerCallback", call_data.function_name());
242   EXPECT_EQ("tab-group-visuals-changed", call_data.arg1()->GetString());
243   EXPECT_EQ(expected_group_id.ToString(), call_data.arg2()->GetString());
244 
245   const base::DictionaryValue* visual_data;
246   ASSERT_TRUE(call_data.arg3()->GetAsDictionary(&visual_data));
247   ExpectVisualDataDictionary(new_visual_data, visual_data);
248 }
249 
TEST_F(TabStripUIHandlerTest,GroupTab)250 TEST_F(TabStripUIHandlerTest, GroupTab) {
251   // Add a tab inside of a group.
252   AddTab(browser(), GURL("http://foo"));
253   tab_groups::TabGroupId group_id =
254       browser()->tab_strip_model()->AddToNewGroup({0});
255 
256   // Add another tab, and try to group it.
257   AddTab(browser(), GURL("http://foo"));
258   base::ListValue args;
259   args.AppendInteger(extensions::ExtensionTabUtil::GetTabId(
260       browser()->tab_strip_model()->GetWebContentsAt(0)));
261   args.AppendString(group_id.ToString());
262   handler()->HandleGroupTab(&args);
263 
264   ASSERT_EQ(group_id, browser()->tab_strip_model()->GetTabGroupForTab(0));
265 }
266 
TEST_F(TabStripUIHandlerTest,MoveGroup)267 TEST_F(TabStripUIHandlerTest, MoveGroup) {
268   AddTab(browser(), GURL("http://foo/1"));
269   AddTab(browser(), GURL("http://foo/2"));
270   tab_groups::TabGroupId group_id =
271       browser()->tab_strip_model()->AddToNewGroup({0});
272 
273   // Move the group to index 1.
274   int new_index = 1;
275   base::ListValue args;
276   args.AppendString(group_id.ToString());
277   args.AppendInteger(new_index);
278   handler()->HandleMoveGroup(&args);
279 
280   std::vector<int> tabs_in_group = browser()
281                                        ->tab_strip_model()
282                                        ->group_model()
283                                        ->GetTabGroup(group_id)
284                                        ->ListTabs();
285   ASSERT_EQ(new_index, tabs_in_group.front());
286   ASSERT_EQ(new_index, tabs_in_group.back());
287 }
288 
TEST_F(TabStripUIHandlerTest,MoveGroupAcrossWindows)289 TEST_F(TabStripUIHandlerTest, MoveGroupAcrossWindows) {
290   AddTab(browser(), GURL("http://foo"));
291 
292   // Create a new window with the same profile, and add a group to it.
293   std::unique_ptr<BrowserWindow> new_window(CreateBrowserWindow());
294   std::unique_ptr<Browser> new_browser =
295       CreateBrowser(profile(), browser()->type(), false, new_window.get());
296   AddTab(new_browser.get(), GURL("http://foo"));
297   AddTab(new_browser.get(), GURL("http://foo"));
298   tab_groups::TabGroupId group_id =
299       new_browser.get()->tab_strip_model()->AddToNewGroup({0, 1});
300 
301   // Create some visual data to make sure it gets transferred.
302   const tab_groups::TabGroupVisualData visual_data(
303       base::ASCIIToUTF16("My group"), tab_groups::TabGroupColorId::kGreen);
304   new_browser.get()
305       ->tab_strip_model()
306       ->group_model()
307       ->GetTabGroup(group_id)
308       ->SetVisualData(visual_data);
309 
310   content::WebContents* moved_contents1 =
311       new_browser.get()->tab_strip_model()->GetWebContentsAt(0);
312   content::WebContents* moved_contents2 =
313       new_browser.get()->tab_strip_model()->GetWebContentsAt(1);
314   web_ui()->ClearTrackedCalls();
315 
316   int new_index = -1;
317   base::ListValue args;
318   args.AppendString(group_id.ToString());
319   args.AppendInteger(new_index);
320   handler()->HandleMoveGroup(&args);
321 
322   ASSERT_EQ(0U, new_browser.get()
323                     ->tab_strip_model()
324                     ->group_model()
325                     ->ListTabGroups()
326                     .size());
327   ASSERT_EQ(moved_contents1, browser()->tab_strip_model()->GetWebContentsAt(1));
328   ASSERT_EQ(moved_contents2, browser()->tab_strip_model()->GetWebContentsAt(2));
329 
330   base::Optional<tab_groups::TabGroupId> new_group_id =
331       browser()->tab_strip_model()->GetTabGroupForTab(1);
332   ASSERT_TRUE(new_group_id.has_value());
333   ASSERT_EQ(browser()->tab_strip_model()->GetTabGroupForTab(1),
334             browser()->tab_strip_model()->GetTabGroupForTab(2));
335 
336   const tab_groups::TabGroupVisualData* new_visual_data =
337       browser()
338           ->tab_strip_model()
339           ->group_model()
340           ->GetTabGroup(new_group_id.value())
341           ->visual_data();
342   ASSERT_EQ(visual_data.title(), new_visual_data->title());
343   ASSERT_EQ(visual_data.color(), new_visual_data->color());
344 
345   // Test that a WebUI event for the ID change was sent first.
346   const content::TestWebUI::CallData& call_data =
347       *web_ui()->call_data().front();
348   EXPECT_EQ("cr.webUIListenerCallback", call_data.function_name());
349   EXPECT_EQ("tab-group-id-replaced", call_data.arg1()->GetString());
350   EXPECT_EQ(group_id.ToString(), call_data.arg2()->GetString());
351   EXPECT_EQ(new_group_id.value().ToString(), call_data.arg3()->GetString());
352 }
353 
TEST_F(TabStripUIHandlerTest,MoveGroupAcrossProfiles)354 TEST_F(TabStripUIHandlerTest, MoveGroupAcrossProfiles) {
355   AddTab(browser(), GURL("http://foo"));
356 
357   TestingProfile* different_profile =
358       profile_manager()->CreateTestingProfile("different_profile");
359   std::unique_ptr<BrowserWindow> new_window(CreateBrowserWindow());
360   std::unique_ptr<Browser> new_browser = CreateBrowser(
361       different_profile, browser()->type(), false, new_window.get());
362   AddTab(new_browser.get(), GURL("http://foo"));
363   tab_groups::TabGroupId group_id =
364       new_browser.get()->tab_strip_model()->AddToNewGroup({0});
365 
366   int new_index = -1;
367   base::ListValue args;
368   args.AppendString(group_id.ToString());
369   args.AppendInteger(new_index);
370   handler()->HandleMoveGroup(&args);
371 
372   ASSERT_TRUE(
373       new_browser.get()->tab_strip_model()->group_model()->ContainsTabGroup(
374           group_id));
375 
376   // Close all tabs before destructing.
377   new_browser.get()->tab_strip_model()->CloseAllTabs();
378 }
379 
TEST_F(TabStripUIHandlerTest,MoveTab)380 TEST_F(TabStripUIHandlerTest, MoveTab) {
381   AddTab(browser(), GURL("http://foo"));
382   AddTab(browser(), GURL("http://foo"));
383 
384   content::WebContents* contents_prev_at_0 =
385       browser()->tab_strip_model()->GetWebContentsAt(0);
386   content::WebContents* contents_prev_at_1 =
387       browser()->tab_strip_model()->GetWebContentsAt(1);
388 
389   // Move tab at index 0 to index 1.
390   base::ListValue args;
391   args.AppendInteger(
392       extensions::ExtensionTabUtil::GetTabId(contents_prev_at_0));
393   args.AppendInteger(1);
394   handler()->HandleMoveTab(&args);
395 
396   ASSERT_EQ(1, browser()->tab_strip_model()->GetIndexOfWebContents(
397                    contents_prev_at_0));
398   ASSERT_EQ(0, browser()->tab_strip_model()->GetIndexOfWebContents(
399                    contents_prev_at_1));
400 }
401 
TEST_F(TabStripUIHandlerTest,MoveTabAcrossProfiles)402 TEST_F(TabStripUIHandlerTest, MoveTabAcrossProfiles) {
403   AddTab(browser(), GURL("http://foo"));
404 
405   TestingProfile* different_profile =
406       profile_manager()->CreateTestingProfile("different_profile");
407   std::unique_ptr<BrowserWindow> new_window(CreateBrowserWindow());
408   std::unique_ptr<Browser> new_browser = CreateBrowser(
409       different_profile, browser()->type(), false, new_window.get());
410   AddTab(new_browser.get(), GURL("http://foo"));
411 
412   base::ListValue args;
413   args.AppendInteger(extensions::ExtensionTabUtil::GetTabId(
414       new_browser->tab_strip_model()->GetWebContentsAt(0)));
415   args.AppendInteger(1);
416   handler()->HandleMoveTab(&args);
417 
418   ASSERT_FALSE(browser()->tab_strip_model()->ContainsIndex(1));
419 
420   // Close all tabs before destructing.
421   new_browser.get()->tab_strip_model()->CloseAllTabs();
422 }
423 
TEST_F(TabStripUIHandlerTest,MoveTabAcrossWindows)424 TEST_F(TabStripUIHandlerTest, MoveTabAcrossWindows) {
425   AddTab(browser(), GURL("http://foo"));
426 
427   std::unique_ptr<BrowserWindow> new_window(CreateBrowserWindow());
428   std::unique_ptr<Browser> new_browser =
429       CreateBrowser(profile(), browser()->type(), false, new_window.get());
430   AddTab(new_browser.get(), GURL("http://foo"));
431   content::WebContents* moved_contents =
432       new_browser.get()->tab_strip_model()->GetWebContentsAt(0);
433 
434   base::ListValue args;
435   args.AppendInteger(extensions::ExtensionTabUtil::GetTabId(
436       new_browser->tab_strip_model()->GetWebContentsAt(0)));
437   args.AppendInteger(1);
438   handler()->HandleMoveTab(&args);
439 
440   ASSERT_EQ(moved_contents, browser()->tab_strip_model()->GetWebContentsAt(1));
441 
442   // Close all tabs before destructing.
443   new_browser.get()->tab_strip_model()->CloseAllTabs();
444 }
445 
TEST_F(TabStripUIHandlerTest,TabCreated)446 TEST_F(TabStripUIHandlerTest, TabCreated) {
447   AddTab(browser(), GURL("http://foo"));
448 
449   const content::TestWebUI::CallData& call_data =
450       *web_ui()->call_data().front();
451   EXPECT_EQ("cr.webUIListenerCallback", call_data.function_name());
452   EXPECT_EQ("tab-created", call_data.arg1()->GetString());
453 
454   const base::DictionaryValue* tab_data;
455   ASSERT_TRUE(call_data.arg2()->GetAsDictionary(&tab_data));
456 
457   int tab_id;
458   ASSERT_TRUE(tab_data->GetInteger("id", &tab_id));
459   ASSERT_EQ(extensions::ExtensionTabUtil::GetTabId(
460                 browser()->tab_strip_model()->GetWebContentsAt(0)),
461             tab_id);
462 
463   bool is_active;
464   ASSERT_TRUE(tab_data->GetBoolean("active", &is_active));
465   ASSERT_TRUE(is_active);
466 
467   int tab_index;
468   ASSERT_TRUE(tab_data->GetInteger("index", &tab_index));
469   ASSERT_EQ(0, tab_index);
470 }
471 
TEST_F(TabStripUIHandlerTest,TabRemoved)472 TEST_F(TabStripUIHandlerTest, TabRemoved) {
473   // Two tabs so the browser does not close when a tab is closed.
474   AddTab(browser(), GURL("http://foo"));
475   AddTab(browser(), GURL("http://foo"));
476   int expected_tab_id = extensions::ExtensionTabUtil::GetTabId(
477       browser()->tab_strip_model()->GetWebContentsAt(0));
478 
479   web_ui()->ClearTrackedCalls();
480   browser()->tab_strip_model()->GetWebContentsAt(0)->Close();
481 
482   const content::TestWebUI::CallData& call_data =
483       *web_ui()->call_data().front();
484   EXPECT_EQ("cr.webUIListenerCallback", call_data.function_name());
485   EXPECT_EQ("tab-removed", call_data.arg1()->GetString());
486   EXPECT_EQ(expected_tab_id, call_data.arg2()->GetInt());
487 }
488 
TEST_F(TabStripUIHandlerTest,TabMoved)489 TEST_F(TabStripUIHandlerTest, TabMoved) {
490   AddTab(browser(), GURL("http://foo"));
491   AddTab(browser(), GURL("http://foo"));
492 
493   int from_index = 0;
494   int expected_to_index = 1;
495   int expected_tab_id = extensions::ExtensionTabUtil::GetTabId(
496       browser()->tab_strip_model()->GetWebContentsAt(from_index));
497 
498   browser()->tab_strip_model()->MoveWebContentsAt(from_index, expected_to_index,
499                                                   false);
500 
501   const content::TestWebUI::CallData& call_data = *web_ui()->call_data().back();
502   EXPECT_EQ("cr.webUIListenerCallback", call_data.function_name());
503   EXPECT_EQ("tab-moved", call_data.arg1()->GetString());
504   EXPECT_EQ(expected_tab_id, call_data.arg2()->GetInt());
505   EXPECT_EQ(expected_to_index, call_data.arg3()->GetInt());
506 }
507 
TEST_F(TabStripUIHandlerTest,TabReplaced)508 TEST_F(TabStripUIHandlerTest, TabReplaced) {
509   AddTab(browser(), GURL("http://foo"));
510   int expected_previous_id = extensions::ExtensionTabUtil::GetTabId(
511       browser()->tab_strip_model()->GetWebContentsAt(0));
512 
513   web_ui()->ClearTrackedCalls();
514   browser()->tab_strip_model()->ReplaceWebContentsAt(
515       0, content::WebContentsTester::CreateTestWebContents(profile(), nullptr));
516   int expected_new_id = extensions::ExtensionTabUtil::GetTabId(
517       browser()->tab_strip_model()->GetWebContentsAt(0));
518 
519   const content::TestWebUI::CallData& call_data =
520       *web_ui()->call_data().front();
521   EXPECT_EQ("cr.webUIListenerCallback", call_data.function_name());
522   EXPECT_EQ("tab-replaced", call_data.arg1()->GetString());
523   ASSERT_EQ(expected_previous_id, call_data.arg2()->GetInt());
524   ASSERT_EQ(expected_new_id, call_data.arg3()->GetInt());
525 }
526 
TEST_F(TabStripUIHandlerTest,TabActivated)527 TEST_F(TabStripUIHandlerTest, TabActivated) {
528   AddTab(browser(), GURL("http://foo"));
529   AddTab(browser(), GURL("http://foo"));
530   AddTab(browser(), GURL("http://foo"));
531 
532   web_ui()->ClearTrackedCalls();
533   browser()->tab_strip_model()->ActivateTabAt(1);
534 
535   const content::TestWebUI::CallData& call_data = *web_ui()->call_data().back();
536   EXPECT_EQ("cr.webUIListenerCallback", call_data.function_name());
537   EXPECT_EQ("tab-active-changed", call_data.arg1()->GetString());
538   EXPECT_EQ(extensions::ExtensionTabUtil::GetTabId(
539                 browser()->tab_strip_model()->GetWebContentsAt(1)),
540             call_data.arg2()->GetInt());
541 }
542 
TEST_F(TabStripUIHandlerTest,UngroupTab)543 TEST_F(TabStripUIHandlerTest, UngroupTab) {
544   // Add a tab inside of a group.
545   AddTab(browser(), GURL("http://foo"));
546   browser()->tab_strip_model()->AddToNewGroup({0});
547 
548   // Add another tab at index 1, and try to group it.
549   base::ListValue args;
550   args.AppendInteger(extensions::ExtensionTabUtil::GetTabId(
551       browser()->tab_strip_model()->GetWebContentsAt(0)));
552   handler()->HandleUngroupTab(&args);
553 
554   ASSERT_FALSE(browser()->tab_strip_model()->GetTabGroupForTab(0).has_value());
555 }
556 
TEST_F(TabStripUIHandlerTest,CloseTab)557 TEST_F(TabStripUIHandlerTest, CloseTab) {
558   AddTab(browser(), GURL("http://foo"));
559   AddTab(browser(), GURL("http://bar"));
560 
561   base::ListValue args;
562   args.AppendInteger(extensions::ExtensionTabUtil::GetTabId(
563       browser()->tab_strip_model()->GetWebContentsAt(0)));
564   args.AppendBoolean(false);  // If the tab is closed by swipe.
565   handler()->HandleCloseTab(&args);
566 
567   ASSERT_EQ(1, browser()->tab_strip_model()->GetTabCount());
568 }
569