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,GetGroupVisualData)136 TEST_F(TabStripUIHandlerTest, GetGroupVisualData) {
137   AddTab(browser(), GURL("http://foo/1"));
138   AddTab(browser(), GURL("http://foo/2"));
139   tab_groups::TabGroupId group1 =
140       browser()->tab_strip_model()->AddToNewGroup({0});
141   const tab_groups::TabGroupVisualData group1_visuals(
142       base::ASCIIToUTF16("Group 1"), tab_groups::TabGroupColorId::kGreen);
143   browser()
144       ->tab_strip_model()
145       ->group_model()
146       ->GetTabGroup(group1)
147       ->SetVisualData(group1_visuals);
148   tab_groups::TabGroupId group2 =
149       browser()->tab_strip_model()->AddToNewGroup({1});
150   const tab_groups::TabGroupVisualData group2_visuals(
151       base::ASCIIToUTF16("Group 2"), tab_groups::TabGroupColorId::kCyan);
152   browser()
153       ->tab_strip_model()
154       ->group_model()
155       ->GetTabGroup(group2)
156       ->SetVisualData(group2_visuals);
157 
158   base::ListValue args;
159   args.AppendString("callback-id");
160   handler()->HandleGetGroupVisualData(&args);
161 
162   const content::TestWebUI::CallData& call_data = *web_ui()->call_data().back();
163   EXPECT_EQ("cr.webUIResponse", call_data.function_name());
164   EXPECT_EQ("callback-id", call_data.arg1()->GetString());
165   EXPECT_TRUE(call_data.arg2()->GetBool());
166 
167   const base::DictionaryValue* returned_data;
168   ASSERT_TRUE(call_data.arg3()->GetAsDictionary(&returned_data));
169 
170   const base::DictionaryValue* group1_dict;
171   ASSERT_TRUE(returned_data->GetDictionary(group1.ToString(), &group1_dict));
172   ExpectVisualDataDictionary(group1_visuals, group1_dict);
173 
174   const base::DictionaryValue* group2_dict;
175   ASSERT_TRUE(returned_data->GetDictionary(group2.ToString(), &group2_dict));
176   ExpectVisualDataDictionary(group2_visuals, group2_dict);
177 }
178 
TEST_F(TabStripUIHandlerTest,GroupVisualDataChangedEvent)179 TEST_F(TabStripUIHandlerTest, GroupVisualDataChangedEvent) {
180   AddTab(browser(), GURL("http://foo"));
181   tab_groups::TabGroupId expected_group_id =
182       browser()->tab_strip_model()->AddToNewGroup({0});
183   const tab_groups::TabGroupVisualData new_visual_data(
184       base::ASCIIToUTF16("My new title"), tab_groups::TabGroupColorId::kGreen);
185   browser()
186       ->tab_strip_model()
187       ->group_model()
188       ->GetTabGroup(expected_group_id)
189       ->SetVisualData(new_visual_data);
190 
191   const content::TestWebUI::CallData& call_data = *web_ui()->call_data().back();
192   EXPECT_EQ("cr.webUIListenerCallback", call_data.function_name());
193   EXPECT_EQ("tab-group-visuals-changed", call_data.arg1()->GetString());
194   EXPECT_EQ(expected_group_id.ToString(), call_data.arg2()->GetString());
195 
196   const base::DictionaryValue* visual_data;
197   ASSERT_TRUE(call_data.arg3()->GetAsDictionary(&visual_data));
198   ExpectVisualDataDictionary(new_visual_data, visual_data);
199 }
200 
TEST_F(TabStripUIHandlerTest,GroupTab)201 TEST_F(TabStripUIHandlerTest, GroupTab) {
202   // Add a tab inside of a group.
203   AddTab(browser(), GURL("http://foo"));
204   tab_groups::TabGroupId group_id =
205       browser()->tab_strip_model()->AddToNewGroup({0});
206 
207   // Add another tab, and try to group it.
208   AddTab(browser(), GURL("http://foo"));
209   base::ListValue args;
210   args.AppendInteger(extensions::ExtensionTabUtil::GetTabId(
211       browser()->tab_strip_model()->GetWebContentsAt(0)));
212   args.AppendString(group_id.ToString());
213   handler()->HandleGroupTab(&args);
214 
215   ASSERT_EQ(group_id, browser()->tab_strip_model()->GetTabGroupForTab(0));
216 }
217 
TEST_F(TabStripUIHandlerTest,MoveGroup)218 TEST_F(TabStripUIHandlerTest, MoveGroup) {
219   AddTab(browser(), GURL("http://foo/1"));
220   AddTab(browser(), GURL("http://foo/2"));
221   tab_groups::TabGroupId group_id =
222       browser()->tab_strip_model()->AddToNewGroup({0});
223   web_ui()->ClearTrackedCalls();
224 
225   // Move the group to index 1.
226   int new_index = 1;
227   base::ListValue args;
228   args.AppendString(group_id.ToString());
229   args.AppendInteger(new_index);
230   handler()->HandleMoveGroup(&args);
231 
232   std::vector<int> tabs_in_group = browser()
233                                        ->tab_strip_model()
234                                        ->group_model()
235                                        ->GetTabGroup(group_id)
236                                        ->ListTabs();
237   ASSERT_EQ(new_index, tabs_in_group.front());
238   ASSERT_EQ(new_index, tabs_in_group.back());
239 
240   EXPECT_EQ(1U, web_ui()->call_data().size());
241   const content::TestWebUI::CallData& call_data =
242       *web_ui()->call_data().front();
243   EXPECT_EQ("cr.webUIListenerCallback", call_data.function_name());
244   EXPECT_EQ("tab-group-moved", call_data.arg1()->GetString());
245   EXPECT_EQ(group_id.ToString(), call_data.arg2()->GetString());
246 }
247 
TEST_F(TabStripUIHandlerTest,MoveGroupAcrossWindows)248 TEST_F(TabStripUIHandlerTest, MoveGroupAcrossWindows) {
249   AddTab(browser(), GURL("http://foo"));
250 
251   // Create a new window with the same profile, and add a group to it.
252   std::unique_ptr<BrowserWindow> new_window(CreateBrowserWindow());
253   std::unique_ptr<Browser> new_browser =
254       CreateBrowser(profile(), browser()->type(), false, new_window.get());
255   AddTab(new_browser.get(), GURL("http://foo"));
256   AddTab(new_browser.get(), GURL("http://foo"));
257   tab_groups::TabGroupId group_id =
258       new_browser.get()->tab_strip_model()->AddToNewGroup({0, 1});
259 
260   // Create some visual data to make sure it gets transferred.
261   const tab_groups::TabGroupVisualData visual_data(
262       base::ASCIIToUTF16("My group"), tab_groups::TabGroupColorId::kGreen);
263   new_browser.get()
264       ->tab_strip_model()
265       ->group_model()
266       ->GetTabGroup(group_id)
267       ->SetVisualData(visual_data);
268 
269   content::WebContents* moved_contents1 =
270       new_browser.get()->tab_strip_model()->GetWebContentsAt(0);
271   content::WebContents* moved_contents2 =
272       new_browser.get()->tab_strip_model()->GetWebContentsAt(1);
273   web_ui()->ClearTrackedCalls();
274 
275   int new_index = -1;
276   base::ListValue args;
277   args.AppendString(group_id.ToString());
278   args.AppendInteger(new_index);
279   handler()->HandleMoveGroup(&args);
280 
281   ASSERT_EQ(0U, new_browser.get()
282                     ->tab_strip_model()
283                     ->group_model()
284                     ->ListTabGroups()
285                     .size());
286   ASSERT_EQ(moved_contents1, browser()->tab_strip_model()->GetWebContentsAt(1));
287   ASSERT_EQ(moved_contents2, browser()->tab_strip_model()->GetWebContentsAt(2));
288 
289   base::Optional<tab_groups::TabGroupId> new_group_id =
290       browser()->tab_strip_model()->GetTabGroupForTab(1);
291   ASSERT_TRUE(new_group_id.has_value());
292   ASSERT_EQ(browser()->tab_strip_model()->GetTabGroupForTab(1),
293             browser()->tab_strip_model()->GetTabGroupForTab(2));
294 
295   const tab_groups::TabGroupVisualData* new_visual_data =
296       browser()
297           ->tab_strip_model()
298           ->group_model()
299           ->GetTabGroup(new_group_id.value())
300           ->visual_data();
301   ASSERT_EQ(visual_data.title(), new_visual_data->title());
302   ASSERT_EQ(visual_data.color(), new_visual_data->color());
303 }
304 
TEST_F(TabStripUIHandlerTest,MoveGroupAcrossProfiles)305 TEST_F(TabStripUIHandlerTest, MoveGroupAcrossProfiles) {
306   AddTab(browser(), GURL("http://foo"));
307 
308   TestingProfile* different_profile =
309       profile_manager()->CreateTestingProfile("different_profile");
310   std::unique_ptr<BrowserWindow> new_window(CreateBrowserWindow());
311   std::unique_ptr<Browser> new_browser = CreateBrowser(
312       different_profile, browser()->type(), false, new_window.get());
313   AddTab(new_browser.get(), GURL("http://foo"));
314   tab_groups::TabGroupId group_id =
315       new_browser.get()->tab_strip_model()->AddToNewGroup({0});
316 
317   int new_index = -1;
318   base::ListValue args;
319   args.AppendString(group_id.ToString());
320   args.AppendInteger(new_index);
321   handler()->HandleMoveGroup(&args);
322 
323   ASSERT_TRUE(
324       new_browser.get()->tab_strip_model()->group_model()->ContainsTabGroup(
325           group_id));
326 
327   // Close all tabs before destructing.
328   new_browser.get()->tab_strip_model()->CloseAllTabs();
329 }
330 
TEST_F(TabStripUIHandlerTest,MoveTab)331 TEST_F(TabStripUIHandlerTest, MoveTab) {
332   AddTab(browser(), GURL("http://foo"));
333   AddTab(browser(), GURL("http://foo"));
334 
335   content::WebContents* contents_prev_at_0 =
336       browser()->tab_strip_model()->GetWebContentsAt(0);
337   content::WebContents* contents_prev_at_1 =
338       browser()->tab_strip_model()->GetWebContentsAt(1);
339 
340   // Move tab at index 0 to index 1.
341   base::ListValue args;
342   args.AppendInteger(
343       extensions::ExtensionTabUtil::GetTabId(contents_prev_at_0));
344   args.AppendInteger(1);
345   handler()->HandleMoveTab(&args);
346 
347   ASSERT_EQ(1, browser()->tab_strip_model()->GetIndexOfWebContents(
348                    contents_prev_at_0));
349   ASSERT_EQ(0, browser()->tab_strip_model()->GetIndexOfWebContents(
350                    contents_prev_at_1));
351 }
352 
TEST_F(TabStripUIHandlerTest,MoveTabAcrossProfiles)353 TEST_F(TabStripUIHandlerTest, MoveTabAcrossProfiles) {
354   AddTab(browser(), GURL("http://foo"));
355 
356   TestingProfile* different_profile =
357       profile_manager()->CreateTestingProfile("different_profile");
358   std::unique_ptr<BrowserWindow> new_window(CreateBrowserWindow());
359   std::unique_ptr<Browser> new_browser = CreateBrowser(
360       different_profile, browser()->type(), false, new_window.get());
361   AddTab(new_browser.get(), GURL("http://foo"));
362 
363   base::ListValue args;
364   args.AppendInteger(extensions::ExtensionTabUtil::GetTabId(
365       new_browser->tab_strip_model()->GetWebContentsAt(0)));
366   args.AppendInteger(1);
367   handler()->HandleMoveTab(&args);
368 
369   ASSERT_FALSE(browser()->tab_strip_model()->ContainsIndex(1));
370 
371   // Close all tabs before destructing.
372   new_browser.get()->tab_strip_model()->CloseAllTabs();
373 }
374 
TEST_F(TabStripUIHandlerTest,MoveTabAcrossWindows)375 TEST_F(TabStripUIHandlerTest, MoveTabAcrossWindows) {
376   AddTab(browser(), GURL("http://foo"));
377 
378   std::unique_ptr<BrowserWindow> new_window(CreateBrowserWindow());
379   std::unique_ptr<Browser> new_browser =
380       CreateBrowser(profile(), browser()->type(), false, new_window.get());
381   AddTab(new_browser.get(), GURL("http://foo"));
382   content::WebContents* moved_contents =
383       new_browser.get()->tab_strip_model()->GetWebContentsAt(0);
384 
385   base::ListValue args;
386   args.AppendInteger(extensions::ExtensionTabUtil::GetTabId(
387       new_browser->tab_strip_model()->GetWebContentsAt(0)));
388   args.AppendInteger(1);
389   handler()->HandleMoveTab(&args);
390 
391   ASSERT_EQ(moved_contents, browser()->tab_strip_model()->GetWebContentsAt(1));
392 
393   // Close all tabs before destructing.
394   new_browser.get()->tab_strip_model()->CloseAllTabs();
395 }
396 
TEST_F(TabStripUIHandlerTest,TabCreated)397 TEST_F(TabStripUIHandlerTest, TabCreated) {
398   AddTab(browser(), GURL("http://foo"));
399 
400   const content::TestWebUI::CallData& call_data =
401       *web_ui()->call_data().front();
402   EXPECT_EQ("cr.webUIListenerCallback", call_data.function_name());
403   EXPECT_EQ("tab-created", call_data.arg1()->GetString());
404 
405   const base::DictionaryValue* tab_data;
406   ASSERT_TRUE(call_data.arg2()->GetAsDictionary(&tab_data));
407 
408   int tab_id;
409   ASSERT_TRUE(tab_data->GetInteger("id", &tab_id));
410   ASSERT_EQ(extensions::ExtensionTabUtil::GetTabId(
411                 browser()->tab_strip_model()->GetWebContentsAt(0)),
412             tab_id);
413 
414   bool is_active;
415   ASSERT_TRUE(tab_data->GetBoolean("active", &is_active));
416   ASSERT_TRUE(is_active);
417 
418   int tab_index;
419   ASSERT_TRUE(tab_data->GetInteger("index", &tab_index));
420   ASSERT_EQ(0, tab_index);
421 }
422 
TEST_F(TabStripUIHandlerTest,TabRemoved)423 TEST_F(TabStripUIHandlerTest, TabRemoved) {
424   // Two tabs so the browser does not close when a tab is closed.
425   AddTab(browser(), GURL("http://foo"));
426   AddTab(browser(), GURL("http://foo"));
427   int expected_tab_id = extensions::ExtensionTabUtil::GetTabId(
428       browser()->tab_strip_model()->GetWebContentsAt(0));
429 
430   web_ui()->ClearTrackedCalls();
431   browser()->tab_strip_model()->GetWebContentsAt(0)->Close();
432 
433   const content::TestWebUI::CallData& call_data =
434       *web_ui()->call_data().front();
435   EXPECT_EQ("cr.webUIListenerCallback", call_data.function_name());
436   EXPECT_EQ("tab-removed", call_data.arg1()->GetString());
437   EXPECT_EQ(expected_tab_id, call_data.arg2()->GetInt());
438 }
439 
TEST_F(TabStripUIHandlerTest,TabMoved)440 TEST_F(TabStripUIHandlerTest, TabMoved) {
441   AddTab(browser(), GURL("http://foo"));
442   AddTab(browser(), GURL("http://foo"));
443 
444   int from_index = 0;
445   int expected_to_index = 1;
446   int expected_tab_id = extensions::ExtensionTabUtil::GetTabId(
447       browser()->tab_strip_model()->GetWebContentsAt(from_index));
448 
449   browser()->tab_strip_model()->MoveWebContentsAt(from_index, expected_to_index,
450                                                   false);
451 
452   const content::TestWebUI::CallData& call_data = *web_ui()->call_data().back();
453   EXPECT_EQ("cr.webUIListenerCallback", call_data.function_name());
454   EXPECT_EQ("tab-moved", call_data.arg1()->GetString());
455   EXPECT_EQ(expected_tab_id, call_data.arg2()->GetInt());
456   EXPECT_EQ(expected_to_index, call_data.arg3()->GetInt());
457   EXPECT_EQ(false, call_data.arg4()->GetBool());
458 }
459 
TEST_F(TabStripUIHandlerTest,TabMovedAndPinned)460 TEST_F(TabStripUIHandlerTest, TabMovedAndPinned) {
461   AddTab(browser(), GURL("http://foo"));
462   AddTab(browser(), GURL("http://foo"));
463   web_ui()->ClearTrackedCalls();
464 
465   int from_index = 1;
466   int expected_to_index = 0;
467   int expected_tab_id = extensions::ExtensionTabUtil::GetTabId(
468       browser()->tab_strip_model()->GetWebContentsAt(from_index));
469 
470   browser()->tab_strip_model()->SetTabPinned(from_index, true);
471 
472   const content::TestWebUI::CallData& moved_event =
473       *web_ui()->call_data().front();
474   EXPECT_EQ("cr.webUIListenerCallback", moved_event.function_name());
475   EXPECT_EQ("tab-moved", moved_event.arg1()->GetString());
476   EXPECT_EQ(expected_tab_id, moved_event.arg2()->GetInt());
477   EXPECT_EQ(expected_to_index, moved_event.arg3()->GetInt());
478   EXPECT_EQ(true, moved_event.arg4()->GetBool());
479 
480   const content::TestWebUI::CallData& updated_event =
481       *web_ui()->call_data().back();
482   EXPECT_EQ("cr.webUIListenerCallback", updated_event.function_name());
483   EXPECT_EQ("tab-updated", updated_event.arg1()->GetString());
484   const base::DictionaryValue* updated_data;
485   ASSERT_TRUE(updated_event.arg2()->GetAsDictionary(&updated_data));
486   bool pinned;
487   ASSERT_TRUE(updated_data->GetBoolean("pinned", &pinned));
488   ASSERT_TRUE(pinned);
489 }
490 
TEST_F(TabStripUIHandlerTest,TabReplaced)491 TEST_F(TabStripUIHandlerTest, TabReplaced) {
492   AddTab(browser(), GURL("http://foo"));
493   int expected_previous_id = extensions::ExtensionTabUtil::GetTabId(
494       browser()->tab_strip_model()->GetWebContentsAt(0));
495 
496   web_ui()->ClearTrackedCalls();
497   browser()->tab_strip_model()->ReplaceWebContentsAt(
498       0, content::WebContentsTester::CreateTestWebContents(profile(), nullptr));
499   int expected_new_id = extensions::ExtensionTabUtil::GetTabId(
500       browser()->tab_strip_model()->GetWebContentsAt(0));
501 
502   const content::TestWebUI::CallData& call_data =
503       *web_ui()->call_data().front();
504   EXPECT_EQ("cr.webUIListenerCallback", call_data.function_name());
505   EXPECT_EQ("tab-replaced", call_data.arg1()->GetString());
506   ASSERT_EQ(expected_previous_id, call_data.arg2()->GetInt());
507   ASSERT_EQ(expected_new_id, call_data.arg3()->GetInt());
508 }
509 
TEST_F(TabStripUIHandlerTest,TabActivated)510 TEST_F(TabStripUIHandlerTest, TabActivated) {
511   AddTab(browser(), GURL("http://foo"));
512   AddTab(browser(), GURL("http://foo"));
513   AddTab(browser(), GURL("http://foo"));
514 
515   web_ui()->ClearTrackedCalls();
516   browser()->tab_strip_model()->ActivateTabAt(1);
517 
518   const content::TestWebUI::CallData& call_data = *web_ui()->call_data().back();
519   EXPECT_EQ("cr.webUIListenerCallback", call_data.function_name());
520   EXPECT_EQ("tab-active-changed", call_data.arg1()->GetString());
521   EXPECT_EQ(extensions::ExtensionTabUtil::GetTabId(
522                 browser()->tab_strip_model()->GetWebContentsAt(1)),
523             call_data.arg2()->GetInt());
524 }
525 
TEST_F(TabStripUIHandlerTest,UngroupTab)526 TEST_F(TabStripUIHandlerTest, UngroupTab) {
527   // Add a tab inside of a group.
528   AddTab(browser(), GURL("http://foo"));
529   browser()->tab_strip_model()->AddToNewGroup({0});
530 
531   // Add another tab at index 1, and try to group it.
532   base::ListValue args;
533   args.AppendInteger(extensions::ExtensionTabUtil::GetTabId(
534       browser()->tab_strip_model()->GetWebContentsAt(0)));
535   handler()->HandleUngroupTab(&args);
536 
537   ASSERT_FALSE(browser()->tab_strip_model()->GetTabGroupForTab(0).has_value());
538 }
539 
TEST_F(TabStripUIHandlerTest,CloseTab)540 TEST_F(TabStripUIHandlerTest, CloseTab) {
541   AddTab(browser(), GURL("http://foo"));
542   AddTab(browser(), GURL("http://bar"));
543 
544   base::ListValue args;
545   args.AppendInteger(extensions::ExtensionTabUtil::GetTabId(
546       browser()->tab_strip_model()->GetWebContentsAt(0)));
547   args.AppendBoolean(false);  // If the tab is closed by swipe.
548   handler()->HandleCloseTab(&args);
549 
550   ASSERT_EQ(1, browser()->tab_strip_model()->GetTabCount());
551 }
552 
TEST_F(TabStripUIHandlerTest,RemoveTabIfInvalidContextMenu)553 TEST_F(TabStripUIHandlerTest, RemoveTabIfInvalidContextMenu) {
554   AddTab(browser(), GURL("http://foo"));
555 
556   std::unique_ptr<BrowserWindow> new_window(CreateBrowserWindow());
557   std::unique_ptr<Browser> new_browser =
558       CreateBrowser(profile(), browser()->type(), false, new_window.get());
559   AddTab(new_browser.get(), GURL("http://bar"));
560 
561   web_ui()->ClearTrackedCalls();
562 
563   base::ListValue args;
564   args.AppendInteger(extensions::ExtensionTabUtil::GetTabId(
565       new_browser->tab_strip_model()->GetWebContentsAt(0)));
566   args.AppendDouble(50);
567   args.AppendDouble(100);
568   handler()->HandleShowTabContextMenu(&args);
569 
570   const content::TestWebUI::CallData& call_data = *web_ui()->call_data().back();
571   EXPECT_EQ("cr.webUIListenerCallback", call_data.function_name());
572   EXPECT_EQ("tab-removed", call_data.arg1()->GetString());
573   EXPECT_EQ(extensions::ExtensionTabUtil::GetTabId(
574                 new_browser->tab_strip_model()->GetWebContentsAt(0)),
575             call_data.arg2()->GetInt());
576 
577   // Close all tabs before destructing.
578   new_browser.get()->tab_strip_model()->CloseAllTabs();
579 }
580