| // Copyright 2022 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "base/memory/ref_counted.h" |
| #include "base/test/scoped_feature_list.h" |
| #include "chrome/browser/extensions/api/side_panel/side_panel_api.h" |
| #include "chrome/browser/extensions/extension_browsertest.h" |
| #include "chrome/browser/extensions/extension_tab_util.h" |
| #include "chrome/browser/ui/browser.h" |
| #include "chrome/browser/ui/browser_window.h" |
| #include "chrome/browser/ui/extensions/extension_action_test_helper.h" |
| #include "chrome/browser/ui/views/frame/browser_view.h" |
| #include "chrome/browser/ui/views/side_panel/extensions/extension_side_panel_coordinator.h" |
| #include "chrome/browser/ui/views/side_panel/extensions/extension_side_panel_manager.h" |
| #include "chrome/browser/ui/views/side_panel/side_panel_coordinator.h" |
| #include "chrome/browser/ui/views/side_panel/side_panel_entry.h" |
| #include "chrome/browser/ui/views/side_panel/side_panel_entry_observer.h" |
| #include "chrome/browser/ui/views/side_panel/side_panel_registry.h" |
| #include "chrome/browser/ui/views/side_panel/side_panel_registry_observer.h" |
| #include "chrome/test/base/ui_test_utils.h" |
| #include "components/sessions/content/session_tab_helper.h" |
| #include "content/public/test/browser_test.h" |
| #include "content/public/test/browser_test_utils.h" |
| #include "content/public/test/test_utils.h" |
| #include "extensions/browser/api_test_utils.h" |
| #include "extensions/browser/test_image_loader.h" |
| #include "extensions/common/constants.h" |
| #include "extensions/common/extension_features.h" |
| #include "extensions/test/extension_test_message_listener.h" |
| #include "ui/gfx/image/image_unittest_util.h" |
| |
| namespace extensions { |
| namespace { |
| |
| SidePanelEntry::Key GetKey(const ExtensionId& id) { |
| return SidePanelEntry::Key(SidePanelEntry::Id::kExtension, id); |
| } |
| |
| // A class which waits on various SidePanelEntryObserver events. |
| class TestSidePanelEntryWaiter : public SidePanelEntryObserver { |
| public: |
| explicit TestSidePanelEntryWaiter(SidePanelEntry* entry) { |
| side_panel_entry_observation_.Observe(entry); |
| } |
| |
| ~TestSidePanelEntryWaiter() override = default; |
| TestSidePanelEntryWaiter(const TestSidePanelEntryWaiter& other) = delete; |
| TestSidePanelEntryWaiter& operator=(const TestSidePanelEntryWaiter& other) = |
| delete; |
| |
| void WaitForEntryShown() { entry_shown_run_loop_.Run(); } |
| |
| void WaitForEntryHidden() { entry_hidden_run_loop_.Run(); } |
| |
| void WaitForIconUpdated() { icon_updated_run_loop_.Run(); } |
| |
| private: |
| void OnEntryShown(SidePanelEntry* entry) override { |
| entry_shown_run_loop_.QuitWhenIdle(); |
| } |
| |
| void OnEntryHidden(SidePanelEntry* entry) override { |
| entry_hidden_run_loop_.QuitWhenIdle(); |
| } |
| |
| void OnEntryIconUpdated(SidePanelEntry* entry) override { |
| icon_updated_run_loop_.QuitWhenIdle(); |
| } |
| |
| base::RunLoop entry_shown_run_loop_; |
| base::RunLoop entry_hidden_run_loop_; |
| base::RunLoop icon_updated_run_loop_; |
| base::ScopedObservation<SidePanelEntry, SidePanelEntryObserver> |
| side_panel_entry_observation_{this}; |
| }; |
| |
| // A class which waits for an extension's SidePanelEntry to be registered and/or |
| // deregistered. |
| class ExtensionSidePanelRegistryWaiter : public SidePanelRegistryObserver { |
| public: |
| explicit ExtensionSidePanelRegistryWaiter(SidePanelRegistry* registry, |
| const ExtensionId& extension_id) |
| : extension_id_(extension_id) { |
| side_panel_registry_observation_.Observe(registry); |
| } |
| |
| ~ExtensionSidePanelRegistryWaiter() override = default; |
| ExtensionSidePanelRegistryWaiter( |
| const ExtensionSidePanelRegistryWaiter& other) = delete; |
| ExtensionSidePanelRegistryWaiter& operator=( |
| const ExtensionSidePanelRegistryWaiter& other) = delete; |
| |
| // Waits until the entry for `extension_id_` is registered. |
| void WaitForRegistration() { registration_run_loop_.Run(); } |
| |
| // Waits until the entry for `extension_id_` is deregistered. |
| void WaitForDeregistration() { deregistration_run_loop_.Run(); } |
| |
| private: |
| // SidePanelRegistryObserver implementation. |
| void OnEntryRegistered(SidePanelRegistry* registry, |
| SidePanelEntry* entry) override { |
| if (entry->key() == GetKey(extension_id_)) { |
| registration_run_loop_.QuitWhenIdle(); |
| } |
| } |
| |
| void OnEntryWillDeregister(SidePanelRegistry* registry, |
| SidePanelEntry* entry) override { |
| if (entry->key() == GetKey(extension_id_)) { |
| deregistration_run_loop_.QuitWhenIdle(); |
| } |
| } |
| |
| ExtensionId extension_id_; |
| base::RunLoop registration_run_loop_; |
| base::RunLoop deregistration_run_loop_; |
| base::ScopedObservation<SidePanelRegistry, SidePanelRegistryObserver> |
| side_panel_registry_observation_{this}; |
| }; |
| |
| class ExtensionSidePanelBrowserTest : public ExtensionBrowserTest { |
| public: |
| ExtensionSidePanelBrowserTest() { |
| feature_list_.InitAndEnableFeature( |
| extensions_features::kExtensionSidePanelIntegration); |
| } |
| |
| protected: |
| int GetCurrentTabId() { |
| return ExtensionTabUtil::GetTabId( |
| browser()->tab_strip_model()->GetActiveWebContents()); |
| } |
| |
| SidePanelRegistry* GetCurrentTabRegistry() { |
| return SidePanelRegistry::Get( |
| browser()->tab_strip_model()->GetActiveWebContents()); |
| } |
| |
| void OpenNewTab() { |
| int tab_count = browser()->tab_strip_model()->count(); |
| ui_test_utils::NavigateToURLWithDisposition( |
| browser(), GURL("http://example.com"), |
| WindowOpenDisposition::NEW_FOREGROUND_TAB, |
| ui_test_utils::BROWSER_TEST_WAIT_FOR_TAB); |
| ASSERT_EQ(tab_count + 1, browser()->tab_strip_model()->count()); |
| } |
| |
| // Calls chrome.sidePanel.setOptions() for the given `extension`, `path` and |
| // `enabled` and returns when the API call is complete. |
| void RunSetOptions(const Extension& extension, |
| absl::optional<int> tab_id, |
| absl::optional<std::string> path, |
| bool enabled) { |
| auto function = base::MakeRefCounted<SidePanelSetOptionsFunction>(); |
| function->set_extension(&extension); |
| |
| std::string tab_id_arg = |
| tab_id.has_value() ? base::StringPrintf(R"("tabId":%d,)", *tab_id) : ""; |
| std::string path_arg = |
| path.has_value() ? base::StringPrintf(R"("path":"%s",)", path->c_str()) |
| : ""; |
| std::string args = |
| base::StringPrintf(R"([{%s%s"enabled":%s}])", tab_id_arg.c_str(), |
| path_arg.c_str(), enabled ? "true" : "false"); |
| EXPECT_TRUE(api_test_utils::RunFunction(function.get(), args, profile())) |
| << function->GetError(); |
| } |
| |
| // Calls chrome.sidePanel.setPanelBehavior() for the given `extension` and |
| // `openPanelOnActionClick`, and returns when the API call is complete. |
| void RunSetPanelBehavior(const Extension& extension, |
| bool openPanelOnActionClick) { |
| auto function = base::MakeRefCounted<SidePanelSetPanelBehaviorFunction>(); |
| function->set_extension(&extension); |
| |
| std::string args = |
| base::StringPrintf(R"([{"openPanelOnActionClick":%s}])", |
| openPanelOnActionClick ? "true" : "false"); |
| EXPECT_TRUE(api_test_utils::RunFunction(function.get(), args, profile())) |
| << function->GetError(); |
| } |
| |
| // Disables the extension's side panel for the current tab. |
| void DisableForCurrentTab(const Extension& extension) { |
| ExtensionSidePanelRegistryWaiter waiter(global_registry(), extension.id()); |
| RunSetOptions(extension, GetCurrentTabId(), /*path=*/absl::nullopt, |
| /*enabled=*/false); |
| waiter.WaitForDeregistration(); |
| EXPECT_FALSE(global_registry()->GetEntryForKey(GetKey(extension.id()))); |
| EXPECT_FALSE(side_panel_coordinator()->IsSidePanelShowing()); |
| } |
| |
| // Shows a side panel entry and waits for the entry to be shown. |
| void ShowEntryAndWait(const SidePanelEntry::Key& key) { |
| TestSidePanelEntryWaiter extension_entry_waiter( |
| global_registry()->GetEntryForKey(key)); |
| side_panel_coordinator()->Show(key); |
| extension_entry_waiter.WaitForEntryShown(); |
| EXPECT_TRUE(side_panel_coordinator()->IsSidePanelShowing()); |
| } |
| |
| // Runs a script in the extension's side panel WebContents to retrieve the |
| // value of document.sidePanelTemp. |
| std::string GetGlobalVariableInExtensionSidePanel( |
| const ExtensionId& extension_id, |
| content::WebContents* web_contents) { |
| auto* extension_coordinator = |
| web_contents |
| ? extensions::ExtensionSidePanelManager::GetOrCreateForWebContents( |
| browser()->profile(), web_contents) |
| ->GetExtensionCoordinatorForTesting(extension_id) |
| : extensions::ExtensionSidePanelManager::GetOrCreateForBrowser( |
| browser()) |
| ->GetExtensionCoordinatorForTesting(extension_id); |
| |
| static constexpr char kScript[] = R"( |
| document.sidePanelTemp ? document.sidePanelTemp : 'undefined'; |
| )"; |
| |
| return content::EvalJs( |
| extension_coordinator->GetHostWebContentsForTesting(), kScript) |
| .ExtractString(); |
| } |
| |
| // Runs a script in the extension's side panel WebContents to set the value of |
| // document.sidePanelTemp to `value`. |
| void SetGlobalVariableInExtensionSidePanel(const ExtensionId& extension_id, |
| content::WebContents* web_contents, |
| const std::string& value) { |
| auto* extension_coordinator = |
| web_contents |
| ? extensions::ExtensionSidePanelManager::GetOrCreateForWebContents( |
| browser()->profile(), web_contents) |
| ->GetExtensionCoordinatorForTesting(extension_id) |
| : extensions::ExtensionSidePanelManager::GetOrCreateForBrowser( |
| browser()) |
| ->GetExtensionCoordinatorForTesting(extension_id); |
| |
| std::string script = |
| base::StringPrintf(R"(document.sidePanelTemp = "%s";)", value.c_str()); |
| ASSERT_TRUE(content::ExecuteScript( |
| extension_coordinator->GetHostWebContentsForTesting(), script.c_str())); |
| } |
| |
| SidePanelRegistry* global_registry() { |
| return SidePanelCoordinator::GetGlobalSidePanelRegistry(browser()); |
| } |
| |
| SidePanelCoordinator* side_panel_coordinator() { |
| return BrowserView::GetBrowserViewForBrowser(browser()) |
| ->side_panel_coordinator(); |
| } |
| |
| private: |
| base::test::ScopedFeatureList feature_list_; |
| }; |
| |
| // Test that only extensions with side panel content will have a SidePanelEntry |
| // registered. |
| IN_PROC_BROWSER_TEST_F(ExtensionSidePanelBrowserTest, |
| ExtensionEntryVisibleInSidePanel) { |
| // Load two extensions: one with a side panel entry in its manifest and one |
| // without. |
| scoped_refptr<const extensions::Extension> no_side_panel_extension = |
| LoadExtension(test_data_dir_.AppendASCII("common/background_script")); |
| ASSERT_TRUE(no_side_panel_extension); |
| |
| scoped_refptr<const extensions::Extension> side_panel_extension = |
| LoadExtension( |
| test_data_dir_.AppendASCII("api_test/side_panel/simple_default")); |
| ASSERT_TRUE(side_panel_extension); |
| |
| // Check that only the extension with the side panel entry in its manifest is |
| // shown as an entry in the global side panel registry. |
| EXPECT_TRUE(global_registry()->GetEntryForKey(SidePanelEntry::Key( |
| SidePanelEntry::Id::kExtension, side_panel_extension->id()))); |
| EXPECT_FALSE(global_registry()->GetEntryForKey(SidePanelEntry::Key( |
| SidePanelEntry::Id::kExtension, no_side_panel_extension->id()))); |
| |
| // Unloading the extension should remove it from the registry. |
| UnloadExtension(side_panel_extension->id()); |
| EXPECT_FALSE(global_registry()->GetEntryForKey(SidePanelEntry::Key( |
| SidePanelEntry::Id::kExtension, side_panel_extension->id()))); |
| } |
| |
| // Test that an extension's view is shown/behaves correctly in the side panel. |
| IN_PROC_BROWSER_TEST_F(ExtensionSidePanelBrowserTest, |
| ExtensionViewVisibleInsideSidePanel) { |
| ExtensionTestMessageListener default_path_listener("default_path"); |
| |
| scoped_refptr<const extensions::Extension> extension = LoadExtension( |
| test_data_dir_.AppendASCII("api_test/side_panel/simple_default")); |
| ASSERT_TRUE(extension); |
| |
| SidePanelEntry::Key extension_key = GetKey(extension->id()); |
| SidePanelEntry* extension_entry = |
| global_registry()->GetEntryForKey(extension_key); |
| ASSERT_TRUE(extension_entry); |
| |
| // The key for the extension should be registered, but the side panel isn't |
| // shown yet. |
| EXPECT_FALSE(side_panel_coordinator()->IsSidePanelShowing()); |
| |
| side_panel_coordinator()->Show(extension_key); |
| |
| // Wait until the view in the side panel is active by listening for the |
| // message sent from the view's script. |
| ASSERT_TRUE(default_path_listener.WaitUntilSatisfied()); |
| EXPECT_TRUE(side_panel_coordinator()->IsSidePanelShowing()); |
| |
| // Reset the `default_path_listener`. |
| default_path_listener.Reset(); |
| |
| // Close and reopen the side panel. The extension's view should be recreated. |
| side_panel_coordinator()->Close(); |
| EXPECT_FALSE(side_panel_coordinator()->IsSidePanelShowing()); |
| side_panel_coordinator()->Show(extension_key); |
| |
| ASSERT_TRUE(default_path_listener.WaitUntilSatisfied()); |
| EXPECT_TRUE(side_panel_coordinator()->IsSidePanelShowing()); |
| |
| // Now unload the extension. The key should no longer exist in the global |
| // registry and the side panel should close as a result. |
| UnloadExtension(extension->id()); |
| EXPECT_FALSE(global_registry()->GetEntryForKey(extension_key)); |
| EXPECT_FALSE(side_panel_coordinator()->IsSidePanelShowing()); |
| } |
| |
| // Test that an extension's SidePanelEntry is registered for new browser |
| // windows. |
| IN_PROC_BROWSER_TEST_F(ExtensionSidePanelBrowserTest, MultipleBrowsers) { |
| // Load an extension and verify that its SidePanelEntry is registered. |
| scoped_refptr<const extensions::Extension> extension = LoadExtension( |
| test_data_dir_.AppendASCII("api_test/side_panel/simple_default")); |
| ASSERT_TRUE(extension); |
| SidePanelEntry::Key extension_key = GetKey(extension->id()); |
| |
| EXPECT_TRUE(global_registry()->GetEntryForKey(extension_key)); |
| |
| // Open a new browser window. The extension's SidePanelEntry should also be |
| // registered for the new window's global SidePanelRegistry. |
| Browser* second_browser = CreateBrowser(browser()->profile()); |
| SidePanelRegistry* second_global_registry = |
| SidePanelCoordinator::GetGlobalSidePanelRegistry(second_browser); |
| EXPECT_TRUE(second_global_registry->GetEntryForKey(extension_key)); |
| } |
| |
| // Test that if the side panel is closed while the extension's side panel view |
| // is still loading, there will not be a crash. Regression for |
| // crbug.com/1403168. |
| IN_PROC_BROWSER_TEST_F(ExtensionSidePanelBrowserTest, SidePanelQuicklyClosed) { |
| // Load an extension and verify that its SidePanelEntry is registered. |
| scoped_refptr<const extensions::Extension> extension = LoadExtension( |
| test_data_dir_.AppendASCII("api_test/side_panel/simple_default")); |
| ASSERT_TRUE(extension); |
| SidePanelEntry::Key extension_key = GetKey(extension->id()); |
| |
| EXPECT_TRUE(global_registry()->GetEntryForKey(extension_key)); |
| EXPECT_FALSE(side_panel_coordinator()->IsSidePanelShowing()); |
| |
| // Quickly open the side panel showing the extension's side panel entry then |
| // close it. The test should not cause any crashes after it is complete. |
| side_panel_coordinator()->Show(extension_key); |
| EXPECT_TRUE(side_panel_coordinator()->IsSidePanelShowing()); |
| side_panel_coordinator()->Close(); |
| } |
| |
| // Test that the extension's side panel entry shows the extension's icon. |
| IN_PROC_BROWSER_TEST_F(ExtensionSidePanelBrowserTest, EntryShowsExtensionIcon) { |
| // Load an extension and verify that its SidePanelEntry is registered. |
| scoped_refptr<const extensions::Extension> extension = LoadExtension( |
| test_data_dir_.AppendASCII("api_test/side_panel/simple_default")); |
| ASSERT_TRUE(extension); |
| |
| SidePanelEntry::Key extension_key = GetKey(extension->id()); |
| SidePanelEntry* extension_entry = |
| global_registry()->GetEntryForKey(extension_key); |
| |
| // Check that the entry's icon bitmap is identical to the bitmap of the |
| // extension's icon scaled down to `extension_misc::EXTENSION_ICON_BITTY`. |
| SkBitmap expected_icon_bitmap = TestImageLoader::LoadAndGetExtensionBitmap( |
| extension.get(), "icon.png", extension_misc::EXTENSION_ICON_BITTY); |
| const SkBitmap& actual_icon_bitmap = |
| *extension_entry->icon().GetImage().ToSkBitmap(); |
| EXPECT_TRUE( |
| gfx::test::AreBitmapsEqual(expected_icon_bitmap, actual_icon_bitmap)); |
| } |
| |
| // Test that sidePanel.setOptions() will register and deregister the extension's |
| // SidePanelEntry when called with enabled: true/false. |
| IN_PROC_BROWSER_TEST_F(ExtensionSidePanelBrowserTest, SetOptions_Enabled) { |
| ExtensionTestMessageListener panel_2_listener("panel_2"); |
| |
| // Load an extension without a default side panel path. |
| scoped_refptr<const extensions::Extension> extension = LoadExtension( |
| test_data_dir_.AppendASCII("api_test/side_panel/setoptions")); |
| ASSERT_TRUE(extension); |
| SidePanelEntry::Key extension_key = GetKey(extension->id()); |
| EXPECT_FALSE(global_registry()->GetEntryForKey(extension_key)); |
| |
| { |
| // Call setOptions({enabled: true}) and wait for the extension's |
| // SidePanelEntry to be registered. |
| ExtensionSidePanelRegistryWaiter waiter(global_registry(), extension->id()); |
| RunSetOptions(*extension, /*tab_id=*/absl::nullopt, "panel_1.html", |
| /*enabled=*/true); |
| waiter.WaitForRegistration(); |
| } |
| |
| EXPECT_TRUE(global_registry()->GetEntryForKey(extension_key)); |
| |
| { |
| // Call setOptions({enabled: false}) and wait for the extension's |
| // SidePanelEntry to be deregistered. |
| ExtensionSidePanelRegistryWaiter waiter(global_registry(), extension->id()); |
| RunSetOptions(*extension, /*tab_id=*/absl::nullopt, /*path=*/absl::nullopt, |
| /*enabled=*/false); |
| waiter.WaitForDeregistration(); |
| } |
| |
| EXPECT_FALSE(global_registry()->GetEntryForKey(extension_key)); |
| |
| { |
| // Sanity check that re-enabling the side panel will register the entry |
| // again and a view with the new side panel path can be shown. |
| ExtensionSidePanelRegistryWaiter waiter(global_registry(), extension->id()); |
| RunSetOptions(*extension, /*tab_id=*/absl::nullopt, "panel_2.html", |
| /*enabled=*/true); |
| waiter.WaitForRegistration(); |
| } |
| |
| EXPECT_TRUE(global_registry()->GetEntryForKey(extension_key)); |
| side_panel_coordinator()->Show(extension_key); |
| |
| // Wait until the view in the side panel is active by listening for the |
| // message sent from the view's script. |
| ASSERT_TRUE(panel_2_listener.WaitUntilSatisfied()); |
| EXPECT_TRUE(side_panel_coordinator()->IsSidePanelShowing()); |
| |
| { |
| // Calling setOptions({enabled: false}) when the extension's SidePanelEntry |
| // is shown should close the side panel. |
| ExtensionSidePanelRegistryWaiter waiter(global_registry(), extension->id()); |
| RunSetOptions(*extension, /*tab_id=*/absl::nullopt, /*path=*/absl::nullopt, |
| /*enabled=*/false); |
| waiter.WaitForDeregistration(); |
| } |
| |
| EXPECT_FALSE(global_registry()->GetEntryForKey(extension_key)); |
| EXPECT_FALSE(side_panel_coordinator()->IsSidePanelShowing()); |
| } |
| |
| // Test that sidePanel.setOptions() will change what is shown in the extension's |
| // SidePanelEntry's view when called with different paths. |
| IN_PROC_BROWSER_TEST_F(ExtensionSidePanelBrowserTest, SetOptions_Path) { |
| ExtensionTestMessageListener default_path_listener("default_path"); |
| ExtensionTestMessageListener panel_1_listener("panel_1"); |
| |
| scoped_refptr<const extensions::Extension> extension = LoadExtension( |
| test_data_dir_.AppendASCII("api_test/side_panel/simple_default")); |
| ASSERT_TRUE(extension); |
| auto* extension_coordinator = |
| extensions::ExtensionSidePanelManager::GetOrCreateForBrowser(browser()) |
| ->GetExtensionCoordinatorForTesting(extension->id()); |
| |
| SidePanelEntry::Key extension_key = GetKey(extension->id()); |
| EXPECT_TRUE(global_registry()->GetEntryForKey(extension_key)); |
| |
| // Check that the extension's side panel view shows the most recently set |
| // path. |
| RunSetOptions(*extension, /*tab_id=*/absl::nullopt, "panel_1.html", |
| /*enabled=*/true); |
| side_panel_coordinator()->Show(extension_key); |
| ASSERT_TRUE(panel_1_listener.WaitUntilSatisfied()); |
| EXPECT_FALSE(default_path_listener.was_satisfied()); |
| EXPECT_TRUE(side_panel_coordinator()->IsSidePanelShowing()); |
| |
| // Check that changing the path while the view is active will cause the view |
| // to navigate to the new path. |
| RunSetOptions(*extension, /*tab_id=*/absl::nullopt, "default_path.html", |
| /*enabled=*/true); |
| ASSERT_TRUE(default_path_listener.WaitUntilSatisfied()); |
| EXPECT_TRUE(side_panel_coordinator()->IsSidePanelShowing()); |
| |
| // Switch to the reading list in the side panel and check that the extension |
| // view is cached (i.e. the view exists but is not shown, and its web contents |
| // still exists). |
| ShowEntryAndWait(SidePanelEntry::Key(SidePanelEntry::Id::kReadingList)); |
| |
| EXPECT_TRUE(global_registry()->GetEntryForKey(extension_key)->CachedView()); |
| |
| panel_1_listener.Reset(); |
| content::WebContentsDestroyedWatcher destroyed_watcher( |
| extension_coordinator->GetHostWebContentsForTesting()); |
| |
| // Test calling setOptions with a different path when the extension's view is |
| // cached. The cached view should then be invalidated and its web contents are |
| // destroyed. |
| RunSetOptions(*extension, /*tab_id=*/absl::nullopt, "panel_1.html", |
| /*enabled=*/true); |
| destroyed_watcher.Wait(); |
| |
| // When the extension's entry is shown again, the view with the updated path |
| // should be active. |
| side_panel_coordinator()->Show(extension_key); |
| ASSERT_TRUE(panel_1_listener.WaitUntilSatisfied()); |
| } |
| |
| // Test that calling window.close() from an extension side panel deletes the |
| // panel's web contents and closes the extension's side panel if it's also |
| // shown. |
| IN_PROC_BROWSER_TEST_F(ExtensionSidePanelBrowserTest, WindowCloseCalled) { |
| // Install an extension and show its side panel. |
| scoped_refptr<const extensions::Extension> extension = LoadExtension( |
| test_data_dir_.AppendASCII("api_test/side_panel/simple_default")); |
| ASSERT_TRUE(extension); |
| SidePanelEntry::Key extension_key = GetKey(extension->id()); |
| EXPECT_TRUE(global_registry()->GetEntryForKey(extension_key)); |
| |
| { |
| ExtensionTestMessageListener default_path_listener("default_path"); |
| side_panel_coordinator()->Show(extension_key); |
| ASSERT_TRUE(default_path_listener.WaitUntilSatisfied()); |
| EXPECT_TRUE(side_panel_coordinator()->IsSidePanelShowing()); |
| } |
| |
| auto* extension_coordinator = |
| extensions::ExtensionSidePanelManager::GetOrCreateForBrowser(browser()) |
| ->GetExtensionCoordinatorForTesting(extension->id()); |
| |
| // Call window.close() from the extension's side panel page and wait for the |
| // web contents to be destroyed. |
| { |
| content::WebContentsDestroyedWatcher destroyed_watcher( |
| extension_coordinator->GetHostWebContentsForTesting()); |
| ASSERT_TRUE(content::ExecuteScript( |
| extension_coordinator->GetHostWebContentsForTesting(), |
| "window.close();")); |
| destroyed_watcher.Wait(); |
| } |
| |
| // The side panel should now be closed. |
| EXPECT_FALSE(side_panel_coordinator()->IsSidePanelShowing()); |
| |
| // Show the extension's side panel again. |
| ExtensionTestMessageListener default_path_listener("default_path"); |
| side_panel_coordinator()->Show(extension_key); |
| ASSERT_TRUE(default_path_listener.WaitUntilSatisfied()); |
| EXPECT_TRUE(side_panel_coordinator()->IsSidePanelShowing()); |
| |
| // Show another side panel type so the extension's panel's view gets cached. |
| ShowEntryAndWait(SidePanelEntry::Key(SidePanelEntry::Id::kReadingList)); |
| EXPECT_TRUE(global_registry()->GetEntryForKey(extension_key)->CachedView()); |
| |
| // Calling window.close() from within the panel should invalidate the cached |
| // view when the extension panel is not shown. |
| content::WebContentsDestroyedWatcher destroyed_watcher( |
| extension_coordinator->GetHostWebContentsForTesting()); |
| ASSERT_TRUE(content::ExecuteScript( |
| extension_coordinator->GetHostWebContentsForTesting(), |
| "window.close();")); |
| destroyed_watcher.Wait(); |
| |
| // The side panel should be open because the reading list entry is still |
| // shown. |
| EXPECT_TRUE(side_panel_coordinator()->IsSidePanelShowing()); |
| } |
| |
| // Test that calling window.close() from an extension's side panel deletes the |
| // panel's web contents and closes the extension's side panel if it's also |
| // shown. |
| IN_PROC_BROWSER_TEST_F(ExtensionSidePanelBrowserTest, |
| WindowCloseCalledFromTabSpecificPanel) { |
| scoped_refptr<const extensions::Extension> extension = LoadExtension( |
| test_data_dir_.AppendASCII("api_test/side_panel/setoptions")); |
| ASSERT_TRUE(extension); |
| |
| content::WebContents* active_web_contents = |
| browser()->tab_strip_model()->GetActiveWebContents(); |
| |
| SidePanelEntry::Key extension_key = GetKey(extension->id()); |
| // Call setOptions({enabled: true}) with a tab ID and new path, and wait for |
| // the extension's SidePanelEntry to be registered. |
| ExtensionSidePanelRegistryWaiter waiter( |
| SidePanelRegistry::Get(active_web_contents), extension->id()); |
| RunSetOptions(*extension, GetCurrentTabId(), "panel_2.html", |
| /*enabled=*/true); |
| waiter.WaitForRegistration(); |
| |
| ExtensionTestMessageListener panel_2_listener("panel_2"); |
| side_panel_coordinator()->Show(extension_key); |
| |
| // Wait until the view in the side panel is active by listening for the |
| // message sent from the view's script. |
| ASSERT_TRUE(panel_2_listener.WaitUntilSatisfied()); |
| EXPECT_TRUE(side_panel_coordinator()->IsSidePanelShowing()); |
| |
| auto* extension_coordinator = |
| extensions::ExtensionSidePanelManager::GetOrCreateForWebContents( |
| browser()->profile(), active_web_contents) |
| ->GetExtensionCoordinatorForTesting(extension->id()); |
| content::WebContentsDestroyedWatcher destroyed_watcher( |
| extension_coordinator->GetHostWebContentsForTesting()); |
| ASSERT_TRUE(content::ExecuteScript( |
| extension_coordinator->GetHostWebContentsForTesting(), |
| "window.close();")); |
| destroyed_watcher.Wait(); |
| } |
| |
| // Test that calling window.close() from an extension side panel when it is |
| // shown closes the side panel even if another entry is loading and will be |
| // shown. |
| IN_PROC_BROWSER_TEST_F(ExtensionSidePanelBrowserTest, |
| WindowCloseCalledWhenLoading) { |
| // Install an extension and show its side panel. |
| scoped_refptr<const extensions::Extension> extension = LoadExtension( |
| test_data_dir_.AppendASCII("api_test/side_panel/simple_default")); |
| ASSERT_TRUE(extension); |
| SidePanelEntry::Key extension_key = GetKey(extension->id()); |
| EXPECT_TRUE(global_registry()->GetEntryForKey(extension_key)); |
| |
| { |
| ExtensionTestMessageListener default_path_listener("default_path"); |
| side_panel_coordinator()->Show(extension_key); |
| ASSERT_TRUE(default_path_listener.WaitUntilSatisfied()); |
| EXPECT_TRUE(side_panel_coordinator()->IsSidePanelShowing()); |
| } |
| |
| auto* extension_coordinator = |
| extensions::ExtensionSidePanelManager::GetOrCreateForBrowser(browser()) |
| ->GetExtensionCoordinatorForTesting(extension->id()); |
| |
| // Start showing another entry and call window.close() from the extension's |
| // side panel page while the other entry is still loading but not shown. The |
| // extension's side panel web content should still be destroyed and the side |
| // panel will close. |
| { |
| side_panel_coordinator()->Show(SidePanelEntry::Id::kReadingList); |
| |
| content::WebContentsDestroyedWatcher destroyed_watcher( |
| extension_coordinator->GetHostWebContentsForTesting()); |
| ASSERT_TRUE(content::ExecuteScript( |
| extension_coordinator->GetHostWebContentsForTesting(), |
| "window.close();")); |
| destroyed_watcher.Wait(); |
| } |
| |
| EXPECT_FALSE(side_panel_coordinator()->IsSidePanelShowing()); |
| } |
| |
| // Test that calling sidePanel.setOptions({enabled: false}) for a specific tab |
| // will hide the extension's global side panel for that tab. |
| IN_PROC_BROWSER_TEST_F(ExtensionSidePanelBrowserTest, HideGlobalPanelForTab) { |
| scoped_refptr<const extensions::Extension> extension = LoadExtension( |
| test_data_dir_.AppendASCII("api_test/side_panel/simple_default")); |
| ASSERT_TRUE(extension); |
| |
| SidePanelEntry::Key extension_key = GetKey(extension->id()); |
| EXPECT_TRUE(global_registry()->GetEntryForKey(extension_key)); |
| |
| // Show the extension's side panel and set a global variable to change the |
| // state of the side panel's page. |
| ExtensionTestMessageListener default_path_listener("default_path"); |
| side_panel_coordinator()->Show(extension_key); |
| ASSERT_TRUE(default_path_listener.WaitUntilSatisfied()); |
| EXPECT_TRUE(side_panel_coordinator()->IsSidePanelShowing()); |
| |
| SetGlobalVariableInExtensionSidePanel( |
| extension->id(), /*web_contents=*/nullptr, "altered_state"); |
| EXPECT_EQ("altered_state", GetGlobalVariableInExtensionSidePanel( |
| extension->id(), /*web_contents=*/nullptr)); |
| |
| // Disable the extension's side panel for the current tab. |
| DisableForCurrentTab(*extension); |
| |
| // Calling sidePanel.setOptions({enabled: true}) for the current tab should |
| // re-register the entry. |
| { |
| ExtensionSidePanelRegistryWaiter waiter(global_registry(), extension->id()); |
| RunSetOptions(*extension, GetCurrentTabId(), /*path=*/absl::nullopt, |
| /*enabled=*/true); |
| waiter.WaitForRegistration(); |
| EXPECT_TRUE(global_registry()->GetEntryForKey(extension_key)); |
| EXPECT_FALSE(side_panel_coordinator()->IsSidePanelShowing()); |
| } |
| |
| // Show the side panel entry and check its state to verify that it's the same |
| // page as before. |
| ShowEntryAndWait(extension_key); |
| EXPECT_EQ("altered_state", GetGlobalVariableInExtensionSidePanel( |
| extension->id(), /*web_contents=*/nullptr)); |
| |
| // Disable the extension's side panel for the current tab again. |
| DisableForCurrentTab(*extension); |
| |
| // Open a new tab and navigate to it. The extension's side panel should be |
| // available again since it's not disabled for the new tab. |
| { |
| ExtensionSidePanelRegistryWaiter waiter(global_registry(), extension->id()); |
| OpenNewTab(); |
| ASSERT_TRUE(browser()->tab_strip_model()->IsTabSelected(1)); |
| |
| waiter.WaitForRegistration(); |
| EXPECT_TRUE(global_registry()->GetEntryForKey(extension_key)); |
| EXPECT_FALSE(side_panel_coordinator()->IsSidePanelShowing()); |
| } |
| |
| // Show the side panel entry and check its state to verify that it's the same |
| // page as before. |
| ShowEntryAndWait(extension_key); |
| EXPECT_EQ("altered_state", GetGlobalVariableInExtensionSidePanel( |
| extension->id(), /*web_contents=*/nullptr)); |
| |
| // Go back to the first tab where the side panel is disabled and verify the |
| // extension's side panel is no longer there. |
| { |
| ExtensionSidePanelRegistryWaiter waiter(global_registry(), extension->id()); |
| browser()->tab_strip_model()->ActivateTabAt(0); |
| waiter.WaitForDeregistration(); |
| EXPECT_FALSE(global_registry()->GetEntryForKey(extension_key)); |
| EXPECT_FALSE(side_panel_coordinator()->IsSidePanelShowing()); |
| } |
| } |
| |
| // Test that the saved view state for the hidden global extension side panel is |
| // invalidated if setOptions({enabled: false}) is called without a tab ID. |
| IN_PROC_BROWSER_TEST_F(ExtensionSidePanelBrowserTest, |
| DisableGlobalPanelWhileHidden) { |
| scoped_refptr<const extensions::Extension> extension = LoadExtension( |
| test_data_dir_.AppendASCII("api_test/side_panel/simple_default")); |
| ASSERT_TRUE(extension); |
| auto* extension_coordinator = |
| extensions::ExtensionSidePanelManager::GetOrCreateForBrowser(browser()) |
| ->GetExtensionCoordinatorForTesting(extension->id()); |
| |
| SidePanelEntry::Key extension_key = GetKey(extension->id()); |
| EXPECT_TRUE(global_registry()->GetEntryForKey(extension_key)); |
| |
| // Show the extension's side panel. |
| ExtensionTestMessageListener default_path_listener("default_path"); |
| side_panel_coordinator()->Show(extension_key); |
| ASSERT_TRUE(default_path_listener.WaitUntilSatisfied()); |
| EXPECT_TRUE(side_panel_coordinator()->IsSidePanelShowing()); |
| |
| // Disable the extension's side panel for the current tab. |
| { |
| ExtensionSidePanelRegistryWaiter waiter(global_registry(), extension->id()); |
| RunSetOptions(*extension, GetCurrentTabId(), /*path=*/absl::nullopt, |
| /*enabled=*/false); |
| waiter.WaitForDeregistration(); |
| EXPECT_FALSE(global_registry()->GetEntryForKey(extension_key)); |
| EXPECT_FALSE(side_panel_coordinator()->IsSidePanelShowing()); |
| } |
| |
| // There should be web contents from the saved view. |
| ASSERT_TRUE(extension_coordinator->GetHostWebContentsForTesting()); |
| content::WebContentsDestroyedWatcher destroyed_watcher( |
| extension_coordinator->GetHostWebContentsForTesting()); |
| |
| // Calling setOptions({enabled: false}) for all tabs should destroy the |
| // contents. |
| RunSetOptions(*extension, /*tab_id=*/absl::nullopt, /*path=*/absl::nullopt, |
| /*enabled=*/false); |
| destroyed_watcher.Wait(); |
| |
| // Sanity check that calling setOptions({enabled: true}) for all tabs while on |
| // a tab where the panel is disabled should be a no-op. |
| RunSetOptions(*extension, /*tab_id=*/absl::nullopt, "default_path.html", |
| /*enabled=*/true); |
| EXPECT_FALSE(global_registry()->GetEntryForKey(extension_key)); |
| |
| // Open a new tab and navigate to it. The extension's side panel should be |
| // available again since it's not disabled for the new tab. |
| { |
| ExtensionSidePanelRegistryWaiter waiter(global_registry(), extension->id()); |
| OpenNewTab(); |
| ASSERT_TRUE(browser()->tab_strip_model()->IsTabSelected(1)); |
| |
| waiter.WaitForRegistration(); |
| EXPECT_TRUE(global_registry()->GetEntryForKey(extension_key)); |
| EXPECT_FALSE(side_panel_coordinator()->IsSidePanelShowing()); |
| } |
| } |
| |
| // Test that when the extension's side panel is shown, switching from a tab |
| // where the panel is enabled to one where it's disabled then back to the first |
| // tab will re-register the entry but not show it. This behavior is a little |
| // weird, but trying to have it reopen causes far more complexity than is |
| // worthwhile. |
| IN_PROC_BROWSER_TEST_F(ExtensionSidePanelBrowserTest, ReEnabledPanelNotShown) { |
| // Open a second tab and switch back to the first tab. |
| OpenNewTab(); |
| ASSERT_TRUE(browser()->tab_strip_model()->IsTabSelected(1)); |
| |
| int second_tab_id = GetCurrentTabId(); |
| browser()->tab_strip_model()->ActivateTabAt(0); |
| |
| scoped_refptr<const extensions::Extension> extension = LoadExtension( |
| test_data_dir_.AppendASCII("api_test/side_panel/simple_default")); |
| ASSERT_TRUE(extension); |
| |
| SidePanelEntry::Key extension_key = GetKey(extension->id()); |
| EXPECT_TRUE(global_registry()->GetEntryForKey(extension_key)); |
| |
| // Show the extension's side panel. |
| ExtensionTestMessageListener default_path_listener("default_path"); |
| side_panel_coordinator()->Show(extension_key); |
| ASSERT_TRUE(default_path_listener.WaitUntilSatisfied()); |
| EXPECT_TRUE(side_panel_coordinator()->IsSidePanelShowing()); |
| |
| // Disable the extension's side panel for the second tab, which shouldn't do |
| // anything here since we're on the first tab. |
| RunSetOptions(*extension, second_tab_id, /*path=*/absl::nullopt, |
| /*enabled=*/false); |
| EXPECT_TRUE(side_panel_coordinator()->IsSidePanelShowing()); |
| |
| // Switch to the second tab and verify that the extension's entry is no longer |
| // registered. |
| { |
| ExtensionSidePanelRegistryWaiter waiter(global_registry(), extension->id()); |
| browser()->tab_strip_model()->ActivateTabAt(1); |
| waiter.WaitForDeregistration(); |
| EXPECT_FALSE(global_registry()->GetEntryForKey(extension_key)); |
| EXPECT_FALSE(side_panel_coordinator()->IsSidePanelShowing()); |
| } |
| |
| // Switch back to the first tab and verify that the extension's entry is |
| // registered again but is not showing. |
| { |
| ExtensionSidePanelRegistryWaiter waiter(global_registry(), extension->id()); |
| browser()->tab_strip_model()->ActivateTabAt(0); |
| waiter.WaitForRegistration(); |
| EXPECT_TRUE(global_registry()->GetEntryForKey(extension_key)); |
| EXPECT_FALSE(side_panel_coordinator()->IsSidePanelShowing()); |
| } |
| } |
| |
| // Test that calling setOptions on the current tab while the global entry is |
| // showing should show the new entry for the current tab. |
| IN_PROC_BROWSER_TEST_F(ExtensionSidePanelBrowserTest, |
| TabSpecificPanelShownOnOptionsUpdate) { |
| scoped_refptr<const extensions::Extension> extension = LoadExtension( |
| test_data_dir_.AppendASCII("api_test/side_panel/simple_default")); |
| ASSERT_TRUE(extension); |
| |
| SidePanelEntry::Key extension_key = GetKey(extension->id()); |
| EXPECT_TRUE(global_registry()->GetEntryForKey(extension_key)); |
| |
| ShowEntryAndWait(extension_key); |
| |
| { |
| ExtensionTestMessageListener panel_1_listener("panel_1"); |
| // Call setOptions({enabled: true}) with a tab ID and new path, and wait for |
| // the extension's SidePanelEntry to be registered. The extension's side |
| // panel should then show the new entry for the first tab which displays |
| // `panel_1.html`. |
| ExtensionSidePanelRegistryWaiter waiter( |
| SidePanelRegistry::Get( |
| browser()->tab_strip_model()->GetActiveWebContents()), |
| extension->id()); |
| RunSetOptions(*extension, GetCurrentTabId(), "panel_1.html", |
| /*enabled=*/true); |
| waiter.WaitForRegistration(); |
| ASSERT_TRUE(panel_1_listener.WaitUntilSatisfied()); |
| EXPECT_TRUE(side_panel_coordinator()->IsSidePanelShowing()); |
| |
| EXPECT_EQ(GetCurrentTabRegistry()->active_entry().value(), |
| GetCurrentTabRegistry()->GetEntryForKey(extension_key)); |
| } |
| |
| // Sanity check that calling setOptions() for the current tab with a different |
| // tab will change the view shown in the side panel's contextual entry. |
| ExtensionTestMessageListener default_path_listener("default_path"); |
| RunSetOptions(*extension, GetCurrentTabId(), "default_path.html", |
| /*enabled=*/true); |
| ASSERT_TRUE(default_path_listener.WaitUntilSatisfied()); |
| EXPECT_TRUE(side_panel_coordinator()->IsSidePanelShowing()); |
| } |
| |
| // Test that when switching tabs, the new tab shows the extension's contextual |
| // entry if one exists, or the global entry if there is no tab-specific entry |
| // specified for that tab. |
| IN_PROC_BROWSER_TEST_F(ExtensionSidePanelBrowserTest, |
| ShowTabSpecificPaneOnTabSwitch) { |
| // Open a second tab and switch back to the first tab. |
| OpenNewTab(); |
| ASSERT_TRUE(browser()->tab_strip_model()->IsTabSelected(1)); |
| |
| int second_tab_id = GetCurrentTabId(); |
| browser()->tab_strip_model()->ActivateTabAt(0); |
| |
| scoped_refptr<const extensions::Extension> extension = LoadExtension( |
| test_data_dir_.AppendASCII("api_test/side_panel/simple_default")); |
| ASSERT_TRUE(extension); |
| |
| SidePanelEntry::Key extension_key = GetKey(extension->id()); |
| EXPECT_TRUE(global_registry()->GetEntryForKey(extension_key)); |
| |
| // Call setOptions for the second tab. |
| ExtensionSidePanelRegistryWaiter waiter( |
| SidePanelRegistry::Get(browser()->tab_strip_model()->GetWebContentsAt(1)), |
| extension->id()); |
| RunSetOptions(*extension, second_tab_id, "panel_1.html", |
| /*enabled=*/true); |
| waiter.WaitForRegistration(); |
| |
| // Show the extension's side panel on the first tab. |
| ShowEntryAndWait(extension_key); |
| |
| // Switch to the second tab: this should cause the extension's entry for that |
| // tab to be shown. |
| ExtensionTestMessageListener panel_1_listener("panel_1"); |
| browser()->tab_strip_model()->ActivateTabAt(1); |
| ASSERT_TRUE(panel_1_listener.WaitUntilSatisfied()); |
| EXPECT_TRUE(side_panel_coordinator()->IsSidePanelShowing()); |
| |
| // Switch back to the first tab: the global entry should be shown. |
| TestSidePanelEntryWaiter entry_shown_waiter( |
| global_registry()->GetEntryForKey(extension_key)); |
| browser()->tab_strip_model()->ActivateTabAt(0); |
| entry_shown_waiter.WaitForEntryShown(); |
| } |
| |
| // Test that the view state between the extension's global side panel entry and |
| // all of its tab-specific side panel entries are independent of each other. |
| IN_PROC_BROWSER_TEST_F(ExtensionSidePanelBrowserTest, |
| TabSpecificPanelsOwnViewState) { |
| // Open a second tab and switch back to the first tab. |
| OpenNewTab(); |
| ASSERT_TRUE(browser()->tab_strip_model()->IsTabSelected(1)); |
| |
| int second_tab_id = GetCurrentTabId(); |
| browser()->tab_strip_model()->ActivateTabAt(0); |
| int first_tab_id = GetCurrentTabId(); |
| |
| scoped_refptr<const extensions::Extension> extension = LoadExtension( |
| test_data_dir_.AppendASCII("api_test/side_panel/simple_default")); |
| ASSERT_TRUE(extension); |
| |
| // Set a local variable's value to "GLOBAL" for the extension's global side |
| // panel's WebContents. |
| SidePanelEntry::Key extension_key = GetKey(extension->id()); |
| |
| { |
| ExtensionTestMessageListener default_path_listener("default_path"); |
| side_panel_coordinator()->Show(extension_key); |
| ASSERT_TRUE(default_path_listener.WaitUntilSatisfied()); |
| EXPECT_TRUE(side_panel_coordinator()->IsSidePanelShowing()); |
| } |
| |
| EXPECT_EQ("undefined", GetGlobalVariableInExtensionSidePanel( |
| extension->id(), /*web_contents=*/nullptr)); |
| SetGlobalVariableInExtensionSidePanel(extension->id(), |
| /*web_contents=*/nullptr, "GLOBAL"); |
| EXPECT_EQ("GLOBAL", GetGlobalVariableInExtensionSidePanel( |
| extension->id(), /*web_contents=*/nullptr)); |
| |
| auto* first_tab_contents = browser()->tab_strip_model()->GetWebContentsAt(0); |
| auto* second_tab_contents = browser()->tab_strip_model()->GetWebContentsAt(1); |
| |
| { |
| // Set a local variable's value to "TAB 1" for the extension's side panel's |
| // view on the first tab. |
| ExtensionTestMessageListener default_path_listener("default_path"); |
| |
| SidePanelRegistry* first_tab_registry = |
| SidePanelRegistry::Get(first_tab_contents); |
| ExtensionSidePanelRegistryWaiter waiter(first_tab_registry, |
| extension->id()); |
| RunSetOptions(*extension, first_tab_id, "default_path.html", |
| /*enabled=*/true); |
| waiter.WaitForRegistration(); |
| |
| ASSERT_TRUE(default_path_listener.WaitUntilSatisfied()); |
| |
| // Despite sharing the same path, the instance of default_path.html that is |
| // specifically registered for `first_tab_id` is a different entry/view than |
| // default_path.html registered for all tabs. |
| EXPECT_EQ("undefined", GetGlobalVariableInExtensionSidePanel( |
| extension->id(), first_tab_contents)); |
| SetGlobalVariableInExtensionSidePanel(extension->id(), first_tab_contents, |
| "TAB 1"); |
| } |
| |
| { |
| // Set a local variable's value to "TAB 2" for the extension's side panel's |
| // view on the second tab. |
| SidePanelRegistry* second_tab_registry = |
| SidePanelRegistry::Get(second_tab_contents); |
| ExtensionSidePanelRegistryWaiter waiter(second_tab_registry, |
| extension->id()); |
| RunSetOptions(*extension, second_tab_id, "default_path.html", |
| /*enabled=*/true); |
| waiter.WaitForRegistration(); |
| |
| TestSidePanelEntryWaiter entry_shown_waiter( |
| second_tab_registry->GetEntryForKey(extension_key)); |
| browser()->tab_strip_model()->ActivateTabAt(1); |
| entry_shown_waiter.WaitForEntryShown(); |
| |
| EXPECT_EQ("undefined", GetGlobalVariableInExtensionSidePanel( |
| extension->id(), second_tab_contents)); |
| SetGlobalVariableInExtensionSidePanel(extension->id(), second_tab_contents, |
| "TAB 2"); |
| } |
| |
| // Check that the global variable's value for the extension's global and |
| // contextual (first tab) entries are not affected. |
| EXPECT_EQ("GLOBAL", GetGlobalVariableInExtensionSidePanel( |
| extension->id(), /*web_contents=*/nullptr)); |
| EXPECT_EQ("TAB 1", GetGlobalVariableInExtensionSidePanel(extension->id(), |
| first_tab_contents)); |
| } |
| |
| // Test that unloading an extension after its tab-specific side panel is moved |
| // to another browser does not crash. This tests a rare use case where the |
| // extension's contextual SidePanelEntry is deregistered before its global one, |
| // all while the extension itself is being unloaded. See |
| // ExtensionSidePanelCoordinator::CreateVIew for more details. |
| IN_PROC_BROWSER_TEST_F(ExtensionSidePanelBrowserTest, |
| UnloadExtensionAfterMovingTab) { |
| OpenNewTab(); |
| ASSERT_TRUE(browser()->tab_strip_model()->IsTabSelected(1)); |
| auto* second_tab_contents = browser()->tab_strip_model()->GetWebContentsAt(1); |
| int second_tab_id = GetCurrentTabId(); |
| |
| // Load an extension and verify that its SidePanelEntry is registered. |
| scoped_refptr<const extensions::Extension> extension = LoadExtension( |
| test_data_dir_.AppendASCII("api_test/side_panel/simple_default")); |
| ASSERT_TRUE(extension); |
| SidePanelEntry::Key extension_key = GetKey(extension->id()); |
| EXPECT_TRUE(global_registry()->GetEntryForKey(extension_key)); |
| |
| { |
| // Register a SidePanelEntry for the extension for the second tab. |
| SidePanelRegistry* second_tab_registry = |
| SidePanelRegistry::Get(second_tab_contents); |
| ExtensionSidePanelRegistryWaiter waiter(second_tab_registry, |
| extension->id()); |
| RunSetOptions(*extension, second_tab_id, "panel_1.html", |
| /*enabled=*/true); |
| waiter.WaitForRegistration(); |
| |
| ExtensionTestMessageListener panel_1_listener("panel_1"); |
| side_panel_coordinator()->Show(extension_key); |
| ASSERT_TRUE(panel_1_listener.WaitUntilSatisfied()); |
| } |
| |
| // Detach the second tab from `browser()` |
| std::unique_ptr<content::WebContents> detached_contents = |
| browser()->tab_strip_model()->DetachWebContentsAtForInsertion( |
| /*index=*/1); |
| ASSERT_EQ(second_tab_contents, detached_contents.get()); |
| |
| // Open a new browser window and add `detached_contents` to a new tab. |
| Browser* second_browser = CreateBrowser(browser()->profile()); |
| TabStripModel* target_tab_strip = |
| ExtensionTabUtil::GetEditableTabStripModel(second_browser); |
| target_tab_strip->InsertWebContentsAt( |
| /*index=*/1, std::move(detached_contents), AddTabTypes::ADD_NONE); |
| |
| // Switch to the newly moved tab. |
| ASSERT_EQ(2, second_browser->tab_strip_model()->count()); |
| second_browser->tab_strip_model()->ActivateTabAt(1); |
| EXPECT_TRUE(side_panel_coordinator()->IsSidePanelShowing()); |
| |
| // Unloading the extension at this point should not crash the browser. |
| UnloadExtension(extension->id()); |
| EXPECT_FALSE(side_panel_coordinator()->IsSidePanelShowing()); |
| } |
| |
| // Test that when the openSidePanelOnClick pref is true, clicking the extension |
| // icon will show the extension's entry if it's not shown, or close |
| // the side panel if the extension's entry is shown. |
| IN_PROC_BROWSER_TEST_F(ExtensionSidePanelBrowserTest, |
| ToggleExtensionEntryOnUserAction) { |
| scoped_refptr<const extensions::Extension> extension = LoadExtension( |
| test_data_dir_.AppendASCII("api_test/side_panel/simple_default")); |
| ASSERT_TRUE(extension); |
| |
| // Create a helper that will click the extension's icon from the menu to |
| // trigger an extension action. |
| std::unique_ptr<ExtensionActionTestHelper> action_helper = |
| ExtensionActionTestHelper::Create(browser()); |
| |
| SidePanelEntry::Key extension_key = GetKey(extension->id()); |
| |
| RunSetPanelBehavior(*extension, /*openPanelOnActionClick=*/true); |
| EXPECT_FALSE(side_panel_coordinator()->IsSidePanelShowing()); |
| |
| { |
| ExtensionTestMessageListener default_path_listener("default_path"); |
| // Clicking the icon should show the extension's entry. |
| action_helper->Press(extension->id()); |
| ASSERT_TRUE(default_path_listener.WaitUntilSatisfied()); |
| EXPECT_TRUE(side_panel_coordinator()->IsSidePanelShowing()); |
| } |
| |
| // Switch over to another side panel entry. |
| ShowEntryAndWait(SidePanelEntry::Key(SidePanelEntry::Id::kReadingList)); |
| |
| { |
| TestSidePanelEntryWaiter entry_shown_waiter( |
| global_registry()->GetEntryForKey(extension_key)); |
| // Since the extension's entry is not shown, clicking the icon should show |
| // it. |
| action_helper->Press(extension->id()); |
| entry_shown_waiter.WaitForEntryShown(); |
| EXPECT_TRUE(side_panel_coordinator()->IsSidePanelShowing()); |
| } |
| |
| { |
| TestSidePanelEntryWaiter entry_hidden_waiter( |
| global_registry()->GetEntryForKey(extension_key)); |
| // Clicking the icon when the extension's entry is shown should close the |
| // side panel. |
| action_helper->Press(extension->id()); |
| entry_hidden_waiter.WaitForEntryHidden(); |
| } |
| |
| EXPECT_FALSE(side_panel_coordinator()->IsSidePanelShowing()); |
| } |
| |
| // Test that extension action behavior falls back to defaults if the extension |
| // has no side panel panel for the current tab (global or contextual) or if the |
| // openSidePanelOnClick pref is false. |
| IN_PROC_BROWSER_TEST_F(ExtensionSidePanelBrowserTest, |
| FallbackActionWithoutSidePanel) { |
| scoped_refptr<const extensions::Extension> extension = LoadExtension( |
| test_data_dir_.AppendASCII("api_test/side_panel/with_action_onclick")); |
| ASSERT_TRUE(extension); |
| |
| // Create a helper that will click the extension's icon from the menu to |
| // trigger an extension action. |
| std::unique_ptr<ExtensionActionTestHelper> action_helper = |
| ExtensionActionTestHelper::Create(browser()); |
| |
| SidePanelEntry::Key extension_key = GetKey(extension->id()); |
| |
| RunSetPanelBehavior(*extension, /*openPanelOnActionClick=*/true); |
| EXPECT_FALSE(side_panel_coordinator()->IsSidePanelShowing()); |
| |
| { |
| ExtensionTestMessageListener default_path_listener("default_path"); |
| // Clicking the icon should show the extension's entry. |
| action_helper->Press(extension->id()); |
| ASSERT_TRUE(default_path_listener.WaitUntilSatisfied()); |
| EXPECT_TRUE(side_panel_coordinator()->IsSidePanelShowing()); |
| } |
| |
| // Set the pref to false. |
| RunSetPanelBehavior(*extension, /*openPanelOnActionClick=*/false); |
| |
| { |
| ExtensionTestMessageListener action_clicked_listener("action_clicked"); |
| // Since the pref is false, clicking the icon will fall back to triggering |
| // chrome.action.onClicked, which satisfies `action_clicked_listener`. |
| action_helper->Press(extension->id()); |
| ASSERT_TRUE(action_clicked_listener.WaitUntilSatisfied()); |
| EXPECT_TRUE(side_panel_coordinator()->IsSidePanelShowing()); |
| } |
| |
| // Set the pref to true but disable the extension's side panel for the current |
| // tab. |
| RunSetPanelBehavior(*extension, /*openPanelOnActionClick=*/true); |
| DisableForCurrentTab(*extension); |
| |
| { |
| ExtensionTestMessageListener default_path_listener("default_path"); |
| ExtensionTestMessageListener action_clicked_listener("action_clicked"); |
| // Clicking the icon will fall back to triggering chrome.action.onClicked, |
| action_helper->Press(extension->id()); |
| ASSERT_TRUE(action_clicked_listener.WaitUntilSatisfied()); |
| EXPECT_FALSE(default_path_listener.was_satisfied()); |
| } |
| } |
| |
| // TODO(crbug.com/1378048): Add a test here which requires a browser in |
| // ExtensionViewHost for both global and contextual extension entries. One |
| // example of this is having a link in the page that the user can open in a new |
| // tab. |
| |
| class ExtensionSidePanelDisabledBrowserTest : public ExtensionBrowserTest { |
| public: |
| ExtensionSidePanelDisabledBrowserTest() { |
| feature_list_.InitAndDisableFeature( |
| extensions_features::kExtensionSidePanelIntegration); |
| } |
| |
| protected: |
| SidePanelRegistry* global_registry() { |
| return SidePanelCoordinator::GetGlobalSidePanelRegistry(browser()); |
| } |
| |
| private: |
| base::test::ScopedFeatureList feature_list_; |
| }; |
| |
| // Tests that an extension's SidePanelEntry is not registered if the |
| // `kExtensionSidePanelIntegration` feature flag is not enabled. |
| IN_PROC_BROWSER_TEST_F(ExtensionSidePanelDisabledBrowserTest, |
| NoSidePanelEntry) { |
| // Load an extension and verify that it does not have a registered |
| // SidePanelEntry as the feature is disabled. |
| scoped_refptr<const extensions::Extension> extension = LoadExtension( |
| test_data_dir_.AppendASCII("api_test/side_panel/simple_default")); |
| ASSERT_TRUE(extension); |
| SidePanelEntry::Key extension_key = GetKey(extension->id()); |
| |
| EXPECT_FALSE(global_registry()->GetEntryForKey(extension_key)); |
| } |
| |
| } // namespace |
| } // namespace extensions |