blob: c4aa4201d6a725caa82b4985f5f385743f9ac98d [file] [log] [blame]
// 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 <stdint.h>
#include "base/files/file_path.h"
#include "base/files/file_util.h"
#include "base/files/scoped_temp_dir.h"
#include "base/macros.h"
#include "base/run_loop.h"
#include "base/scoped_observer.h"
#include "base/strings/string_piece.h"
#include "base/strings/stringprintf.h"
#include "base/test/metrics/histogram_tester.h"
#include "base/threading/thread_restrictions.h"
#include "build/build_config.h"
#include "chrome/browser/download/download_prefs.h"
#include "chrome/browser/extensions/api/extension_action/extension_action_api.h"
#include "chrome/browser/extensions/api/extension_action/test_extension_action_api_observer.h"
#include "chrome/browser/extensions/api/extension_action/test_icon_image_observer.h"
#include "chrome/browser/extensions/extension_action_icon_factory.h"
#include "chrome/browser/extensions/extension_action_runner.h"
#include "chrome/browser/extensions/extension_apitest.h"
#include "chrome/browser/extensions/extension_tab_util.h"
#include "chrome/browser/extensions/extension_util.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/browser/ui/browser_commands.h"
#include "chrome/browser/ui/browser_finder.h"
#include "chrome/browser/ui/browser_navigator_params.h"
#include "chrome/browser/ui/browser_window.h"
#include "chrome/browser/ui/extensions/extension_action_test_helper.h"
#include "chrome/browser/ui/tabs/tab_strip_model.h"
#include "chrome/browser/ui/ui_features.h"
#include "chrome/common/pref_names.h"
#include "chrome/common/url_constants.h"
#include "chrome/test/base/ui_test_utils.h"
#include "components/prefs/pref_service.h"
#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/web_contents.h"
#include "content/public/test/browser_test.h"
#include "content/public/test/browser_test_utils.h"
#include "content/public/test/content_browser_test_utils.h"
#include "content/public/test/download_test_observer.h"
#include "content/public/test/test_navigation_observer.h"
#include "content/public/test/test_utils.h"
#include "extensions/browser/extension_action.h"
#include "extensions/browser/extension_action_manager.h"
#include "extensions/browser/extension_host.h"
#include "extensions/browser/extension_host_observer.h"
#include "extensions/browser/extension_registry.h"
#include "extensions/browser/extension_system.h"
#include "extensions/browser/process_manager.h"
#include "extensions/browser/test_extension_registry_observer.h"
#include "extensions/common/feature_switch.h"
#include "extensions/common/scoped_worker_based_extensions_channel.h"
#include "extensions/test/extension_test_message_listener.h"
#include "extensions/test/result_catcher.h"
#include "net/test/embedded_test_server/embedded_test_server.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "ui/base/resource/resource_bundle.h"
#include "ui/gfx/geometry/rect.h"
#include "ui/gfx/geometry/size.h"
#include "ui/gfx/image/canvas_image_source.h"
#include "ui/gfx/image/image_skia.h"
#include "ui/gfx/image/image_skia_operations.h"
#include "ui/gfx/image/image_unittest_util.h"
#include "ui/gfx/skia_util.h"
using content::WebContents;
namespace extensions {
namespace {
void ExecuteExtensionAction(Browser* browser, const Extension* extension) {
ExtensionActionRunner::GetForWebContents(
browser->tab_strip_model()->GetActiveWebContents())
->RunAction(extension, true);
}
const char kEmptyImageDataError[] =
"The imageData property must contain an ImageData object or dictionary "
"of ImageData objects.";
const char kEmptyPathError[] = "The path property must not be empty.";
// Makes sure |bar_rendering| has |model_icon| in the middle (there's additional
// padding that correlates to the rest of the button, and this is ignored).
void VerifyIconsMatch(const gfx::Image& bar_rendering,
const gfx::Image& model_icon) {
gfx::Rect icon_portion(gfx::Point(), bar_rendering.Size());
icon_portion.ClampToCenteredSize(model_icon.Size());
EXPECT_TRUE(gfx::test::AreBitmapsEqual(
model_icon.AsImageSkia().GetRepresentation(1.0f).GetBitmap(),
gfx::ImageSkiaOperations::ExtractSubset(bar_rendering.AsImageSkia(),
icon_portion)
.GetRepresentation(1.0f)
.GetBitmap()));
}
class BrowserActionApiTest : public ExtensionApiTest {
public:
BrowserActionApiTest() {}
~BrowserActionApiTest() override {}
void TearDownOnMainThread() override {
// Clean up the test util first, so that any created UI properly removes
// itself before profile destruction.
browser_action_test_util_.reset();
ExtensionApiTest::TearDownOnMainThread();
}
protected:
ExtensionActionTestHelper* GetBrowserActionsBar() {
if (!browser_action_test_util_)
browser_action_test_util_ = ExtensionActionTestHelper::Create(browser());
return browser_action_test_util_.get();
}
ExtensionAction* GetBrowserAction(Browser* browser,
const Extension& extension) {
ExtensionAction* extension_action =
ExtensionActionManager::Get(browser->profile())
->GetExtensionAction(extension);
return extension_action->action_type() == ActionInfo::TYPE_BROWSER
? extension_action
: nullptr;
}
private:
std::unique_ptr<ExtensionActionTestHelper> browser_action_test_util_;
DISALLOW_COPY_AND_ASSIGN(BrowserActionApiTest);
};
// Canvas tests rely on the harness producing pixel output in order to read back
// pixels from a canvas element. So we have to override the setup function.
class BrowserActionApiCanvasTest : public BrowserActionApiTest {
public:
void SetUp() override {
EnablePixelOutput();
BrowserActionApiTest::SetUp();
}
};
using ContextType = ExtensionBrowserTest::ContextType;
class BrowserActionApiLazyTest
: public BrowserActionApiTest,
public testing::WithParamInterface<ContextType> {
public:
BrowserActionApiLazyTest() {
// Service Workers are currently only available on certain channels, so set
// the channel for those tests.
if (GetParam() == ContextType::kServiceWorker)
current_channel_ = std::make_unique<ScopedWorkerBasedExtensionsChannel>();
}
const extensions::Extension* LoadExtensionWithParamFlags(
const base::FilePath& path) {
int flags = kFlagEnableFileAccess;
if (GetParam() == ContextType::kServiceWorker)
flags |= ExtensionBrowserTest::kFlagRunAsServiceWorkerBasedExtension;
return LoadExtensionWithFlags(path, flags);
}
void RunUpdateTest(base::StringPiece path, bool expect_failure) {
ExtensionTestMessageListener ready_listener("ready", true);
ASSERT_TRUE(embedded_test_server()->Start());
const Extension* extension =
LoadExtensionWithParamFlags(test_data_dir_.AppendASCII(path));
ASSERT_TRUE(extension) << message_;
// Test that there is a browser action in the toolbar.
ASSERT_EQ(1, GetBrowserActionsBar()->NumberOfBrowserActions());
ASSERT_TRUE(ready_listener.WaitUntilSatisfied());
ExtensionAction* action = GetBrowserAction(browser(), *extension);
EXPECT_EQ("This is the default title.",
action->GetTitle(ExtensionAction::kDefaultTabId));
EXPECT_EQ(
"", action->GetExplicitlySetBadgeText(ExtensionAction::kDefaultTabId));
EXPECT_EQ(SkColorSetARGB(0, 0, 0, 0),
action->GetBadgeBackgroundColor(ExtensionAction::kDefaultTabId));
// Tell the extension to update the browser action state and then
// catch the result.
ResultCatcher catcher;
ready_listener.Reply("update");
if (expect_failure) {
EXPECT_FALSE(catcher.GetNextResult());
EXPECT_EQ("The source image could not be decoded.", catcher.message());
return;
}
EXPECT_TRUE(catcher.GetNextResult());
// Test that we received the changes.
EXPECT_EQ("Modified", action->GetTitle(ExtensionAction::kDefaultTabId));
EXPECT_EQ("badge", action->GetExplicitlySetBadgeText(
ExtensionAction::kDefaultTabId));
EXPECT_EQ(SkColorSetARGB(255, 255, 255, 255),
action->GetBadgeBackgroundColor(ExtensionAction::kDefaultTabId));
}
void RunEnableTest(base::StringPiece path, bool start_enabled) {
ExtensionTestMessageListener ready_listener("ready", true);
const Extension* extension =
LoadExtensionWithParamFlags(test_data_dir_.AppendASCII(path));
ASSERT_TRUE(extension) << message_;
// Test that there is a browser action in the toolbar.
ASSERT_EQ(1, GetBrowserActionsBar()->NumberOfBrowserActions());
ASSERT_TRUE(ready_listener.WaitUntilSatisfied());
ExtensionAction* action = GetBrowserAction(browser(), *extension);
// Tell the extension to enable/disable the browser action state and then
// catch the result.
ResultCatcher catcher;
if (start_enabled) {
action->SetIsVisible(ExtensionAction::kDefaultTabId, true);
ready_listener.Reply("start enabled");
} else {
action->SetIsVisible(ExtensionAction::kDefaultTabId, false);
ready_listener.Reply("start disabled");
}
EXPECT_TRUE(catcher.GetNextResult());
// Test that changes were applied.
EXPECT_EQ(!start_enabled,
action->GetIsVisible(ExtensionAction::kDefaultTabId));
}
private:
base::test::ScopedFeatureList feature_list_;
std::unique_ptr<extensions::ScopedWorkerBasedExtensionsChannel>
current_channel_;
};
IN_PROC_BROWSER_TEST_P(BrowserActionApiLazyTest, Basic) {
ExtensionTestMessageListener ready_listener("ready", false);
ASSERT_TRUE(embedded_test_server()->Start());
const Extension* extension = LoadExtensionWithParamFlags(
test_data_dir_.AppendASCII("browser_action/basics"));
ASSERT_TRUE(extension) << message_;
// Test that there is a browser action in the toolbar.
ASSERT_EQ(1, GetBrowserActionsBar()->NumberOfBrowserActions());
ASSERT_TRUE(ready_listener.WaitUntilSatisfied());
// Open a URL in the tab, so the event handler can check the tab's
// "url" and "title" properties.
ui_test_utils::NavigateToURL(
browser(), embedded_test_server()->GetURL("/extensions/test_file.txt"));
ResultCatcher catcher;
// Simulate the browser action being clicked.
ExecuteExtensionAction(browser(), extension);
EXPECT_TRUE(catcher.GetNextResult());
}
IN_PROC_BROWSER_TEST_P(BrowserActionApiLazyTest, Disable) {
ASSERT_NO_FATAL_FAILURE(RunEnableTest("browser_action/enable", true));
}
IN_PROC_BROWSER_TEST_P(BrowserActionApiLazyTest, Enable) {
ASSERT_NO_FATAL_FAILURE(RunEnableTest("browser_action/enable", false));
}
IN_PROC_BROWSER_TEST_P(BrowserActionApiLazyTest, Update) {
ASSERT_NO_FATAL_FAILURE(RunUpdateTest("browser_action/update", false));
}
IN_PROC_BROWSER_TEST_P(BrowserActionApiLazyTest, UpdateSvg) {
// TODO(crbug.com/1064671): Service Workers currently don't support loading
// SVG images.
const bool expect_failure = GetParam() == ContextType::kServiceWorker;
ASSERT_NO_FATAL_FAILURE(
RunUpdateTest("browser_action/update_svg", expect_failure));
}
INSTANTIATE_TEST_SUITE_P(EventPage,
BrowserActionApiLazyTest,
::testing::Values(ContextType::kEventPage));
INSTANTIATE_TEST_SUITE_P(ServiceWorker,
BrowserActionApiLazyTest,
::testing::Values(ContextType::kServiceWorker));
IN_PROC_BROWSER_TEST_F(BrowserActionApiCanvasTest, DynamicBrowserAction) {
ASSERT_TRUE(RunExtensionTest("browser_action/no_icon")) << message_;
const Extension* extension = GetSingleLoadedExtension();
ASSERT_TRUE(extension) << message_;
#if defined(OS_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::ScaleFactor> supported_scale_factors;
supported_scale_factors.push_back(ui::SCALE_FACTOR_100P);
supported_scale_factors.push_back(ui::SCALE_FACTOR_200P);
ui::SetSupportedScaleFactors(supported_scale_factors);
#endif
// We should not be creating icons asynchronously, so we don't need an
// observer.
ExtensionActionIconFactory icon_factory(
profile(), extension, GetBrowserAction(browser(), *extension), nullptr);
// Test that there is a browser action in the toolbar.
ASSERT_EQ(1, GetBrowserActionsBar()->NumberOfBrowserActions());
EXPECT_TRUE(GetBrowserActionsBar()->HasIcon(0));
gfx::Image action_icon = icon_factory.GetIcon(0);
uint32_t action_icon_last_id = action_icon.ToSkBitmap()->getGenerationID();
// Let's check that |GetIcon| doesn't always return bitmap with new id.
ASSERT_EQ(action_icon_last_id,
icon_factory.GetIcon(0).ToSkBitmap()->getGenerationID());
gfx::Image last_bar_icon = GetBrowserActionsBar()->GetIcon(0);
EXPECT_TRUE(gfx::test::AreImagesEqual(last_bar_icon,
GetBrowserActionsBar()->GetIcon(0)));
// The reason we don't test more standard scales (like 1x, 2x, etc.) is that
// these may be generated from the provided scales.
float kSmallIconScale = 21.f / ExtensionAction::ActionIconSize();
float kLargeIconScale = 42.f / ExtensionAction::ActionIconSize();
ASSERT_FALSE(ui::IsSupportedScale(kSmallIconScale));
ASSERT_FALSE(ui::IsSupportedScale(kLargeIconScale));
// Tell the extension to update the icon using ImageData object.
ResultCatcher catcher;
GetBrowserActionsBar()->Press(0);
ASSERT_TRUE(catcher.GetNextResult());
EXPECT_FALSE(gfx::test::AreImagesEqual(last_bar_icon,
GetBrowserActionsBar()->GetIcon(0)));
last_bar_icon = GetBrowserActionsBar()->GetIcon(0);
action_icon = icon_factory.GetIcon(0);
uint32_t action_icon_current_id = action_icon.ToSkBitmap()->getGenerationID();
EXPECT_GT(action_icon_current_id, action_icon_last_id);
action_icon_last_id = action_icon_current_id;
VerifyIconsMatch(last_bar_icon, action_icon);
// Check that only the smaller size was set (only a 21px icon was provided).
EXPECT_TRUE(action_icon.ToImageSkia()->HasRepresentation(kSmallIconScale));
EXPECT_FALSE(action_icon.ToImageSkia()->HasRepresentation(kLargeIconScale));
// Tell the extension to update the icon using path.
GetBrowserActionsBar()->Press(0);
ASSERT_TRUE(catcher.GetNextResult());
// Make sure the browser action bar updated.
EXPECT_FALSE(gfx::test::AreImagesEqual(last_bar_icon,
GetBrowserActionsBar()->GetIcon(0)));
last_bar_icon = GetBrowserActionsBar()->GetIcon(0);
action_icon = icon_factory.GetIcon(0);
action_icon_current_id = action_icon.ToSkBitmap()->getGenerationID();
EXPECT_GT(action_icon_current_id, action_icon_last_id);
action_icon_last_id = action_icon_current_id;
VerifyIconsMatch(last_bar_icon, action_icon);
// Check that only the smaller size was set (only a 21px icon was provided).
EXPECT_TRUE(action_icon.ToImageSkia()->HasRepresentation(kSmallIconScale));
EXPECT_FALSE(action_icon.ToImageSkia()->HasRepresentation(kLargeIconScale));
// Tell the extension to update the icon using dictionary of ImageData
// objects.
GetBrowserActionsBar()->Press(0);
ASSERT_TRUE(catcher.GetNextResult());
EXPECT_FALSE(gfx::test::AreImagesEqual(last_bar_icon,
GetBrowserActionsBar()->GetIcon(0)));
last_bar_icon = GetBrowserActionsBar()->GetIcon(0);
action_icon = icon_factory.GetIcon(0);
action_icon_current_id = action_icon.ToSkBitmap()->getGenerationID();
EXPECT_GT(action_icon_current_id, action_icon_last_id);
action_icon_last_id = action_icon_current_id;
VerifyIconsMatch(last_bar_icon, action_icon);
// Check both sizes were set (as two icon sizes were provided).
EXPECT_TRUE(action_icon.ToImageSkia()->HasRepresentation(kSmallIconScale));
EXPECT_TRUE(action_icon.AsImageSkia().HasRepresentation(kLargeIconScale));
// Tell the extension to update the icon using dictionary of paths.
GetBrowserActionsBar()->Press(0);
ASSERT_TRUE(catcher.GetNextResult());
EXPECT_FALSE(gfx::test::AreImagesEqual(last_bar_icon,
GetBrowserActionsBar()->GetIcon(0)));
last_bar_icon = GetBrowserActionsBar()->GetIcon(0);
action_icon = icon_factory.GetIcon(0);
action_icon_current_id = action_icon.ToSkBitmap()->getGenerationID();
EXPECT_GT(action_icon_current_id, action_icon_last_id);
action_icon_last_id = action_icon_current_id;
VerifyIconsMatch(last_bar_icon, action_icon);
// Check both sizes were set (as two icon sizes were provided).
EXPECT_TRUE(action_icon.ToImageSkia()->HasRepresentation(kSmallIconScale));
EXPECT_TRUE(action_icon.AsImageSkia().HasRepresentation(kLargeIconScale));
// Tell the extension to update the icon using dictionary of ImageData
// objects, but setting only one size.
GetBrowserActionsBar()->Press(0);
ASSERT_TRUE(catcher.GetNextResult());
EXPECT_FALSE(gfx::test::AreImagesEqual(last_bar_icon,
GetBrowserActionsBar()->GetIcon(0)));
last_bar_icon = GetBrowserActionsBar()->GetIcon(0);
action_icon = icon_factory.GetIcon(0);
action_icon_current_id = action_icon.ToSkBitmap()->getGenerationID();
EXPECT_GT(action_icon_current_id, action_icon_last_id);
action_icon_last_id = action_icon_current_id;
VerifyIconsMatch(last_bar_icon, action_icon);
// Check that only the smaller size was set (only a 21px icon was provided).
EXPECT_TRUE(action_icon.ToImageSkia()->HasRepresentation(kSmallIconScale));
EXPECT_FALSE(action_icon.ToImageSkia()->HasRepresentation(kLargeIconScale));
// Tell the extension to update the icon using dictionary of paths, but
// setting only one size.
GetBrowserActionsBar()->Press(0);
ASSERT_TRUE(catcher.GetNextResult());
EXPECT_FALSE(gfx::test::AreImagesEqual(last_bar_icon,
GetBrowserActionsBar()->GetIcon(0)));
last_bar_icon = GetBrowserActionsBar()->GetIcon(0);
action_icon = icon_factory.GetIcon(0);
action_icon_current_id = action_icon.ToSkBitmap()->getGenerationID();
EXPECT_GT(action_icon_current_id, action_icon_last_id);
action_icon_last_id = action_icon_current_id;
VerifyIconsMatch(last_bar_icon, action_icon);
// Check that only the smaller size was set (only a 21px icon was provided).
EXPECT_TRUE(action_icon.ToImageSkia()->HasRepresentation(kSmallIconScale));
EXPECT_FALSE(action_icon.ToImageSkia()->HasRepresentation(kLargeIconScale));
// Tell the extension to update the icon using dictionary of ImageData
// objects, but setting only size 42.
GetBrowserActionsBar()->Press(0);
ASSERT_TRUE(catcher.GetNextResult());
EXPECT_FALSE(gfx::test::AreImagesEqual(last_bar_icon,
GetBrowserActionsBar()->GetIcon(0)));
last_bar_icon = GetBrowserActionsBar()->GetIcon(0);
action_icon = icon_factory.GetIcon(0);
action_icon_current_id = action_icon.ToSkBitmap()->getGenerationID();
EXPECT_GT(action_icon_current_id, action_icon_last_id);
action_icon_last_id = action_icon_current_id;
// Check that only the larger size was set (only a 42px icon was provided).
EXPECT_FALSE(action_icon.ToImageSkia()->HasRepresentation(kSmallIconScale));
EXPECT_TRUE(action_icon.AsImageSkia().HasRepresentation(kLargeIconScale));
// Try setting icon with empty dictionary of ImageData objects.
GetBrowserActionsBar()->Press(0);
ASSERT_FALSE(catcher.GetNextResult());
EXPECT_EQ(kEmptyImageDataError, catcher.message());
// Try setting icon with empty dictionary of path objects.
GetBrowserActionsBar()->Press(0);
ASSERT_FALSE(catcher.GetNextResult());
EXPECT_EQ(kEmptyPathError, catcher.message());
}
IN_PROC_BROWSER_TEST_F(BrowserActionApiCanvasTest, InvisibleIconBrowserAction) {
// Turn this on so errors are reported.
ExtensionActionSetIconFunction::SetReportErrorForInvisibleIconForTesting(
true);
ASSERT_TRUE(RunExtensionTest("browser_action/invisible_icon")) << message_;
const Extension* extension = GetSingleLoadedExtension();
ASSERT_TRUE(extension) << message_;
// Test there is a browser action in the toolbar.
ASSERT_EQ(1, GetBrowserActionsBar()->NumberOfBrowserActions());
EXPECT_TRUE(GetBrowserActionsBar()->HasIcon(0));
gfx::Image initial_bar_icon = GetBrowserActionsBar()->GetIcon(0);
ExtensionHost* background_page =
ProcessManager::Get(profile())->GetBackgroundHostForExtension(
extension->id());
ASSERT_TRUE(background_page);
static constexpr char kScript[] =
"setIcon(%s).then(function(arg) {"
" domAutomationController.send(arg);"
"});";
const std::string histogram_name =
"Extensions.DynamicExtensionActionIconWasVisible";
const std::string new_histogram_name =
"Extensions.DynamicExtensionActionIconWasVisibleRendered";
{
base::HistogramTester histogram_tester;
std::string result;
EXPECT_TRUE(ExecuteScriptAndExtractString(
background_page->host_contents(),
base::StringPrintf(kScript, "invisibleImageData"), &result));
EXPECT_EQ("Icon not sufficiently visible.", result);
// The icon should not have changed.
EXPECT_TRUE(gfx::test::AreImagesEqual(initial_bar_icon,
GetBrowserActionsBar()->GetIcon(0)));
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)));
}
{
base::HistogramTester histogram_tester;
std::string result;
EXPECT_TRUE(ExecuteScriptAndExtractString(
background_page->host_contents(),
base::StringPrintf(kScript, "visibleImageData"), &result));
EXPECT_EQ("", result);
// The icon should have changed.
EXPECT_FALSE(gfx::test::AreImagesEqual(initial_bar_icon,
GetBrowserActionsBar()->GetIcon(0)));
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)));
}
}
IN_PROC_BROWSER_TEST_P(BrowserActionApiLazyTest,
TabSpecificBrowserActionState) {
ASSERT_TRUE(RunExtensionTest("browser_action/tab_specific_state")) <<
message_;
const Extension* extension = GetSingleLoadedExtension();
ASSERT_TRUE(extension) << message_;
// Test that there is a browser action in the toolbar and that it has an icon.
ASSERT_EQ(1, GetBrowserActionsBar()->NumberOfBrowserActions());
EXPECT_TRUE(GetBrowserActionsBar()->HasIcon(0));
// Execute the action, its title should change.
ResultCatcher catcher;
GetBrowserActionsBar()->Press(0);
ASSERT_TRUE(catcher.GetNextResult());
EXPECT_EQ("Showing icon 2", GetBrowserActionsBar()->GetTooltip(0));
// Open a new tab, the title should go back.
chrome::NewTab(browser());
EXPECT_EQ("hi!", GetBrowserActionsBar()->GetTooltip(0));
// Go back to first tab, changed title should reappear.
browser()->tab_strip_model()->ActivateTabAt(
0, {TabStripModel::GestureType::kOther});
EXPECT_EQ("Showing icon 2", GetBrowserActionsBar()->GetTooltip(0));
// Reload that tab, default title should come back.
ui_test_utils::NavigateToURL(browser(), GURL("about:blank"));
EXPECT_EQ("hi!", GetBrowserActionsBar()->GetTooltip(0));
}
// Test that calling chrome.browserAction.setIcon() can set the icon for
// extension.
IN_PROC_BROWSER_TEST_P(BrowserActionApiLazyTest, BrowserActionSetIcon) {
ASSERT_TRUE(RunExtensionTest("browser_action/set_icon")) << message_;
const Extension* extension = GetSingleLoadedExtension();
ASSERT_TRUE(extension) << message_;
int tab_id = ExtensionTabUtil::GetTabId(
browser()->tab_strip_model()->GetActiveWebContents());
ExtensionAction* browser_action = GetBrowserAction(browser(), *extension);
ASSERT_TRUE(browser_action)
<< "Browser action test extension should have a browser action.";
EXPECT_FALSE(browser_action->default_icon());
EXPECT_EQ(0u,
browser_action->GetExplicitlySetIcon(tab_id).RepresentationCount());
// Simulate a click on the browser action icon. The onClicked handler will
// call setIcon().
{
ResultCatcher catcher;
GetBrowserActionsBar()->Press(0);
ASSERT_TRUE(catcher.GetNextResult());
}
// The call to setIcon in background.html set an icon, so the
// current tab's setting should have changed, but the default setting
// should not have changed.
EXPECT_FALSE(browser_action->default_icon());
EXPECT_EQ(1u,
browser_action->GetExplicitlySetIcon(tab_id).RepresentationCount());
}
// Test that calling chrome.browserAction.setPopup() can enable and change
// a popup.
IN_PROC_BROWSER_TEST_P(BrowserActionApiLazyTest, BrowserActionAddPopup) {
ASSERT_TRUE(RunExtensionTest("browser_action/add_popup")) << message_;
const Extension* extension = GetSingleLoadedExtension();
ASSERT_TRUE(extension) << message_;
int tab_id = ExtensionTabUtil::GetTabId(
browser()->tab_strip_model()->GetActiveWebContents());
ExtensionAction* browser_action = GetBrowserAction(browser(), *extension);
ASSERT_TRUE(browser_action)
<< "Browser action test extension should have a browser action.";
ASSERT_FALSE(browser_action->HasPopup(tab_id));
ASSERT_FALSE(browser_action->HasPopup(ExtensionAction::kDefaultTabId));
// Simulate a click on the browser action icon. The onClicked handler
// will add a popup.
{
ResultCatcher catcher;
GetBrowserActionsBar()->Press(0);
ASSERT_TRUE(catcher.GetNextResult());
}
// The call to setPopup in background.html set a tab id, so the
// current tab's setting should have changed, but the default setting
// should not have changed.
ASSERT_TRUE(browser_action->HasPopup(tab_id))
<< "Clicking on the browser action should have caused a popup to "
<< "be added.";
ASSERT_FALSE(browser_action->HasPopup(ExtensionAction::kDefaultTabId))
<< "Clicking on the browser action should not have set a default "
<< "popup.";
ASSERT_STREQ("/a_popup.html",
browser_action->GetPopupUrl(tab_id).path().c_str());
// Now change the popup from a_popup.html to another_popup.html by loading
// a page which removes the popup using chrome.browserAction.setPopup().
{
ResultCatcher catcher;
ui_test_utils::NavigateToURL(
browser(),
GURL(extension->GetResourceURL("change_popup.html")));
ASSERT_TRUE(catcher.GetNextResult());
}
// The call to setPopup in change_popup.html did not use a tab id,
// so the default setting should have changed as well as the current tab.
ASSERT_TRUE(browser_action->HasPopup(tab_id));
ASSERT_TRUE(browser_action->HasPopup(ExtensionAction::kDefaultTabId));
ASSERT_STREQ("/another_popup.html",
browser_action->GetPopupUrl(tab_id).path().c_str());
}
// Test that calling chrome.browserAction.setPopup() can remove a popup.
IN_PROC_BROWSER_TEST_P(BrowserActionApiLazyTest, BrowserActionRemovePopup) {
// Load the extension, which has a browser action with a default popup.
ASSERT_TRUE(RunExtensionTest("browser_action/remove_popup")) << message_;
const Extension* extension = GetSingleLoadedExtension();
ASSERT_TRUE(extension) << message_;
int tab_id = ExtensionTabUtil::GetTabId(
browser()->tab_strip_model()->GetActiveWebContents());
ExtensionAction* browser_action = GetBrowserAction(browser(), *extension);
ASSERT_TRUE(browser_action)
<< "Browser action test extension should have a browser action.";
ASSERT_TRUE(browser_action->HasPopup(tab_id))
<< "Expect a browser action popup before the test removes it.";
ASSERT_TRUE(browser_action->HasPopup(ExtensionAction::kDefaultTabId))
<< "Expect a browser action popup is the default for all tabs.";
// Load a page which removes the popup using chrome.browserAction.setPopup().
{
ResultCatcher catcher;
ui_test_utils::NavigateToURL(
browser(),
GURL(extension->GetResourceURL("remove_popup.html")));
ASSERT_TRUE(catcher.GetNextResult());
}
ASSERT_FALSE(browser_action->HasPopup(tab_id))
<< "Browser action popup should have been removed.";
ASSERT_TRUE(browser_action->HasPopup(ExtensionAction::kDefaultTabId))
<< "Browser action popup default should not be changed by setting "
<< "a specific tab id.";
}
IN_PROC_BROWSER_TEST_P(BrowserActionApiLazyTest, IncognitoBasic) {
ExtensionTestMessageListener ready_listener("ready", false);
ASSERT_TRUE(embedded_test_server()->Start());
scoped_refptr<const Extension> extension = LoadExtensionWithParamFlags(
test_data_dir_.AppendASCII("browser_action/basics"));
ASSERT_TRUE(extension) << message_;
// Test that there is a browser action in the toolbar.
ASSERT_EQ(1, GetBrowserActionsBar()->NumberOfBrowserActions());
// Open an incognito window and test that the browser action isn't there by
// default.
Browser* incognito_browser = CreateIncognitoBrowser(browser()->profile());
ASSERT_EQ(0, ExtensionActionTestHelper::Create(incognito_browser)
->NumberOfBrowserActions());
ASSERT_TRUE(ready_listener.WaitUntilSatisfied());
// Now enable the extension in incognito mode, and test that the browser
// action shows up.
// SetIsIncognitoEnabled() requires a reload of the extension, so we have to
// wait for it.
ExtensionTestMessageListener incognito_ready_listener("ready", false);
TestExtensionRegistryObserver registry_observer(
ExtensionRegistry::Get(profile()), extension->id());
extensions::util::SetIsIncognitoEnabled(
extension->id(), browser()->profile(), true);
extension = registry_observer.WaitForExtensionLoaded();
ASSERT_EQ(1, ExtensionActionTestHelper::Create(incognito_browser)
->NumberOfBrowserActions());
ASSERT_TRUE(incognito_ready_listener.WaitUntilSatisfied());
// Open a URL in the tab, so the event handler can check the tab's
// "url" and "title" properties.
ui_test_utils::NavigateToURL(
incognito_browser,
embedded_test_server()->GetURL("/extensions/test_file.txt"));
ResultCatcher catcher;
// Simulate the browser action being clicked.
ExecuteExtensionAction(incognito_browser, extension.get());
EXPECT_TRUE(catcher.GetNextResult());
}
IN_PROC_BROWSER_TEST_P(BrowserActionApiLazyTest, IncognitoUpdate) {
ASSERT_TRUE(embedded_test_server()->Start());
ExtensionTestMessageListener incognito_not_allowed_listener(
"incognito not allowed", false);
scoped_refptr<const Extension> extension = LoadExtensionWithParamFlags(
test_data_dir_.AppendASCII("browser_action/update"));
ASSERT_TRUE(extension) << message_;
ASSERT_TRUE(incognito_not_allowed_listener.WaitUntilSatisfied());
// Test that there is a browser action in the toolbar.
ASSERT_EQ(1, GetBrowserActionsBar()->NumberOfBrowserActions());
// Open an incognito window and test that the browser action isn't there by
// default.
Browser* incognito_browser = CreateIncognitoBrowser(browser()->profile());
ASSERT_EQ(0, ExtensionActionTestHelper::Create(incognito_browser)
->NumberOfBrowserActions());
// Set up a listener so we can reply for the extension to do the update.
// This listener also adds a sequence point between the browser and the
// renderer for the transition between incognito mode not being allowed
// and it's being allowed. This ensures the browser ignores the renderer's
// execution until the transition is completed, since the script will
// start and stop multiple times during the initial load of the extension
// and the enabling of incognito mode.
ExtensionTestMessageListener incognito_allowed_listener("incognito allowed",
true);
// Now enable the extension in incognito mode, and test that the browser
// action shows up. SetIsIncognitoEnabled() requires a reload of the
// extension, so we have to wait for it to finish.
TestExtensionRegistryObserver registry_observer(
ExtensionRegistry::Get(profile()), extension->id());
extensions::util::SetIsIncognitoEnabled(extension->id(), browser()->profile(),
true);
extension = registry_observer.WaitForExtensionLoaded();
ASSERT_TRUE(extension);
ASSERT_EQ(1, ExtensionActionTestHelper::Create(incognito_browser)
->NumberOfBrowserActions());
ASSERT_TRUE(incognito_allowed_listener.WaitUntilSatisfied());
ExtensionAction* action = GetBrowserAction(incognito_browser, *extension);
EXPECT_EQ("This is the default title.",
action->GetTitle(ExtensionAction::kDefaultTabId));
EXPECT_EQ("",
action->GetExplicitlySetBadgeText(ExtensionAction::kDefaultTabId));
EXPECT_EQ(SkColorSetARGB(0, 0, 0, 0),
action->GetBadgeBackgroundColor(ExtensionAction::kDefaultTabId));
// Tell the extension to update the browser action state and then
// catch the result.
ResultCatcher incognito_catcher;
incognito_allowed_listener.Reply("incognito update");
ASSERT_TRUE(incognito_catcher.GetNextResult());
// Test that we received the changes.
EXPECT_EQ("Modified", action->GetTitle(ExtensionAction::kDefaultTabId));
EXPECT_EQ("badge",
action->GetExplicitlySetBadgeText(ExtensionAction::kDefaultTabId));
EXPECT_EQ(SkColorSetARGB(255, 255, 255, 255),
action->GetBadgeBackgroundColor(ExtensionAction::kDefaultTabId));
}
// Tests that events are dispatched to the correct profile for split mode
// extensions.
IN_PROC_BROWSER_TEST_P(BrowserActionApiLazyTest, IncognitoSplit) {
ResultCatcher catcher;
const Extension* extension = LoadExtensionWithFlags(
test_data_dir_.AppendASCII("browser_action/split_mode"),
kFlagEnableIncognito);
ASSERT_TRUE(extension) << message_;
// Open an incognito browser.
Browser* incognito_browser = CreateIncognitoBrowser(browser()->profile());
ASSERT_EQ(1, ExtensionActionTestHelper::Create(incognito_browser)
->NumberOfBrowserActions());
// A click in the regular profile should open a tab in the regular profile.
ExecuteExtensionAction(browser(), extension);
ASSERT_TRUE(catcher.GetNextResult()) << catcher.message();
// A click in the incognito profile should open a tab in the
// incognito profile.
ExecuteExtensionAction(incognito_browser, extension);
ASSERT_TRUE(catcher.GetNextResult()) << catcher.message();
}
IN_PROC_BROWSER_TEST_F(BrowserActionApiTest, CloseBackgroundPage) {
ExtensionTestMessageListener listener("ready", /*will_reply=*/false);
ASSERT_TRUE(LoadExtension(
test_data_dir_.AppendASCII("browser_action/close_background")));
const Extension* extension = GetSingleLoadedExtension();
ASSERT_TRUE(listener.WaitUntilSatisfied());
// There is a background page and a browser action with no badge text.
extensions::ProcessManager* manager =
extensions::ProcessManager::Get(browser()->profile());
ExtensionHost* extension_host =
manager->GetBackgroundHostForExtension(extension->id());
ASSERT_TRUE(extension_host);
ExtensionAction* action = GetBrowserAction(browser(), *extension);
ASSERT_EQ("",
action->GetExplicitlySetBadgeText(ExtensionAction::kDefaultTabId));
// A helper class to wait for the ExtensionHost to shut down.
// TODO(devlin): Hoist this somewhere more common and track down other similar
// usages.
class ExtensionHostDestructionObserver : public ExtensionHostObserver {
public:
explicit ExtensionHostDestructionObserver(ExtensionHost* host) {
host_observer_.Add(host);
}
ExtensionHostDestructionObserver(
const ExtensionHostDestructionObserver& other) = delete;
ExtensionHostDestructionObserver& operator=(
const ExtensionHostDestructionObserver& other) = delete;
~ExtensionHostDestructionObserver() override = default;
void OnExtensionHostDestroyed(ExtensionHost* host) override {
ASSERT_TRUE(host_observer_.IsObserving(host));
host_observer_.Remove(host);
run_loop_.QuitWhenIdle();
}
void Wait() { run_loop_.Run(); }
private:
base::RunLoop run_loop_;
ScopedObserver<ExtensionHost, ExtensionHostObserver> host_observer_{this};
};
ExtensionHostDestructionObserver host_destroyed_observer(extension_host);
// Click the browser action.
ExecuteExtensionAction(browser(), extension);
host_destroyed_observer.Wait();
EXPECT_FALSE(manager->GetBackgroundHostForExtension(extension->id()));
EXPECT_EQ("X",
action->GetExplicitlySetBadgeText(ExtensionAction::kDefaultTabId));
}
IN_PROC_BROWSER_TEST_P(BrowserActionApiLazyTest, BadgeBackgroundColor) {
ASSERT_TRUE(embedded_test_server()->Start());
ASSERT_TRUE(RunExtensionTest("browser_action/color")) << message_;
const Extension* extension = GetSingleLoadedExtension();
ASSERT_TRUE(extension) << message_;
// Test that there is a browser action in the toolbar.
ASSERT_EQ(1, GetBrowserActionsBar()->NumberOfBrowserActions());
// Test that CSS values (#FF0000) set color correctly.
ExtensionAction* action = GetBrowserAction(browser(), *extension);
ASSERT_EQ(SkColorSetARGB(255, 255, 0, 0),
action->GetBadgeBackgroundColor(ExtensionAction::kDefaultTabId));
// Tell the extension to update the browser action state.
ResultCatcher catcher;
ui_test_utils::NavigateToURL(browser(),
GURL(extension->GetResourceURL("update.html")));
ASSERT_TRUE(catcher.GetNextResult());
// Test that CSS values (#0F0) set color correctly.
ASSERT_EQ(SkColorSetARGB(255, 0, 255, 0),
action->GetBadgeBackgroundColor(ExtensionAction::kDefaultTabId));
ui_test_utils::NavigateToURL(browser(),
GURL(extension->GetResourceURL("update2.html")));
ASSERT_TRUE(catcher.GetNextResult());
// Test that array values set color correctly.
ASSERT_EQ(SkColorSetARGB(255, 255, 255, 255),
action->GetBadgeBackgroundColor(ExtensionAction::kDefaultTabId));
ui_test_utils::NavigateToURL(browser(),
GURL(extension->GetResourceURL("update3.html")));
ASSERT_TRUE(catcher.GetNextResult());
// Test that hsl() values 'hsl(120, 100%, 50%)' set color correctly.
ASSERT_EQ(SkColorSetARGB(255, 0, 255, 0),
action->GetBadgeBackgroundColor(ExtensionAction::kDefaultTabId));
// Test basic color keyword set correctly.
ui_test_utils::NavigateToURL(browser(),
GURL(extension->GetResourceURL("update4.html")));
ASSERT_TRUE(catcher.GetNextResult());
ASSERT_EQ(SkColorSetARGB(255, 0, 0, 255),
action->GetBadgeBackgroundColor(ExtensionAction::kDefaultTabId));
}
IN_PROC_BROWSER_TEST_P(BrowserActionApiLazyTest, Getters) {
ASSERT_TRUE(RunExtensionTest("browser_action/getters")) << message_;
const Extension* extension = GetSingleLoadedExtension();
ASSERT_TRUE(extension) << message_;
// Test that there is a browser action in the toolbar.
ASSERT_EQ(1, GetBrowserActionsBar()->NumberOfBrowserActions());
// Test the getters for defaults.
ResultCatcher catcher;
ui_test_utils::NavigateToURL(browser(),
GURL(extension->GetResourceURL("update.html")));
ASSERT_TRUE(catcher.GetNextResult());
// Test the getters for a specific tab.
ui_test_utils::NavigateToURL(browser(),
GURL(extension->GetResourceURL("update2.html")));
ASSERT_TRUE(catcher.GetNextResult());
}
// Verify triggering browser action.
IN_PROC_BROWSER_TEST_P(BrowserActionApiLazyTest, TestTriggerBrowserAction) {
ASSERT_TRUE(embedded_test_server()->Start());
ASSERT_TRUE(RunExtensionTest("trigger_actions/browser_action")) << message_;
const Extension* extension = GetSingleLoadedExtension();
ASSERT_TRUE(extension) << message_;
// Test that there is a browser action in the toolbar.
ASSERT_EQ(1, GetBrowserActionsBar()->NumberOfBrowserActions());
ui_test_utils::NavigateToURL(browser(),
embedded_test_server()->GetURL("/simple.html"));
ExtensionAction* browser_action = GetBrowserAction(browser(), *extension);
EXPECT_TRUE(browser_action);
// Simulate a click on the browser action icon.
{
ResultCatcher catcher;
GetBrowserActionsBar()->Press(0);
EXPECT_TRUE(catcher.GetNextResult());
}
WebContents* tab =
browser()->tab_strip_model()->GetActiveWebContents();
EXPECT_TRUE(tab);
// Verify that the browser action turned the background color red.
const std::string script =
"window.domAutomationController.send(document.body.style."
"backgroundColor);";
std::string result;
EXPECT_TRUE(content::ExecuteScriptAndExtractString(tab, script, &result));
EXPECT_EQ(result, "red");
}
IN_PROC_BROWSER_TEST_P(BrowserActionApiLazyTest,
BrowserActionWithRectangularIcon) {
ExtensionTestMessageListener ready_listener("ready", true);
const Extension* extension = LoadExtension(
test_data_dir_.AppendASCII("browser_action").AppendASCII("rect_icon"));
ASSERT_TRUE(extension);
EXPECT_TRUE(ready_listener.WaitUntilSatisfied());
// Wait for the default icon to load before accessing the underlying
// gfx::Image.
TestIconImageObserver::WaitForExtensionActionIcon(extension, profile());
gfx::Image first_icon = GetBrowserActionsBar()->GetIcon(0);
ASSERT_FALSE(first_icon.IsEmpty());
TestExtensionActionAPIObserver observer(profile(), extension->id());
ResultCatcher catcher;
ready_listener.Reply(std::string());
EXPECT_TRUE(catcher.GetNextResult());
// Wait for extension action to be updated.
observer.Wait();
gfx::Image next_icon = GetBrowserActionsBar()->GetIcon(0);
ASSERT_FALSE(next_icon.IsEmpty());
EXPECT_FALSE(gfx::test::AreImagesEqual(first_icon, next_icon));
}
// Verify video can enter and exit Picture-in_Picture when browser action icon
// is clicked.
IN_PROC_BROWSER_TEST_F(BrowserActionApiTest,
TestPictureInPictureOnBrowserActionIconClick) {
ASSERT_TRUE(StartEmbeddedTestServer());
ASSERT_TRUE(
RunExtensionTest("trigger_actions/browser_action_picture_in_picture"))
<< message_;
const Extension* extension = GetSingleLoadedExtension();
ASSERT_TRUE(extension) << message_;
// Test that there is a browser action in the toolbar.
ASSERT_EQ(1, GetBrowserActionsBar()->NumberOfBrowserActions());
ExtensionAction* browser_action = GetBrowserAction(browser(), *extension);
EXPECT_TRUE(browser_action);
// Find the background page.
ProcessManager* process_manager =
extensions::ProcessManager::Get(browser()->profile());
content::WebContents* web_contents =
process_manager->GetBackgroundHostForExtension(extension->id())
->web_contents();
ASSERT_TRUE(web_contents);
content::PictureInPictureWindowController* window_controller =
content::PictureInPictureWindowController::GetOrCreateForWebContents(
web_contents);
ASSERT_TRUE(window_controller->GetWindowForTesting());
EXPECT_FALSE(window_controller->GetWindowForTesting()->IsVisible());
// Click on the browser action icon to enter Picture-in-Picture.
ResultCatcher catcher;
GetBrowserActionsBar()->Press(0);
EXPECT_TRUE(catcher.GetNextResult());
EXPECT_TRUE(window_controller->GetWindowForTesting()->IsVisible());
// Click on the browser action icon to exit Picture-in-Picture.
GetBrowserActionsBar()->Press(0);
EXPECT_TRUE(catcher.GetNextResult());
EXPECT_FALSE(window_controller->GetWindowForTesting()->IsVisible());
}
} // namespace
} // namespace extensions