chromium/chrome/browser/extensions/api/tabs/tabs_api_unittest.cc

// Copyright 2016 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#ifdef UNSAFE_BUFFERS_BUILD
// TODO(crbug.com/40285824): Remove this and convert code to safer constructs.
#pragma allow_unsafe_buffers
#endif

#include "chrome/browser/extensions/api/tabs/tabs_api.h"

#include <memory>
#include <optional>
#include <utility>

#include "base/containers/contains.h"
#include "base/run_loop.h"
#include "base/strings/stringprintf.h"
#include "base/strings/utf_string_conversions.h"
#include "base/test/scoped_feature_list.h"
#include "base/test/values_test_util.h"
#include "build/chromeos_buildflags.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/extensions/api/tab_groups/tab_groups_util.h"
#include "chrome/browser/extensions/api/tabs/tabs_constants.h"
#include "chrome/browser/extensions/extension_service_test_base.h"
#include "chrome/browser/extensions/extension_tab_util.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/sessions/session_tab_helper_factory.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/browser/ui/browser_list.h"
#include "chrome/browser/ui/tabs/saved_tab_groups/saved_tab_group_service_factory.h"
#include "chrome/browser/ui/tabs/tab_group.h"
#include "chrome/browser/ui/tabs/tab_group_model.h"
#include "chrome/browser/ui/tabs/tab_strip_model.h"
#include "chrome/browser/ui/ui_features.h"
#include "chrome/common/pref_names.h"
#include "chrome/test/base/test_browser_window.h"
#include "components/saved_tab_groups/features.h"
#include "components/sessions/content/session_tab_helper.h"
#include "components/tab_groups/tab_group_id.h"
#include "content/public/browser/navigation_entry.h"
#include "content/public/test/navigation_simulator.h"
#include "content/public/test/web_contents_tester.h"
#include "extensions/browser/api_test_utils.h"
#include "extensions/common/constants.h"
#include "extensions/common/extension_builder.h"

#if BUILDFLAG(IS_CHROMEOS_ASH)
#include "ash/test/ash_test_helper.h"
#include "ash/test/test_window_builder.h"
#include "chrome/browser/ui/chromeos/window_pin_util.h"
#endif

#if BUILDFLAG(IS_CHROMEOS)
#include "chrome/browser/chromeos/policy/dlp/test/mock_dlp_content_manager.h"
#endif

namespace extensions {

namespace {

base::Value::List RunTabsQueryFunction(content::BrowserContext* browser_context,
                                       const Extension* extension,
                                       const std::string& query_info) {}

// Creates an extension with "tabs" permission.
scoped_refptr<const Extension> CreateTabsExtension() {}

// Creates a WebContents, attaches it to the tab strip, and navigates so we
// have |urls| as history.
content::WebContents* CreateAndAppendWebContentsWithHistory(
    Profile* profile,
    TabStripModel* tab_strip_model,
    const std::vector<GURL>& urls) {}

}  // namespace

class TabsApiUnitTest : public ExtensionServiceTestBase {};

void TabsApiUnitTest::SetUp() {}

void TabsApiUnitTest::TearDown() {}

bool TabsApiUnitTest::CommitPendingLoadForController(
    content::NavigationController& controller) {}

// Bug fix for crbug.com/1196309. Ensure that an extension can't update the tab
// strip while a tab drag is in progress.
TEST_F(TabsApiUnitTest, IsTabStripEditable) {}

TEST_F(TabsApiUnitTest, QueryWithoutTabsPermission) {}

TEST_F(TabsApiUnitTest, QueryWithHostPermission) {}

// Test that using the PDF extension for tab updates is treated as a
// renderer-initiated navigation. crbug.com/660498
TEST_F(TabsApiUnitTest, PDFExtensionNavigation) {}

// Tests that non-validation failure in tabs.executeScript results in error, and
// not bad_message.
// Regression test for https://crbug.com/642794.
TEST_F(TabsApiUnitTest, ExecuteScriptNoTabIsNonFatalError) {}

// Tests that calling chrome.tabs.update updates the URL as expected.
TEST_F(TabsApiUnitTest, TabsUpdate) {}

// Tests that calling chrome.tabs.update does not update a saved tab.
TEST_F(TabsApiUnitTest, TabsUpdateSavedTabGroupTab) {}

// Tests that calling chrome.tabs.update with a JavaScript URL results
// in an error.
TEST_F(TabsApiUnitTest, TabsUpdateJavaScriptUrlNotAllowed) {}

// Test that the tabs.move() function correctly rearranges sets of tabs within a
// single window.
TEST_F(TabsApiUnitTest, TabsMoveWithinWindow) {}

// Test that the tabs.move() function correctly rearranges sets of tabs across
// windows.
TEST_F(TabsApiUnitTest, TabsMoveAcrossWindows) {}

// Tests that calling chrome.tabs.move doesn't move a saved tab.
TEST_F(TabsApiUnitTest, TabsMoveSavedTabGroupTabAllowed) {}

// Test that the tabs.group() function correctly rearranges sets of tabs within
// a single window before grouping.
TEST_F(TabsApiUnitTest, TabsGroupWithinWindow) {}

// Test that the tabs.group() function correctly groups tabs even when given
// out-of-order or duplicate tab IDs.
TEST_F(TabsApiUnitTest, TabsGroupMixedTabIds) {}

// Test that the tabs.group() function throws an error if both createProperties
// and groupId are specified.
TEST_F(TabsApiUnitTest, TabsGroupParamsError) {}

// Test that the tabs.group() function correctly rearranges sets of tabs across
// windows before grouping.
TEST_F(TabsApiUnitTest, TabsGroupAcrossWindows) {}

// Test that grouping tabs that are in a saved group should fail.
TEST_F(TabsApiUnitTest, TabsGroupForSavedTabGroupTab) {}

// Test that the tabs.ungroup() function correctly ungroups tabs from a single
// group and deletes it.
TEST_F(TabsApiUnitTest, TabsUngroupSingleGroup) {}

// Saved groups should be ungroupable from extensions.
TEST_F(TabsApiUnitTest, TabsUngroupSingleGroupForSavedTabGroup) {}

// Test that the tabs.ungroup() function correctly ungroups tabs from several
// different groups and deletes any empty ones.
TEST_F(TabsApiUnitTest, TabsUngroupFromMultipleGroups) {}

TEST_F(TabsApiUnitTest, TabsGoForwardNoSelectedTabError) {}

TEST_F(TabsApiUnitTest, TabsGoForwardAndBack) {}

TEST_F(TabsApiUnitTest, TabsGoForwardAndBackSavedTabGroupTab) {}

TEST_F(TabsApiUnitTest, TabsGoForwardAndBackWithoutTabId) {}

#if BUILDFLAG(IS_CHROMEOS)
// Ensure tabs.captureVisibleTab respects any Data Leak Prevention restrictions.
TEST_F(TabsApiUnitTest, ScreenshotsRestricted) {
  // Setup the function and extension.
  scoped_refptr<const Extension> extension =
      ExtensionBuilder("Screenshot")
          .AddAPIPermission("tabs")
          .AddHostPermission("<all_urls>")
          .Build();
  auto function = base::MakeRefCounted<TabsCaptureVisibleTabFunction>();
  function->set_extension(extension.get());

  // Add a visible tab.
  std::unique_ptr<content::WebContents> web_contents =
      content::WebContentsTester::CreateTestWebContents(profile(), nullptr);
  content::WebContentsTester* web_contents_tester =
      content::WebContentsTester::For(web_contents.get());
  const GURL kGoogle("http://www.google.com");
  GetTabStripModel()->AppendWebContents(std::move(web_contents),
                                        /*foreground=*/true);
  web_contents_tester->NavigateAndCommit(kGoogle);

  // Setup Data Leak Prevention restriction.
  policy::MockDlpContentManager mock_dlp_content_manager;
  policy::ScopedDlpContentObserverForTesting scoped_dlp_content_observer_(
      &mock_dlp_content_manager);
  EXPECT_CALL(mock_dlp_content_manager, IsScreenshotApiRestricted(testing::_))
      .Times(1)
      .WillOnce(testing::Return(true));

  // Run the function and check result.
  std::string error = api_test_utils::RunFunctionAndReturnError(
      function.get(), "[{}]", profile(), api_test_utils::FunctionMode::kNone);
  EXPECT_EQ(tabs_constants::kScreenshotsDisabledByDlp, error);
}
#endif  // BUILDFLAG(IS_CHROMEOS)

#if BUILDFLAG(IS_CHROMEOS_ASH)
TEST_F(TabsApiUnitTest, DontCreateTabsInLockedFullscreenMode) {
  scoped_refptr<const Extension> extension_with_tabs_permission =
      CreateTabsExtension();

  ash::TestWindowBuilder builder;
  std::unique_ptr<aura::Window> window =
      builder.SetTestWindowDelegate().AllowAllWindowStates().Build();
  browser_window()->SetNativeWindow(window.get());

  auto function = base::MakeRefCounted<TabsCreateFunction>();

  function->set_extension(extension_with_tabs_permission.get());

  // In locked fullscreen mode we should not be able to create any tabs.
  PinWindow(browser_window()->GetNativeWindow(), /*trusted=*/true);

  EXPECT_EQ(tabs_constants::kLockedFullscreenModeNewTabError,
            api_test_utils::RunFunctionAndReturnError(
                function.get(), "[{}]", profile(),
                api_test_utils::FunctionMode::kNone));
}

// Screenshot should return an error when disabled in user profile preferences.
TEST_F(TabsApiUnitTest, ScreenshotDisabledInProfilePreferences) {
  // Setup the function and extension.
  scoped_refptr<const Extension> extension =
      ExtensionBuilder("Screenshot")
          .AddAPIPermission("tabs")
          .AddHostPermission("<all_urls>")
          .Build();
  auto function = base::MakeRefCounted<TabsCaptureVisibleTabFunction>();
  function->set_extension(extension.get());

  // Add a visible tab.
  std::unique_ptr<content::WebContents> web_contents =
      content::WebContentsTester::CreateTestWebContents(profile(), nullptr);
  content::WebContentsTester* web_contents_tester =
      content::WebContentsTester::For(web_contents.get());
  const GURL kGoogle("http://www.google.com");
  GetTabStripModel()->AppendWebContents(std::move(web_contents),
                                        /*foreground=*/true);
  web_contents_tester->NavigateAndCommit(kGoogle);

  // Disable screenshot.
  browser()->profile()->GetPrefs()->SetBoolean(prefs::kDisableScreenshots,
                                               true);

  // Run the function and check result.
  std::string error = api_test_utils::RunFunctionAndReturnError(
      function.get(), "[{}]", profile(), api_test_utils::FunctionMode::kNone);
  EXPECT_EQ(tabs_constants::kScreenshotsDisabled, error);
}
#endif  // BUILDFLAG(IS_CHROMEOS_ASH)

TEST_F(TabsApiUnitTest, CannotDuplicatePictureInPictureWindows) {}

// Tests that calling chrome.tabs.discard discards the tab.
TEST_F(TabsApiUnitTest, TabsDiscard) {}

// Tests that calling chrome.tabs.discard on a saved tab does not discard.
TEST_F(TabsApiUnitTest, TabsDiscardSavedTabGroupTabNotAllowed) {}

#if BUILDFLAG(IS_CHROMEOS)
// Tests that calling chrome.tabs.discard on a saved tab does discard for
// extensions with locked fullscreen permission. Locked fullscreen permission
// is ChromeOS only.
TEST_F(TabsApiUnitTest,
       TabsDiscardSavedTabGroupTabAllowedForLockedFullscreenPermission) {
  scoped_refptr<const Extension> extension =
      ExtensionBuilder("DiscardTest")
          .SetID("pmgljoohajacndjcjlajcopidgnhphcl")
          .AddAPIPermission("lockWindowFullscreenPrivate")
          .Build();
  const GURL kExampleCom("http://example.com");

  // Add a web contents to the browser.
  std::unique_ptr<content::WebContents> contents(
      content::WebContentsTester::CreateTestWebContents(profile(), nullptr));
  content::WebContents* web_contents = contents.get();
  GetTabStripModel()->AppendWebContents(std::move(contents), true);
  EXPECT_EQ(GetActiveWebContents(), web_contents);
  CreateSessionServiceTabHelper(web_contents);
  int index = GetTabStripModel()->GetIndexOfWebContents(web_contents);
  int tab_id = sessions::SessionTabHelper::IdForTab(web_contents).id();

  // Navigate the browser to example.com
  content::WebContentsTester* web_contents_tester =
      content::WebContentsTester::For(web_contents);
  web_contents_tester->NavigateAndCommit(kExampleCom);
  EXPECT_EQ(kExampleCom, web_contents->GetLastCommittedURL());

  // Group the tab and save it.
  tab_groups::TabGroupId group = GetTabStripModel()->AddToNewGroup(
      {GetTabStripModel()->GetIndexOfWebContents(web_contents)});
  tab_groups::TabGroupVisualData visual_data(
      u"Initial title", tab_groups::TabGroupColorId::kBlue);
  browser()
      ->tab_strip_model()
      ->group_model()
      ->GetTabGroup(group)
      ->SetVisualData(visual_data);

  tab_groups::SavedTabGroupKeyedService* saved_service =
      tab_groups::SavedTabGroupServiceFactory::GetInstance()->GetForProfile(
          browser()->profile());
  ASSERT_NE(saved_service, nullptr);
  saved_service->SaveGroup(group);

  // The tab discard function should not fail.
  auto function = base::MakeRefCounted<TabsDiscardFunction>();
  function->set_extension(extension);
  ASSERT_TRUE(api_test_utils::RunFunction(
      function.get(), base::StringPrintf("[%d]", tab_id), profile(),
      api_test_utils::FunctionMode::kNone));
  // Check that the tab was discarded.
  content::WebContents* new_contents_at_index =
      GetTabStripModel()->GetWebContentsAt(index);
  EXPECT_TRUE(new_contents_at_index->WasDiscarded());

  // Clean up.
  saved_service->UnsaveGroup(group, tab_groups::ClosingSource::kUnknown);
}
#endif  // BUILDFLAG(IS_CHROMEOS)

}  // namespace extensions