| // Copyright 2012 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include <limits.h> |
| #include <stddef.h> |
| #include <stdint.h> |
| |
| #include <array> |
| #include <memory> |
| #include <string> |
| |
| #include "base/files/scoped_temp_dir.h" |
| #include "base/format_macros.h" |
| #include "base/memory/ref_counted.h" |
| #include "base/run_loop.h" |
| #include "base/strings/pattern.h" |
| #include "base/strings/string_split.h" |
| #include "base/strings/string_util.h" |
| #include "base/strings/stringprintf.h" |
| #include "base/strings/to_string.h" |
| #include "base/test/gmock_expected_support.h" |
| #include "base/test/scoped_feature_list.h" |
| #include "base/test/simple_test_tick_clock.h" |
| #include "base/test/test_future.h" |
| #include "base/threading/thread_restrictions.h" |
| #include "base/values.h" |
| #include "build/build_config.h" |
| #include "build/chromeos_buildflags.h" |
| #include "chrome/browser/browser_process.h" |
| #include "chrome/browser/devtools/devtools_window_testing.h" |
| #include "chrome/browser/extensions/api/tabs/tabs_api.h" |
| #include "chrome/browser/extensions/api/tabs/tabs_constants.h" |
| #include "chrome/browser/extensions/api/tabs/tabs_windows_api.h" |
| #include "chrome/browser/extensions/browser_extension_window_controller.h" |
| #include "chrome/browser/extensions/extension_apitest.h" |
| #include "chrome/browser/extensions/extension_tab_util.h" |
| #include "chrome/browser/extensions/profile_util.h" |
| #include "chrome/browser/extensions/window_controller.h" |
| #include "chrome/browser/prefs/incognito_mode_prefs.h" |
| #include "chrome/browser/profiles/profile.h" |
| #include "chrome/browser/ui/browser_window/public/browser_window_features.h" |
| #include "chrome/browser/ui/browser_window/public/browser_window_interface.h" |
| #include "chrome/browser/ui/tabs/saved_tab_groups/saved_tab_group_utils.h" |
| #include "chrome/browser/ui/tabs/tab_list_interface.h" |
| #include "chrome/browser/ui/ui_features.h" |
| #include "chrome/browser/ui/zoom/chrome_zoom_level_prefs.h" |
| #include "chrome/common/chrome_switches.h" |
| #include "chrome/common/webui_url_constants.h" |
| #include "components/policy/core/common/policy_pref_names.h" |
| #include "components/prefs/pref_service.h" |
| #include "components/saved_tab_groups/public/tab_group_sync_service.h" |
| #include "components/tabs/public/split_tab_id.h" |
| #include "content/public/browser/browser_context.h" |
| #include "content/public/browser/render_frame_host.h" |
| #include "content/public/browser/storage_partition.h" |
| #include "content/public/common/content_features.h" |
| #include "content/public/common/url_constants.h" |
| #include "content/public/test/browser_test.h" |
| #include "content/public/test/browser_test_utils.h" |
| #include "content/public/test/test_navigation_observer.h" |
| #include "extensions/browser/api_test_utils.h" |
| #include "extensions/browser/event_router.h" |
| #include "extensions/browser/extension_function_dispatcher.h" |
| #include "extensions/browser/test_event_router_observer.h" |
| #include "extensions/common/constants.h" |
| #include "extensions/common/error_utils.h" |
| #include "extensions/common/extension_builder.h" |
| #include "extensions/common/manifest_constants.h" |
| #include "extensions/common/mojom/context_type.mojom.h" |
| #include "extensions/test/extension_test_message_listener.h" |
| #include "extensions/test/result_catcher.h" |
| #include "net/test/embedded_test_server/embedded_test_server.h" |
| #include "pdf/buildflags.h" |
| #include "testing/gmock/include/gmock/gmock.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| #include "third_party/blink/public/common/page/page_zoom.h" |
| #include "ui/base/base_window.h" |
| #include "ui/base/ozone_buildflags.h" |
| #include "ui/base/window_open_disposition.h" |
| #include "ui/display/display.h" |
| #include "ui/display/screen.h" |
| #include "ui/gfx/geometry/rect.h" |
| #include "ui/views/widget/widget_interactive_uitest_utils.h" |
| #include "url/gurl.h" |
| |
| #if !BUILDFLAG(IS_ANDROID) |
| #include "chrome/browser/resource_coordinator/tab_lifecycle_unit_external.h" |
| #include "chrome/browser/resource_coordinator/tab_lifecycle_unit_source.h" |
| #include "chrome/browser/resource_coordinator/tab_manager.h" |
| #include "chrome/browser/resource_coordinator/time.h" |
| #include "chrome/browser/resource_coordinator/utils.h" |
| #include "chrome/browser/ui/browser.h" |
| #include "chrome/browser/ui/browser_commands.h" |
| #include "chrome/browser/ui/browser_finder.h" |
| #include "chrome/browser/ui/browser_list.h" |
| #include "chrome/browser/ui/browser_tabstrip.h" |
| #include "chrome/browser/ui/browser_window.h" |
| #include "chrome/browser/ui/tabs/tab_strip_model.h" |
| #include "chrome/browser/ui/web_applications/test/isolated_web_app_test_utils.h" |
| #include "chrome/browser/web_applications/isolated_web_apps/commands/install_isolated_web_app_command.h" |
| #include "chrome/browser/web_applications/isolated_web_apps/install/isolated_web_app_install_source.h" |
| #include "chrome/browser/web_applications/isolated_web_apps/isolated_web_app_trust_checker.h" |
| #include "chrome/browser/web_applications/isolated_web_apps/isolated_web_app_url_info.h" |
| #include "chrome/browser/web_applications/isolated_web_apps/test/isolated_web_app_builder.h" |
| #include "chrome/browser/web_applications/test/web_app_install_test_utils.h" |
| #include "chrome/browser/web_applications/web_app_command_scheduler.h" |
| #include "chrome/browser/web_applications/web_app_provider.h" |
| #include "chrome/browser/web_applications/web_app_provider_factory.h" |
| #include "chrome/test/base/in_process_browser_test.h" |
| #include "chrome/test/base/ui_test_utils.h" |
| #include "components/webapps/isolated_web_apps/test_support/signing_keys.h" |
| #endif |
| |
| #if BUILDFLAG(IS_MAC) |
| #include "ui/base/test/scoped_fake_nswindow_fullscreen.h" |
| #endif |
| |
| #if BUILDFLAG(ENABLE_PDF) |
| #include "base/test/with_feature_override.h" |
| #include "chrome/browser/pdf/pdf_extension_test_base.h" |
| #include "chrome/browser/pdf/pdf_extension_test_util.h" |
| #include "chrome/browser/pdf/test_pdf_viewer_stream_manager.h" |
| #include "pdf/pdf_features.h" |
| #endif |
| |
| #if BUILDFLAG(ENABLE_PLATFORM_APPS) |
| #include "chrome/browser/apps/platform_apps/app_browsertest_util.h" |
| #include "extensions/browser/app_window/app_window.h" |
| #include "extensions/browser/app_window/app_window_registry.h" |
| #endif |
| |
| namespace extensions { |
| |
| namespace keys = tabs_constants; |
| namespace utils = api_test_utils; |
| |
| namespace { |
| |
| #if BUILDFLAG(ENABLE_EXTENSIONS) |
| |
| // Returns true if |val| contains any privacy information, e.g. url, |
| // pendingUrl, title or faviconUrl. |
| bool HasAnyPrivacySensitiveFields(const base::Value::Dict& dict) { |
| constexpr std::array privacySensitiveKeys{ |
| tabs_constants::kUrlKey, tabs_constants::kTitleKey, |
| tabs_constants::kFaviconUrlKey, tabs_constants::kPendingUrlKey}; |
| for (auto* key : privacySensitiveKeys) { |
| if (dict.contains(key)) { |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| #endif // BUILDFLAG(ENABLE_EXTENSIONS) |
| |
| class TestFunctionDispatcherDelegate |
| : public extensions::ExtensionFunctionDispatcher::Delegate { |
| public: |
| explicit TestFunctionDispatcherDelegate(BrowserWindowInterface* browser) |
| : browser_(browser) {} |
| ~TestFunctionDispatcherDelegate() override = default; |
| |
| private: |
| extensions::WindowController* GetExtensionWindowController() const override { |
| return BrowserExtensionWindowController::From(browser_); |
| } |
| |
| content::WebContents* GetAssociatedWebContents() const override { |
| return nullptr; |
| } |
| |
| raw_ptr<BrowserWindowInterface> browser_; |
| }; |
| |
| class ExtensionTabsTest : public ExtensionApiTest { |
| public: |
| ExtensionTabsTest() = default; |
| |
| ExtensionTabsTest(const ExtensionTabsTest&) = delete; |
| ExtensionTabsTest& operator=(const ExtensionTabsTest&) = delete; |
| |
| TabListInterface* GetTabListInterface(BrowserWindowInterface* browser) { |
| return TabListInterface::From(browser); |
| } |
| TabListInterface* GetTabListInterface() { |
| return GetTabListInterface(browser_window_interface()); |
| } |
| |
| std::string GetWindowType(BrowserWindowInterface* test_browser, |
| scoped_refptr<const Extension> extension) { |
| auto function = base::MakeRefCounted<WindowsGetFunction>(); |
| function->set_extension(extension.get()); |
| std::string args = base::StringPrintf( |
| R"([%u, {"windowTypes": ["normal", "popup", "devtools", "app"]}])", |
| ExtensionTabUtil::GetWindowId(test_browser)); |
| base::Value::Dict result = |
| utils::ToDict(utils::RunFunctionAndReturnSingleResult(function.get(), |
| args, profile())); |
| return api_test_utils::GetString(result, "type"); |
| } |
| |
| std::optional<base::Value> RunFunctionWithDispatcherDelegateAndReturnValue( |
| scoped_refptr<ExtensionFunction> function, |
| const std::string& args, |
| BrowserWindowInterface* browser) { |
| auto dispatcher = std::make_unique<extensions::ExtensionFunctionDispatcher>( |
| browser->GetProfile()); |
| TestFunctionDispatcherDelegate dispatcher_delegate(browser); |
| dispatcher->set_delegate(&dispatcher_delegate); |
| return utils::RunFunctionWithDelegateAndReturnSingleResult( |
| std::move(function), args, std::move(dispatcher), |
| utils::FunctionMode::kNone); |
| } |
| }; |
| |
| #if BUILDFLAG(ENABLE_PLATFORM_APPS) |
| |
| using ExtensionTabsTestWithApps = PlatformAppBrowserTest; |
| |
| #endif |
| |
| class ExtensionWindowCreateTest : public ExtensionBrowserTest { |
| public: |
| // Runs chrome.windows.create(), expecting an error. |
| std::string RunCreateWindowExpectError(const std::string& args) { |
| auto function = base::MakeRefCounted<WindowsCreateFunction>(); |
| function->set_extension(ExtensionBuilder("Test").Build().get()); |
| return api_test_utils::RunFunctionAndReturnError(function.get(), args, |
| profile()); |
| } |
| }; |
| |
| #if BUILDFLAG(ENABLE_EXTENSIONS) |
| const int kUndefinedId = INT_MIN; |
| |
| const ExtensionTabUtil::ScrubTabBehavior kDontScrubBehavior = { |
| ExtensionTabUtil::kDontScrubTab, ExtensionTabUtil::kDontScrubTab}; |
| |
| int GetTabId(const base::Value::Dict& tab) { |
| return tab.FindInt(extension_misc::kId).value_or(kUndefinedId); |
| } |
| |
| int GetTabWindowId(const base::Value::Dict& tab) { |
| return tab.FindInt(keys::kWindowIdKey).value_or(kUndefinedId); |
| } |
| |
| int GetWindowId(const base::Value::Dict& window) { |
| return window.FindInt(extension_misc::kId).value_or(kUndefinedId); |
| } |
| #endif |
| |
| } // namespace |
| |
| #if BUILDFLAG(ENABLE_EXTENSIONS) |
| |
| IN_PROC_BROWSER_TEST_F(ExtensionTabsTest, GetWindow) { |
| int window_id = ExtensionTabUtil::GetWindowId(browser_window_interface()); |
| |
| // Invalid window ID error. |
| auto function = base::MakeRefCounted<WindowsGetFunction>(); |
| scoped_refptr<const Extension> extension(ExtensionBuilder("Test").Build()); |
| function->set_extension(extension.get()); |
| EXPECT_TRUE(base::MatchPattern( |
| utils::RunFunctionAndReturnError( |
| function.get(), base::StringPrintf("[%u]", window_id + 1), profile()), |
| ExtensionTabUtil::kWindowNotFoundError)); |
| |
| // Basic window details. |
| gfx::Rect bounds; |
| if (browser_window_interface()->GetWindow()->IsMinimized()) { |
| bounds = browser_window_interface()->GetWindow()->GetRestoredBounds(); |
| } else { |
| bounds = browser_window_interface()->GetWindow()->GetBounds(); |
| } |
| |
| function = base::MakeRefCounted<WindowsGetFunction>(); |
| function->set_extension(extension.get()); |
| base::Value::Dict result = |
| utils::ToDict(utils::RunFunctionAndReturnSingleResult( |
| function.get(), base::StringPrintf("[%u]", window_id), profile())); |
| EXPECT_EQ(window_id, GetWindowId(result)); |
| EXPECT_FALSE(api_test_utils::GetBoolean(result, "incognito")); |
| EXPECT_EQ("normal", api_test_utils::GetString(result, "type")); |
| EXPECT_EQ(bounds.x(), api_test_utils::GetInteger(result, "left")); |
| EXPECT_EQ(bounds.y(), api_test_utils::GetInteger(result, "top")); |
| EXPECT_EQ(bounds.width(), api_test_utils::GetInteger(result, "width")); |
| EXPECT_EQ(bounds.height(), api_test_utils::GetInteger(result, "height")); |
| |
| // With "populate" enabled. |
| function = base::MakeRefCounted<WindowsGetFunction>(); |
| function->set_extension(extension.get()); |
| result = utils::ToDict(utils::RunFunctionAndReturnSingleResult( |
| function.get(), |
| base::StringPrintf("[%u, {\"populate\": true}]", window_id), profile())); |
| |
| EXPECT_EQ(window_id, GetWindowId(result)); |
| // "populate" was enabled so tabs should be populated. |
| base::Value::List tabs = |
| api_test_utils::GetList(result, ExtensionTabUtil::kTabsKey); |
| ASSERT_FALSE(tabs.empty()); |
| std::optional<int> tab0_id = tabs[0].GetDict().FindInt(extension_misc::kId); |
| ASSERT_TRUE(tab0_id.has_value()); |
| EXPECT_GE(*tab0_id, 0); |
| |
| // TODO(aa): Can't assume window is focused. On mac, calling Activate() from a |
| // browser test doesn't seem to do anything, so can't test the opposite |
| // either. |
| EXPECT_EQ(browser_window_interface()->GetWindow()->IsActive(), |
| api_test_utils::GetBoolean(result, "focused")); |
| |
| // TODO(aa): Minimized and maximized dimensions. Is there a way to set |
| // minimize/maximize programmatically? |
| |
| // Check window type. |
| EXPECT_EQ("normal", GetWindowType(browser_window_interface(), extension)); |
| Browser* test_browser = Browser::Create( |
| Browser::CreateParams(Browser::TYPE_POPUP, profile(), true)); |
| EXPECT_EQ("popup", GetWindowType(test_browser, extension)); |
| DevToolsWindow* devtools = DevToolsWindowTesting::OpenDevToolsWindowSync( |
| GetTabListInterface()->GetTab(0)->GetContents(), false /* is_docked */); |
| EXPECT_EQ("devtools", |
| GetWindowType(DevToolsWindowTesting::Get(devtools)->browser(), |
| extension)); |
| DevToolsWindowTesting::CloseDevToolsWindowSync(devtools); |
| test_browser = Browser::Create(Browser::CreateParams::CreateForApp( |
| "test-app", true, gfx::Rect(), profile(), true)); |
| EXPECT_EQ("app", GetWindowType(test_browser, extension)); |
| test_browser = Browser::Create(Browser::CreateParams::CreateForAppPopup( |
| "test-app-popup", true, gfx::Rect(), profile(), true)); |
| EXPECT_EQ("popup", GetWindowType(test_browser, extension)); |
| |
| // Incognito. |
| Browser* incognito_browser = CreateIncognitoBrowser(); |
| int incognito_window_id = ExtensionTabUtil::GetWindowId(incognito_browser); |
| |
| // Without "include_incognito". |
| function = base::MakeRefCounted<WindowsGetFunction>(); |
| function->set_extension(extension.get()); |
| EXPECT_TRUE(base::MatchPattern( |
| utils::RunFunctionAndReturnError( |
| function.get(), base::StringPrintf("[%u]", incognito_window_id), |
| profile()), |
| ExtensionTabUtil::kWindowNotFoundError)); |
| |
| // With "include_incognito". |
| function = base::MakeRefCounted<WindowsGetFunction>(); |
| function->set_extension(extension.get()); |
| result = utils::ToDict(utils::RunFunctionAndReturnSingleResult( |
| function.get(), base::StringPrintf("[%u]", incognito_window_id), |
| profile(), api_test_utils::FunctionMode::kIncognito)); |
| EXPECT_TRUE(api_test_utils::GetBoolean(result, "incognito")); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(ExtensionTabsTest, GetCurrentWindow) { |
| int window_id = ExtensionTabUtil::GetWindowId(browser_window_interface()); |
| Browser* new_browser = CreateBrowser(profile()); |
| int new_id = ExtensionTabUtil::GetWindowId(new_browser); |
| |
| // Get the current window using new_browser. |
| auto function = base::MakeRefCounted<WindowsGetCurrentFunction>(); |
| scoped_refptr<const Extension> extension(ExtensionBuilder("Test").Build()); |
| function->set_extension(extension.get()); |
| base::Value::Dict result = |
| utils::ToDict(RunFunctionWithDispatcherDelegateAndReturnValue( |
| function.get(), "[]", new_browser)); |
| |
| // The id should match the window id of the browser instance that was passed |
| // to RunFunctionWithDispatcherDelegateAndReturnValue. |
| EXPECT_EQ(new_id, GetWindowId(result)); |
| EXPECT_FALSE(result.contains(ExtensionTabUtil::kTabsKey)); |
| |
| // Get the current window using the old window and make the tabs populated. |
| function = base::MakeRefCounted<WindowsGetCurrentFunction>(); |
| function->set_extension(extension.get()); |
| result = utils::ToDict(RunFunctionWithDispatcherDelegateAndReturnValue( |
| function.get(), "[{\"populate\": true}]", browser_window_interface())); |
| |
| // The id should match the window id of the browser instance that was passed |
| // to RunFunctionWithDispatcherDelegateAndReturnValue. |
| EXPECT_EQ(window_id, GetWindowId(result)); |
| // "populate" was enabled so tabs should be populated. |
| base::Value::List tabs = |
| api_test_utils::GetList(result, ExtensionTabUtil::kTabsKey); |
| ASSERT_FALSE(tabs.empty()); |
| std::optional<int> tab0_id = tabs[0].GetDict().FindInt(extension_misc::kId); |
| ASSERT_TRUE(tab0_id.has_value()); |
| // The tab id should not be -1 as this is a browser window. |
| EXPECT_GE(*tab0_id, 0); |
| } |
| |
| #if BUILDFLAG(ENABLE_PLATFORM_APPS) |
| |
| // TODO(crbug.com/40745605): Test is flaky on Linux debug builds. |
| #if BUILDFLAG(IS_LINUX) && !defined(NDEBUG) |
| #define MAYBE_GetAllWindows DISABLED_GetAllWindows |
| #else |
| #define MAYBE_GetAllWindows GetAllWindows |
| #endif |
| IN_PROC_BROWSER_TEST_F(ExtensionTabsTestWithApps, MAYBE_GetAllWindows) { |
| const size_t NUM_WINDOWS = 5; |
| std::set<int> window_ids; |
| std::set<int> result_ids; |
| window_ids.insert(ExtensionTabUtil::GetWindowId(browser_window_interface())); |
| |
| for (size_t i = 0; i < NUM_WINDOWS - 1; ++i) { |
| Browser* new_browser = CreateBrowser(profile()); |
| window_ids.insert(ExtensionTabUtil::GetWindowId(new_browser)); |
| } |
| |
| // Application windows should not be accessible to extensions (app windows are |
| // only accessible to the owning item). |
| AppWindow* app_window = CreateTestAppWindow("{}"); |
| |
| // Undocked DevTools window should not be accessible, unless included in the |
| // type filter mask. |
| DevToolsWindow* devtools = DevToolsWindowTesting::OpenDevToolsWindowSync( |
| TabListInterface::From(browser_window_interface()) |
| ->GetTab(0) |
| ->GetContents(), |
| false /* is_docked */); |
| |
| auto function = base::MakeRefCounted<WindowsGetAllFunction>(); |
| scoped_refptr<const Extension> extension(ExtensionBuilder("Test").Build()); |
| function->set_extension(extension.get()); |
| base::Value::List windows = utils::ToList( |
| utils::RunFunctionAndReturnSingleResult(function.get(), "[]", profile())); |
| |
| EXPECT_EQ(window_ids.size(), windows.size()); |
| for (const base::Value& result_window : windows) { |
| base::Value::Dict result_window_dict = utils::ToDict(result_window); |
| result_ids.insert(GetWindowId(result_window_dict)); |
| |
| // "populate" was not passed in so tabs are not populated. |
| const base::Value::List* tabs = |
| result_window_dict.FindList(ExtensionTabUtil::kTabsKey); |
| EXPECT_FALSE(tabs); |
| } |
| // The returned ids should contain all the current browser instance ids. |
| EXPECT_EQ(window_ids, result_ids); |
| |
| result_ids.clear(); |
| function = base::MakeRefCounted<WindowsGetAllFunction>(); |
| function->set_extension(extension.get()); |
| windows = utils::ToList(utils::RunFunctionAndReturnSingleResult( |
| function.get(), "[{\"populate\": true}]", profile())); |
| |
| EXPECT_EQ(window_ids.size(), windows.size()); |
| for (const base::Value& result_window : windows) { |
| base::Value::Dict result_window_dict = utils::ToDict(result_window); |
| result_ids.insert(GetWindowId(result_window_dict)); |
| |
| // "populate" was enabled so tabs should be populated. |
| const base::Value::List* tabs = |
| result_window_dict.FindList(ExtensionTabUtil::kTabsKey); |
| EXPECT_TRUE(tabs); |
| } |
| // The returned ids should contain all the current app, browser and |
| // devtools instance ids. |
| EXPECT_EQ(window_ids, result_ids); |
| |
| DevToolsWindowTesting::CloseDevToolsWindowSync(devtools); |
| |
| CloseAppWindow(app_window); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(ExtensionTabsTestWithApps, GetAllWindowsAllTypes) { |
| const size_t NUM_WINDOWS = 5; |
| std::set<int> window_ids; |
| std::set<int> result_ids; |
| window_ids.insert(ExtensionTabUtil::GetWindowId(browser_window_interface())); |
| |
| for (size_t i = 0; i < NUM_WINDOWS - 1; ++i) { |
| Browser* new_browser = CreateBrowser(profile()); |
| window_ids.insert(ExtensionTabUtil::GetWindowId(new_browser)); |
| } |
| |
| // Application windows should not be accessible to extensions (app windows are |
| // only accessible to the owning item). |
| AppWindow* app_window = CreateTestAppWindow("{}"); |
| |
| // Undocked DevTools window should be accessible too, since they have been |
| // explicitly requested as part of the type filter mask. |
| DevToolsWindow* devtools = DevToolsWindowTesting::OpenDevToolsWindowSync( |
| TabListInterface::From(browser_window_interface()) |
| ->GetTab(0) |
| ->GetContents(), |
| false /* is_docked */); |
| window_ids.insert(ExtensionTabUtil::GetWindowId( |
| DevToolsWindowTesting::Get(devtools)->browser())); |
| |
| auto function = base::MakeRefCounted<WindowsGetAllFunction>(); |
| scoped_refptr<const Extension> extension(ExtensionBuilder("Test").Build()); |
| function->set_extension(extension.get()); |
| base::Value::List windows( |
| utils::ToList(utils::RunFunctionAndReturnSingleResult( |
| function.get(), |
| "[{\"windowTypes\": [\"app\", \"devtools\", \"normal\", \"panel\", " |
| "\"popup\"]}]", |
| profile()))); |
| |
| EXPECT_EQ(window_ids.size(), windows.size()); |
| for (const base::Value& result_window : windows) { |
| base::Value::Dict result_window_dict = utils::ToDict(result_window); |
| result_ids.insert(GetWindowId(result_window_dict)); |
| |
| // "populate" was not passed in so tabs are not populated. |
| const base::Value::List* tabs = |
| result_window_dict.FindList(ExtensionTabUtil::kTabsKey); |
| EXPECT_FALSE(tabs); |
| } |
| // The returned ids should contain all the browser and devtools instance ids. |
| EXPECT_EQ(window_ids, result_ids); |
| |
| result_ids.clear(); |
| function = base::MakeRefCounted<WindowsGetAllFunction>(); |
| function->set_extension(extension.get()); |
| windows = utils::ToList(utils::RunFunctionAndReturnSingleResult( |
| function.get(), |
| "[{\"populate\": true, \"windowTypes\": [\"app\", \"devtools\", " |
| "\"normal\", \"panel\", \"popup\"]}]", |
| profile())); |
| |
| EXPECT_EQ(window_ids.size(), windows.size()); |
| for (const base::Value& result_window : windows) { |
| base::Value::Dict result_window_dict = utils::ToDict(result_window); |
| result_ids.insert(GetWindowId(result_window_dict)); |
| |
| // "populate" was enabled so tabs should be populated. |
| const base::Value::List* tabs = |
| result_window_dict.FindList(ExtensionTabUtil::kTabsKey); |
| EXPECT_TRUE(tabs); |
| } |
| // The returned ids should contain all the browser and devtools instance ids. |
| EXPECT_EQ(window_ids, result_ids); |
| |
| DevToolsWindowTesting::CloseDevToolsWindowSync(devtools); |
| |
| CloseAppWindow(app_window); |
| } |
| |
| #endif // BUIDFLAG(ENABLE_PLATFORM_APPS) |
| |
| IN_PROC_BROWSER_TEST_F(ExtensionTabsTest, UpdateNoPermissions) { |
| // The test empty extension has no permissions, therefore it should not get |
| // tab data in the function result. |
| auto update_tab_function = base::MakeRefCounted<TabsUpdateFunction>(); |
| scoped_refptr<const Extension> empty_extension( |
| ExtensionBuilder("Test").Build()); |
| update_tab_function->set_extension(empty_extension.get()); |
| // Without a callback the function will not generate a result. |
| update_tab_function->set_has_callback(true); |
| |
| const base::Value::Dict result = |
| utils::ToDict(utils::RunFunctionAndReturnSingleResult( |
| update_tab_function.get(), |
| "[null, {\"url\": \"about:blank\", \"pinned\": true}]", profile())); |
| // The url is stripped since the extension does not have tab permissions. |
| EXPECT_FALSE(result.contains("url")); |
| EXPECT_TRUE(api_test_utils::GetBoolean(result, "pinned")); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(ExtensionTabsTest, |
| DisallowNonIncognitoUrlInIncognitoWindow) { |
| Browser* incognito = CreateIncognitoBrowser(); |
| |
| auto update_tab_function = base::MakeRefCounted<TabsUpdateFunction>(); |
| scoped_refptr<const Extension> extension(ExtensionBuilder("Test").Build()); |
| update_tab_function->set_extension(extension.get()); |
| update_tab_function->set_include_incognito_information(true); |
| |
| std::string error = api_test_utils::RunFunctionAndReturnError( |
| update_tab_function.get(), |
| std::string("[null, {\"url\": \"") + chrome::kChromeUIExtensionsURL + |
| chrome::kExtensionConfigureCommandsSubPage + "\"}]", |
| incognito->profile(), // incognito doesn't have any tabs. |
| api_test_utils::FunctionMode::kNone); |
| EXPECT_EQ(ErrorUtils::FormatErrorMessage( |
| tabs_constants::kURLsNotAllowedInIncognitoError, |
| std::string(chrome::kChromeUIExtensionsURL) + |
| chrome::kExtensionConfigureCommandsSubPage), |
| error); |
| |
| // Ensure the tab was not updated. It should stay as the new tab page. |
| TabListInterface* tab_list = GetTabListInterface(incognito); |
| EXPECT_EQ(1, tab_list->GetTabCount()); |
| EXPECT_EQ(GURL(url::kAboutBlankURL), |
| tab_list->GetActiveTab()->GetContents()->GetLastCommittedURL()); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(ExtensionTabsTest, DefaultToIncognitoWhenItIsForced) { |
| static const char kArgsWithoutExplicitIncognitoParam[] = |
| "[{\"url\": \"about:blank\"}]"; |
| // Force Incognito mode. |
| IncognitoModePrefs::SetAvailability( |
| profile()->GetPrefs(), policy::IncognitoModeAvailability::kForced); |
| // Run without an explicit "incognito" param. |
| auto function = base::MakeRefCounted<WindowsCreateFunction>(); |
| function->SetRenderFrameHost(GetTabListInterface() |
| ->GetActiveTab() |
| ->GetContents() |
| ->GetPrimaryMainFrame()); |
| scoped_refptr<const Extension> extension(ExtensionBuilder("Test").Build()); |
| function->set_extension(extension.get()); |
| base::Value::Dict result = |
| utils::ToDict(utils::RunFunctionAndReturnSingleResult( |
| function.get(), kArgsWithoutExplicitIncognitoParam, profile(), |
| api_test_utils::FunctionMode::kIncognito)); |
| |
| // Make sure it is a new(different) window. |
| EXPECT_NE(ExtensionTabUtil::GetWindowId(browser_window_interface()), |
| GetWindowId(result)); |
| // ... and it is incognito. |
| EXPECT_TRUE(api_test_utils::GetBoolean(result, "incognito")); |
| |
| // Now try creating a window from incognito window. |
| Browser* incognito_browser = CreateIncognitoBrowser(); |
| // Run without an explicit "incognito" param. |
| function = base::MakeRefCounted<WindowsCreateFunction>(); |
| function->SetRenderFrameHost(GetTabListInterface() |
| ->GetActiveTab() |
| ->GetContents() |
| ->GetPrimaryMainFrame()); |
| function->set_extension(extension.get()); |
| result = utils::ToDict(utils::RunFunctionAndReturnSingleResult( |
| function.get(), kArgsWithoutExplicitIncognitoParam, |
| incognito_browser->profile(), api_test_utils::FunctionMode::kIncognito)); |
| // Make sure it is a new(different) window. |
| EXPECT_NE(ExtensionTabUtil::GetWindowId(incognito_browser), |
| GetWindowId(result)); |
| // ... and it is incognito. |
| EXPECT_TRUE(api_test_utils::GetBoolean(result, "incognito")); |
| } |
| |
| // Regression test for crbug.com/427147470. Verifies that opening |
| // chrome-extension:// URLs using chrome.tabs.create() from non-extension |
| // contexts (e.g. WebUI pages) works as expected. |
| IN_PROC_BROWSER_TEST_F(ExtensionTabsTest, |
| CreateExtensionTabFromNonExtensionContext) { |
| auto function = base::MakeRefCounted<TabsCreateFunction>(); |
| function->SetRenderFrameHost(GetTabListInterface() |
| ->GetActiveTab() |
| ->GetContents() |
| ->GetPrimaryMainFrame()); |
| function->set_extension(nullptr); |
| |
| const Extension* extension = |
| LoadExtension(test_data_dir_.AppendASCII("../options_page")); |
| ASSERT_TRUE(extension); |
| GURL extension_url = extension->ResolveExtensionURL("options.html"); |
| |
| const std::string args_with_extension_url = |
| base::StringPrintf(R"([{ "url": "%s" }])", extension_url.spec()); |
| base::Value::Dict result = |
| utils::ToDict(utils::RunFunctionAndReturnSingleResult( |
| function.get(), args_with_extension_url, profile())); |
| |
| int tab_id = GetTabId(result); |
| content::WebContents* created_tab = nullptr; |
| ExtensionTabUtil::GetTabById(tab_id, profile(), /*include_incognito=*/false, |
| &created_tab); |
| ASSERT_TRUE(created_tab); |
| content::WaitForLoadStop(created_tab); |
| EXPECT_EQ(created_tab->GetPrimaryMainFrame()->GetLastCommittedURL(), |
| extension_url); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(ExtensionTabsTest, |
| DefaultToIncognitoWhenItIsForcedAndNoArgs) { |
| static const char kEmptyArgs[] = "[]"; |
| // Force Incognito mode. |
| IncognitoModePrefs::SetAvailability( |
| profile()->GetPrefs(), policy::IncognitoModeAvailability::kForced); |
| // Run without an explicit "incognito" param. |
| auto function = base::MakeRefCounted<WindowsCreateFunction>(); |
| scoped_refptr<const Extension> extension(ExtensionBuilder("Test").Build()); |
| function->set_extension(extension.get()); |
| base::Value::Dict result = |
| utils::ToDict(utils::RunFunctionAndReturnSingleResult( |
| function.get(), kEmptyArgs, profile(), |
| api_test_utils::FunctionMode::kIncognito)); |
| |
| // Make sure it is a new(different) window. |
| EXPECT_NE(ExtensionTabUtil::GetWindowId(browser_window_interface()), |
| GetWindowId(result)); |
| // ... and it is incognito. |
| EXPECT_TRUE(api_test_utils::GetBoolean(result, "incognito")); |
| |
| // Now try creating a window from incognito window. |
| Browser* incognito_browser = CreateIncognitoBrowser(); |
| // Run without an explicit "incognito" param. |
| function = base::MakeRefCounted<WindowsCreateFunction>(); |
| function->set_extension(extension.get()); |
| result = utils::ToDict(utils::RunFunctionAndReturnSingleResult( |
| function.get(), kEmptyArgs, incognito_browser->profile(), |
| api_test_utils::FunctionMode::kIncognito)); |
| // Make sure it is a new(different) window. |
| EXPECT_NE(ExtensionTabUtil::GetWindowId(incognito_browser), |
| GetWindowId(result)); |
| // ... and it is incognito. |
| EXPECT_TRUE(api_test_utils::GetBoolean(result, "incognito")); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(ExtensionTabsTest, |
| DontCreateNormalWindowWhenIncognitoForced) { |
| static const char kArgsWithExplicitIncognitoParam[] = |
| "[{\"url\": \"about:blank\", \"incognito\": false }]"; |
| // Force Incognito mode. |
| IncognitoModePrefs::SetAvailability( |
| profile()->GetPrefs(), policy::IncognitoModeAvailability::kForced); |
| |
| // Run with an explicit "incognito" param. |
| auto function = base::MakeRefCounted<WindowsCreateFunction>(); |
| scoped_refptr<const Extension> extension(ExtensionBuilder("Test").Build()); |
| function->set_extension(extension.get()); |
| EXPECT_TRUE(base::MatchPattern( |
| utils::RunFunctionAndReturnError( |
| function.get(), kArgsWithExplicitIncognitoParam, profile()), |
| keys::kIncognitoModeIsForced)); |
| |
| // Now try opening a normal window from incognito window. |
| Browser* incognito_browser = CreateIncognitoBrowser(); |
| // Run with an explicit "incognito" param. |
| function = base::MakeRefCounted<WindowsCreateFunction>(); |
| function->set_extension(extension.get()); |
| EXPECT_TRUE(base::MatchPattern( |
| utils::RunFunctionAndReturnError(function.get(), |
| kArgsWithExplicitIncognitoParam, |
| incognito_browser->profile()), |
| keys::kIncognitoModeIsForced)); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(ExtensionTabsTest, |
| DontCreateIncognitoWindowWhenIncognitoDisabled) { |
| static const char kArgs[] = |
| "[{\"url\": \"about:blank\", \"incognito\": true }]"; |
| |
| Browser* incognito_browser = CreateIncognitoBrowser(); |
| // Disable Incognito mode. |
| IncognitoModePrefs::SetAvailability( |
| profile()->GetPrefs(), policy::IncognitoModeAvailability::kDisabled); |
| // Run in normal window. |
| auto function = base::MakeRefCounted<WindowsCreateFunction>(); |
| scoped_refptr<const Extension> extension(ExtensionBuilder("Test").Build()); |
| function->set_extension(extension.get()); |
| EXPECT_TRUE(base::MatchPattern( |
| utils::RunFunctionAndReturnError(function.get(), kArgs, profile()), |
| keys::kIncognitoModeIsDisabled)); |
| |
| // Run in incognito window. |
| function = base::MakeRefCounted<WindowsCreateFunction>(); |
| function->set_extension(extension.get()); |
| EXPECT_TRUE(base::MatchPattern( |
| utils::RunFunctionAndReturnError(function.get(), kArgs, |
| incognito_browser->profile()), |
| keys::kIncognitoModeIsDisabled)); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(ExtensionTabsTest, QueryCurrentWindowTabs) { |
| const size_t kExtraWindows = 3; |
| for (size_t i = 0; i < kExtraWindows; ++i) { |
| CreateBrowser(profile()); |
| } |
| |
| GURL url(url::kAboutBlankURL); |
| ASSERT_TRUE(AddTabAtIndex(0, url, ui::PAGE_TRANSITION_LINK)); |
| int window_id = ExtensionTabUtil::GetWindowId(browser_window_interface()); |
| |
| // Get tabs in the 'current' window called from non-focused browser. |
| auto function = base::MakeRefCounted<TabsQueryFunction>(); |
| function->set_extension(ExtensionBuilder("Test").Build().get()); |
| |
| base::Value::List result_tabs = |
| utils::ToList(RunFunctionWithDispatcherDelegateAndReturnValue( |
| function.get(), "[{\"currentWindow\":true}]", |
| browser_window_interface())); |
| |
| // We should have one initial tab and one added tab. |
| EXPECT_EQ(2u, result_tabs.size()); |
| for (const base::Value& result_tab : result_tabs) { |
| EXPECT_EQ(window_id, GetTabWindowId(utils::ToDict(result_tab))); |
| } |
| |
| // Get tabs NOT in the 'current' window called from non-focused browser. |
| function = base::MakeRefCounted<TabsQueryFunction>(); |
| function->set_extension(ExtensionBuilder("Test").Build().get()); |
| result_tabs = utils::ToList(RunFunctionWithDispatcherDelegateAndReturnValue( |
| function.get(), "[{\"currentWindow\":false}]", |
| browser_window_interface())); |
| |
| // We should have one tab for each extra window. |
| EXPECT_EQ(kExtraWindows, result_tabs.size()); |
| for (const base::Value& result_tab : result_tabs) { |
| EXPECT_NE(window_id, GetTabWindowId(utils::ToDict(result_tab))); |
| } |
| } |
| |
| IN_PROC_BROWSER_TEST_F(ExtensionTabsTest, QueryAllTabsWithDevTools) { |
| const size_t kNumWindows = 3; |
| std::set<int> window_ids; |
| window_ids.insert(ExtensionTabUtil::GetWindowId(browser_window_interface())); |
| for (size_t i = 0; i < kNumWindows - 1; ++i) { |
| Browser* new_browser = CreateBrowser(profile()); |
| window_ids.insert(ExtensionTabUtil::GetWindowId(new_browser)); |
| } |
| |
| // Undocked DevTools window should not be accessible. |
| DevToolsWindow* devtools = DevToolsWindowTesting::OpenDevToolsWindowSync( |
| GetTabListInterface()->GetTab(0)->GetContents(), false /* is_docked */); |
| |
| // Get tabs in the 'current' window called from non-focused browser. |
| auto function = base::MakeRefCounted<TabsQueryFunction>(); |
| function->set_extension(ExtensionBuilder("Test").Build().get()); |
| base::Value::List result_tabs( |
| utils::ToList(utils::RunFunctionAndReturnSingleResult( |
| function.get(), "[{}]", profile()))); |
| |
| std::set<int> result_ids; |
| // We should have one tab per browser except for DevTools. |
| EXPECT_EQ(kNumWindows, result_tabs.size()); |
| for (const base::Value& result_tab : result_tabs) { |
| result_ids.insert(GetTabWindowId(utils::ToDict(result_tab))); |
| } |
| EXPECT_EQ(window_ids, result_ids); |
| |
| DevToolsWindowTesting::CloseDevToolsWindowSync(devtools); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(ExtensionTabsTest, QueryTabGroups) { |
| ASSERT_TRUE(browser()->tab_strip_model()->SupportsTabGroups()); |
| |
| GURL url(url::kAboutBlankURL); |
| ASSERT_TRUE(AddTabAtIndex(0, url, ui::PAGE_TRANSITION_LINK)); |
| ASSERT_TRUE(AddTabAtIndex(0, url, ui::PAGE_TRANSITION_LINK)); |
| ASSERT_TRUE(AddTabAtIndex(0, url, ui::PAGE_TRANSITION_LINK)); |
| tab_groups::TabGroupId group_id = |
| browser()->tab_strip_model()->AddToNewGroup({0, 1}); |
| |
| auto function = base::MakeRefCounted<TabsQueryFunction>(); |
| function->set_extension(ExtensionBuilder("Test").Build().get()); |
| constexpr char kFormatQueryArgs[] = R"([{"groupId":%d}])"; |
| const std::string args = base::StringPrintf( |
| kFormatQueryArgs, ExtensionTabUtil::GetGroupId(group_id)); |
| base::Value::List result( |
| utils::ToList(utils::RunFunctionAndReturnSingleResult(function.get(), |
| args, profile()))); |
| |
| EXPECT_EQ(2u, result.size()); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(ExtensionTabsTest, DontCreateTabInClosingPopupWindow) { |
| // Test creates new popup window, closes it right away and then tries to open |
| // a new tab in it. Tab should not be opened in the popup window, but in a |
| // tabbed browser window. |
| Browser* popup_browser = Browser::Create( |
| Browser::CreateParams(Browser::TYPE_POPUP, profile(), true)); |
| int window_id = ExtensionTabUtil::GetWindowId(popup_browser); |
| chrome::CloseWindow(popup_browser); |
| |
| auto create_tab_function = base::MakeRefCounted<TabsCreateFunction>(); |
| create_tab_function->set_extension(ExtensionBuilder("Test").Build().get()); |
| // Without a callback the function will not generate a result. |
| create_tab_function->set_has_callback(true); |
| |
| static const char kNewBlankTabArgs[] = |
| "[{\"url\": \"about:blank\", \"windowId\": %u}]"; |
| |
| const base::Value::Dict result = |
| utils::ToDict(utils::RunFunctionAndReturnSingleResult( |
| create_tab_function.get(), |
| base::StringPrintf(kNewBlankTabArgs, window_id), profile())); |
| |
| EXPECT_NE(window_id, GetTabWindowId(result)); |
| } |
| |
| #endif // BUILDFLAG(ENABLE_EXTENSIONS) |
| |
| IN_PROC_BROWSER_TEST_F(ExtensionTabsTest, InvalidUpdateWindowState) { |
| int window_id = ExtensionTabUtil::GetWindowId(browser_window_interface()); |
| |
| static const char kArgsMinimizedWithFocus[] = |
| "[%u, {\"state\": \"minimized\", \"focused\": true}]"; |
| auto function = base::MakeRefCounted<WindowsUpdateFunction>(); |
| scoped_refptr<const Extension> extension(ExtensionBuilder("Test").Build()); |
| function->set_extension(extension.get()); |
| EXPECT_TRUE(base::MatchPattern( |
| utils::RunFunctionAndReturnError( |
| function.get(), |
| base::StringPrintf(kArgsMinimizedWithFocus, window_id), profile()), |
| keys::kInvalidWindowStateError)); |
| |
| static const char kArgsMaximizedWithoutFocus[] = |
| "[%u, {\"state\": \"maximized\", \"focused\": false}]"; |
| function = base::MakeRefCounted<WindowsUpdateFunction>(); |
| function->set_extension(extension.get()); |
| EXPECT_TRUE(base::MatchPattern( |
| utils::RunFunctionAndReturnError( |
| function.get(), |
| base::StringPrintf(kArgsMaximizedWithoutFocus, window_id), profile()), |
| keys::kInvalidWindowStateError)); |
| |
| static const char kArgsMinimizedWithBounds[] = |
| "[%u, {\"state\": \"minimized\", \"width\": 500}]"; |
| function = base::MakeRefCounted<WindowsUpdateFunction>(); |
| function->set_extension(extension.get()); |
| EXPECT_TRUE(base::MatchPattern( |
| utils::RunFunctionAndReturnError( |
| function.get(), |
| base::StringPrintf(kArgsMinimizedWithBounds, window_id), profile()), |
| keys::kInvalidWindowStateError)); |
| |
| static const char kArgsMaximizedWithBounds[] = |
| "[%u, {\"state\": \"maximized\", \"width\": 500}]"; |
| function = base::MakeRefCounted<WindowsUpdateFunction>(); |
| function->set_extension(extension.get()); |
| EXPECT_TRUE(base::MatchPattern( |
| utils::RunFunctionAndReturnError( |
| function.get(), |
| base::StringPrintf(kArgsMaximizedWithBounds, window_id), profile()), |
| keys::kInvalidWindowStateError)); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(ExtensionTabsTest, InvalidUpdateWindowBounds) { |
| scoped_refptr<const Extension> extension(ExtensionBuilder("Test").Build()); |
| |
| // Get the display bounds so we can test whether the window intersects. |
| gfx::Rect displays; |
| for (const auto& display : display::Screen::Get()->GetAllDisplays()) { |
| displays.Union(display.bounds()); |
| } |
| |
| int window_id = ExtensionTabUtil::GetWindowId(browser_window_interface()); |
| gfx::Rect window_bounds = |
| browser_window_interface()->GetWindow()->GetBounds(); |
| |
| static const char kArgsUpdateFunction[] = "[%u, {\"left\": %d, \"top\": %d}]"; |
| // We use a small value to move the window outside or inside the bounds. |
| int window_offset = window_bounds.size().width() * 0.1; |
| |
| { |
| // Window bounds that do not intersect with the display are not valid. |
| int window_left = displays.right() + window_offset; |
| int window_top = displays.bottom() + window_offset; |
| auto function = base::MakeRefCounted<WindowsUpdateFunction>(); |
| function->set_extension(extension.get()); |
| EXPECT_TRUE(base::MatchPattern( |
| utils::RunFunctionAndReturnError( |
| function.get(), |
| base::StringPrintf(kArgsUpdateFunction, window_id, window_left, |
| window_top), |
| profile()), |
| keys::kInvalidWindowBoundsError)); |
| } |
| |
| { |
| // Window bounds that intersect less than 50% with the display are not |
| // valid. |
| int window_left = displays.right() - window_offset; |
| int window_top = displays.bottom() - window_offset; |
| auto function = base::MakeRefCounted<WindowsUpdateFunction>(); |
| function->set_extension(extension.get()); |
| EXPECT_TRUE(base::MatchPattern( |
| utils::RunFunctionAndReturnError( |
| function.get(), |
| base::StringPrintf(kArgsUpdateFunction, window_id, window_left, |
| window_top), |
| profile()), |
| keys::kInvalidWindowBoundsError)); |
| } |
| |
| { |
| // Window bounds that intersect 50% or more with the display are valid. |
| int window_left = displays.right() - window_bounds.width() + window_offset; |
| int window_top = displays.bottom() - window_bounds.height() + window_offset; |
| auto function = base::MakeRefCounted<WindowsUpdateFunction>(); |
| function->set_extension(extension.get()); |
| EXPECT_TRUE( |
| utils::RunFunction(function.get(), |
| base::StringPrintf(kArgsUpdateFunction, window_id, |
| window_left, window_top), |
| profile(), api_test_utils::FunctionMode::kNone)); |
| } |
| } |
| |
| #if BUILDFLAG(ENABLE_EXTENSIONS) |
| |
| IN_PROC_BROWSER_TEST_F(ExtensionTabsTest, UpdateDevToolsWindow) { |
| DevToolsWindow* devtools = DevToolsWindowTesting::OpenDevToolsWindowSync( |
| GetTabListInterface()->GetTab(0)->GetContents(), false /* is_docked */); |
| |
| auto get_function = base::MakeRefCounted<WindowsGetFunction>(); |
| scoped_refptr<const Extension> extension( |
| ExtensionBuilder("Test").Build().get()); |
| get_function->set_extension(extension.get()); |
| base::Value::Dict result = |
| utils::ToDict(utils::RunFunctionAndReturnSingleResult( |
| get_function.get(), |
| base::StringPrintf( |
| "[%u, {\"windowTypes\": [\"devtools\"]}]", |
| ExtensionTabUtil::GetWindowId( |
| DevToolsWindowTesting::Get(devtools)->browser())), |
| profile())); |
| |
| // Verify the updating width/height works. |
| int32_t new_width = api_test_utils::GetInteger(result, "width") - 50; |
| int32_t new_height = api_test_utils::GetInteger(result, "height") - 50; |
| |
| auto update_function = base::MakeRefCounted<WindowsUpdateFunction>(); |
| result = utils::ToDict(utils::RunFunctionAndReturnSingleResult( |
| update_function.get(), |
| base::StringPrintf("[%u, {\"width\": %d, \"height\": %d}]", |
| ExtensionTabUtil::GetWindowId( |
| DevToolsWindowTesting::Get(devtools)->browser()), |
| new_width, new_height), |
| profile())); |
| |
| EXPECT_EQ(new_width, api_test_utils::GetInteger(result, "width")); |
| EXPECT_EQ(new_height, api_test_utils::GetInteger(result, "height")); |
| |
| DevToolsWindowTesting::CloseDevToolsWindowSync(devtools); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(ExtensionTabsTest, ExtensionAPICannotNavigateDevtools) { |
| scoped_refptr<const Extension> extension = |
| ExtensionBuilder("Test").AddAPIPermission("tabs").Build(); |
| |
| DevToolsWindow* devtools = DevToolsWindowTesting::OpenDevToolsWindowSync( |
| GetTabListInterface()->GetTab(0)->GetContents(), false /* is_docked */); |
| |
| auto function = base::MakeRefCounted<TabsUpdateFunction>(); |
| function->set_extension(extension.get()); |
| |
| EXPECT_TRUE(base::MatchPattern( |
| utils::RunFunctionAndReturnError( |
| function.get(), |
| base::StringPrintf( |
| "[%d, {\"url\":\"http://example.com\"}]", |
| ExtensionTabUtil::GetTabId( |
| DevToolsWindowTesting::Get(devtools)->main_web_contents())), |
| DevToolsWindowTesting::Get(devtools)->browser()->GetProfile()), |
| tabs_constants::kNotAllowedForDevToolsError)); |
| |
| DevToolsWindowTesting::CloseDevToolsWindowSync(devtools); |
| } |
| |
| #if BUILDFLAG(IS_MAC) |
| // https://crbug.com/836327 |
| #define MAYBE_AcceptState DISABLED_AcceptState |
| #else |
| #define MAYBE_AcceptState AcceptState |
| #endif |
| IN_PROC_BROWSER_TEST_F(ExtensionWindowCreateTest, MAYBE_AcceptState) { |
| #if BUILDFLAG(IS_MAC) |
| ui::test::ScopedFakeNSWindowFullscreen fake_fullscreen; |
| #endif |
| |
| auto function = base::MakeRefCounted<WindowsCreateFunction>(); |
| scoped_refptr<const Extension> extension(ExtensionBuilder("Test").Build()); |
| function->set_extension(extension.get()); |
| function->SetBrowserContextForTesting(profile()); |
| |
| base::Value::Dict result = |
| utils::ToDict(utils::RunFunctionAndReturnSingleResult( |
| function.get(), "[{\"state\": \"minimized\"}]", profile(), |
| api_test_utils::FunctionMode::kIncognito)); |
| int window_id = GetWindowId(result); |
| std::string error; |
| |
| WindowController* new_controller = |
| ExtensionTabUtil::GetControllerFromWindowID( |
| ChromeExtensionFunctionDetails(function.get()), window_id, &error); |
| ASSERT_TRUE(new_controller); |
| EXPECT_TRUE(error.empty()); |
| Browser* new_browser = new_controller->GetBrowser(); |
| ASSERT_TRUE(new_browser); |
| |
| // TODO(crbug.com/40254339): Remove this workaround if this wait is no longer |
| // needed. |
| // These builds flags are limiting the check for IsMinimized() for Linux. |
| // For Linux, we only check X11 window manager and not Wayland since our |
| // current fix only applies to X11. |
| #if BUILDFLAG(IS_LINUX) |
| // Must be checked inside IS_LINUX to compile on windows/mac. |
| #if BUILDFLAG(IS_OZONE_X11) |
| // DesktopWindowTreeHostX11::IsMinimized() relies on an asynchronous update |
| // from the window server |
| views::test::PropertyWaiter minimize_waiter( |
| base::BindRepeating(&BrowserWindow::IsMinimized, |
| base::Unretained(new_browser->window())), |
| true); |
| EXPECT_TRUE(minimize_waiter.Wait()); |
| #elif BUILDFLAG(IS_OZONE_WAYLAND) |
| // TODO(crbug.com/40252593): Find a fix/workaround for wayland and add |
| // verification of IsMinimized() for as well. |
| #endif |
| #else |
| EXPECT_TRUE(new_controller->window()->IsMinimized()); |
| #endif // BUILDFLAG(IS_LINUX) |
| |
| function = base::MakeRefCounted<WindowsCreateFunction>(); |
| function->set_extension(extension.get()); |
| function->SetBrowserContextForTesting(profile()); |
| result = utils::ToDict(utils::RunFunctionAndReturnSingleResult( |
| function.get(), "[{\"state\": \"fullscreen\"}]", profile(), |
| api_test_utils::FunctionMode::kIncognito)); |
| window_id = GetWindowId(result); |
| |
| new_controller = ExtensionTabUtil::GetControllerFromWindowID( |
| ChromeExtensionFunctionDetails(function.get()), window_id, &error); |
| ASSERT_TRUE(new_controller); |
| EXPECT_TRUE(error.empty()); |
| EXPECT_TRUE(new_controller->GetBrowser()->window()->IsFullscreen()); |
| |
| // Let the message loop run so that |fake_fullscreen| finishes transition. |
| content::RunAllPendingInMessageLoop(); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(ExtensionWindowCreateTest, ValidateCreateWindowState) { |
| EXPECT_TRUE( |
| base::MatchPattern(RunCreateWindowExpectError( |
| "[{\"state\": \"minimized\", \"focused\": true}]"), |
| keys::kInvalidWindowStateError)); |
| EXPECT_TRUE(base::MatchPattern( |
| RunCreateWindowExpectError( |
| "[{\"state\": \"maximized\", \"focused\": false}]"), |
| keys::kInvalidWindowStateError)); |
| EXPECT_TRUE(base::MatchPattern( |
| RunCreateWindowExpectError( |
| "[{\"state\": \"fullscreen\", \"focused\": false}]"), |
| keys::kInvalidWindowStateError)); |
| EXPECT_TRUE( |
| base::MatchPattern(RunCreateWindowExpectError( |
| "[{\"state\": \"minimized\", \"width\": 500}]"), |
| keys::kInvalidWindowStateError)); |
| EXPECT_TRUE( |
| base::MatchPattern(RunCreateWindowExpectError( |
| "[{\"state\": \"maximized\", \"width\": 500}]"), |
| keys::kInvalidWindowStateError)); |
| EXPECT_TRUE( |
| base::MatchPattern(RunCreateWindowExpectError( |
| "[{\"state\": \"fullscreen\", \"width\": 500}]"), |
| keys::kInvalidWindowStateError)); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(ExtensionWindowCreateTest, ValidateCreateWindowBounds) { |
| // Get the display bounds so we can test whether the window intersects. |
| gfx::Rect displays; |
| for (const auto& display : display::Screen::Get()->GetAllDisplays()) { |
| displays.Union(display.bounds()); |
| } |
| |
| static const char kArgsCreateFunction[] = |
| "[{\"left\": %d, \"top\": %d, \"width\": %d, \"height\": %d }]"; |
| int window_width = 100; |
| int window_height = 100; |
| // We use a small value to move the window outside or inside the bounds. |
| int window_offset = 10; |
| |
| { |
| // Window bounds that do not intersect with the display are not valid. |
| int window_left = displays.right() + window_offset; |
| int window_top = displays.bottom() + window_offset; |
| EXPECT_TRUE( |
| base::MatchPattern(RunCreateWindowExpectError(base::StringPrintf( |
| kArgsCreateFunction, window_left, window_top, |
| window_width, window_height)), |
| keys::kInvalidWindowBoundsError)); |
| } |
| |
| { |
| // Window bounds that intersect less than 50% with the display are not |
| // valid. |
| int window_left = displays.right() - window_offset; |
| int window_top = displays.bottom() - window_offset; |
| EXPECT_TRUE( |
| base::MatchPattern(RunCreateWindowExpectError(base::StringPrintf( |
| kArgsCreateFunction, window_left, window_top, |
| window_width, window_height)), |
| keys::kInvalidWindowBoundsError)); |
| } |
| |
| { |
| // Window bounds that intersect 50% or more with the display are valid. |
| int window_left = displays.right() - window_width + window_offset; |
| int window_top = displays.bottom() - window_height + window_offset; |
| auto function = base::MakeRefCounted<WindowsCreateFunction>(); |
| function->set_extension(ExtensionBuilder("Test").Build().get()); |
| EXPECT_TRUE(utils::RunFunction( |
| function.get(), |
| base::StringPrintf(kArgsCreateFunction, window_left, window_top, |
| window_width, window_height), |
| profile(), api_test_utils::FunctionMode::kNone)); |
| } |
| |
| { |
| // Window bounds that specify size and not position should be adjusted |
| // to the screen in case the window is not visible. |
| // For this, update the current window bounds so the new window position |
| // needs to be adjusted to fit. |
| gfx::Rect current_window_bounds = |
| browser_window_interface()->GetWindow()->GetBounds(); |
| current_window_bounds.set_x(current_window_bounds.x() + |
| current_window_bounds.width() - window_offset); |
| current_window_bounds.set_y(current_window_bounds.y() + |
| current_window_bounds.height() - window_offset); |
| browser_window_interface()->GetWindow()->SetBounds(current_window_bounds); |
| |
| static const char kArgsCreateFunctionOnlySize[] = |
| "[{\"width\": %d, \"height\": %d }]"; |
| auto function = base::MakeRefCounted<WindowsCreateFunction>(); |
| function->set_extension(ExtensionBuilder("Test").Build().get()); |
| EXPECT_TRUE( |
| utils::RunFunction(function.get(), |
| base::StringPrintf(kArgsCreateFunctionOnlySize, |
| window_width, window_height), |
| profile(), api_test_utils::FunctionMode::kNone)); |
| } |
| } |
| |
| IN_PROC_BROWSER_TEST_F(ExtensionWindowCreateTest, CreatePopupWindowFromWebUI) { |
| auto function = base::MakeRefCounted<WindowsCreateFunction>(); |
| function->SetBrowserContextForTesting(profile()); |
| function->set_source_context_type(mojom::ContextType::kUntrustedWebUi); |
| |
| const base::Value::Dict result = |
| utils::ToDict(utils::RunFunctionAndReturnSingleResult( |
| function.get(), R"([{"type": "popup"}])", profile())); |
| int window_id = GetWindowId(result); |
| |
| std::string error; |
| EXPECT_TRUE(ExtensionTabUtil::GetControllerFromWindowID( |
| ChromeExtensionFunctionDetails(function.get()), window_id, &error)); |
| EXPECT_TRUE(error.empty()); |
| } |
| |
| struct ExtensionWindowCreateIwaParam { |
| std::string test_name; |
| bool want_success; |
| std::string args; |
| }; |
| |
| // Test that `windows.create` functions correctly for Isolated Web Apps. |
| class ExtensionWindowCreateIwaTest |
| : public InProcessBrowserTest, |
| public testing::WithParamInterface<ExtensionWindowCreateIwaParam> { |
| public: |
| ExtensionWindowCreateIwaTest() { |
| scoped_feature_list_.InitAndEnableFeature(features::kIsolatedWebApps); |
| set_open_about_blank_on_browser_launch(false); |
| } |
| |
| void SetUpOnMainThread() override { |
| InProcessBrowserTest::SetUpOnMainThread(); |
| |
| web_app::test::WaitUntilReady( |
| web_app::WebAppProvider::GetForTest(profile())); |
| ASSERT_TRUE(scoped_temp_dir_.CreateUniqueTempDir()); |
| } |
| |
| void SetUpDefaultCommandLine(base::CommandLine* command_line) override { |
| InProcessBrowserTest::SetUpDefaultCommandLine(command_line); |
| // Suppress "Welcome to Google Chrome" window |
| command_line->AppendSwitch(switches::kNoFirstRun); |
| command_line->AppendSwitch(switches::kNoStartupWindow); |
| command_line->AppendSwitch(switches::kKeepAliveForTest); |
| } |
| |
| void TearDownOnMainThread() override { |
| if (BrowserList::GetInstance()->empty()) { |
| // Tests crash during teardown if no browser has opened combined with the |
| // command line switches above. Open a browser to avoid the crash. |
| CreateBrowser(profile()); |
| } |
| InProcessBrowserTest::TearDownOnMainThread(); |
| } |
| |
| Profile* profile() { |
| // We cannot use `profile()` here, because `browser()` is |
| // `nullptr` due to the command line switches above. |
| return profile_util::GetLastUsedProfile(); |
| } |
| |
| protected: |
| web_app::IsolatedWebAppUrlInfo InstallAndTrustBundle() { |
| auto bundle = web_app::IsolatedWebAppBuilder(web_app::ManifestBuilder()) |
| .AddHtml("/", "Hello extensions!") |
| .BuildBundle(web_app::test::GetDefaultEd25519KeyPair()); |
| return bundle->InstallChecked(profile()); |
| } |
| |
| private: |
| base::test::ScopedFeatureList scoped_feature_list_; |
| web_app::OsIntegrationManager::ScopedSuppressForTesting os_hooks_suppress_; |
| base::ScopedTempDir scoped_temp_dir_; |
| }; |
| |
| IN_PROC_BROWSER_TEST_P(ExtensionWindowCreateIwaTest, CreateWindowForIwa) { |
| auto url_info = InstallAndTrustBundle(); |
| |
| EXPECT_EQ(BrowserList::GetInstance()->size(), 0ul); |
| |
| scoped_refptr<const Extension> extension = |
| ExtensionBuilder("ExtensionWindowCreateIwaTest").Build(); |
| auto function = base::MakeRefCounted<WindowsCreateFunction>(); |
| function->set_extension(extension); |
| bool result = |
| api_test_utils::RunFunction(function.get(), GetParam().args, profile(), |
| api_test_utils::FunctionMode::kNone); |
| if (GetParam().want_success) { |
| EXPECT_TRUE(result) << function->GetError(); |
| |
| // A single browser for the IWA should now be open. |
| ASSERT_EQ(BrowserList::GetInstance()->size(), 1ul); |
| Browser* iwa_browser = *BrowserList::GetInstance()->begin(); |
| TabListInterface* tab_list = TabListInterface::From(iwa_browser); |
| ASSERT_EQ(tab_list->GetTabCount(), 1); |
| |
| auto* web_contents = tab_list->GetActiveTab()->GetContents(); |
| content::WaitForLoadStop(web_contents); |
| EXPECT_EQ(web_contents->GetURL(), url_info.origin().GetURL()); |
| |
| static constexpr std::string_view kLaunchQueueScript = R"( |
| new Promise(async (resolve) => { |
| window.launchQueue.setConsumer(launchParams => { |
| resolve(launchParams.targetURL); |
| }); |
| }); |
| )"; |
| |
| EXPECT_EQ(content::EvalJs(web_contents, kLaunchQueueScript), |
| url_info.origin().GetURL().Resolve("/index.html")); |
| } else { |
| EXPECT_FALSE(result); |
| // No browser should have opened. |
| EXPECT_EQ(BrowserList::GetInstance()->size(), 0ul); |
| } |
| } |
| |
| INSTANTIATE_TEST_SUITE_P( |
| /* no prefix */, |
| ExtensionWindowCreateIwaTest, |
| testing::Values( |
| ExtensionWindowCreateIwaParam{.test_name = "iwa_and_https", |
| .want_success = false, |
| .args = R"([{ |
| "url": [ |
| "isolated-app://4tkrnsmftl4ggvvdkfth3piainqragus2qbhf7rlz2a3wo3rh4wqaaic/index.html", |
| "https://example.com" |
| ] |
| }])"}, |
| ExtensionWindowCreateIwaParam{.test_name = "https_and_iwa_and_https", |
| .want_success = false, |
| .args = R"([{ |
| "url": [ |
| "https://example.com", |
| "isolated-app://4tkrnsmftl4ggvvdkfth3piainqragus2qbhf7rlz2a3wo3rh4wqaaic/index.html", |
| "https://example.com" |
| ] |
| }])"}, |
| // If we ever support tabbed IWAs, then this test must be updated to |
| // `.want_success true`. |
| ExtensionWindowCreateIwaParam{.test_name = "iwa_and_iwa", |
| .want_success = false, |
| .args = R"([{ |
| "url": [ |
| "isolated-app://4tkrnsmftl4ggvvdkfth3piainqragus2qbhf7rlz2a3wo3rh4wqaaic/index.html", |
| "isolated-app://4tkrnsmftl4ggvvdkfth3piainqragus2qbhf7rlz2a3wo3rh4wqaaic/index.html" |
| ] |
| }])"}, |
| ExtensionWindowCreateIwaParam{.test_name = "iwa_and_different_iwa", |
| .want_success = false, |
| .args = R"([{ |
| "url": [ |
| "isolated-app://4tkrnsmftl4ggvvdkfth3piainqragus2qbhf7rlz2a3wo3rh4wqaaic/index.html", |
| "isolated-app://5dp4lo5h6tpc4vuokowxmlqs5gpbainu2nqvuddccx5mqsnje7fqaaic/index.html" |
| ] |
| }])"}, |
| ExtensionWindowCreateIwaParam{.test_name = "invalid_iwa_url", |
| .want_success = false, |
| .args = R"([{ |
| "url": [ |
| "isolated-app://invalid-iwa-url" |
| ] |
| }])"}, |
| // If we ever support tabbed IWAs, this test must be updated: If `tabId` |
| // refers to the tab of the same IWA origin that is specified in `url`, |
| // it should be allowed. |
| ExtensionWindowCreateIwaParam{.test_name = "iwa_and_tab_id", |
| .want_success = false, |
| .args = R"([{ |
| "url": |
| "isolated-app://4tkrnsmftl4ggvvdkfth3piainqragus2qbhf7rlz2a3wo3rh4wqaaic/index.html", |
| "tabId": 1 |
| }])"}, |
| ExtensionWindowCreateIwaParam{.test_name = "iwa", |
| .want_success = true, |
| .args = R"([{ |
| "url": "isolated-app://4tkrnsmftl4ggvvdkfth3piainqragus2qbhf7rlz2a3wo3rh4wqaaic/index.html", |
| }])"}), |
| [](const testing::TestParamInfo<ExtensionWindowCreateIwaTest::ParamType>& |
| info) { return info.param.test_name; }); |
| |
| IN_PROC_BROWSER_TEST_F(ExtensionTabsTest, DuplicateTab) { |
| content::OpenURLParams params(GURL(url::kAboutBlankURL), content::Referrer(), |
| WindowOpenDisposition::NEW_FOREGROUND_TAB, |
| ui::PAGE_TRANSITION_LINK, false); |
| content::WebContents* web_contents = |
| browser()->OpenURL(params, /*navigation_handle_callback=*/{}); |
| int tab_id = ExtensionTabUtil::GetTabId(web_contents); |
| int window_id = ExtensionTabUtil::GetWindowIdOfTab(web_contents); |
| int tab_index = -1; |
| TabStripModel* tab_strip; |
| ExtensionTabUtil::GetTabStripModel(web_contents, &tab_strip, &tab_index); |
| |
| auto duplicate_tab_function = base::MakeRefCounted<TabsDuplicateFunction>(); |
| scoped_refptr<const Extension> empty_tab_extension = |
| ExtensionBuilder("Test").AddAPIPermission("tabs").Build(); |
| duplicate_tab_function->set_extension(empty_tab_extension.get()); |
| duplicate_tab_function->set_has_callback(true); |
| |
| const base::Value::Dict duplicate_result = |
| utils::ToDict(utils::RunFunctionAndReturnSingleResult( |
| duplicate_tab_function.get(), base::StringPrintf("[%u]", tab_id), |
| profile())); |
| |
| int duplicate_tab_id = GetTabId(duplicate_result); |
| int duplicate_tab_window_id = GetTabWindowId(duplicate_result); |
| int duplicate_tab_index = |
| api_test_utils::GetInteger(duplicate_result, "index"); |
| // Duplicate tab id should be different from the original tab id. |
| EXPECT_NE(tab_id, duplicate_tab_id); |
| EXPECT_EQ(window_id, duplicate_tab_window_id); |
| EXPECT_EQ(tab_index + 1, duplicate_tab_index); |
| // The test empty tab extension has tabs permissions, therefore |
| // |duplicate_result| should contain url, pendingUrl, title or faviconUrl |
| // in the function result. |
| EXPECT_TRUE(HasAnyPrivacySensitiveFields(duplicate_result)); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(ExtensionTabsTest, DuplicateTabNoPermission) { |
| content::OpenURLParams params(GURL(url::kAboutBlankURL), content::Referrer(), |
| WindowOpenDisposition::NEW_FOREGROUND_TAB, |
| ui::PAGE_TRANSITION_LINK, false); |
| content::WebContents* web_contents = |
| browser()->OpenURL(params, /*navigation_handle_callback=*/{}); |
| int tab_id = ExtensionTabUtil::GetTabId(web_contents); |
| int window_id = ExtensionTabUtil::GetWindowIdOfTab(web_contents); |
| int tab_index = -1; |
| TabStripModel* tab_strip; |
| ExtensionTabUtil::GetTabStripModel(web_contents, &tab_strip, &tab_index); |
| |
| auto duplicate_tab_function = base::MakeRefCounted<TabsDuplicateFunction>(); |
| scoped_refptr<const Extension> empty_extension( |
| ExtensionBuilder("Test").Build()); |
| duplicate_tab_function->set_extension(empty_extension.get()); |
| duplicate_tab_function->set_has_callback(true); |
| |
| const base::Value::Dict duplicate_result = |
| utils::ToDict(utils::RunFunctionAndReturnSingleResult( |
| duplicate_tab_function.get(), base::StringPrintf("[%u]", tab_id), |
| profile())); |
| |
| int duplicate_tab_id = GetTabId(duplicate_result); |
| int duplicate_tab_window_id = GetTabWindowId(duplicate_result); |
| int duplicate_tab_index = |
| api_test_utils::GetInteger(duplicate_result, "index"); |
| // Duplicate tab id should be different from the original tab id. |
| EXPECT_NE(tab_id, duplicate_tab_id); |
| EXPECT_EQ(window_id, duplicate_tab_window_id); |
| EXPECT_EQ(tab_index + 1, duplicate_tab_index); |
| // The test empty extension has no permissions, therefore |duplicate_result| |
| // should not contain url, pendingUrl, title and faviconUrl in the function |
| // result. |
| EXPECT_FALSE(HasAnyPrivacySensitiveFields(duplicate_result)); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(ExtensionTabsTest, NoTabsEventOnDevTools) { |
| extensions::ResultCatcher catcher; |
| ExtensionTestMessageListener listener("ready", ReplyBehavior::kWillReply); |
| ASSERT_TRUE(LoadExtension(test_data_dir_.AppendASCII("tabs/no_events"))); |
| ASSERT_TRUE(listener.WaitUntilSatisfied()); |
| |
| DevToolsWindow* devtools = DevToolsWindowTesting::OpenDevToolsWindowSync( |
| GetTabListInterface()->GetTab(0)->GetContents(), false /* is_docked */); |
| |
| listener.Reply("stop"); |
| |
| ASSERT_TRUE(catcher.GetNextResult()); |
| |
| DevToolsWindowTesting::CloseDevToolsWindowSync(devtools); |
| } |
| |
| #if BUILDFLAG(ENABLE_PLATFORM_APPS) |
| |
| IN_PROC_BROWSER_TEST_F(ExtensionTabsTestWithApps, NoTabsAppWindow) { |
| extensions::ResultCatcher catcher; |
| ExtensionTestMessageListener listener("ready", ReplyBehavior::kWillReply); |
| ASSERT_TRUE( |
| LoadExtension(test_data_dir_.AppendASCII("api_test/tabs/no_events"))); |
| ASSERT_TRUE(listener.WaitUntilSatisfied()); |
| |
| AppWindow* app_window = CreateTestAppWindow( |
| "{\"outerBounds\": " |
| "{\"width\": 300, \"height\": 300," |
| " \"minWidth\": 200, \"minHeight\": 200," |
| " \"maxWidth\": 400, \"maxHeight\": 400}}"); |
| |
| listener.Reply("stop"); |
| |
| ASSERT_TRUE(catcher.GetNextResult()); |
| |
| CloseAppWindow(app_window); |
| } |
| |
| // Crashes on Mac/Win only. http://crbug.com/708996 |
| #if BUILDFLAG(IS_MAC) |
| #define MAYBE_FilteredEvents DISABLED_FilteredEvents |
| #else |
| #define MAYBE_FilteredEvents FilteredEvents |
| #endif |
| |
| IN_PROC_BROWSER_TEST_F(ExtensionTabsTestWithApps, MAYBE_FilteredEvents) { |
| extensions::ResultCatcher catcher; |
| ExtensionTestMessageListener listener("ready", ReplyBehavior::kWillReply); |
| ASSERT_TRUE( |
| LoadExtension(test_data_dir_.AppendASCII("api_test/windows/events"))); |
| ASSERT_TRUE(listener.WaitUntilSatisfied()); |
| |
| AppWindow* app_window = CreateTestAppWindow( |
| "{\"outerBounds\": " |
| "{\"width\": 300, \"height\": 300," |
| " \"minWidth\": 200, \"minHeight\": 200," |
| " \"maxWidth\": 400, \"maxHeight\": 400}}"); |
| |
| Browser* browser_window = |
| Browser::Create(Browser::CreateParams(profile(), true)); |
| AddBlankTabAndShow(browser_window); |
| |
| DevToolsWindow* devtools_window = |
| DevToolsWindowTesting::OpenDevToolsWindowSync( |
| TabListInterface::From(browser_window_interface()) |
| ->GetTab(0) |
| ->GetContents(), |
| false /* is_docked */); |
| |
| chrome::CloseWindow(browser_window); |
| DevToolsWindowTesting::CloseDevToolsWindowSync(devtools_window); |
| CloseAppWindow(app_window); |
| |
| // TODO(llandwerlin): It seems creating an app window on MacOSX |
| // won't create an activation event whereas it does on all other |
| // platform. Disable focus event tests for now. |
| #if BUILDFLAG(IS_MAC) |
| listener.Reply(""); |
| #else |
| listener.Reply("focus"); |
| #endif |
| |
| ASSERT_TRUE(catcher.GetNextResult()); |
| } |
| |
| #endif // BUILDFLAG(ENABLE_PLATFORM_APPS) |
| |
| IN_PROC_BROWSER_TEST_F(ExtensionTabsTest, OnBoundsChanged) { |
| extensions::ResultCatcher catcher; |
| ExtensionTestMessageListener listener("ready", ReplyBehavior::kWillReply); |
| ASSERT_TRUE(LoadExtension(test_data_dir_.AppendASCII("windows/bounds"))); |
| ASSERT_TRUE(listener.WaitUntilSatisfied()); |
| |
| gfx::Rect rect = browser_window_interface()->GetWindow()->GetBounds(); |
| rect.Inset(10); |
| browser_window_interface()->GetWindow()->SetBounds(rect); |
| |
| listener.Reply(base::StringPrintf( |
| R"({"top": %u, "left": %u, "width": %u, "height": %u})", rect.y(), |
| rect.x(), rect.width(), rect.height())); |
| |
| ASSERT_TRUE(catcher.GetNextResult()); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(ExtensionTabsTest, WindowsCreate) { |
| ASSERT_TRUE(RunExtensionTest("windows/create")) << message_; |
| } |
| |
| IN_PROC_BROWSER_TEST_F(ExtensionTabsTest, ExecuteScriptOnDevTools) { |
| scoped_refptr<const Extension> extension = |
| ExtensionBuilder("Test").AddAPIPermission("tabs").Build(); |
| |
| DevToolsWindow* devtools = DevToolsWindowTesting::OpenDevToolsWindowSync( |
| GetTabListInterface()->GetTab(0)->GetContents(), false /* is_docked */); |
| |
| auto function = base::MakeRefCounted<TabsExecuteScriptFunction>(); |
| function->set_extension(extension.get()); |
| |
| EXPECT_TRUE(base::MatchPattern( |
| utils::RunFunctionAndReturnError( |
| function.get(), |
| base::StringPrintf("[%u, {\"code\": \"true\"}]", |
| api::windows::WINDOW_ID_CURRENT), |
| DevToolsWindowTesting::Get(devtools)->browser()->GetProfile()), |
| manifest_errors::kCannotAccessPageWithUrl)); |
| |
| DevToolsWindowTesting::CloseDevToolsWindowSync(devtools); |
| } |
| |
| // TODO(georgesak): change this browsertest to an unittest. |
| IN_PROC_BROWSER_TEST_F(ExtensionTabsTest, DiscardedProperty) { |
| ASSERT_TRUE(g_browser_process && g_browser_process->GetTabManager()); |
| resource_coordinator::TabManager* tab_manager = |
| g_browser_process->GetTabManager(); |
| |
| // To avoid flakes when focus changes, set the active tab strip model |
| // explicitly. |
| resource_coordinator::GetTabLifecycleUnitSource() |
| ->SetFocusedTabStripModelForTesting(browser()->tab_strip_model()); |
| |
| // Create two additional tabs. |
| content::OpenURLParams params(GURL(url::kAboutBlankURL), content::Referrer(), |
| WindowOpenDisposition::NEW_BACKGROUND_TAB, |
| ui::PAGE_TRANSITION_LINK, false); |
| content::WebContents* web_contents_a = |
| browser()->OpenURL(params, /*navigation_handle_callback=*/{}); |
| content::WebContents* web_contents_b = |
| browser()->OpenURL(params, /*navigation_handle_callback=*/{}); |
| |
| // Set up query function with an extension. |
| scoped_refptr<const Extension> extension = ExtensionBuilder("Test").Build(); |
| auto RunQueryFunction = [this, &extension](const char* query_info) { |
| auto function = base::MakeRefCounted<TabsQueryFunction>(); |
| function->set_extension(extension.get()); |
| return utils::ToList(utils::RunFunctionAndReturnSingleResult( |
| function.get(), query_info, profile())); |
| }; |
| |
| // Get non-discarded tabs. |
| { |
| base::Value::List result(RunQueryFunction("[{\"discarded\": false}]")); |
| |
| // The two created plus the default tab. |
| EXPECT_EQ(3u, result.size()); |
| } |
| |
| // Get discarded tabs. |
| { |
| base::Value::List result(RunQueryFunction("[{\"discarded\": true}]")); |
| EXPECT_EQ(0u, result.size()); |
| } |
| |
| TabListInterface* tab_list = |
| TabListInterface::From(browser_window_interface()); |
| |
| // Creates Tab object to ensure the property is correct for the extension. |
| api::tabs::Tab tab_object_a = ExtensionTabUtil::CreateTabObject( |
| web_contents_a, kDontScrubBehavior, nullptr, tab_list, 0); |
| EXPECT_FALSE(tab_object_a.discarded); |
| |
| // Discards one tab. |
| EXPECT_TRUE(tab_manager->DiscardTabByExtension(web_contents_a)); |
| web_contents_a = GetTabListInterface()->GetTab(1)->GetContents(); |
| |
| // Make sure the property is changed accordingly after discarding the tab. |
| tab_object_a = ExtensionTabUtil::CreateTabObject( |
| web_contents_a, kDontScrubBehavior, nullptr, tab_list, 0); |
| EXPECT_TRUE(tab_object_a.discarded); |
| |
| // Get non-discarded tabs after discarding one tab. |
| { |
| base::Value::List result(RunQueryFunction("[{\"discarded\": false}]")); |
| EXPECT_EQ(2u, result.size()); |
| } |
| |
| // Get discarded tabs after discarding one tab. |
| { |
| base::Value::List result(RunQueryFunction("[{\"discarded\": true}]")); |
| EXPECT_EQ(1u, result.size()); |
| |
| // Make sure the returned tab is the correct one. |
| int tab_id_a = ExtensionTabUtil::GetTabId(web_contents_a); |
| |
| ASSERT_TRUE(result[0].is_dict()); |
| std::optional<int> id = result[0].GetDict().FindInt(extension_misc::kId); |
| ASSERT_TRUE(id); |
| |
| EXPECT_EQ(tab_id_a, *id); |
| } |
| |
| // Discards another created tab. |
| EXPECT_TRUE(tab_manager->DiscardTabByExtension(web_contents_b)); |
| |
| // Get non-discarded tabs after discarding two created tabs. |
| { |
| base::Value::List result(RunQueryFunction("[{\"discarded\": false}]")); |
| ASSERT_EQ(1u, result.size()); |
| |
| // Make sure the returned tab is the correct one. |
| int tab_id_c = ExtensionTabUtil::GetTabId( |
| GetTabListInterface()->GetTab(0)->GetContents()); |
| |
| ASSERT_TRUE(result[0].is_dict()); |
| std::optional<int> id = result[0].GetDict().FindInt(extension_misc::kId); |
| ASSERT_TRUE(id); |
| |
| EXPECT_EQ(tab_id_c, *id); |
| } |
| |
| // Get discarded tabs after discarding two created tabs. |
| { |
| base::Value::List result(RunQueryFunction("[{\"discarded\": true}]")); |
| EXPECT_EQ(2u, result.size()); |
| } |
| |
| // Activates the first created tab. |
| browser()->tab_strip_model()->ActivateTabAt(1); |
| |
| // Get non-discarded tabs after activating a discarded tab. |
| { |
| base::Value::List result(RunQueryFunction("[{\"discarded\": false}]")); |
| EXPECT_EQ(2u, result.size()); |
| } |
| |
| // Get discarded tabs after activating a discarded tab. |
| { |
| base::Value::List result(RunQueryFunction("[{\"discarded\": true}]")); |
| EXPECT_EQ(1u, result.size()); |
| } |
| } |
| |
| // Tests chrome.tabs.discard(tabId). |
| IN_PROC_BROWSER_TEST_F(ExtensionTabsTest, DiscardWithId) { |
| // Create an additional tab. |
| ui_test_utils::NavigateToURLWithDisposition( |
| browser(), GURL(url::kAboutBlankURL), |
| WindowOpenDisposition::NEW_BACKGROUND_TAB, |
| ui_test_utils::BROWSER_TEST_WAIT_FOR_LOAD_STOP); |
| content::WebContents* web_contents = |
| GetTabListInterface()->GetTab(1)->GetContents(); |
| |
| // Set up the function with an extension. |
| scoped_refptr<const Extension> extension = ExtensionBuilder("Test").Build(); |
| auto discard = base::MakeRefCounted<TabsDiscardFunction>(); |
| discard->set_extension(extension.get()); |
| |
| // Run function passing the tab id as argument. |
| int tab_id = ExtensionTabUtil::GetTabId(web_contents); |
| const base::Value::Dict result = |
| utils::ToDict(utils::RunFunctionAndReturnSingleResult( |
| discard.get(), base::StringPrintf("[%u]", tab_id), profile())); |
| |
| // Confirms that TabManager sees the tab as discarded. |
| web_contents = GetTabListInterface()->GetTab(1)->GetContents(); |
| EXPECT_EQ(resource_coordinator::TabLifecycleUnitExternal::FromWebContents( |
| web_contents) |
| ->GetTabState(), |
| ::mojom::LifecycleUnitState::DISCARDED); |
| |
| // Make sure the returned tab is the one discarded and its discarded state is |
| // correct. |
| tab_id = ExtensionTabUtil::GetTabId(web_contents); |
| EXPECT_EQ(tab_id, api_test_utils::GetInteger(result, "id")); |
| EXPECT_TRUE(api_test_utils::GetBoolean(result, "discarded")); |
| // The result should be scrubbed. |
| EXPECT_FALSE(result.contains("url")); |
| |
| // Tests chrome.tabs.discard(tabId) with an already discarded tab. It has to |
| // return the error stating that the tab couldn't be discarded. |
| auto discarded = base::MakeRefCounted<TabsDiscardFunction>(); |
| discarded->set_extension(extension.get()); |
| std::string error = utils::RunFunctionAndReturnError( |
| discarded.get(), base::StringPrintf("[%u]", tab_id), profile()); |
| EXPECT_TRUE(base::MatchPattern(error, keys::kCannotDiscardTab)); |
| } |
| |
| // Tests chrome.tabs.discard(invalidId). |
| IN_PROC_BROWSER_TEST_F(ExtensionTabsTest, DiscardWithInvalidId) { |
| // Create an additional tab. |
| ui_test_utils::NavigateToURLWithDisposition( |
| browser(), GURL(url::kAboutBlankURL), |
| WindowOpenDisposition::NEW_BACKGROUND_TAB, |
| ui_test_utils::BROWSER_TEST_WAIT_FOR_LOAD_STOP); |
| |
| // Set up the function with an extension. |
| scoped_refptr<const Extension> extension = ExtensionBuilder("Test").Build(); |
| auto discard = base::MakeRefCounted<TabsDiscardFunction>(); |
| discard->set_extension(extension.get()); |
| |
| // Run function passing an invalid id as argument. |
| int tab_invalid_id = ExtensionTabUtil::GetTabId( |
| GetTabListInterface()->GetTab(0)->GetContents()); |
| tab_invalid_id = std::max( |
| tab_invalid_id, ExtensionTabUtil::GetTabId( |
| GetTabListInterface()->GetTab(1)->GetContents())); |
| tab_invalid_id++; |
| |
| std::string error = utils::RunFunctionAndReturnError( |
| discard.get(), base::StringPrintf("[%u]", tab_invalid_id), profile()); |
| |
| // State should still be `ACTIVE` as no tab was discarded. |
| EXPECT_EQ(resource_coordinator::TabLifecycleUnitExternal::FromWebContents( |
| GetTabListInterface()->GetTab(1)->GetContents()) |
| ->GetTabState(), |
| ::mojom::LifecycleUnitState::ACTIVE); |
| |
| // Check error message. |
| EXPECT_TRUE(base::MatchPattern(error, ExtensionTabUtil::kTabNotFoundError)); |
| } |
| |
| // Tests chrome.tabs.discard(). |
| IN_PROC_BROWSER_TEST_F(ExtensionTabsTest, DiscardWithoutId) { |
| // Create an additional tab. |
| ui_test_utils::NavigateToURLWithDisposition( |
| browser(), GURL(url::kAboutBlankURL), |
| WindowOpenDisposition::NEW_BACKGROUND_TAB, |
| ui_test_utils::BROWSER_TEST_WAIT_FOR_LOAD_STOP); |
| content::WebContents* web_contents = |
| GetTabListInterface()->GetTab(1)->GetContents(); |
| |
| // Set up the function with an extension. |
| scoped_refptr<const Extension> extension = ExtensionBuilder("Test").Build(); |
| auto discard = base::MakeRefCounted<TabsDiscardFunction>(); |
| discard->set_extension(extension.get()); |
| |
| // Run without passing an id. |
| const base::Value::Dict result = utils::ToDict( |
| utils::RunFunctionAndReturnSingleResult(discard.get(), "[]", profile())); |
| |
| // Confirms that TabManager sees the tab as discarded. |
| web_contents = GetTabListInterface()->GetTab(1)->GetContents(); |
| EXPECT_EQ(resource_coordinator::TabLifecycleUnitExternal::FromWebContents( |
| web_contents) |
| ->GetTabState(), |
| ::mojom::LifecycleUnitState::DISCARDED); |
| |
| // Make sure the returned tab is the one discarded and its discarded state is |
| // correct. |
| EXPECT_EQ(ExtensionTabUtil::GetTabId(web_contents), |
| api_test_utils::GetInteger(result, "id")); |
| EXPECT_TRUE(api_test_utils::GetBoolean(result, "discarded")); |
| // The result should be scrubbed. |
| EXPECT_FALSE(result.contains("url")); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(ExtensionTabsTest, TestGroupDetachedAndReInserted) { |
| // Create the `TabsEventRouter`, which is required to get a tab update event. |
| TabsWindowsAPI::Get(profile())->InitTabsEventRouter(); |
| |
| chrome::AddTabAt(browser(), GURL(), -1, true); |
| chrome::AddTabAt(browser(), GURL(), -1, true); |
| chrome::AddTabAt(browser(), GURL(), -1, true); |
| |
| tab_groups::TabGroupId group = |
| browser()->tab_strip_model()->AddToNewGroup({0, 1}); |
| |
| TestEventRouterObserver event_observer(EventRouter::Get(profile())); |
| |
| std::unique_ptr<DetachedTabCollection> detached_group = |
| browser()->tab_strip_model()->DetachTabGroupForInsertion(group); |
| |
| event_observer.WaitForEventWithName(api::tabs::OnUpdated::kEventName); |
| EXPECT_TRUE(base::Contains(event_observer.events(), |
| api::tabs::OnUpdated::kEventName)); |
| |
| event_observer.ClearEvents(); |
| |
| browser()->tab_strip_model()->InsertDetachedTabGroupAt( |
| std::move(detached_group), 1); |
| |
| // Group added as well as the tab's group changed event should be sent. |
| event_observer.WaitForEventWithName(api::tabs::OnUpdated::kEventName); |
| EXPECT_TRUE(base::Contains(event_observer.events(), |
| api::tabs::OnUpdated::kEventName)); |
| } |
| |
| class ExtensionTabsWithSplitViewTest : public ExtensionTabsTest { |
| public: |
| ExtensionTabsWithSplitViewTest() { |
| scoped_feature_list_.InitAndEnableFeature(features::kSideBySide); |
| } |
| |
| private: |
| base::test::ScopedFeatureList scoped_feature_list_; |
| }; |
| |
| IN_PROC_BROWSER_TEST_F(ExtensionTabsWithSplitViewTest, |
| SplitViewAddedAndRemoved) { |
| // Create the `TabsEventRouter`, which is required to get a tab update event. |
| TabsWindowsAPI::Get(profile())->InitTabsEventRouter(); |
| |
| chrome::AddTabAt(browser(), GURL(), -1, true); |
| chrome::AddTabAt(browser(), GURL(), -1, true); |
| chrome::AddTabAt(browser(), GURL(), -1, true); |
| |
| TestEventRouterObserver event_observer(EventRouter::Get(profile())); |
| |
| browser()->tab_strip_model()->ActivateTabAt(0); |
| split_tabs::SplitTabId split = browser()->tab_strip_model()->AddToNewSplit( |
| {1}, split_tabs::SplitTabVisualData(), |
| split_tabs::SplitTabCreatedSource()); |
| |
| event_observer.WaitForEventWithName(api::tabs::OnUpdated::kEventName); |
| EXPECT_TRUE(base::Contains(event_observer.events(), |
| api::tabs::OnUpdated::kEventName)); |
| |
| event_observer.ClearEvents(); |
| |
| browser()->tab_strip_model()->RemoveSplit(split); |
| |
| event_observer.WaitForEventWithName(api::tabs::OnUpdated::kEventName); |
| EXPECT_TRUE(base::Contains(event_observer.events(), |
| api::tabs::OnUpdated::kEventName)); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(ExtensionTabsTest, Freezing) { |
| // Create a background tab. |
| ui_test_utils::NavigateToURLWithDisposition( |
| browser(), GURL(url::kAboutBlankURL), |
| WindowOpenDisposition::NEW_BACKGROUND_TAB, |
| ui_test_utils::BROWSER_TEST_WAIT_FOR_LOAD_STOP); |
| content::WebContents* web_contents = |
| GetTabListInterface()->GetTab(1)->GetContents(); |
| |
| // Create an extension. |
| scoped_refptr<const Extension> extension = ExtensionBuilder("Test").Build(); |
| |
| // Query all tabs. Their "frozen" property should be false. |
| { |
| auto query_function = base::MakeRefCounted<TabsQueryFunction>(); |
| query_function->set_extension(extension.get()); |
| base::Value::List result( |
| utils::ToList(utils::RunFunctionAndReturnSingleResult( |
| query_function.get(), "[{}]", profile()))); |
| ASSERT_EQ(2u, result.size()); |
| |
| for (auto& element : result) { |
| base::Value::Dict dict = utils::ToDict(element); |
| std::optional<bool> frozen = dict.FindBool("frozen"); |
| ASSERT_TRUE(frozen.has_value()); |
| EXPECT_FALSE(frozen.value()); |
| } |
| } |
| |
| // Create the `TabsEventRouter`, which is required to get a tab update event. |
| TabsWindowsAPI::Get(profile())->InitTabsEventRouter(); |
| |
| // Freeze the background tab and wait for a tab update event. |
| TestEventRouterObserver event_router_observer(EventRouter::Get(profile())); |
| web_contents->SetPageFrozen(true); |
| event_router_observer.WaitForEventWithName(api::tabs::OnUpdated::kEventName); |
| |
| // Check arguments for the tab update event. |
| // |
| // Note: Must simulate dispatching to an actual extension to get arguments, |
| // because the tab details exposed vary by extension. The arguments for the |
| // event received by `WillDispatchEvent()` are empty. |
| { |
| auto event_it = |
| event_router_observer.events().find(api::tabs::OnUpdated::kEventName); |
| ASSERT_NE(event_it, event_router_observer.events().end()); |
| std::optional<base::Value::List> args_out; |
| mojom::EventFilteringInfoPtr event_filtering_info_out; |
| EXPECT_TRUE(event_it->second->will_dispatch_callback.Run( |
| profile(), mojom::ContextType::kUnprivilegedExtension, extension.get(), |
| /*listener_filter=*/nullptr, args_out, event_filtering_info_out, |
| /*dispatch_separate_event_out=*/nullptr)); |
| ASSERT_TRUE(args_out.has_value()); |
| ASSERT_EQ(args_out->size(), 3U); |
| EXPECT_EQ(ExtensionTabUtil::GetTabId(web_contents), |
| args_out.value()[0].GetInt()); |
| auto changed_properties = utils::ToDict(args_out.value()[1]); |
| auto frozen_changed_property = changed_properties.FindBool("frozen"); |
| ASSERT_TRUE(frozen_changed_property.has_value()); |
| EXPECT_TRUE(frozen_changed_property.value()); |
| } |
| |
| // Query frozen tabs. There should be 1 and its "frozen" property should be |
| // true. |
| { |
| auto query_function = base::MakeRefCounted<TabsQueryFunction>(); |
| query_function->set_extension(extension.get()); |
| base::Value::List result( |
| utils::ToList(utils::RunFunctionAndReturnSingleResult( |
| query_function.get(), "[{\"frozen\": true}]", profile()))); |
| ASSERT_EQ(1u, result.size()); |
| base::Value::Dict tab = utils::ToDict(result.front()); |
| std::optional<bool> frozen = tab.FindBool("frozen"); |
| ASSERT_TRUE(frozen.has_value()); |
| EXPECT_TRUE(frozen.value()); |
| EXPECT_EQ(ExtensionTabUtil::GetTabId(web_contents), |
| api_test_utils::GetInteger(tab, "id")); |
| } |
| |
| // Query non-frozen tabs. There should be 1 and its "frozen" property should |
| // be false. |
| { |
| auto query_function = base::MakeRefCounted<TabsQueryFunction>(); |
| query_function->set_extension(extension.get()); |
| base::Value::List result( |
| utils::ToList(utils::RunFunctionAndReturnSingleResult( |
| query_function.get(), "[{\"frozen\": false}]", profile()))); |
| ASSERT_EQ(1u, result.size()); |
| std::optional<bool> frozen = |
| utils::ToDict(result.front()).FindBool("frozen"); |
| ASSERT_TRUE(frozen.has_value()); |
| EXPECT_FALSE(frozen.value()); |
| } |
| } |
| |
| IN_PROC_BROWSER_TEST_F(ExtensionTabsTest, AutoDiscardableProperty) { |
| // Create two additional tabs. |
| content::OpenURLParams params(GURL(url::kAboutBlankURL), content::Referrer(), |
| WindowOpenDisposition::NEW_BACKGROUND_TAB, |
| ui::PAGE_TRANSITION_LINK, false); |
| content::WebContents* web_contents_a = |
| browser()->OpenURL(params, /*navigation_handle_callback=*/{}); |
| content::WebContents* web_contents_b = |
| browser()->OpenURL(params, /*navigation_handle_callback=*/{}); |
| |
| // Creates Tab object to ensure the property is correct for the extension. |
| TabListInterface* tab_list = |
| TabListInterface::From(browser_window_interface()); |
| api::tabs::Tab tab_object_a = ExtensionTabUtil::CreateTabObject( |
| web_contents_a, kDontScrubBehavior, nullptr, tab_list, 0); |
| EXPECT_TRUE(tab_object_a.auto_discardable); |
| |
| // Set up query and update functions with the extension. |
| scoped_refptr<const Extension> extension = ExtensionBuilder("Test").Build(); |
| auto RunQueryFunction = [this, &extension](const char* query_info) { |
| auto function = base::MakeRefCounted<TabsQueryFunction>(); |
| function->set_extension(extension.get()); |
| return utils::ToList(utils::RunFunctionAndReturnSingleResult( |
| function.get(), query_info, profile())); |
| }; |
| auto RunUpdateFunction = [this, &extension](std::string update_info) { |
| auto function = base::MakeRefCounted<TabsUpdateFunction>(); |
| function->set_extension(extension.get()); |
| return utils::ToDict(utils::RunFunctionAndReturnSingleResult( |
| function.get(), update_info, profile())); |
| }; |
| |
| // Queries and results used. |
| const char* kAutoDiscardableQueryInfo = "[{\"autoDiscardable\": true}]"; |
| const char* kNonAutoDiscardableQueryInfo = "[{\"autoDiscardable\": false}]"; |
| |
| // Get auto-discardable tabs. Returns all since tabs are auto-discardable |
| // by default. |
| base::Value::List query_result = RunQueryFunction(kAutoDiscardableQueryInfo); |
| EXPECT_EQ(3u, query_result.size()); |
| |
| // Get non auto-discardable tabs. |
| query_result = RunQueryFunction(kNonAutoDiscardableQueryInfo); |
| EXPECT_EQ(0u, query_result.size()); |
| |
| // Update the auto-discardable state of web contents A. |
| int tab_id_a = ExtensionTabUtil::GetTabId(web_contents_a); |
| base::Value::Dict update_result = RunUpdateFunction( |
| base::StringPrintf("[%u, {\"autoDiscardable\": false}]", tab_id_a)); |
| EXPECT_EQ(tab_id_a, api_test_utils::GetInteger(update_result, "id")); |
| EXPECT_FALSE(api_test_utils::GetBoolean(update_result, "autoDiscardable")); |
| |
| // Make sure the property is changed accordingly after updating the tab. |
| tab_object_a = ExtensionTabUtil::CreateTabObject( |
| web_contents_a, kDontScrubBehavior, nullptr, tab_list, 0); |
| EXPECT_FALSE(tab_object_a.auto_discardable); |
| |
| // Get auto-discardable tabs after changing the status of web contents A. |
| query_result = RunQueryFunction(kAutoDiscardableQueryInfo); |
| EXPECT_EQ(2u, query_result.size()); |
| |
| // Get non auto-discardable tabs after changing the status of web contents A. |
| query_result = RunQueryFunction(kNonAutoDiscardableQueryInfo); |
| ASSERT_EQ(1u, query_result.size()); |
| |
| // Make sure the returned tab is the correct one. |
| ASSERT_TRUE(query_result[0].is_dict()); |
| std::optional<int> tab_id = |
| query_result[0].GetDict().FindInt(extension_misc::kId); |
| ASSERT_TRUE(tab_id); |
| EXPECT_EQ(tab_id_a, *tab_id); |
| |
| // Update the auto-discardable state of web contents B. |
| int tab_id_b = ExtensionTabUtil::GetTabId(web_contents_b); |
| update_result = RunUpdateFunction( |
| base::StringPrintf("[%u, {\"autoDiscardable\": false}]", tab_id_b)); |
| EXPECT_EQ(tab_id_b, api_test_utils::GetInteger(update_result, "id")); |
| EXPECT_FALSE(api_test_utils::GetBoolean(update_result, "autoDiscardable")); |
| |
| // Get auto-discardable tabs after changing the status of both created tabs. |
| query_result = RunQueryFunction(kAutoDiscardableQueryInfo); |
| EXPECT_EQ(1u, query_result.size()); |
| |
| // Make sure the returned tab is the correct one. |
| ASSERT_TRUE(query_result[0].is_dict()); |
| std::optional<int> id_value = |
| query_result[0].GetDict().FindInt(extension_misc::kId); |
| ASSERT_TRUE(id_value); |
| EXPECT_EQ(ExtensionTabUtil::GetTabId( |
| GetTabListInterface()->GetTab(0)->GetContents()), |
| *id_value); |
| |
| // Get auto-discardable tabs after changing the status of both created tabs. |
| query_result = RunQueryFunction(kNonAutoDiscardableQueryInfo); |
| EXPECT_EQ(2u, query_result.size()); |
| |
| // Resets the first tab back to auto-discardable. |
| update_result = RunUpdateFunction( |
| base::StringPrintf("[%u, {\"autoDiscardable\": true}]", tab_id_a)); |
| EXPECT_EQ(tab_id_a, api_test_utils::GetInteger(update_result, "id")); |
| EXPECT_TRUE(api_test_utils::GetBoolean(update_result, "autoDiscardable")); |
| |
| // Get auto-discardable tabs after resetting the status of web contents A. |
| query_result = RunQueryFunction(kAutoDiscardableQueryInfo); |
| EXPECT_EQ(2u, query_result.size()); |
| |
| // Get non auto-discardable tabs after resetting the status of web contents A. |
| query_result = RunQueryFunction(kNonAutoDiscardableQueryInfo); |
| EXPECT_EQ(1u, query_result.size()); |
| } |
| |
| // Tester class for the tabs.zoom* api functions. |
| class ExtensionTabsZoomTest : public ExtensionTabsTest { |
| public: |
| void SetUpOnMainThread() override; |
| |
| // Runs chrome.tabs.setZoom(). |
| bool RunSetZoom(int tab_id, double zoom_factor); |
| |
| // Runs chrome.tabs.getZoom(). |
| testing::AssertionResult RunGetZoom(int tab_id, double* zoom_factor); |
| |
| // Runs chrome.tabs.setZoomSettings(). |
| bool RunSetZoomSettings(int tab_id, const char* mode, const char* scope); |
| |
| // Runs chrome.tabs.getZoomSettings(). |
| testing::AssertionResult RunGetZoomSettings(int tab_id, |
| std::string* mode, |
| std::string* scope); |
| |
| // Runs chrome.tabs.getZoomSettings() and returns default zoom. |
| testing::AssertionResult RunGetDefaultZoom(int tab_id, |
| double* default_zoom_factor); |
| |
| // Runs chrome.tabs.setZoom(), expecting an error. |
| std::string RunSetZoomExpectError(int tab_id, double zoom_factor); |
| |
| // Runs chrome.tabs.setZoomSettings(), expecting an error. |
| std::string RunSetZoomSettingsExpectError(int tab_id, |
| const char* mode, |
| const char* scope); |
| |
| content::WebContents* OpenUrlAndWaitForLoad(const GURL& url); |
| |
| private: |
| scoped_refptr<const Extension> extension_; |
| }; |
| |
| void ExtensionTabsZoomTest::SetUpOnMainThread() { |
| ExtensionTabsTest::SetUpOnMainThread(); |
| extension_ = ExtensionBuilder("Test").Build(); |
| } |
| |
| bool ExtensionTabsZoomTest::RunSetZoom(int tab_id, double zoom_factor) { |
| auto set_zoom_function = base::MakeRefCounted<TabsSetZoomFunction>(); |
| set_zoom_function->set_extension(extension_.get()); |
| set_zoom_function->set_has_callback(true); |
| |
| return utils::RunFunction( |
| set_zoom_function.get(), |
| base::StringPrintf("[%u, %lf]", tab_id, zoom_factor), profile(), |
| api_test_utils::FunctionMode::kNone); |
| } |
| |
| testing::AssertionResult ExtensionTabsZoomTest::RunGetZoom( |
| int tab_id, |
| double* zoom_factor) { |
| auto get_zoom_function = base::MakeRefCounted<TabsGetZoomFunction>(); |
| get_zoom_function->set_extension(extension_.get()); |
| get_zoom_function->set_has_callback(true); |
| |
| std::optional<base::Value> get_zoom_result = |
| utils::RunFunctionAndReturnSingleResult( |
| get_zoom_function.get(), base::StringPrintf("[%u]", tab_id), |
| profile()); |
| |
| if (!get_zoom_result) { |
| return testing::AssertionFailure() << "no result"; |
| } |
| |
| std::optional<double> maybe_value = get_zoom_result->GetIfDouble(); |
| if (!maybe_value.has_value()) { |
| return testing::AssertionFailure() << "result was not a double"; |
| } |
| |
| *zoom_factor = maybe_value.value(); |
| return testing::AssertionSuccess(); |
| } |
| |
| bool ExtensionTabsZoomTest::RunSetZoomSettings(int tab_id, |
| const char* mode, |
| const char* scope) { |
| auto set_zoom_settings_function = |
| base::MakeRefCounted<TabsSetZoomSettingsFunction>(); |
| set_zoom_settings_function->set_extension(extension_.get()); |
| |
| std::string args; |
| if (scope) { |
| args = base::StringPrintf("[%u, {\"mode\": \"%s\", \"scope\": \"%s\"}]", |
| tab_id, mode, scope); |
| } else { |
| args = base::StringPrintf("[%u, {\"mode\": \"%s\"}]", tab_id, mode); |
| } |
| |
| return utils::RunFunction(set_zoom_settings_function.get(), args, profile(), |
| api_test_utils::FunctionMode::kNone); |
| } |
| |
| testing::AssertionResult ExtensionTabsZoomTest::RunGetZoomSettings( |
| int tab_id, |
| std::string* mode, |
| std::string* scope) { |
| DCHECK(mode); |
| DCHECK(scope); |
| auto get_zoom_settings_function = |
| base::MakeRefCounted<TabsGetZoomSettingsFunction>(); |
| get_zoom_settings_function->set_extension(extension_.get()); |
| get_zoom_settings_function->set_has_callback(true); |
| |
| std::optional<base::Value> get_zoom_settings_result = |
| utils::RunFunctionAndReturnSingleResult( |
| get_zoom_settings_function.get(), base::StringPrintf("[%u]", tab_id), |
| profile()); |
| |
| if (!get_zoom_settings_result) { |
| return testing::AssertionFailure() << "no result"; |
| } |
| |
| base::Value::Dict get_zoom_settings_dict = |
| utils::ToDict(std::move(get_zoom_settings_result)); |
| *mode = api_test_utils::GetString(get_zoom_settings_dict, "mode"); |
| *scope = api_test_utils::GetString(get_zoom_settings_dict, "scope"); |
| |
| return testing::AssertionSuccess(); |
| } |
| |
| testing::AssertionResult ExtensionTabsZoomTest::RunGetDefaultZoom( |
| int tab_id, |
| double* default_zoom_factor) { |
| DCHECK(default_zoom_factor); |
| auto get_zoom_settings_function = |
| base::MakeRefCounted<TabsGetZoomSettingsFunction>(); |
| get_zoom_settings_function->set_extension(extension_.get()); |
| get_zoom_settings_function->set_has_callback(true); |
| |
| std::optional<base::Value> get_zoom_settings_result = |
| utils::RunFunctionAndReturnSingleResult( |
| get_zoom_settings_function.get(), base::StringPrintf("[%u]", tab_id), |
| profile()); |
| |
| if (!get_zoom_settings_result && get_zoom_settings_result->is_dict()) { |
| return testing::AssertionFailure() |
| << "no result or result is not a dictionary"; |
| } |
| |
| std::optional<double> default_zoom_factor_setting = |
| get_zoom_settings_result->GetDict().FindDouble("defaultZoomFactor"); |
| if (!default_zoom_factor_setting) { |
| return testing::AssertionFailure() |
| << "default zoom factor not found in result"; |
| } |
| *default_zoom_factor = *default_zoom_factor_setting; |
| |
| return testing::AssertionSuccess(); |
| } |
| |
| std::string ExtensionTabsZoomTest::RunSetZoomExpectError(int tab_id, |
| double zoom_factor) { |
| auto set_zoom_function = base::MakeRefCounted<TabsSetZoomFunction>(); |
| set_zoom_function->set_extension(extension_.get()); |
| set_zoom_function->set_has_callback(true); |
| |
| return utils::RunFunctionAndReturnError( |
| set_zoom_function.get(), |
| base::StringPrintf("[%u, %lf]", tab_id, zoom_factor), profile()); |
| } |
| |
| std::string ExtensionTabsZoomTest::RunSetZoomSettingsExpectError( |
| int tab_id, |
| const char* mode, |
| const char* scope) { |
| auto set_zoom_settings_function = |
| base::MakeRefCounted<TabsSetZoomSettingsFunction>(); |
| set_zoom_settings_function->set_extension(extension_.get()); |
| |
| return utils::RunFunctionAndReturnError( |
| set_zoom_settings_function.get(), |
| base::StringPrintf("[%u, {\"mode\": \"%s\", " |
| "\"scope\": \"%s\"}]", |
| tab_id, mode, scope), |
| profile()); |
| } |
| |
| content::WebContents* ExtensionTabsZoomTest::OpenUrlAndWaitForLoad( |
| const GURL& url) { |
| NavigateToURLInNewTab(url); |
| return GetActiveWebContents(); |
| } |
| |
| namespace { |
| |
| double GetZoomLevel(const content::WebContents* web_contents) { |
| return zoom::ZoomController::FromWebContents(web_contents)->GetZoomLevel(); |
| } |
| |
| content::OpenURLParams GetOpenParams(const char* url) { |
| return content::OpenURLParams(GURL(url), content::Referrer(), |
| WindowOpenDisposition::NEW_FOREGROUND_TAB, |
| ui::PAGE_TRANSITION_LINK, false); |
| } |
| |
| } // namespace |
| |
| IN_PROC_BROWSER_TEST_F(ExtensionTabsZoomTest, SetAndGetZoom) { |
| content::OpenURLParams params(GetOpenParams(url::kAboutBlankURL)); |
| content::WebContents* web_contents = OpenUrlAndWaitForLoad(params.url); |
| int tab_id = ExtensionTabUtil::GetTabId(web_contents); |
| |
| // Test default values before we set anything. |
| double zoom_factor = -1; |
| EXPECT_TRUE(RunGetZoom(tab_id, &zoom_factor)); |
| EXPECT_EQ(1.0, zoom_factor); |
| |
| // Test chrome.tabs.setZoom(). |
| const double kZoomLevel = 0.8; |
| EXPECT_TRUE(RunSetZoom(tab_id, kZoomLevel)); |
| EXPECT_EQ(kZoomLevel, |
| blink::ZoomLevelToZoomFactor(GetZoomLevel(web_contents))); |
| |
| // Test chrome.tabs.getZoom(). |
| zoom_factor = -1; |
| EXPECT_TRUE(RunGetZoom(tab_id, &zoom_factor)); |
| EXPECT_EQ(kZoomLevel, zoom_factor); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(ExtensionTabsZoomTest, GetDefaultZoom) { |
| content::OpenURLParams params(GetOpenParams(url::kAboutBlankURL)); |
| content::WebContents* web_contents = OpenUrlAndWaitForLoad(params.url); |
| int tab_id = ExtensionTabUtil::GetTabId(web_contents); |
| |
| zoom::ZoomController* zoom_controller = |
| zoom::ZoomController::FromWebContents(web_contents); |
| double default_zoom_factor = -1.0; |
| EXPECT_TRUE(RunGetDefaultZoom(tab_id, &default_zoom_factor)); |
| EXPECT_TRUE(blink::ZoomValuesEqual( |
| zoom_controller->GetDefaultZoomLevel(), |
| blink::ZoomFactorToZoomLevel(default_zoom_factor))); |
| |
| // Change the default zoom level and verify GetDefaultZoom returns the |
| // correct value. |
| content::StoragePartition* partition = |
| web_contents->GetBrowserContext()->GetStoragePartition( |
| web_contents->GetSiteInstance()); |
| ChromeZoomLevelPrefs* zoom_prefs = |
| static_cast<ChromeZoomLevelPrefs*>(partition->GetZoomLevelDelegate()); |
| |
| double default_zoom_level = zoom_controller->GetDefaultZoomLevel(); |
| zoom_prefs->SetDefaultZoomLevelPref(default_zoom_level + 0.5); |
| default_zoom_factor = -1.0; |
| EXPECT_TRUE(RunGetDefaultZoom(tab_id, &default_zoom_factor)); |
| EXPECT_TRUE(blink::ZoomValuesEqual( |
| default_zoom_level + 0.5, |
| blink::ZoomFactorToZoomLevel(default_zoom_factor))); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(ExtensionTabsZoomTest, SetToDefaultZoom) { |
| content::OpenURLParams params(GetOpenParams(url::kAboutBlankURL)); |
| content::WebContents* web_contents = OpenUrlAndWaitForLoad(params.url); |
| int tab_id = ExtensionTabUtil::GetTabId(web_contents); |
| |
| zoom::ZoomController* zoom_controller = |
| zoom::ZoomController::FromWebContents(web_contents); |
| double default_zoom_level = zoom_controller->GetDefaultZoomLevel(); |
| double new_default_zoom_level = default_zoom_level + 0.42; |
| |
| content::StoragePartition* partition = |
| web_contents->GetBrowserContext()->GetStoragePartition( |
| web_contents->GetSiteInstance()); |
| ChromeZoomLevelPrefs* zoom_prefs = |
| static_cast<ChromeZoomLevelPrefs*>(partition->GetZoomLevelDelegate()); |
| |
| zoom_prefs->SetDefaultZoomLevelPref(new_default_zoom_level); |
| |
| double observed_zoom_factor = -1.0; |
| EXPECT_TRUE(RunSetZoom(tab_id, 0.0)); |
| EXPECT_TRUE(RunGetZoom(tab_id, &observed_zoom_factor)); |
| EXPECT_TRUE(blink::ZoomValuesEqual( |
| new_default_zoom_level, |
| blink::ZoomFactorToZoomLevel(observed_zoom_factor))); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(ExtensionTabsZoomTest, ZoomSettings) { |
| // In this test we need two URLs that (1) represent real pages (i.e. they |
| // load without causing an error page load), (2) have different domains, and |
| // (3) are zoomable by the extension API (this last condition rules out |
| // chrome:// urls). We achieve this by noting that about:blank meets these |
| // requirements, allowing us to spin up an embedded http server on localhost |
| // to get the other domain. |
| net::EmbeddedTestServer http_server; |
| http_server.ServeFilesFromSourceDirectory("chrome/test/data"); |
| ASSERT_TRUE(http_server.Start()); |
| |
| GURL url_A = http_server.GetURL("/simple.html"); |
| GURL url_B("about:blank"); |
| |
| // Tabs A1 and A2 are navigated to the same origin, while B is navigated |
| // to a different one. |
| content::WebContents* web_contents_A1 = OpenUrlAndWaitForLoad(url_A); |
| content::WebContents* web_contents_A2 = OpenUrlAndWaitForLoad(url_A); |
| content::WebContents* web_contents_B = OpenUrlAndWaitForLoad(url_B); |
| |
| int tab_id_A1 = ExtensionTabUtil::GetTabId(web_contents_A1); |
| int tab_id_A2 = ExtensionTabUtil::GetTabId(web_contents_A2); |
| int tab_id_B = ExtensionTabUtil::GetTabId(web_contents_B); |
| |
| ASSERT_FLOAT_EQ(1.f, |
| blink::ZoomLevelToZoomFactor(GetZoomLevel(web_contents_A1))); |
| ASSERT_FLOAT_EQ(1.f, |
| blink::ZoomLevelToZoomFactor(GetZoomLevel(web_contents_A2))); |
| ASSERT_FLOAT_EQ(1.f, |
| blink::ZoomLevelToZoomFactor(GetZoomLevel(web_contents_B))); |
| |
| // Test per-origin automatic zoom settings. |
| EXPECT_TRUE(RunSetZoom(tab_id_B, 1.f)); |
| EXPECT_TRUE(RunSetZoom(tab_id_A2, 1.1f)); |
| EXPECT_FLOAT_EQ(1.1f, |
| blink::ZoomLevelToZoomFactor(GetZoomLevel(web_contents_A1))); |
| EXPECT_FLOAT_EQ(1.1f, |
| blink::ZoomLevelToZoomFactor(GetZoomLevel(web_contents_A2))); |
| EXPECT_FLOAT_EQ(1.f, |
| blink::ZoomLevelToZoomFactor(GetZoomLevel(web_contents_B))); |
| |
| // Test per-tab automatic zoom settings. |
| EXPECT_TRUE(RunSetZoomSettings(tab_id_A1, "automatic", "per-tab")); |
| EXPECT_TRUE(RunSetZoom(tab_id_A1, 1.2f)); |
| EXPECT_FLOAT_EQ(1.2f, |
| blink::ZoomLevelToZoomFactor(GetZoomLevel(web_contents_A1))); |
| EXPECT_FLOAT_EQ(1.1f, |
| blink::ZoomLevelToZoomFactor(GetZoomLevel(web_contents_A2))); |
| |
| // Test 'manual' mode. |
| EXPECT_TRUE(RunSetZoomSettings(tab_id_A1, "manual", nullptr)); |
| EXPECT_TRUE(RunSetZoom(tab_id_A1, 1.3f)); |
| EXPECT_FLOAT_EQ(1.3f, |
| blink::ZoomLevelToZoomFactor(GetZoomLevel(web_contents_A1))); |
| EXPECT_FLOAT_EQ(1.1f, |
| blink::ZoomLevelToZoomFactor(GetZoomLevel(web_contents_A2))); |
| |
| // Test 'disabled' mode, which will reset A1's zoom to 1.f. |
| EXPECT_TRUE(RunSetZoomSettings(tab_id_A1, "disabled", nullptr)); |
| std::string error = RunSetZoomExpectError(tab_id_A1, 1.4f); |
| EXPECT_TRUE(base::MatchPattern(error, keys::kCannotZoomDisabledTabError)); |
| EXPECT_FLOAT_EQ(1.f, |
| blink::ZoomLevelToZoomFactor(GetZoomLevel(web_contents_A1))); |
| // We should still be able to zoom A2 though. |
| EXPECT_TRUE(RunSetZoom(tab_id_A2, 1.4f)); |
| EXPECT_FLOAT_EQ(1.4f, |
| blink::ZoomLevelToZoomFactor(GetZoomLevel(web_contents_A2))); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(ExtensionTabsZoomTest, PerTabResetsOnNavigation) { |
| net::EmbeddedTestServer http_server; |
| http_server.ServeFilesFromSourceDirectory("chrome/test/data"); |
| ASSERT_TRUE(http_server.Start()); |
| |
| GURL url_A = http_server.GetURL("/simple.html"); |
| GURL url_B("about:blank"); |
| |
| content::WebContents* web_contents = OpenUrlAndWaitForLoad(url_A); |
| int tab_id = ExtensionTabUtil::GetTabId(web_contents); |
| EXPECT_TRUE(RunSetZoomSettings(tab_id, "automatic", "per-tab")); |
| |
| std::string mode; |
| std::string scope; |
| EXPECT_TRUE(RunGetZoomSettings(tab_id, &mode, &scope)); |
| EXPECT_EQ("automatic", mode); |
| EXPECT_EQ("per-tab", scope); |
| |
| // Navigation of tab should reset mode to per-origin. |
| ui_test_utils::NavigateToURLBlockUntilNavigationsComplete(browser(), url_B, |
| 1); |
| EXPECT_TRUE(RunGetZoomSettings(tab_id, &mode, &scope)); |
| EXPECT_EQ("automatic", mode); |
| EXPECT_EQ("per-origin", scope); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(ExtensionTabsZoomTest, GetZoomSettings) { |
| content::OpenURLParams params(GetOpenParams(url::kAboutBlankURL)); |
| content::WebContents* web_contents = OpenUrlAndWaitForLoad(params.url); |
| int tab_id = ExtensionTabUtil::GetTabId(web_contents); |
| |
| std::string mode; |
| std::string scope; |
| |
| EXPECT_TRUE(RunGetZoomSettings(tab_id, &mode, &scope)); |
| EXPECT_EQ("automatic", mode); |
| EXPECT_EQ("per-origin", scope); |
| |
| EXPECT_TRUE(RunSetZoomSettings(tab_id, "automatic", "per-tab")); |
| EXPECT_TRUE(RunGetZoomSettings(tab_id, &mode, &scope)); |
| |
| EXPECT_EQ("automatic", mode); |
| EXPECT_EQ("per-tab", scope); |
| |
| std::string error = |
| RunSetZoomSettingsExpectError(tab_id, "manual", "per-origin"); |
| EXPECT_TRUE(base::MatchPattern(error, keys::kPerOriginOnlyInAutomaticError)); |
| error = RunSetZoomSettingsExpectError(tab_id, "disabled", "per-origin"); |
| EXPECT_TRUE(base::MatchPattern(error, keys::kPerOriginOnlyInAutomaticError)); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(ExtensionTabsZoomTest, CannotZoomInvalidTab) { |
| content::OpenURLParams params(GetOpenParams(url::kAboutBlankURL)); |
| content::WebContents* web_contents = OpenUrlAndWaitForLoad(params.url); |
| int tab_id = ExtensionTabUtil::GetTabId(web_contents); |
| |
| int bogus_id = tab_id + 100; |
| std::string error = RunSetZoomExpectError(bogus_id, 3.14159); |
| EXPECT_TRUE(base::MatchPattern(error, ExtensionTabUtil::kTabNotFoundError)); |
| |
| error = RunSetZoomSettingsExpectError(bogus_id, "manual", "per-tab"); |
| EXPECT_TRUE(base::MatchPattern(error, ExtensionTabUtil::kTabNotFoundError)); |
| |
| const char kNewTestTabArgs[] = "chrome://version"; |
| params = GetOpenParams(kNewTestTabArgs); |
| web_contents = browser()->OpenURL(params, /*navigation_handle_callback=*/{}); |
| tab_id = ExtensionTabUtil::GetTabId(web_contents); |
| |
| // Test chrome.tabs.setZoom(). |
| error = RunSetZoomExpectError(tab_id, 3.14159); |
| EXPECT_TRUE( |
| base::MatchPattern(error, manifest_errors::kCannotAccessChromeUrl)); |
| |
| // chrome.tabs.setZoomSettings(). |
| error = RunSetZoomSettingsExpectError(tab_id, "manual", "per-tab"); |
| EXPECT_TRUE( |
| base::MatchPattern(error, manifest_errors::kCannotAccessChromeUrl)); |
| } |
| |
| #if BUILDFLAG(ENABLE_PDF) |
| class ExtensionApiPdfTest : public base::test::WithFeatureOverride, |
| public PDFExtensionTestBase { |
| public: |
| ExtensionApiPdfTest() |
| : base::test::WithFeatureOverride(chrome_pdf::features::kPdfOopif) {} |
| |
| bool UseOopif() const override { return GetParam(); } |
| }; |
| |
| // Regression test for crbug.com/660498. |
| IN_PROC_BROWSER_TEST_P(ExtensionApiPdfTest, TemporaryAddressSpoof) { |
| content::WebContents* first_web_contents = GetActiveWebContents(); |
| ASSERT_TRUE(first_web_contents); |
| chrome::NewTab(browser()); |
| content::WebContents* second_web_contents = GetActiveWebContents(); |
| ASSERT_NE(first_web_contents, second_web_contents); |
| GURL url = embedded_test_server()->GetURL( |
| "/extensions/api_test/tabs/pdf_extension_test.html"); |
| content::TestNavigationManager navigation_manager( |
| second_web_contents, GURL("http://www.facebook.com:83")); |
| ui_test_utils::NavigateToURLWithDisposition( |
| browser(), url, WindowOpenDisposition::CURRENT_TAB, |
| ui_test_utils::BROWSER_TEST_WAIT_FOR_LOAD_STOP); |
| |
| // Ensure the PDF has loaded, and get the WebContents to click. |
| auto* web_contents_for_click = second_web_contents; |
| if (UseOopif()) { |
| ASSERT_TRUE(GetTestPdfViewerStreamManager(second_web_contents) |
| ->WaitUntilPdfLoadedInFirstChild()); |
| } else { |
| ASSERT_TRUE( |
| pdf_extension_test_util::EnsurePDFHasLoaded(second_web_contents)); |
| |
| auto inner_web_contents = web_contents_for_click->GetInnerWebContents(); |
| ASSERT_EQ(1U, inner_web_contents.size()); |
| // With MimeHandlerViewInCrossProcessFrame input should directly route to |
| // the guest WebContents as there is no longer a BrowserPlugin involved. |
| web_contents_for_click = inner_web_contents[0]; |
| } |
| |
| // (400, 300) in `web_contents_for_click` translates to a different coordinate |
| // in the PDF Viewer. The exact coordinate depends on the PDF Viewer's UI |
| // layout. In the test PDF embedded in pdf_extension_test.html, the entire PDF |
| // content area is a giant link to http://www.facebook.com:83. As long as this |
| // click hits that link target, it triggers the navigation required for test. |
| content::SimulateMouseClickAt(web_contents_for_click, 0, |
| blink::WebMouseEvent::Button::kLeft, |
| gfx::Point(400, 300)); |
| |
| ASSERT_TRUE(navigation_manager.WaitForRequestStart()); |
| |
| browser()->tab_strip_model()->ActivateTabAt( |
| 0, TabStripUserGestureDetails( |
| TabStripUserGestureDetails::GestureType::kOther)); |
| EXPECT_EQ(first_web_contents, GetActiveWebContents()); |
| browser()->tab_strip_model()->ActivateTabAt( |
| 1, TabStripUserGestureDetails( |
| TabStripUserGestureDetails::GestureType::kOther)); |
| EXPECT_EQ(second_web_contents, GetActiveWebContents()); |
| |
| EXPECT_EQ(url, second_web_contents->GetVisibleURL()); |
| |
| // Wait for the TestNavigationManager-monitored navigation to complete to |
| // avoid a race during browser teardown (see crbug.com/882213). |
| ASSERT_TRUE(navigation_manager.WaitForNavigationFinished()); |
| } |
| |
| // TODO(crbug.com/40268279): Stop testing both modes after OOPIF PDF viewer |
| // launches. |
| INSTANTIATE_FEATURE_OVERRIDE_TEST_SUITE(ExtensionApiPdfTest); |
| #endif // BUILDFLAG(ENABLE_PDF) |
| |
| // Tests how chrome.windows.create behaves when setSelfAsOpener parameter is |
| // used. setSelfAsOpener was introduced as a fix for https://crbug.com/713888 |
| // and https://crbug.com/718489. This is a (slightly morphed) regression test |
| // for https://crbug.com/597750. |
| IN_PROC_BROWSER_TEST_F(ExtensionTabsTest, WindowsCreate_WithOpener) { |
| const extensions::Extension* extension = |
| LoadExtension(test_data_dir_.AppendASCII("../simple_with_file")); |
| ASSERT_TRUE(extension); |
| |
| // Navigate a tab to an extension page. |
| GURL extension_url = extension->GetResourceURL("file.html"); |
| content::WebContents* old_contents = GetActiveWebContents(); |
| ASSERT_TRUE(NavigateToURL(old_contents, extension_url)); |
| |
| // Execute chrome.windows.create and store the new tab in |new_contents|. |
| content::WebContents* new_contents = nullptr; |
| { |
| content::WebContentsAddedObserver observer; |
| std::string script = base::StringPrintf( |
| R"( window.name = 'old-contents'; |
| chrome.windows.create({url: '%s', setSelfAsOpener: true}); )", |
| extension_url.spec().c_str()); |
| ASSERT_TRUE(content::ExecJs(old_contents, script)); |
| new_contents = observer.GetWebContents(); |
| ASSERT_TRUE(content::WaitForLoadStop(new_contents)); |
| } |
| |
| // Navigate the old and the new tab to a web URL. |
| ASSERT_TRUE(StartEmbeddedTestServer()); |
| GURL web_url1 = embedded_test_server()->GetURL("/title1.html"); |
| GURL web_url2 = embedded_test_server()->GetURL("/title2.html"); |
| { |
| content::TestNavigationObserver nav_observer(new_contents, 1); |
| ASSERT_TRUE(content::ExecJs( |
| new_contents, "window.location = '" + web_url1.spec() + "';")); |
| nav_observer.Wait(); |
| } |
| { |
| content::TestNavigationObserver nav_observer(old_contents, 1); |
| ASSERT_TRUE(content::ExecJs( |
| old_contents, "window.location = '" + web_url2.spec() + "';")); |
| nav_observer.Wait(); |
| } |
| EXPECT_EQ(web_url1, |
| new_contents->GetPrimaryMainFrame()->GetLastCommittedURL()); |
| EXPECT_EQ(web_url2, |
| old_contents->GetPrimaryMainFrame()->GetLastCommittedURL()); |
| |
| // Verify that the old and new tab are in the same process. |
| EXPECT_EQ(old_contents->GetPrimaryMainFrame()->GetProcess(), |
| new_contents->GetPrimaryMainFrame()->GetProcess()); |
| |
| // Verify the old and new contents are in the same BrowsingInstance. |
| EXPECT_TRUE(old_contents->GetPrimaryMainFrame() |
| ->GetSiteInstance() |
| ->IsRelatedSiteInstance( |
| new_contents->GetPrimaryMainFrame()->GetSiteInstance())); |
| |
| // Verify that the |new_contents| has |window.opener| set. |
| EXPECT_EQ(old_contents->GetPrimaryMainFrame()->GetLastCommittedURL().spec(), |
| EvalJs(new_contents, "window.opener.location.href")); |
| |
| // Verify that |new_contents| can find |old_contents| using window.open/name. |
| std::string location_of_other_window = |
| EvalJs(new_contents, |
| "var w = window.open('', 'old-contents');\n" |
| "w.location.href;") |
| .ExtractString(); |
| EXPECT_EQ(old_contents->GetPrimaryMainFrame()->GetLastCommittedURL().spec(), |
| location_of_other_window); |
| } |
| |
| // Tests how chrome.windows.create behaves when setSelfAsOpener parameter is not |
| // used. setSelfAsOpener was introduced as a fix for https://crbug.com/713888 |
| // and https://crbug.com/718489. |
| IN_PROC_BROWSER_TEST_F(ExtensionTabsTest, WindowsCreate_NoOpener) { |
| const Extension* extension = |
| LoadExtension(test_data_dir_.AppendASCII("../simple_with_file")); |
| ASSERT_TRUE(extension); |
| |
| // Navigate a tab to an extension page. |
| GURL extension_url = extension->GetResourceURL("file.html"); |
| content::WebContents* old_contents = GetActiveWebContents(); |
| ASSERT_TRUE(NavigateToURL(old_contents, extension_url)); |
| |
| // Execute chrome.windows.create and store the new tab in |new_contents|. |
| content::WebContents* new_contents = nullptr; |
| { |
| content::WebContentsAddedObserver observer; |
| std::string script = base::StringPrintf( |
| R"( window.name = 'old-contents'; |
| chrome.windows.create({url: '%s'}); )", |
| extension_url.spec().c_str()); |
| ASSERT_TRUE(content::ExecJs(old_contents, script)); |
| new_contents = observer.GetWebContents(); |
| ASSERT_TRUE(content::WaitForLoadStop(new_contents)); |
| } |
| |
| // Verify the old and new contents are NOT in the same BrowsingInstance. |
| EXPECT_FALSE(old_contents->GetPrimaryMainFrame() |
| ->GetSiteInstance() |
| ->IsRelatedSiteInstance( |
| new_contents->GetPrimaryMainFrame()->GetSiteInstance())); |
| |
| // Verify that the |new_contents| doesn't have |window.opener| set. |
| EXPECT_EQ(false, EvalJs(new_contents, "!!window.opener")); |
| |
| // TODO(lukasza): http://crbug.com/786411: Verify that |new_contents| can NOT |
| // find |old_contents| using window.open/name. This is currently broken, |
| // because browsing instance boundaries are pierced for all extension frames |
| // (we hope this can be limited to background pages / contents). |
| } |
| |
| // Tests the origin of tabs created through chrome.windows.create(). |
| IN_PROC_BROWSER_TEST_F(ExtensionTabsTest, WindowsCreate_OpenerAndOrigin) { |
| const extensions::Extension* extension = |
| LoadExtension(test_data_dir_.AppendASCII("../simple_with_file")); |
| ASSERT_TRUE(extension); |
| |
| // Navigate a tab to an extension page. |
| GURL extension_url = extension->GetResourceURL("file.html"); |
| content::WebContents* web_contents = GetActiveWebContents(); |
| ASSERT_TRUE(NavigateToURL(web_contents, extension_url)); |
| |
| const std::string extension_origin_str = |
| url::Origin::Create(extension->url()).Serialize(); |
| constexpr char kDataURL[] = "data:text/html,<html>test</html>"; |
| std::string extension_url_str = extension_url.spec(); |
| struct TestCase { |
| // The url to use in chrome.windows.create(). |
| std::string url; |
| // If set, its value will be used to specify |setSelfAsOpener|. |
| std::optional<bool> set_self_as_opener; |
| // The origin we expect the new tab to be in, opaque origins will be "null". |
| std::string expected_origin_str; |
| }; |
| auto test_cases = std::to_array<TestCase>({ |
| // about:blank URLs. |
| // With opener relationship, about:blank urls will get the extension's |
| // origin, without opener relationship, they will get opaque/"null" |
| // origin. |
| {url::kAboutBlankURL, true, extension_origin_str}, |
| {url::kAboutBlankURL, false, "null"}, |
| {url::kAboutBlankURL, std::nullopt, "null"}, |
| |
| // data:... URLs. |
| // With opener relationship or not, "data:..." URLs always gets unique |
| // origin, so origin will always be "null" in these cases. |
| {kDataURL, true, "null"}, |
| {kDataURL, false, "null"}, |
| {kDataURL, std::nullopt, "null"}, |
| |
| // chrome-extension:// URLs. |
| // These always get extension origin. |
| {extension_url_str, true, extension_origin_str}, |
| {extension_url_str, false, extension_origin_str}, |
| {extension_url_str, std::nullopt, extension_origin_str}, |
| }); |
| |
| auto run_test_case = [&web_contents](const TestCase& test_case) { |
| std::string maybe_specify_set_self_as_opener; |
| if (test_case.set_self_as_opener) { |
| maybe_specify_set_self_as_opener = |
| base::StringPrintf(", setSelfAsOpener: %s", |
| base::ToString(*test_case.set_self_as_opener)); |
| } |
| std::string script = base::StringPrintf( |
| R"( chrome.windows.create({url: '%s'%s}); )", test_case.url.c_str(), |
| maybe_specify_set_self_as_opener.c_str()); |
| |
| content::WebContents* new_contents = nullptr; |
| { |
| content::WebContentsAddedObserver observer; |
| ASSERT_TRUE(content::ExecJs(web_contents, script)); |
| new_contents = observer.GetWebContents(); |
| } |
| ASSERT_TRUE(new_contents); |
| ASSERT_TRUE(content::WaitForLoadStop(new_contents)); |
| |
| EXPECT_EQ(test_case.expected_origin_str, EvalJs(new_contents, "origin;")); |
| const bool is_opaque_origin = |
| new_contents->GetPrimaryMainFrame()->GetLastCommittedOrigin().opaque(); |
| EXPECT_EQ(test_case.expected_origin_str == "null", is_opaque_origin); |
| }; |
| for (size_t i = 0; i < std::size(test_cases); ++i) { |
| const auto& test_case = test_cases[i]; |
| SCOPED_TRACE( |
| base::StringPrintf("#%" PRIuS " %s", i, test_case.url.c_str())); |
| run_test_case(test_case); |
| } |
| } |
| |
| // Tests updating a URL of a web tab to an about:blank. Verify that the new |
| // frame is placed in the correct process, has the correct origin and that no |
| // DCHECKs are hit anywhere. |
| IN_PROC_BROWSER_TEST_F(ExtensionTabsTest, TabsUpdate_WebToAboutBlank) { |
| ASSERT_TRUE(embedded_test_server()->Start()); |
| const extensions::Extension* extension = |
| LoadExtension(test_data_dir_.AppendASCII("../simple_with_file")); |
| ASSERT_TRUE(extension); |
| GURL extension_url = extension->GetResourceURL("file.html"); |
| url::Origin extension_origin = url::Origin::Create(extension_url); |
| GURL web_url = embedded_test_server()->GetURL("/title1.html"); |
| url::Origin web_origin = url::Origin::Create(web_url); |
| GURL about_blank_url = GURL(url::kAboutBlankURL); |
| |
| // Navigate a tab to an extension page. |
| content::WebContents* extension_contents = GetActiveWebContents(); |
| ASSERT_TRUE(NavigateToURL(extension_contents, extension_url)); |
| EXPECT_EQ( |
| extension_origin, |
| extension_contents->GetPrimaryMainFrame()->GetLastCommittedOrigin()); |
| |
| // Create another tab and navigate it to a web page. |
| content::WebContents* test_contents = nullptr; |
| { |
| content::WebContentsAddedObserver test_contents_observer; |
| ui_test_utils::NavigateToURLWithDisposition( |
| browser(), web_url, WindowOpenDisposition::NEW_FOREGROUND_TAB, |
| ui_test_utils::BROWSER_TEST_WAIT_FOR_LOAD_STOP); |
| test_contents = test_contents_observer.GetWebContents(); |
| } |
| EXPECT_EQ(web_origin, |
| test_contents->GetPrimaryMainFrame()->GetLastCommittedOrigin()); |
| EXPECT_NE(extension_contents->GetPrimaryMainFrame()->GetProcess(), |
| test_contents->GetPrimaryMainFrame()->GetProcess()); |
| |
| // Use |chrome.tabs.update| API to navigate |test_contents| to an about:blank |
| // URL. |
| { |
| content::TestNavigationObserver nav_observer(test_contents, 1); |
| int test_tab_id = ExtensionTabUtil::GetTabId(test_contents); |
| content::ExecuteScriptAsync( |
| extension_contents, |
| content::JsReplace("chrome.tabs.update($1, { url: $2 })", test_tab_id, |
| about_blank_url)); |
| nav_observer.WaitForNavigationFinished(); |
| EXPECT_TRUE(nav_observer.last_navigation_succeeded()); |
| EXPECT_EQ(about_blank_url, nav_observer.last_navigation_url()); |
| } |
| |
| // Verify the origin and process of the about:blank tab. |
| content::RenderFrameHost* test_frame = test_contents->GetPrimaryMainFrame(); |
| EXPECT_EQ(about_blank_url, test_frame->GetLastCommittedURL()); |
| EXPECT_EQ(extension_contents->GetPrimaryMainFrame()->GetProcess(), |
| test_contents->GetPrimaryMainFrame()->GetProcess()); |
| // Note that committing with the extension origin wouldn't be possible when |
| // targeting an incognito window (see also IncognitoApiTest.Incognito test). |
| EXPECT_EQ(extension_origin, test_frame->GetLastCommittedOrigin()); |
| } |
| |
| // Tests updating a URL of a web tab to an about:newtab. Verify that the new |
| // frame is placed in the correct process, has the correct origin and that no |
| // DCHECKs are hit anywhere. |
| IN_PROC_BROWSER_TEST_F(ExtensionTabsTest, TabsUpdate_WebToAboutNewTab) { |
| ASSERT_TRUE(embedded_test_server()->Start()); |
| const extensions::Extension* extension = |
| LoadExtension(test_data_dir_.AppendASCII("../simple_with_file")); |
| ASSERT_TRUE(extension); |
| GURL extension_url = extension->GetResourceURL("file.html"); |
| url::Origin extension_origin = url::Origin::Create(extension_url); |
| GURL web_url = embedded_test_server()->GetURL("/title1.html"); |
| url::Origin web_origin = url::Origin::Create(web_url); |
| |
| // https://crbug.com/1145381: about:version is rewritten to chrome://version |
| // when entered in the omnibox or used in a bookmark. Such rewriting is |
| // definitely undesirable for http-initiated navigations (see r818969), but |
| // it is less clear what should happen in extension-initiated navigations. |
| GURL about_newtab_url = GURL("about:newtab"); |
| GURL chrome_newtab_url = GURL("chrome://new-tab-page/"); |
| |
| // Navigate a tab to an extension page. |
| content::WebContents* extension_contents = GetActiveWebContents(); |
| ASSERT_TRUE(NavigateToURL(extension_contents, extension_url)); |
| EXPECT_EQ( |
| extension_origin, |
| extension_contents->GetPrimaryMainFrame()->GetLastCommittedOrigin()); |
| |
| // Create another tab and navigate it to a web page. |
| content::WebContents* test_contents = nullptr; |
| { |
| content::WebContentsAddedObserver test_contents_observer; |
| ui_test_utils::NavigateToURLWithDisposition( |
| browser(), web_url, WindowOpenDisposition::NEW_FOREGROUND_TAB, |
| ui_test_utils::BROWSER_TEST_WAIT_FOR_LOAD_STOP); |
| test_contents = test_contents_observer.GetWebContents(); |
| } |
| |
| // Use |chrome.tabs.update| API to navigate |test_contents| to an about:newtab |
| // URL. |
| { |
| content::TestNavigationObserver nav_observer(test_contents, 1); |
| int test_tab_id = ExtensionTabUtil::GetTabId(test_contents); |
| content::ExecuteScriptAsync( |
| extension_contents, |
| content::JsReplace("chrome.tabs.update($1, { url: $2 })", test_tab_id, |
| about_newtab_url)); |
| nav_observer.WaitForNavigationFinished(); |
| EXPECT_TRUE(nav_observer.last_navigation_succeeded()); |
| EXPECT_EQ(chrome_newtab_url, nav_observer.last_navigation_url()); |
| } |
| |
| // Verify the origin and process of the about:newtab tab. |
| content::RenderFrameHost* test_frame = test_contents->GetPrimaryMainFrame(); |
| EXPECT_EQ(chrome_newtab_url, test_frame->GetLastCommittedURL()); |
| EXPECT_EQ(url::Origin::Create(chrome_newtab_url), |
| test_frame->GetLastCommittedOrigin()); |
| EXPECT_NE(extension_contents->GetPrimaryMainFrame()->GetProcess(), |
| test_contents->GetPrimaryMainFrame()->GetProcess()); |
| } |
| |
| // Tests updating a URL of a web tab to a non-web-accessible-resource of an |
| // extension - such navigation should be allowed. |
| IN_PROC_BROWSER_TEST_F(ExtensionTabsTest, TabsUpdate_WebToNonWAR) { |
| ASSERT_TRUE(embedded_test_server()->Start()); |
| const extensions::Extension* extension = |
| LoadExtension(test_data_dir_.AppendASCII("../simple_with_file")); |
| ASSERT_TRUE(extension); |
| GURL extension_url = extension->GetResourceURL("file.html"); |
| url::Origin extension_origin = url::Origin::Create(extension_url); |
| GURL web_url = embedded_test_server()->GetURL("/title1.html"); |
| url::Origin web_origin = url::Origin::Create(web_url); |
| GURL non_war_url = extension_url; |
| |
| // Navigate a tab to an extension page. |
| content::WebContents* extension_contents = GetActiveWebContents(); |
| ASSERT_TRUE(NavigateToURL(extension_contents, extension_url)); |
| EXPECT_EQ( |
| extension_origin, |
| extension_contents->GetPrimaryMainFrame()->GetLastCommittedOrigin()); |
| |
| // Create another tab and navigate it to a web page. |
| content::WebContents* test_contents = nullptr; |
| { |
| content::WebContentsAddedObserver test_contents_observer; |
| ui_test_utils::NavigateToURLWithDisposition( |
| browser(), web_url, WindowOpenDisposition::NEW_FOREGROUND_TAB, |
| ui_test_utils::BROWSER_TEST_WAIT_FOR_LOAD_STOP); |
| test_contents = test_contents_observer.GetWebContents(); |
| } |
| EXPECT_EQ(web_origin, |
| test_contents->GetPrimaryMainFrame()->GetLastCommittedOrigin()); |
| EXPECT_NE(extension_contents->GetPrimaryMainFrame()->GetProcess(), |
| test_contents->GetPrimaryMainFrame()->GetProcess()); |
| |
| // Use |chrome.tabs.update| API to navigate |test_contents| to a |
| // non-web-accessible-resource of an extension. |
| { |
| content::TestNavigationObserver nav_observer(test_contents, 1); |
| int test_tab_id = ExtensionTabUtil::GetTabId(test_contents); |
| content::ExecuteScriptAsync( |
| extension_contents, |
| content::JsReplace("chrome.tabs.update($1, { url: $2 })", test_tab_id, |
| non_war_url)); |
| nav_observer.WaitForNavigationFinished(); |
| EXPECT_TRUE(nav_observer.last_navigation_succeeded()); |
| EXPECT_EQ(non_war_url, nav_observer.last_navigation_url()); |
| } |
| |
| // Verify the origin and process of the navigated tab. |
| content::RenderFrameHost* test_frame = test_contents->GetPrimaryMainFrame(); |
| EXPECT_EQ(non_war_url, test_frame->GetLastCommittedURL()); |
| EXPECT_EQ(extension_origin, test_frame->GetLastCommittedOrigin()); |
| EXPECT_EQ(extension_contents->GetPrimaryMainFrame()->GetProcess(), |
| test_contents->GetPrimaryMainFrame()->GetProcess()); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(ExtensionTabsTest, |
| ExtensionAPICannotCreateWindowForDevtools) { |
| DevToolsWindow* devtools = DevToolsWindowTesting::OpenDevToolsWindowSync( |
| GetTabListInterface()->GetTab(0)->GetContents(), false /* is_docked */); |
| auto function = base::MakeRefCounted<WindowsCreateFunction>(); |
| scoped_refptr<const Extension> extension(ExtensionBuilder("Test").Build()); |
| function->set_extension(extension.get()); |
| |
| EXPECT_TRUE(base::MatchPattern( |
| utils::RunFunctionAndReturnError( |
| function.get(), |
| base::StringPrintf( |
| R"([{"tabId": %d}])", |
| ExtensionTabUtil::GetTabId( |
| DevToolsWindowTesting::Get(devtools)->main_web_contents())), |
| DevToolsWindowTesting::Get(devtools)->browser()->GetProfile()), |
| tabs_constants::kNotAllowedForDevToolsError)); |
| |
| DevToolsWindowTesting::CloseDevToolsWindowSync(devtools); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(ExtensionTabsTest, ExtensionAPICannotMoveDevtoolsTab) { |
| DevToolsWindow* devtools = DevToolsWindowTesting::OpenDevToolsWindowSync( |
| GetTabListInterface()->GetTab(0)->GetContents(), false /* is_docked */); |
| auto function = base::MakeRefCounted<TabsMoveFunction>(); |
| |
| EXPECT_TRUE(base::MatchPattern( |
| utils::RunFunctionAndReturnError( |
| function.get(), |
| base::StringPrintf( |
| R"([%d, {"index": -1}])", |
| ExtensionTabUtil::GetTabId( |
| DevToolsWindowTesting::Get(devtools)->main_web_contents())), |
| DevToolsWindowTesting::Get(devtools)->browser()->GetProfile()), |
| tabs_constants::kNotAllowedForDevToolsError)); |
| |
| DevToolsWindowTesting::CloseDevToolsWindowSync(devtools); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(ExtensionTabsTest, ExtensionAPICannotGroupDevtoolsTab) { |
| ASSERT_TRUE(browser()->tab_strip_model()->SupportsTabGroups()); |
| |
| DevToolsWindow* devtools = DevToolsWindowTesting::OpenDevToolsWindowSync( |
| GetTabListInterface()->GetTab(0)->GetContents(), false /* is_docked */); |
| auto function = base::MakeRefCounted<TabsGroupFunction>(); |
| |
| EXPECT_TRUE(base::MatchPattern( |
| utils::RunFunctionAndReturnError( |
| function.get(), |
| base::StringPrintf( |
| R"([{"tabIds": %d}])", |
| ExtensionTabUtil::GetTabId( |
| DevToolsWindowTesting::Get(devtools)->main_web_contents())), |
| DevToolsWindowTesting::Get(devtools)->browser()->GetProfile()), |
| tabs_constants::kNotAllowedForDevToolsError)); |
| |
| DevToolsWindowTesting::CloseDevToolsWindowSync(devtools); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(ExtensionTabsTest, |
| ExtensionAPICannotDiscardDevtoolsTab) { |
| DevToolsWindow* devtools = DevToolsWindowTesting::OpenDevToolsWindowSync( |
| GetTabListInterface()->GetTab(0)->GetContents(), false /* is_docked */); |
| auto function = base::MakeRefCounted<TabsDiscardFunction>(); |
| |
| EXPECT_TRUE(base::MatchPattern( |
| utils::RunFunctionAndReturnError( |
| function.get(), |
| base::StringPrintf( |
| "[%d]", |
| ExtensionTabUtil::GetTabId( |
| DevToolsWindowTesting::Get(devtools)->main_web_contents())), |
| DevToolsWindowTesting::Get(devtools)->browser()->GetProfile()), |
| tabs_constants::kNotAllowedForDevToolsError)); |
| |
| DevToolsWindowTesting::CloseDevToolsWindowSync(devtools); |
| } |
| |
| #endif // BUILDFLAG(ENABLE_EXTENSIONS) |
| |
| } // namespace extensions |