diff options
author | Allan Sandfeld Jensen <allan.jensen@qt.io> | 2022-05-12 15:59:20 +0200 |
---|---|---|
committer | Allan Sandfeld Jensen <allan.jensen@qt.io> | 2022-05-25 06:57:22 +0000 |
commit | f7eaed5286974984ba5f9e3189d8f49d03e99f81 (patch) | |
tree | caed19b2af2024f35449fb0b781d0a25e09d4f8f /chromium/chrome/browser/extensions/api/extension_action | |
parent | 9729c4479fe23554eae6e6dd1f30ff488f470c84 (diff) |
BASELINE: Update Chromium to 100.0.4896.167
Change-Id: I98cbeb5d7543d966ffe04d8cefded0c493a11333
Reviewed-by: Allan Sandfeld Jensen <allan.jensen@qt.io>
Diffstat (limited to 'chromium/chrome/browser/extensions/api/extension_action')
8 files changed, 777 insertions, 64 deletions
diff --git a/chromium/chrome/browser/extensions/api/extension_action/browser_action_apitest.cc b/chromium/chrome/browser/extensions/api/extension_action/browser_action_apitest.cc index 0ecac8acca6..31bd31407f9 100644 --- a/chromium/chrome/browser/extensions/api/extension_action/browser_action_apitest.cc +++ b/chromium/chrome/browser/extensions/api/extension_action/browser_action_apitest.cc @@ -37,8 +37,8 @@ #include "content/public/browser/browser_context.h" #include "content/public/browser/notification_service.h" #include "content/public/browser/overlay_window.h" -#include "content/public/browser/picture_in_picture_window_controller.h" #include "content/public/browser/render_frame_host.h" +#include "content/public/browser/video_picture_in_picture_window_controller.h" #include "content/public/browser/web_contents.h" #include "content/public/test/browser_test.h" #include "content/public/test/browser_test_utils.h" @@ -59,6 +59,7 @@ #include "extensions/test/result_catcher.h" #include "net/test/embedded_test_server/embedded_test_server.h" #include "testing/gmock/include/gmock/gmock.h" +#include "ui/base/layout.h" #include "ui/base/resource/resource_bundle.h" #include "ui/gfx/geometry/rect.h" #include "ui/gfx/geometry/size.h" @@ -186,7 +187,8 @@ class BrowserActionApiTestWithContextType if (expect_failure) { EXPECT_FALSE(catcher.GetNextResult()); - EXPECT_EQ("The source image could not be decoded.", catcher.message()); + EXPECT_THAT(catcher.message(), + testing::EndsWith("The source image could not be decoded.")); return; } @@ -287,7 +289,7 @@ IN_PROC_BROWSER_TEST_F(BrowserActionApiCanvasTest, DynamicBrowserAction) { const Extension* extension = GetSingleLoadedExtension(); ASSERT_TRUE(extension) << message_; -#if defined(OS_MAC) +#if BUILDFLAG(IS_MAC) // We need this on mac so we don't loose 2x representations from browser icon // in transformations gfx::ImageSkia -> NSImage -> gfx::ImageSkia. std::vector<ui::ResourceScaleFactor> supported_scale_factors; @@ -490,8 +492,6 @@ IN_PROC_BROWSER_TEST_F(BrowserActionApiCanvasTest, InvisibleIconBrowserAction) { const std::string histogram_name = "Extensions.DynamicExtensionActionIconWasVisible"; - const std::string new_histogram_name = - "Extensions.DynamicExtensionActionIconWasVisibleRendered"; { base::HistogramTester histogram_tester; std::string result; @@ -504,8 +504,6 @@ IN_PROC_BROWSER_TEST_F(BrowserActionApiCanvasTest, InvisibleIconBrowserAction) { initial_bar_icon, GetBrowserActionsBar()->GetIcon(extension->id()))); EXPECT_THAT(histogram_tester.GetAllSamples(histogram_name), testing::ElementsAre(base::Bucket(0, 1))); - EXPECT_THAT(histogram_tester.GetAllSamples(new_histogram_name), - testing::ElementsAre(base::Bucket(0, 1))); } { @@ -520,8 +518,6 @@ IN_PROC_BROWSER_TEST_F(BrowserActionApiCanvasTest, InvisibleIconBrowserAction) { initial_bar_icon, GetBrowserActionsBar()->GetIcon(extension->id()))); EXPECT_THAT(histogram_tester.GetAllSamples(histogram_name), testing::ElementsAre(base::Bucket(1, 1))); - EXPECT_THAT(histogram_tester.GetAllSamples(new_histogram_name), - testing::ElementsAre(base::Bucket(1, 1))); } } @@ -1019,9 +1015,9 @@ IN_PROC_BROWSER_TEST_F(BrowserActionApiTest, process_manager->GetBackgroundHostForExtension(extension->id()) ->web_contents(); ASSERT_TRUE(web_contents); - content::PictureInPictureWindowController* window_controller = - content::PictureInPictureWindowController::GetOrCreateForWebContents( - web_contents); + content::VideoPictureInPictureWindowController* window_controller = + content::PictureInPictureWindowController:: + GetOrCreateVideoPictureInPictureController(web_contents); ASSERT_TRUE(window_controller->GetWindowForTesting()); EXPECT_FALSE(window_controller->GetWindowForTesting()->IsVisible()); diff --git a/chromium/chrome/browser/extensions/api/extension_action/browser_action_interactive_test.cc b/chromium/chrome/browser/extensions/api/extension_action/browser_action_interactive_test.cc index 095bd7b23cf..cca00675ee2 100644 --- a/chromium/chrome/browser/extensions/api/extension_action/browser_action_interactive_test.cc +++ b/chromium/chrome/browser/extensions/api/extension_action/browser_action_interactive_test.cc @@ -53,7 +53,7 @@ #include "ui/gfx/scrollbar_size.h" #include "ui/views/widget/widget.h" -#if defined(OS_WIN) +#if BUILDFLAG(IS_WIN) #include "ui/views/win/hwnd_util.h" #endif @@ -217,7 +217,7 @@ class BrowserActionInteractiveTest : public ExtensionApiTest { ExtensionHostTestHelper host_helper(profile()); -#if defined(OS_MAC) +#if BUILDFLAG(IS_MAC) // ClickOnView() in an inactive window is not robust on Mac. The click does // not guarantee window activation on trybots. So activate the browser // explicitly, thus causing the bubble to lose focus and dismiss itself. @@ -306,7 +306,7 @@ IN_PROC_BROWSER_TEST_F(BrowserActionInteractiveTest, TestOpenPopupIncognito) { frame_observer.Wait(); // Non-Aura Linux uses a singleton for the popup, so it looks like all windows // have popups if there is any popup open. -#if !((defined(OS_LINUX) || defined(OS_CHROMEOS)) && !defined(USE_AURA)) +#if !((BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS)) && !defined(USE_AURA)) // Starting window does not have a popup. EXPECT_FALSE(ExtensionActionTestHelper::Create(browser())->HasPopup()); #endif @@ -539,7 +539,7 @@ IN_PROC_BROWSER_TEST_F(BrowserActionInteractiveViewsTest, chrome::CloseWindow(browser()); } -#if defined(OS_WIN) +#if BUILDFLAG(IS_WIN) // Forcibly closing a browser HWND with a popup should not cause a crash. IN_PROC_BROWSER_TEST_F(BrowserActionInteractiveTest, DestroyHWNDDoesNotCrash) { OpenPopupViaAPI(false); @@ -563,7 +563,7 @@ IN_PROC_BROWSER_TEST_F(BrowserActionInteractiveTest, DestroyHWNDDoesNotCrash) { EXPECT_EQ(FALSE, ::IsWindow(browser_hwnd)); EXPECT_EQ(FALSE, ::IsWindow(popup_hwnd)); } -#endif // OS_WIN +#endif // BUILDFLAG(IS_WIN) class MainFrameSizeWaiter : public content::WebContentsObserver { public: @@ -592,11 +592,14 @@ class MainFrameSizeWaiter : public content::WebContentsObserver { }; // TODO(crbug.com/1249851): Test crashes on Windows -#if defined(OS_WIN) +#if BUILDFLAG(IS_WIN) #define MAYBE_BrowserActionPopup DISABLED_BrowserActionPopup -#elif defined(OS_LINUX) && defined(THREAD_SANITIZER) +#elif BUILDFLAG(IS_LINUX) && defined(THREAD_SANITIZER) // TODO(crbug.com/1269076): Test is flaky for linux tsan builds #define MAYBE_BrowserActionPopup DISABLED_BrowserActionPopup +#elif BUILDFLAG(IS_MAC) +// TODO(crbug.com/1269076): Test is flaky on Mac as well. +#define MAYBE_BrowserActionPopup DISABLED_BrowserActionPopup #else #define MAYBE_BrowserActionPopup BrowserActionPopup #endif @@ -645,7 +648,7 @@ IN_PROC_BROWSER_TEST_F(BrowserActionInteractiveTest, MAYBE_BrowserActionPopup) { // scrollbars are overlaid, appear on hover and don't increase the height // or width of the popup. const int kScrollbarAdjustment = -#if defined(OS_MAC) +#if BUILDFLAG(IS_MAC) 0; #else gfx::scrollbar_size(); @@ -715,7 +718,7 @@ IN_PROC_BROWSER_TEST_F(BrowserActionInteractiveTest, OpenPopupOnPopup) { // that's what we check when we try to open the popup. // TODO(crbug.com/1115237): Now that this is an interactive test, is this // ifdef still necessary? -#if !defined(OS_MAC) +#if !BUILDFLAG(IS_MAC) EXPECT_TRUE(popup_browser->window()->IsActive()); #endif EXPECT_FALSE(browser()->window()->IsActive()); @@ -1019,7 +1022,7 @@ IN_PROC_BROWSER_TEST_F(NavigatingExtensionPopupInteractiveTest, // The test verification below is applicable only to scenarios where the // download shelf is supported - on ChromeOS, instead of the download shelf, // there is a download notification in the right-bottom corner of the screen. -#if !defined(OS_CHROMEOS) +#if !BUILDFLAG(IS_CHROMEOS) EXPECT_TRUE(browser()->window()->IsDownloadShelfVisible()); #endif } @@ -1054,7 +1057,7 @@ IN_PROC_BROWSER_TEST_F(NavigatingExtensionPopupInteractiveTest, // The test verification below is applicable only to scenarios where the // download shelf is supported - on ChromeOS, instead of the download shelf, // there is a download notification in the right-bottom corner of the screen. -#if !defined(OS_CHROMEOS) +#if !BUILDFLAG(IS_CHROMEOS) EXPECT_TRUE(browser()->window()->IsDownloadShelfVisible()); #endif } diff --git a/chromium/chrome/browser/extensions/api/extension_action/extension_action_api.cc b/chromium/chrome/browser/extensions/api/extension_action/extension_action_api.cc index f175648f6eb..2ea6ab9abce 100644 --- a/chromium/chrome/browser/extensions/api/extension_action/extension_action_api.cc +++ b/chromium/chrome/browser/extensions/api/extension_action/extension_action_api.cc @@ -60,11 +60,85 @@ const char kNoTabError[] = "No tab with id: *."; const char kOpenPopupError[] = "Failed to show popup either because there is an existing popup or another " "error occurred."; +const char kFailedToOpenPopupGenericError[] = "Failed to open popup."; const char kInvalidColorError[] = "The color specification could not be parsed."; +constexpr char kNoActiveWindowFound[] = + "Could not find an active browser window."; +constexpr char kNoActivePopup[] = + "Extension does not have a popup on the active tab."; bool g_report_error_for_invisible_icon = false; +// Returns the browser that was last active in the given `profile`, optionally +// also checking the incognito profile. +Browser* FindLastActiveBrowserWindow(Profile* profile, + bool check_incognito_profile) { + Browser* browser = chrome::FindLastActiveWithProfile(profile); + + if (browser && browser->window()->IsActive()) + return browser; // Found an active browser. + + // It's possible that the last active browser actually corresponds to the + // associated incognito profile, and this won't be returned by + // FindLastActiveWithProfile(). If the extension can operate incognito, then + // check the last active incognito, too. + if (check_incognito_profile && profile->HasPrimaryOTRProfile()) { + Profile* incognito_profile = + profile->GetPrimaryOTRProfile(/*create_if_needed=*/false); + DCHECK(incognito_profile); + Browser* incognito_browser = + chrome::FindLastActiveWithProfile(incognito_profile); + if (incognito_browser->window()->IsActive()) + return incognito_browser; + } + + return nullptr; +} + +// Returns true if the given `extension` has an active popup on the active tab +// of `browser`. +bool HasPopupOnActiveTab(Browser* browser, + content::BrowserContext* browser_context, + const Extension& extension) { + content::WebContents* web_contents = + browser->tab_strip_model()->GetActiveWebContents(); + ExtensionAction* extension_action = + ExtensionActionManager::Get(browser_context) + ->GetExtensionAction(extension); + DCHECK(extension_action); + int tab_id = ExtensionTabUtil::GetTabId(web_contents); + + return extension_action->HasPopup(tab_id) && + extension_action->GetIsVisibleIgnoringDeclarative(tab_id); +} + +// Attempts to open `extension`'s popup in the given `browser`. Returns true on +// success; otherwise, populates `error` and returns false. +bool OpenPopupInBrowser(Browser& browser, + const Extension& extension, + std::string* error, + ShowPopupCallback callback) { + if (!browser.window()->IsToolbarVisible()) { + *error = "Browser window has no toolbar."; + return false; + } + + if (!ExtensionActionAPI::Get(browser.profile()) + ->ShowExtensionActionPopupForAPICall(&extension, &browser, + std::move(callback))) { + // NOTE(devlin): We could have the callback pass more information here about + // why the popup didn't open (e.g., another active popup vs popup closing + // before display, as may happen if the window closes), but it's not clear + // whether that would be significantly helpful to developers and it may + // leak other information about the user's browser. + *error = kFailedToOpenPopupGenericError; + return false; + } + + return true; +} + } // namespace // @@ -117,7 +191,8 @@ void ExtensionActionAPI::RemoveObserver(Observer* observer) { bool ExtensionActionAPI::ShowExtensionActionPopupForAPICall( const Extension* extension, - Browser* browser) { + Browser* browser, + ShowPopupCallback callback) { ExtensionAction* extension_action = ExtensionActionManager::Get(browser_context_)->GetExtensionAction( *extension); @@ -134,7 +209,7 @@ bool ExtensionActionAPI::ShowExtensionActionPopupForAPICall( // no toolbar. return extensions_container && extensions_container->ShowToolbarActionPopupForAPICall( - extension->id()); + extension->id(), std::move(callback)); } void ExtensionActionAPI::NotifyChange(ExtensionAction* extension_action, @@ -225,7 +300,8 @@ void ExtensionActionAPI::DispatchEventToExtension( return; auto event = std::make_unique<Event>( - histogram_value, event_name, std::move(*event_args).TakeList(), context); + histogram_value, event_name, std::move(*event_args).TakeListDeprecated(), + context); event->user_gesture = EventRouter::USER_GESTURE_ENABLED; EventRouter::Get(context) ->DispatchEventToExtension(extension_id, std::move(event)); @@ -380,12 +456,13 @@ ExtensionActionSetIconFunction::RunExtensionAction() { // setIcon can take a variant argument: either a dictionary of canvas // ImageData, or an icon index. - base::DictionaryValue* canvas_set = NULL; - if (details_->GetDictionary("imageData", &canvas_set)) { + base::Value* canvas_set = details_->FindDictKey("imageData"); + if (canvas_set) { gfx::ImageSkia icon; ExtensionAction::IconParseResult parse_result = - ExtensionAction::ParseIconFromCanvasDictionary(*canvas_set, &icon); + ExtensionAction::ParseIconFromCanvasDictionary( + base::Value::AsDictionaryValue(*canvas_set), &icon); if (parse_result != ExtensionAction::IconParseResult::kSuccess) { switch (parse_result) { @@ -410,12 +487,6 @@ ExtensionActionSetIconFunction::RunExtensionAction() { const bool is_visible = image_util::IsIconSufficientlyVisible(bitmap); UMA_HISTOGRAM_BOOLEAN("Extensions.DynamicExtensionActionIconWasVisible", is_visible); - const bool is_visible_rendered = - extensions::ui_util::IsRenderedIconSufficientlyVisibleForBrowserContext( - bitmap, browser_context()); - UMA_HISTOGRAM_BOOLEAN( - "Extensions.DynamicExtensionActionIconWasVisibleRendered", - is_visible_rendered); if (!is_visible && g_report_error_for_invisible_icon) return RespondNow(Error("Icon not sufficiently visible.")); @@ -478,7 +549,7 @@ ExtensionActionSetBadgeBackgroundColorFunction::RunExtensionAction() { EXTENSION_FUNCTION_VALIDATE(color_value); SkColor color = 0; if (color_value->is_list()) { - base::Value::ConstListView list = color_value->GetList(); + base::Value::ConstListView list = color_value->GetListDeprecated(); EXTENSION_FUNCTION_VALIDATE(list.size() == 4); @@ -575,33 +646,99 @@ ExtensionFunction::ResponseAction ActionGetUserSettingsFunction::Run() { return RespondNow(OneArgument(std::move(ui_settings))); } +ActionOpenPopupFunction::ActionOpenPopupFunction() = default; +ActionOpenPopupFunction::~ActionOpenPopupFunction() = default; + +ExtensionFunction::ResponseAction ActionOpenPopupFunction::Run() { + // Unfortunately, the action API types aren't compiled. However, the bindings + // should still valid the form of the arguments. + EXTENSION_FUNCTION_VALIDATE(args().size() == 1u); + EXTENSION_FUNCTION_VALIDATE(extension()); + const base::Value& options = args()[0]; + + // TODO(https://crbug.com/1245093): Support specifying the tab ID? This is + // kind of racy (because really what the extension probably cares about is + // the document ID; tab ID persists across pages, whereas document ID would + // detect things like navigations). + int window_id = extension_misc::kCurrentWindowId; + if (options.is_dict()) { + const base::Value* window_value = options.FindKey("windowId"); + if (window_value) { + EXTENSION_FUNCTION_VALIDATE(window_value->is_int()); + window_id = window_value->GetInt(); + } + } + + Browser* browser = nullptr; + Profile* profile = Profile::FromBrowserContext(browser_context()); + std::string error; + if (window_id == extension_misc::kCurrentWindowId) { + browser = + FindLastActiveBrowserWindow(profile, include_incognito_information()); + if (!browser) + error = kNoActiveWindowFound; + } else { + browser = ExtensionTabUtil::GetBrowserInProfileWithId( + profile, window_id, include_incognito_information(), &error); + } + + if (!browser) { + DCHECK(!error.empty()); + return RespondNow(Error(std::move(error))); + } + + if (!HasPopupOnActiveTab(browser, browser_context(), *extension())) + return RespondNow(Error(kNoActivePopup)); + + if (!OpenPopupInBrowser( + *browser, *extension(), &error, + base::BindOnce(&ActionOpenPopupFunction::OnShowPopupComplete, + this))) { + DCHECK(!error.empty()); + return RespondNow(Error(std::move(error))); + } + + // The function responds in OnShowPopupComplete(). Note that the function is + // kept alive by the ref-count owned by the ShowPopupCallback. + return RespondLater(); +} + +void ActionOpenPopupFunction::OnShowPopupComplete(ExtensionHost* popup_host) { + DCHECK(!did_respond()); + + ResponseValue response_value; + if (popup_host) { + // TODO(https://crbug.com/1245093): Return the tab for which the extension + // popup was shown? + DCHECK(popup_host->document_element_available()); + response_value = NoArguments(); + } else { + response_value = Error(kFailedToOpenPopupGenericError); + } + + Respond(std::move(response_value)); +} + BrowserActionOpenPopupFunction::BrowserActionOpenPopupFunction() = default; BrowserActionOpenPopupFunction::~BrowserActionOpenPopupFunction() = default; ExtensionFunction::ResponseAction BrowserActionOpenPopupFunction::Run() { // We only allow the popup in the active window. Profile* profile = Profile::FromBrowserContext(browser_context()); - Browser* browser = chrome::FindLastActiveWithProfile(profile); - // It's possible that the last active browser actually corresponds to the - // associated incognito profile, and this won't be returned by - // FindLastActiveWithProfile. If the browser we found isn't active and the - // extension can operate incognito, then check the last active incognito, too. - if ((!browser || !browser->window()->IsActive()) && - util::IsIncognitoEnabled(extension()->id(), profile) && - profile->HasPrimaryOTRProfile()) { - browser = chrome::FindLastActiveWithProfile( - profile->GetPrimaryOTRProfile(/*create_if_needed=*/true)); - } + Browser* browser = + FindLastActiveBrowserWindow(profile, include_incognito_information()); + + if (!browser) + return RespondNow(Error(kNoActiveWindowFound)); + + if (!HasPopupOnActiveTab(browser, browser_context(), *extension())) + return RespondNow(Error(kNoActivePopup)); - // If there's no active browser, or the Toolbar isn't visible, abort. - // Otherwise, try to open a popup in the active browser. - // TODO(justinlin): Remove toolbar check when http://crbug.com/308645 is - // fixed. - if (!browser || !browser->window()->IsActive() || - !browser->window()->IsToolbarVisible() || - !ExtensionActionAPI::Get(profile)->ShowExtensionActionPopupForAPICall( - extension_.get(), browser)) { - return RespondNow(Error(kOpenPopupError)); + std::string error; + if (!OpenPopupInBrowser(*browser, *extension(), &error, + ShowPopupCallback())) { + DCHECK(!error.empty()); + return RespondNow(Error(std::move(error))); } // Even if this is for an incognito window, we want to use the normal profile. diff --git a/chromium/chrome/browser/extensions/api/extension_action/extension_action_api.h b/chromium/chrome/browser/extensions/api/extension_action/extension_action_api.h index 2d88df41a87..b5589c3caa2 100644 --- a/chromium/chrome/browser/extensions/api/extension_action/extension_action_api.h +++ b/chromium/chrome/browser/extensions/api/extension_action/extension_action_api.h @@ -10,6 +10,7 @@ #include "base/memory/raw_ptr.h" #include "base/observer_list.h" #include "base/scoped_observation.h" +#include "chrome/browser/ui/extensions/extension_popup_types.h" #include "extensions/browser/browser_context_keyed_api_factory.h" #include "extensions/browser/extension_action.h" #include "extensions/browser/extension_event_histogram_value.h" @@ -72,9 +73,11 @@ class ExtensionActionAPI : public BrowserContextKeyedAPI { void AddObserver(Observer* observer); void RemoveObserver(Observer* observer); - // Opens the popup for the given |extension| in the given |browser|'s window. + // Opens the popup for the given |extension| in the given |browser|'s window, + // invoking |callback| when complete. bool ShowExtensionActionPopupForAPICall(const Extension* extension, - Browser* browser); + Browser* browser, + ShowPopupCallback callback); // Notifies that there has been a change in the given |extension_action|. void NotifyChange(ExtensionAction* extension_action, @@ -356,6 +359,31 @@ class ActionGetUserSettingsFunction : public ExtensionFunction { ~ActionGetUserSettingsFunction() override; }; +// Note: action.openPopup() and browserAction.openPopup() have subtly different +// implementations: +// * action.openPopup() allows the extension to specify a window ID. +// * browserAction.openPopup() will time out after 10 seconds; +// action.openPopup() does not time out and instead waits for the popup to +// either be shown or encounter an error. +// * browserAction.openPopup() returns a handle to the HTMLWindow of the +// popup; action.openPopup() returns nothing. +// Due to these differences, the implementations are distinct classes. +class ActionOpenPopupFunction : public ExtensionFunction { + public: + DECLARE_EXTENSION_FUNCTION("action.openPopup", ACTION_OPENPOPUP) + + ActionOpenPopupFunction(); + ActionOpenPopupFunction(const ActionOpenPopupFunction&) = delete; + ActionOpenPopupFunction& operator=(const ActionOpenPopupFunction&) = delete; + + protected: + // ExtensionFunction: + ~ActionOpenPopupFunction() override; + ResponseAction Run() override; + + void OnShowPopupComplete(ExtensionHost* popup_host); +}; + // // browserAction.* aliases for supported browserAction APIs. // @@ -456,6 +484,9 @@ class BrowserActionDisableFunction : public ExtensionActionHideFunction { ~BrowserActionDisableFunction() override {} }; +// Note: action.openPopup() and browserAction.openPopup() have subtly different +// implementations. See ActionOpenPopupFunction above. +// TODO(devlin): Remove browserAction.openPopup(). class BrowserActionOpenPopupFunction : public ExtensionFunction, public ExtensionHostRegistry::Observer { public: diff --git a/chromium/chrome/browser/extensions/api/extension_action/extension_action_api_interactive_uitest.cc b/chromium/chrome/browser/extensions/api/extension_action/extension_action_api_interactive_uitest.cc new file mode 100644 index 00000000000..6f13209824a --- /dev/null +++ b/chromium/chrome/browser/extensions/api/extension_action/extension_action_api_interactive_uitest.cc @@ -0,0 +1,258 @@ +// Copyright 2021 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/strings/stringprintf.h" +#include "base/test/bind.h" +#include "chrome/browser/extensions/extension_apitest.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/test/base/interactive_test_utils.h" +#include "chrome/test/base/ui_test_utils.h" +#include "components/version_info/channel.h" +#include "content/public/test/browser_test.h" +#include "extensions/browser/browsertest_util.h" +#include "extensions/browser/extension_action.h" +#include "extensions/browser/extension_action_manager.h" +#include "extensions/browser/extension_host_registry.h" +#include "extensions/common/features/feature_channel.h" +#include "extensions/test/result_catcher.h" +#include "extensions/test/test_extension_dir.h" + +namespace extensions { + +// An interactive UI test suite for the chrome.action API. This is used for +// tests where things like focus and window activation are important. +class ActionAPIInteractiveUITest : public ExtensionApiTest { + public: + ActionAPIInteractiveUITest() = default; + ~ActionAPIInteractiveUITest() override = default; + + // Loads a common "stub" extension with an action specified in its manifest; + // tests then execute script in this extension's context. + // This allows us to more seamlessly integrate C++ and JS execution by + // inlining the JS here in this file, rather than needing to separate it out + // into separate test files and coordinate messaging back-and-forth. + const Extension* LoadStubExtension() { + return LoadExtension( + test_data_dir_.AppendASCII("extension_action/stub_action")); + } + + // Runs the given `script` in the background service worker context for the + // `extension`, and waits for a corresponding test success notification. + void RunScriptTest(const std::string& script, const Extension& extension) { + ResultCatcher result_catcher; + base::RunLoop run_loop; + auto callback = [&run_loop](base::Value value) { run_loop.Quit(); }; + browsertest_util::ExecuteScriptInServiceWorker( + profile(), extension.id(), script, + base::BindLambdaForTesting(callback)); + run_loop.Run(); + EXPECT_TRUE(result_catcher.GetNextResult()) << result_catcher.message(); + } + + // Same as `RunScriptTest()`, but wraps `script` in a test.runTests() call + // with a single function. + void WrapAndRunScript(const std::string& script, const Extension& extension) { + constexpr char kTemplate[] = + R"(chrome.test.runTests([ + async function openPopupTest() { + %s + } + ]);)"; + std::string wrapped_script = base::StringPrintf(kTemplate, script.c_str()); + RunScriptTest(wrapped_script, extension); + } + + // Returns the active popup for the given `extension`, if one exists. + ExtensionHost* GetPopup(const Extension& extension) { + ExtensionHostRegistry* registry = ExtensionHostRegistry::Get(profile()); + std::vector<ExtensionHost*> hosts = + registry->GetHostsForExtension(extension.id()); + ExtensionHost* found_host = nullptr; + for (auto* host : hosts) { + if (host->extension_host_type() == mojom::ViewType::kExtensionPopup) { + if (found_host) { + ADD_FAILURE() << "Multiple popups found!"; + return nullptr; + } + found_host = host; + } + } + return found_host; + } + + // Returns true if the given `browser` has an active popup. + bool BrowserHasPopup(Browser* browser) { + return ExtensionActionTestHelper::Create(browser)->HasPopup(); + } + + // The action.openPopup() function is currently scoped to dev channel. + ScopedCurrentChannel scoped_current_channel_{version_info::Channel::DEV}; +}; + +// Tests displaying a popup in the active window when no window ID is specified. +IN_PROC_BROWSER_TEST_F(ActionAPIInteractiveUITest, OpenPopupInActiveWindow) { + const Extension* extension = LoadStubExtension(); + ASSERT_TRUE(extension); + + constexpr char kScript[] = + R"(await chrome.action.openPopup(); + chrome.test.succeed();)"; + WrapAndRunScript(kScript, *extension); + + EXPECT_TRUE(BrowserHasPopup(browser())); + ExtensionHost* host = GetPopup(*extension); + ASSERT_TRUE(host); + EXPECT_TRUE(host->has_loaded_once()); + EXPECT_EQ(extension->GetResourceURL("popup.html"), + host->main_frame_host()->GetLastCommittedURL()); +} + +// Tests displaying a popup in a window specified in the API call. +IN_PROC_BROWSER_TEST_F(ActionAPIInteractiveUITest, OpenPopupInSpecifiedWindow) { + const Extension* extension = LoadStubExtension(); + ASSERT_TRUE(extension); + + Browser* second_browser = CreateBrowser(profile()); + ASSERT_TRUE(second_browser); + ui_test_utils::BrowserActivationWaiter(second_browser).WaitForActivation(); + + // TODO(https://crbug.com/1245093): We should allow extensions to open a + // popup in an inactive window. Currently, this fails, so try to open the + // popup in the active window (but with a specified ID). + EXPECT_FALSE(browser()->window()->IsActive()); + EXPECT_TRUE(second_browser->window()->IsActive()); + + int window_id = ExtensionTabUtil::GetWindowId(second_browser); + + constexpr char kScript[] = + R"(await chrome.action.openPopup({windowId: %d}); + chrome.test.succeed();)"; + WrapAndRunScript(base::StringPrintf(kScript, window_id), *extension); + + // The popup should be shown on the second browser. + { + EXPECT_TRUE(BrowserHasPopup(second_browser)); + ExtensionHost* host = GetPopup(*extension); + ASSERT_TRUE(host); + EXPECT_TRUE(host->has_loaded_once()); + EXPECT_EQ(extension->GetResourceURL("popup.html"), + host->main_frame_host()->GetLastCommittedURL()); + } + + EXPECT_FALSE(BrowserHasPopup(browser())); +} + +// Tests a series of action.openPopup() invocations that are expected to fail. +IN_PROC_BROWSER_TEST_F(ActionAPIInteractiveUITest, OpenPopupFailures) { + const Extension* extension = LoadStubExtension(); + ASSERT_TRUE(extension); + + constexpr char kScript[] = + R"(chrome.test.runTests([ + async function openPopupFailsWithFakeWindow() { + const fakeWindowId = 99999; + await chrome.test.assertPromiseRejects( + chrome.action.openPopup({windowId: fakeWindowId}), + `Error: No window with id: ${fakeWindowId}.`); + chrome.test.succeed(); + }, + async function openPopupFailsWhenNoPopupSpecified() { + // Specifying an empty string for the popup means "no popup". + await chrome.action.setPopup({popup: ''}); + await chrome.test.assertPromiseRejects( + chrome.action.openPopup(), + 'Error: Extension does not have a popup on the active tab.'); + chrome.test.succeed(); + }, + async function openPopupFailsWhenPopupIsDisabled() { + await chrome.action.setPopup({popup: 'popup.html'}); + await chrome.action.disable(); + await chrome.test.assertPromiseRejects( + chrome.action.openPopup(), + 'Error: Extension does not have a popup on the active tab.'); + chrome.test.succeed(); + }, + async function tryToOpenPopupThatClosesBeforeItsOpened() { + await chrome.action.enable(); + // insta_close_popup.html has a script file that synchronously + // calls window.close() during document parsing. This results in + // the popup closing before it's actually shown. + await chrome.action.setPopup({popup: 'insta_close_popup.html'}); + await chrome.test.assertPromiseRejects( + chrome.action.openPopup(), + 'Error: Failed to open popup.'); + chrome.test.succeed(); + }, + ]);)"; + RunScriptTest(kScript, *extension); + EXPECT_FALSE(BrowserHasPopup(browser())); +} + +// Tests that openPopup() will not succeed if a popup is only visible on a tab +// because of a declarative condition. +// https://crbug.com/1289846. +IN_PROC_BROWSER_TEST_F(ActionAPIInteractiveUITest, + DontOpenPopupForDeclarativelyShownAction) { + ASSERT_TRUE(StartEmbeddedTestServer()); + + constexpr char kManifest[] = + R"({ + "name": "My Extension", + "manifest_version": 3, + "version": "0.1", + "background": { "service_worker": "worker.js" }, + "action": { "default_popup": "popup.html" }, + "permissions": ["declarativeContent"] + })"; + constexpr char kWorkerJs[] = "// Intentionally blank"; + constexpr char kPopupHtml[] = "<html>Hello, World!</html>"; + + TestExtensionDir test_dir; + test_dir.WriteManifest(kManifest); + test_dir.WriteFile(FILE_PATH_LITERAL("worker.js"), kWorkerJs); + test_dir.WriteFile(FILE_PATH_LITERAL("popup.html"), kPopupHtml); + + const Extension* extension = LoadExtension(test_dir.UnpackedPath()); + ASSERT_TRUE(extension); + + constexpr char kDisableActionAndSetRule[] = + R"(await chrome.action.disable(); + await chrome.declarativeContent.onPageChanged.addRules([{ + conditions: [ + new chrome.declarativeContent.PageStateMatcher({ + pageUrl: {hostEquals: 'example.com'} + }) + ], + actions: [ + new chrome.declarativeContent.ShowAction(), + ] + }]); + chrome.test.succeed();)"; + + WrapAndRunScript(kDisableActionAndSetRule, *extension); + + GURL url = embedded_test_server()->GetURL("example.com", "/simple.html"); + ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), url)); + + int tab_id = ExtensionTabUtil::GetTabId( + browser()->tab_strip_model()->GetActiveWebContents()); + + const ExtensionAction* extension_action = + ExtensionActionManager::Get(profile())->GetExtensionAction(*extension); + ASSERT_TRUE(extension_action); + EXPECT_TRUE(extension_action->GetIsVisible(tab_id)); + EXPECT_FALSE(extension_action->GetIsVisibleIgnoringDeclarative(tab_id)); + + constexpr char kTryOpenPopup[] = + R"(await chrome.test.assertPromiseRejects( + chrome.action.openPopup(), + 'Error: Extension does not have a popup on the active tab.'); + chrome.test.succeed();)"; + WrapAndRunScript(kTryOpenPopup, *extension); +} + +} // namespace extensions diff --git a/chromium/chrome/browser/extensions/api/extension_action/extension_action_apitest.cc b/chromium/chrome/browser/extensions/api/extension_action/extension_action_apitest.cc index 1963eb194c5..27e7e7268ea 100644 --- a/chromium/chrome/browser/extensions/api/extension_action/extension_action_apitest.cc +++ b/chromium/chrome/browser/extensions/api/extension_action/extension_action_apitest.cc @@ -449,8 +449,7 @@ IN_PROC_BROWSER_TEST_P(MultiActionAPITest, OnClickedDispatching) { chrome.test.assertTrue(tab.id > 0); chrome.test.assertTrue(tab.index > -1); chrome.test.notifyPass(); - }); - chrome.test.sendMessage('ready');)"; + });)"; const char* background_specification = GetParam() == ActionInfo::TYPE_ACTION @@ -472,10 +471,8 @@ IN_PROC_BROWSER_TEST_P(MultiActionAPITest, OnClickedDispatching) { ExtensionActionTestHelper::Create(browser()); EXPECT_EQ(0, toolbar_helper->NumberOfBrowserActions()); - ExtensionTestMessageListener listener("ready", /*will_reply=*/false); const Extension* extension = LoadExtension(test_dir.UnpackedPath()); ASSERT_TRUE(extension); - ASSERT_TRUE(listener.WaitUntilSatisfied()); ASSERT_EQ(1, toolbar_helper->NumberOfBrowserActions()); EXPECT_TRUE(toolbar_helper->HasAction(extension->id())); @@ -560,7 +557,7 @@ IN_PROC_BROWSER_TEST_P(MultiActionAPITest, PopupCreation) { // Tests that sessionStorage does not persist between closing and opening of a // popup. // TODO(crbug/1256760): Flaky on Linux. -#if defined(OS_LINUX) +#if BUILDFLAG(IS_LINUX) #define MAYBE_SessionStorageDoesNotPersistBetweenOpenings \ DISABLED_SessionStorageDoesNotPersistBetweenOpenings #else @@ -1007,6 +1004,112 @@ IN_PROC_BROWSER_TEST_P(MultiActionAPITest, SetIconWithSelfDefined) { base::StringPrintf(kSetIconScript, tab_id)); } +// Tests calling setIcon() for a tab with an invalid icon path specified. +IN_PROC_BROWSER_TEST_P(MultiActionAPITest, SetIconInTabWithInvalidPath) { + constexpr char kManifestTemplate[] = + R"({ + "name": "Bad Icon Path", + "manifest_version": %d, + "version": "0.1", + "%s": {} + })"; + + constexpr char kPageJsTemplate[] = + R"(function setIcon(details) { + chrome.%s.setIcon(details, () => { + chrome.test.assertLastError("%s"); + chrome.test.notifyPass(); + }); + })"; + + constexpr char kExpectedError[] = + "Could not load action icon 'does_not_exist.png'."; + + TestExtensionDir test_dir; + test_dir.WriteManifest(base::StringPrintf( + kManifestTemplate, GetManifestVersionForActionType(GetParam()), + GetManifestKeyForActionType(GetParam()))); + + test_dir.WriteFile(FILE_PATH_LITERAL("page.html"), kPageHtmlTemplate); + test_dir.WriteFile( + FILE_PATH_LITERAL("page.js"), + base::StringPrintf(kPageJsTemplate, GetAPINameForActionType(GetParam()), + kExpectedError)); + + const Extension* extension = LoadExtension(test_dir.UnpackedPath()); + ASSERT_TRUE(extension); + + ExtensionAction* action = GetExtensionAction(*extension); + ASSERT_TRUE(action); + + ASSERT_TRUE(ui_test_utils::NavigateToURL( + browser(), extension->GetResourceURL("page.html"))); + content::WebContents* web_contents = + browser()->tab_strip_model()->GetActiveWebContents(); + + int tab_id = GetActiveTabId(); + EXPECT_TRUE(ActionHasDefaultState(*action, tab_id)); + EnsureActionIsEnabledOnActiveTab(action); + + // Calling setIcon with an invalid path in a non-service worker context should + // emit a console error in that context and call the callback with lastError + // set. + content::WebContentsConsoleObserver console_observer(web_contents); + console_observer.SetPattern(kExpectedError); + + constexpr char kSetIconScript[] = + "setIcon({tabId: %d, path: 'does_not_exist.png'});"; + RunTestAndWaitForSuccess(web_contents, + base::StringPrintf(kSetIconScript, tab_id)); + console_observer.Wait(); +} + +// Tests calling setIcon() in the service worker with an invalid icon path +// specified. Regression test for https://crbug.com/1262029. +IN_PROC_BROWSER_TEST_F(ExtensionActionAPITest, SetIconInWorkerWithInvalidPath) { + constexpr char kManifestTemplate[] = + R"({ + "name": "Bad Icon Path In Worker", + "manifest_version": 3, + "version": "0.1", + "action": {}, + "background": {"service_worker": "worker.js" } + })"; + + constexpr char kBackgroundJs[] = + R"(let expectedError = "%s"; + chrome.test.runTests([ + function withCallback() { + chrome.action.setIcon({path: 'does_not_exist.png'}, () => { + chrome.test.assertLastError(expectedError); + chrome.test.succeed(); + }); + }, + async function withPromise() { + await chrome.test.assertPromiseRejects( + chrome.action.setIcon({path: 'does_not_exist.png'}), + 'Error: ' + expectedError); + chrome.test.succeed(); + } + ]);)"; + + constexpr char kExpectedError[] = + "Failed to set icon 'does_not_exist.png': Failed to fetch"; + + TestExtensionDir test_dir; + test_dir.WriteManifest(kManifestTemplate); + + test_dir.WriteFile(FILE_PATH_LITERAL("worker.js"), + base::StringPrintf(kBackgroundJs, kExpectedError)); + + // Calling setIcon with an invalid path in a service worker context should + // reject the promise or call the callback with lastError set. + ResultCatcher result_catcher; + const Extension* extension = LoadExtension(test_dir.UnpackedPath()); + ASSERT_TRUE(extension); + EXPECT_TRUE(result_catcher.GetNextResult()) << result_catcher.message(); +} + // Tests various getter and setter methods. IN_PROC_BROWSER_TEST_P(MultiActionAPITest, GettersAndSetters) { // Load up an extension with default values. diff --git a/chromium/chrome/browser/extensions/api/extension_action/page_action_browsertest.cc b/chromium/chrome/browser/extensions/api/extension_action/page_action_browsertest.cc new file mode 100644 index 00000000000..38aed9231f6 --- /dev/null +++ b/chromium/chrome/browser/extensions/api/extension_action/page_action_browsertest.cc @@ -0,0 +1,184 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include <stddef.h> + +#include "base/strings/utf_string_conversions.h" +#include "build/build_config.h" +#include "chrome/browser/extensions/extension_action_test_util.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/tabs/tab_strip_model.h" +#include "chrome/test/base/ui_test_utils.h" +#include "content/public/test/browser_test.h" +#include "extensions/browser/extension_action.h" +#include "extensions/browser/extension_action_manager.h" +#include "extensions/browser/extension_registry.h" +#include "extensions/common/extension.h" +#include "extensions/common/switches.h" +#include "net/test/embedded_test_server/embedded_test_server.h" + +namespace extensions { +namespace { + +const std::string kSubscribePageAction = "subscribe_page_action/src"; +const std::string kFeedPage = "/feeds/feed.html"; +const std::string kNoFeedPage = "/feeds/no_feed.html"; + +const std::string kHashPageA = + "/extensions/api_test/page_action/hash_change/test_page_A.html"; +const std::string kHashPageAHash = kHashPageA + "#asdf"; +const std::string kHashPageB = + "/extensions/api_test/page_action/hash_change/test_page_B.html"; + +using ContextType = ExtensionBrowserTest::ContextType; + +class PageActionBrowserTest : public ExtensionBrowserTest, + public testing::WithParamInterface<ContextType> { + public: + PageActionBrowserTest() : ExtensionBrowserTest(GetParam()) {} + ~PageActionBrowserTest() override = default; + PageActionBrowserTest(const PageActionBrowserTest& other) = delete; + PageActionBrowserTest& operator=(const PageActionBrowserTest& other) = delete; +}; + +INSTANTIATE_TEST_SUITE_P(PersistentBackground, + PageActionBrowserTest, + ::testing::Values(ContextType::kPersistentBackground)); + +INSTANTIATE_TEST_SUITE_P(ServiceWorker, + PageActionBrowserTest, + ::testing::Values(ContextType::kServiceWorker)); + +IN_PROC_BROWSER_TEST_P(PageActionBrowserTest, PageActionCrash25562) { + ASSERT_TRUE(embedded_test_server()->Start()); + + // This page action will not show an icon, since it doesn't specify one but + // is included here to test for a crash (http://crbug.com/25562). + ASSERT_TRUE(LoadExtension( + test_data_dir_.AppendASCII("browsertest") + .AppendASCII("crash_25562"))); + + // Navigate to the feed page. + GURL feed_url = embedded_test_server()->GetURL(kFeedPage); + ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), feed_url)); + // We should now have one page action ready to go in the LocationBar. + ASSERT_TRUE(WaitForPageActionVisibilityChangeTo(1)); +} + +// Tests that we can load page actions in the Omnibox. +IN_PROC_BROWSER_TEST_P(PageActionBrowserTest, PageAction) { + ASSERT_TRUE(embedded_test_server()->Start()); + + ASSERT_TRUE(LoadExtension( + test_data_dir_.AppendASCII(kSubscribePageAction))); + + ASSERT_TRUE(WaitForPageActionVisibilityChangeTo(0)); + + // Navigate to the feed page. + GURL feed_url = embedded_test_server()->GetURL(kFeedPage); + ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), feed_url)); + // We should now have one page action ready to go in the LocationBar. + ASSERT_TRUE(WaitForPageActionVisibilityChangeTo(1)); + + // Navigate to a page with no feed. + GURL no_feed = embedded_test_server()->GetURL(kNoFeedPage); + ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), no_feed)); + // Make sure the page action goes away. + ASSERT_TRUE(WaitForPageActionVisibilityChangeTo(0)); +} + +// Tests that we don't lose the page action icon on same-document navigations. +IN_PROC_BROWSER_TEST_P(PageActionBrowserTest, SameDocumentNavigation) { + ASSERT_TRUE(embedded_test_server()->Start()); + + base::FilePath extension_path(test_data_dir_.AppendASCII("api_test") + .AppendASCII("page_action") + .AppendASCII("hash_change")); + ASSERT_TRUE(LoadExtension(extension_path)); + + // Page action should become visible when we navigate here. + GURL feed_url = embedded_test_server()->GetURL(kHashPageA); + ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), feed_url)); + ASSERT_TRUE(WaitForPageActionVisibilityChangeTo(1)); + + // Same-document navigation, page action should remain. + feed_url = embedded_test_server()->GetURL(kHashPageAHash); + ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), feed_url)); + ASSERT_TRUE(WaitForPageActionVisibilityChangeTo(1)); + + // Not a same-document navigation, page action should go away. + feed_url = embedded_test_server()->GetURL(kHashPageB); + ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), feed_url)); + ASSERT_TRUE(WaitForPageActionVisibilityChangeTo(0)); +} + +// Tests that the location bar forgets about unloaded page actions. +// Flaky on Linux: https://crbug.com/1283900 +#if BUILDFLAG(IS_LINUX) +#define MAYBE_UnloadPageAction DISABLED_UnloadPageAction +#else +#define MAYBE_UnloadPageAction UnloadPageAction +#endif +IN_PROC_BROWSER_TEST_P(PageActionBrowserTest, MAYBE_UnloadPageAction) { + ASSERT_TRUE(embedded_test_server()->Start()); + + base::FilePath extension_path( + test_data_dir_.AppendASCII(kSubscribePageAction)); + ASSERT_TRUE(LoadExtension(extension_path)); + + // Navigation prompts the location bar to load page actions. + GURL feed_url = embedded_test_server()->GetURL(kFeedPage); + ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), feed_url)); + content::WebContents* tab = + browser()->tab_strip_model()->GetActiveWebContents(); + EXPECT_EQ(1u, extension_action_test_util::GetTotalPageActionCount(tab)); + + UnloadExtension(last_loaded_extension_id()); + + // Make sure the page action goes away when it's unloaded. + EXPECT_EQ(0u, extension_action_test_util::GetTotalPageActionCount(tab)); +} + +// Regression test for crbug.com/44415. +// TODO(crbug.com/1284555): Re-enable test to run with service workers after +// fixing flakiness. +using PageActionWithoutServiceWorkerTest = ExtensionBrowserTest; +IN_PROC_BROWSER_TEST_F(PageActionWithoutServiceWorkerTest, + PageActionRefreshCrash) { + ExtensionRegistry* registry = + extensions::ExtensionRegistry::Get(browser()->profile()); + + size_t size_before = registry->enabled_extensions().size(); + + base::FilePath base_path = test_data_dir_.AppendASCII("browsertest") + .AppendASCII("crash_44415"); + // Load extension A. + const Extension* extensionA = LoadExtension(base_path.AppendASCII("ExtA")); + ASSERT_TRUE(extensionA); + ASSERT_TRUE(WaitForPageActionVisibilityChangeTo(1)); + ASSERT_EQ(size_before + 1, registry->enabled_extensions().size()); + + // Load extension B. + const Extension* extensionB = LoadExtension(base_path.AppendASCII("ExtB")); + ASSERT_TRUE(extensionB); + ASSERT_TRUE(WaitForPageActionVisibilityChangeTo(2)); + ASSERT_EQ(size_before + 2, registry->enabled_extensions().size()); + + std::string idA = extensionA->id(); + ReloadExtension(extensionA->id()); + // ExtensionA has changed, so refetch it. + ASSERT_EQ(size_before + 2, registry->enabled_extensions().size()); + extensionA = registry->enabled_extensions().GetByID(idA); + + ReloadExtension(extensionB->id()); + + // This is where it would crash, before http://crbug.com/44415 was fixed. + ReloadExtension(extensionA->id()); +} + +} // namespace +} // namespace extensions diff --git a/chromium/chrome/browser/extensions/api/extension_action/page_action_interactive_test.cc b/chromium/chrome/browser/extensions/api/extension_action/page_action_interactive_test.cc index 09c8a9b7e61..7a70b718560 100644 --- a/chromium/chrome/browser/extensions/api/extension_action/page_action_interactive_test.cc +++ b/chromium/chrome/browser/extensions/api/extension_action/page_action_interactive_test.cc @@ -28,7 +28,8 @@ IN_PROC_BROWSER_TEST_F(PageActionInteractiveTest, ShowPageActionPopup) { ResultCatcher catcher; ASSERT_TRUE(ExtensionActionAPI::Get(browser()->profile()) - ->ShowExtensionActionPopupForAPICall(extension, browser())); + ->ShowExtensionActionPopupForAPICall(extension, browser(), + ShowPopupCallback())); ASSERT_TRUE(catcher.GetNextResult()); } |