summaryrefslogtreecommitdiffstats
path: root/chromium/chrome/browser/extensions/api/extension_action
diff options
context:
space:
mode:
authorAllan Sandfeld Jensen <allan.jensen@theqtcompany.com>2016-08-01 12:59:39 +0200
committerAllan Sandfeld Jensen <allan.jensen@qt.io>2016-08-04 12:40:43 +0000
commit28b1110370900897ab652cb420c371fab8857ad4 (patch)
tree41b32127d23b0df4f2add2a27e12dc87bddb260e /chromium/chrome/browser/extensions/api/extension_action
parent399c965b6064c440ddcf4015f5f8e9d131c7a0a6 (diff)
BASELINE: Update Chromium to 53.0.2785.41
Also adds a few extra files for extensions. Change-Id: Iccdd55d98660903331cf8b7b29188da781830af4 Reviewed-by: Michael BrĂ¼ning <michael.bruning@qt.io>
Diffstat (limited to 'chromium/chrome/browser/extensions/api/extension_action')
-rw-r--r--chromium/chrome/browser/extensions/api/extension_action/OWNERS1
-rw-r--r--chromium/chrome/browser/extensions/api/extension_action/browser_action_apitest.cc799
-rw-r--r--chromium/chrome/browser/extensions/api/extension_action/browser_action_browsertest.cc116
-rw-r--r--chromium/chrome/browser/extensions/api/extension_action/browser_action_interactive_test.cc385
-rw-r--r--chromium/chrome/browser/extensions/api/extension_action/browser_action_unittest.cc39
-rw-r--r--chromium/chrome/browser/extensions/api/extension_action/extension_action_api.cc639
-rw-r--r--chromium/chrome/browser/extensions/api/extension_action/extension_action_api.h450
-rw-r--r--chromium/chrome/browser/extensions/api/extension_action/extension_action_prefs_unittest.cc66
-rw-r--r--chromium/chrome/browser/extensions/api/extension_action/extension_page_actions_api_constants.cc15
-rw-r--r--chromium/chrome/browser/extensions/api/extension_action/extension_page_actions_api_constants.h21
-rw-r--r--chromium/chrome/browser/extensions/api/extension_action/page_action_apitest.cc251
11 files changed, 2782 insertions, 0 deletions
diff --git a/chromium/chrome/browser/extensions/api/extension_action/OWNERS b/chromium/chrome/browser/extensions/api/extension_action/OWNERS
new file mode 100644
index 00000000000..739c305cfca
--- /dev/null
+++ b/chromium/chrome/browser/extensions/api/extension_action/OWNERS
@@ -0,0 +1 @@
+jyasskin@chromium.org
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
new file mode 100644
index 00000000000..0ae5d37aee4
--- /dev/null
+++ b/chromium/chrome/browser/extensions/api/extension_action/browser_action_apitest.cc
@@ -0,0 +1,799 @@
+// 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/macros.h"
+#include "build/build_config.h"
+#include "chrome/browser/extensions/api/extension_action/extension_action_api.h"
+#include "chrome/browser/extensions/browser_action_test_util.h"
+#include "chrome/browser/extensions/extension_action.h"
+#include "chrome/browser/extensions/extension_action_icon_factory.h"
+#include "chrome/browser/extensions/extension_action_manager.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/tabs/tab_strip_model.h"
+#include "chrome/common/url_constants.h"
+#include "chrome/test/base/ui_test_utils.h"
+#include "content/public/browser/notification_service.h"
+#include "content/public/browser/render_frame_host.h"
+#include "content/public/browser/web_contents.h"
+#include "content/public/test/browser_test_utils.h"
+#include "extensions/browser/extension_registry.h"
+#include "extensions/browser/extension_system.h"
+#include "extensions/browser/notification_types.h"
+#include "extensions/browser/process_manager.h"
+#include "extensions/browser/test_extension_registry_observer.h"
+#include "extensions/common/feature_switch.h"
+#include "extensions/test/extension_test_message_listener.h"
+#include "extensions/test/result_catcher.h"
+#include "grit/theme_resources.h"
+#include "net/dns/mock_host_resolver.h"
+#include "net/test/embedded_test_server/embedded_test_server.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);
+}
+
+// An ImageSkia source that will do nothing (i.e., have a blank skia). We need
+// this because we need a blank canvas at a certain size, and that can't be done
+// by just using a null ImageSkia.
+class BlankImageSource : public gfx::CanvasImageSource {
+ public:
+ explicit BlankImageSource(const gfx::Size& size)
+ : gfx::CanvasImageSource(size, false) {}
+ ~BlankImageSource() override {}
+
+ void Draw(gfx::Canvas* canvas) override {}
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(BlankImageSource);
+};
+
+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).sk_bitmap(),
+ gfx::ImageSkiaOperations::ExtractSubset(bar_rendering.AsImageSkia(),
+ icon_portion)
+ .GetRepresentation(1.0f)
+ .sk_bitmap()));
+}
+
+class BrowserActionApiTest : public ExtensionApiTest {
+ public:
+ BrowserActionApiTest() {}
+ ~BrowserActionApiTest() override {}
+
+ protected:
+ BrowserActionTestUtil* GetBrowserActionsBar() {
+ if (!browser_action_test_util_)
+ browser_action_test_util_.reset(new BrowserActionTestUtil(browser()));
+ return browser_action_test_util_.get();
+ }
+
+ bool OpenPopup(int index) {
+ ResultCatcher catcher;
+ content::WindowedNotificationObserver popup_observer(
+ content::NOTIFICATION_LOAD_COMPLETED_MAIN_FRAME,
+ content::NotificationService::AllSources());
+ GetBrowserActionsBar()->Press(index);
+ popup_observer.Wait();
+ EXPECT_TRUE(catcher.GetNextResult()) << catcher.message();
+ return GetBrowserActionsBar()->HasPopup();
+ }
+
+ ExtensionAction* GetBrowserAction(const Extension& extension) {
+ return ExtensionActionManager::Get(browser()->profile())->
+ GetBrowserAction(extension);
+ }
+
+ private:
+ std::unique_ptr<BrowserActionTestUtil> browser_action_test_util_;
+
+ DISALLOW_COPY_AND_ASSIGN(BrowserActionApiTest);
+};
+
+IN_PROC_BROWSER_TEST_F(BrowserActionApiTest, Basic) {
+ ASSERT_TRUE(embedded_test_server()->Start());
+ ASSERT_TRUE(RunExtensionTest("browser_action/basics")) << message_;
+ const Extension* extension = GetSingleLoadedExtension();
+ ASSERT_TRUE(extension) << message_;
+
+ // Test that there is a browser action in the toolbar.
+ ASSERT_EQ(1, GetBrowserActionsBar()->NumberOfBrowserActions());
+
+ // 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()) << catcher.message();
+
+ // Test that we received the changes.
+ ExtensionAction* action = GetBrowserAction(*extension);
+ ASSERT_EQ("Modified", action->GetTitle(ExtensionAction::kDefaultTabId));
+ ASSERT_EQ("badge", action->GetBadgeText(ExtensionAction::kDefaultTabId));
+ ASSERT_EQ(SkColorSetARGB(255, 255, 255, 255),
+ action->GetBadgeBackgroundColor(ExtensionAction::kDefaultTabId));
+
+ // Simulate the browser action being clicked.
+ ui_test_utils::NavigateToURL(
+ browser(), embedded_test_server()->GetURL("/extensions/test_file.txt"));
+
+ ExecuteExtensionAction(browser(), extension);
+
+ ASSERT_TRUE(catcher.GetNextResult()) << catcher.message();
+}
+
+IN_PROC_BROWSER_TEST_F(BrowserActionApiTest, DynamicBrowserAction) {
+ ASSERT_TRUE(RunExtensionTest("browser_action/no_icon")) << message_;
+ const Extension* extension = GetSingleLoadedExtension();
+ ASSERT_TRUE(extension) << message_;
+
+#if defined (OS_MACOSX)
+ // 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(*extension),
+ NULL);
+ // 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(BrowserActionApiTest, 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, true);
+ 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));
+}
+
+// http://code.google.com/p/chromium/issues/detail?id=70829
+// Mac used to be ok, but then mac 10.5 started failing too. =(
+IN_PROC_BROWSER_TEST_F(BrowserActionApiTest, DISABLED_BrowserActionPopup) {
+ ASSERT_TRUE(
+ LoadExtension(test_data_dir_.AppendASCII("browser_action/popup")));
+ BrowserActionTestUtil* actions_bar = GetBrowserActionsBar();
+ const Extension* extension = GetSingleLoadedExtension();
+ ASSERT_TRUE(extension) << message_;
+
+ // The extension's popup's size grows by |growFactor| each click.
+ const int growFactor = 500;
+ gfx::Size minSize = BrowserActionTestUtil::GetMinPopupSize();
+ gfx::Size middleSize = gfx::Size(growFactor, growFactor);
+ gfx::Size maxSize = BrowserActionTestUtil::GetMaxPopupSize();
+
+ // Ensure that two clicks will exceed the maximum allowed size.
+ ASSERT_GT(minSize.height() + growFactor * 2, maxSize.height());
+ ASSERT_GT(minSize.width() + growFactor * 2, maxSize.width());
+
+ // Simulate a click on the browser action and verify the size of the resulting
+ // popup. The first one tries to be 0x0, so it should be the min values.
+ ASSERT_TRUE(OpenPopup(0));
+ EXPECT_EQ(minSize, actions_bar->GetPopupSize());
+ EXPECT_TRUE(actions_bar->HidePopup());
+
+ ASSERT_TRUE(OpenPopup(0));
+ EXPECT_EQ(middleSize, actions_bar->GetPopupSize());
+ EXPECT_TRUE(actions_bar->HidePopup());
+
+ // One more time, but this time it should be constrained by the max values.
+ ASSERT_TRUE(OpenPopup(0));
+ EXPECT_EQ(maxSize, actions_bar->GetPopupSize());
+ EXPECT_TRUE(actions_bar->HidePopup());
+}
+
+// Test that calling chrome.browserAction.setPopup() can enable and change
+// a popup.
+IN_PROC_BROWSER_TEST_F(BrowserActionApiTest, 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(*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_F(BrowserActionApiTest, 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(*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_F(BrowserActionApiTest, IncognitoBasic) {
+ ASSERT_TRUE(embedded_test_server()->Start());
+
+ ASSERT_TRUE(RunExtensionTest("browser_action/basics")) << message_;
+ const Extension* extension = GetSingleLoadedExtension();
+ 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.
+ Profile* incognito_profile = browser()->profile()->GetOffTheRecordProfile();
+ base::RunLoop().RunUntilIdle(); // Wait for profile initialization.
+ Browser* incognito_browser =
+ new Browser(Browser::CreateParams(incognito_profile));
+
+ ASSERT_EQ(0,
+ BrowserActionTestUtil(incognito_browser).NumberOfBrowserActions());
+
+ // 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.
+ TestExtensionRegistryObserver registry_observer(
+ ExtensionRegistry::Get(profile()), extension->id());
+ extensions::util::SetIsIncognitoEnabled(
+ extension->id(), browser()->profile(), true);
+ registry_observer.WaitForExtensionLoaded();
+
+ ASSERT_EQ(1,
+ BrowserActionTestUtil(incognito_browser).NumberOfBrowserActions());
+
+ // TODO(mpcomplete): simulate a click and have it do the right thing in
+ // incognito.
+}
+
+// Tests that events are dispatched to the correct profile for split mode
+// extensions.
+IN_PROC_BROWSER_TEST_F(BrowserActionApiTest, IncognitoSplit) {
+ ResultCatcher catcher;
+ const Extension* extension = LoadExtensionWithFlags(
+ test_data_dir_.AppendASCII("browser_action/split_mode"),
+ kFlagEnableIncognito);
+ ASSERT_TRUE(extension) << message_;
+
+ // Open an incognito window.
+ Profile* incognito_profile = browser()->profile()->GetOffTheRecordProfile();
+ Browser* incognito_browser =
+ new Browser(Browser::CreateParams(incognito_profile));
+ base::RunLoop().RunUntilIdle(); // Wait for profile initialization.
+ // Navigate just to have a tab in this window, otherwise wonky things happen.
+ OpenURLOffTheRecord(browser()->profile(), GURL("about:blank"));
+ ASSERT_EQ(1,
+ BrowserActionTestUtil(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();
+}
+
+// Disabled because of failures (crashes) on ASAN bot.
+// See http://crbug.com/98861.
+IN_PROC_BROWSER_TEST_F(BrowserActionApiTest, DISABLED_CloseBackgroundPage) {
+ ASSERT_TRUE(LoadExtension(
+ test_data_dir_.AppendASCII("browser_action/close_background")));
+ const Extension* extension = GetSingleLoadedExtension();
+
+ // There is a background page and a browser action with no badge text.
+ extensions::ProcessManager* manager =
+ extensions::ProcessManager::Get(browser()->profile());
+ ASSERT_TRUE(manager->GetBackgroundHostForExtension(extension->id()));
+ ExtensionAction* action = GetBrowserAction(*extension);
+ ASSERT_EQ("", action->GetBadgeText(ExtensionAction::kDefaultTabId));
+
+ content::WindowedNotificationObserver host_destroyed_observer(
+ extensions::NOTIFICATION_EXTENSION_HOST_DESTROYED,
+ content::NotificationService::AllSources());
+
+ // Click the browser action.
+ ExecuteExtensionAction(browser(), extension);
+
+ // It can take a moment for the background page to actually get destroyed
+ // so we wait for the notification before checking that it's really gone
+ // and the badge text has been set.
+ host_destroyed_observer.Wait();
+ ASSERT_FALSE(manager->GetBackgroundHostForExtension(extension->id()));
+ ASSERT_EQ("X", action->GetBadgeText(ExtensionAction::kDefaultTabId));
+}
+
+IN_PROC_BROWSER_TEST_F(BrowserActionApiTest, 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(*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_F(BrowserActionApiTest, 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_F(BrowserActionApiTest, 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(*extension);
+ EXPECT_TRUE(browser_action != NULL);
+
+ // 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 != NULL);
+
+ // 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");
+}
+
+// Test that a browser action popup with a web iframe works correctly. This
+// primarily targets --isolate-extensions and --site-per-process modes, where
+// the iframe runs in a separate process. See https://crbug.com/546267.
+IN_PROC_BROWSER_TEST_F(BrowserActionApiTest, BrowserActionPopupWithIframe) {
+ host_resolver()->AddRule("*", "127.0.0.1");
+ ASSERT_TRUE(embedded_test_server()->Start());
+
+ ASSERT_TRUE(LoadExtension(
+ test_data_dir_.AppendASCII("browser_action/popup_with_iframe")));
+ BrowserActionTestUtil* actions_bar = GetBrowserActionsBar();
+ const Extension* extension = GetSingleLoadedExtension();
+ ASSERT_TRUE(extension) << message_;
+
+ // Simulate a click on the browser action to open the popup.
+ ASSERT_TRUE(OpenPopup(0));
+
+ // Find the RenderFrameHost associated with the iframe in the popup.
+ content::RenderFrameHost* frame_host = nullptr;
+ extensions::ProcessManager* manager =
+ extensions::ProcessManager::Get(browser()->profile());
+ std::set<content::RenderFrameHost*> frame_hosts =
+ manager->GetRenderFrameHostsForExtension(extension->id());
+ for (auto host : frame_hosts) {
+ if (host->GetFrameName() == "child_frame") {
+ frame_host = host;
+ break;
+ }
+ }
+
+ ASSERT_TRUE(frame_host);
+ EXPECT_EQ(extension->GetResourceURL("frame.html"),
+ frame_host->GetLastCommittedURL());
+ EXPECT_TRUE(frame_host->GetParent());
+
+ // Navigate the popup's iframe to a (cross-site) web page, and wait for that
+ // page to send a message, which will ensure that the page has loaded.
+ GURL foo_url(embedded_test_server()->GetURL("foo.com", "/popup_iframe.html"));
+ std::string script = "location.href = '" + foo_url.spec() + "'";
+ std::string result;
+ EXPECT_TRUE(
+ content::ExecuteScriptAndExtractString(frame_host, script, &result));
+ EXPECT_EQ("DONE", result);
+
+ EXPECT_TRUE(actions_bar->HidePopup());
+}
+
+IN_PROC_BROWSER_TEST_F(BrowserActionApiTest, BrowserActionWithRectangularIcon) {
+ ExtensionTestMessageListener ready_listener("ready", true);
+ ASSERT_TRUE(LoadExtension(
+ test_data_dir_.AppendASCII("browser_action").AppendASCII("rect_icon")));
+ EXPECT_TRUE(ready_listener.WaitUntilSatisfied());
+ gfx::Image first_icon = GetBrowserActionsBar()->GetIcon(0);
+ ResultCatcher catcher;
+ ready_listener.Reply(std::string());
+ EXPECT_TRUE(catcher.GetNextResult());
+ gfx::Image next_icon = GetBrowserActionsBar()->GetIcon(0);
+ EXPECT_FALSE(gfx::test::AreImagesEqual(first_icon, next_icon));
+}
+
+// Test that we don't try and show a browser action popup with
+// browserAction.openPopup if there is no toolbar (e.g., for web popup windows).
+// Regression test for crbug.com/584747.
+IN_PROC_BROWSER_TEST_F(BrowserActionApiTest, BrowserActionOpenPopupOnPopup) {
+ // Open a new web popup window.
+ chrome::NavigateParams params(browser(), GURL("http://www.google.com/"),
+ ui::PAGE_TRANSITION_LINK);
+ params.disposition = NEW_POPUP;
+ params.window_action = chrome::NavigateParams::SHOW_WINDOW;
+ ui_test_utils::NavigateToURL(&params);
+ Browser* popup_browser = params.browser;
+ // Verify it is a popup, and it is the active window.
+ ASSERT_TRUE(popup_browser);
+ // The window isn't considered "active" on MacOSX for odd reasons. The more
+ // important test is that it *is* considered the last active browser, since
+ // that's what we check when we try to open the popup.
+#if !defined(OS_MACOSX)
+ EXPECT_TRUE(popup_browser->window()->IsActive());
+#endif
+ EXPECT_FALSE(browser()->window()->IsActive());
+ EXPECT_FALSE(popup_browser->SupportsWindowFeature(Browser::FEATURE_TOOLBAR));
+ EXPECT_EQ(popup_browser,
+ chrome::FindLastActiveWithProfile(browser()->profile()));
+
+ // Load up the extension, which will call chrome.browserAction.openPopup()
+ // when it is loaded and verify that the popup didn't open.
+ ExtensionTestMessageListener listener("ready", true);
+ EXPECT_TRUE(LoadExtension(
+ test_data_dir_.AppendASCII("browser_action/open_popup_on_reply")));
+ EXPECT_TRUE(listener.WaitUntilSatisfied());
+
+ ResultCatcher catcher;
+ listener.Reply(std::string());
+ EXPECT_TRUE(catcher.GetNextResult()) << message_;
+}
+
+} // namespace
+} // namespace extensions
diff --git a/chromium/chrome/browser/extensions/api/extension_action/browser_action_browsertest.cc b/chromium/chrome/browser/extensions/api/extension_action/browser_action_browsertest.cc
new file mode 100644
index 00000000000..d474eff3540
--- /dev/null
+++ b/chromium/chrome/browser/extensions/api/extension_action/browser_action_browsertest.cc
@@ -0,0 +1,116 @@
+// Copyright 2014 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/files/file_path.h"
+#include "base/message_loop/message_loop.h"
+#include "chrome/browser/extensions/extension_action.h"
+#include "chrome/browser/extensions/extension_action_manager.h"
+#include "chrome/browser/extensions/extension_browsertest.h"
+#include "content/public/test/test_utils.h"
+#include "extensions/browser/extension_registry.h"
+#include "extensions/browser/extension_system.h"
+#include "extensions/browser/state_store.h"
+#include "extensions/common/extension.h"
+#include "extensions/test/extension_test_message_listener.h"
+#include "third_party/skia/include/core/SkColor.h"
+
+namespace extensions {
+
+namespace {
+
+// A key into the StateStore; we don't use any results, but need to know when
+// it's initialized.
+const char kBrowserActionStorageKey[] = "browser_action";
+// The name of the extension we add.
+const char kExtensionName[] = "Default Persistence Test Extension";
+
+void QuitMessageLoop(content::MessageLoopRunner* runner,
+ std::unique_ptr<base::Value> value) {
+ runner->Quit();
+}
+
+// We need to wait for the state store to initialize and respond to requests
+// so we can see if the preferences persist. Do this by posting our own request
+// to the state store, which should be handled after all others.
+void WaitForStateStore(Profile* profile, const std::string& extension_id) {
+ scoped_refptr<content::MessageLoopRunner> runner =
+ new content::MessageLoopRunner;
+ ExtensionSystem::Get(profile)->state_store()->GetExtensionValue(
+ extension_id, kBrowserActionStorageKey,
+ base::Bind(&QuitMessageLoop, base::RetainedRef(runner)));
+ runner->Run();
+}
+
+} // namespace
+
+// Setup for the test by loading an extension, which should set the browser
+// action background to blue.
+IN_PROC_BROWSER_TEST_F(ExtensionBrowserTest,
+ PRE_BrowserActionDefaultPersistence) {
+ ExtensionTestMessageListener listener("Background Color Set",
+ false /* won't send custom reply */);
+
+ const Extension* extension =
+ LoadExtension(test_data_dir_.AppendASCII("api_test")
+ .AppendASCII("browser_action")
+ .AppendASCII("default_persistence"));
+ ASSERT_TRUE(extension);
+ ASSERT_EQ(kExtensionName, extension->name());
+ WaitForStateStore(profile(), extension->id());
+
+ // Make sure we've given the extension enough time to set the background color
+ // in chrome.runtime.onInstalled.
+ ASSERT_TRUE(listener.WaitUntilSatisfied());
+
+ ExtensionAction* extension_action =
+ ExtensionActionManager::Get(profile())->GetBrowserAction(*extension);
+ ASSERT_TRUE(extension_action);
+ EXPECT_EQ(SK_ColorBLUE, extension_action->GetBadgeBackgroundColor(0));
+}
+
+// When Chrome restarts, the Extension will immediately update the browser
+// action, but will not modify the badge background color. Thus, the background
+// should remain blue (persisting the default set in onInstalled()).
+IN_PROC_BROWSER_TEST_F(ExtensionBrowserTest, BrowserActionDefaultPersistence) {
+ // Find the extension (it's a shame we don't have an ID for this, but it
+ // was generated in the last test).
+ const Extension* extension = NULL;
+ const ExtensionSet& extension_set =
+ ExtensionRegistry::Get(profile())->enabled_extensions();
+ for (ExtensionSet::const_iterator iter = extension_set.begin();
+ iter != extension_set.end();
+ ++iter) {
+ if ((*iter)->name() == kExtensionName) {
+ extension = iter->get();
+ break;
+ }
+ }
+ ASSERT_TRUE(extension) << "Could not find extension in registry.";
+
+ ExtensionAction* extension_action =
+ ExtensionActionManager::Get(profile())->GetBrowserAction(*extension);
+ ASSERT_TRUE(extension_action);
+
+ // If the extension hasn't already set the badge text, then we should wait for
+ // it to do so.
+ if (extension_action->GetBadgeText(0) != "Hello") {
+ ExtensionTestMessageListener listener("Badge Text Set",
+ false /* won't send custom reply */);
+ ASSERT_TRUE(listener.WaitUntilSatisfied());
+ }
+
+ // If this log becomes frequent, this test is losing its effectiveness, and
+ // we need to find a more invasive way of ensuring the test's StateStore
+ // initializes after extensions get their onStartup event.
+ if (ExtensionSystem::Get(profile())->state_store()->IsInitialized())
+ LOG(WARNING) << "State store already initialized; test guaranteed to pass.";
+
+ // Wait for the StateStore to load, and fetch the defaults.
+ WaitForStateStore(profile(), extension->id());
+
+ // Ensure the BrowserAction's badge background is still blue.
+ EXPECT_EQ(SK_ColorBLUE, extension_action->GetBadgeBackgroundColor(0));
+}
+
+} // namespace extensions
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
new file mode 100644
index 00000000000..009343c5d6d
--- /dev/null
+++ b/chromium/chrome/browser/extensions/api/extension_action/browser_action_interactive_test.cc
@@ -0,0 +1,385 @@
+// Copyright 2013 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 "build/build_config.h"
+#include "chrome/browser/extensions/browser_action_test_util.h"
+#include "chrome/browser/extensions/extension_action.h"
+#include "chrome/browser/extensions/extension_action_manager.h"
+#include "chrome/browser/extensions/extension_apitest.h"
+#include "chrome/browser/extensions/extension_service.h"
+#include "chrome/browser/sessions/session_tab_helper.h"
+#include "chrome/browser/ui/browser.h"
+#include "chrome/browser/ui/browser_commands.h"
+#include "chrome/browser/ui/browser_finder.h"
+#include "chrome/browser/ui/browser_list.h"
+#include "chrome/browser/ui/browser_window.h"
+#include "chrome/browser/ui/tabs/tab_strip_model.h"
+#include "chrome/browser/ui/toolbar/toolbar_actions_model.h"
+#include "chrome/test/base/interactive_test_utils.h"
+#include "chrome/test/base/ui_test_utils.h"
+#include "content/public/browser/notification_service.h"
+#include "extensions/browser/extension_registry.h"
+#include "extensions/browser/extension_system.h"
+#include "extensions/common/extension.h"
+#include "extensions/common/extension_set.h"
+#include "extensions/common/permissions/permissions_data.h"
+#include "extensions/test/extension_test_message_listener.h"
+#include "extensions/test/result_catcher.h"
+
+#if defined(OS_WIN)
+#include "ui/views/win/hwnd_util.h"
+#endif
+
+namespace extensions {
+namespace {
+
+// chrome.browserAction API tests that interact with the UI in such a way that
+// they cannot be run concurrently (i.e. openPopup API tests that require the
+// window be focused/active).
+class BrowserActionInteractiveTest : public ExtensionApiTest {
+ public:
+ BrowserActionInteractiveTest() {}
+ ~BrowserActionInteractiveTest() override {}
+
+ protected:
+ // Function to control whether to run popup tests for the current platform.
+ // These tests require RunExtensionSubtest to work as expected and the browser
+ // window to able to be made active automatically. Returns false for platforms
+ // where these conditions are not met.
+ bool ShouldRunPopupTest() {
+ // TODO(justinlin): http://crbug.com/177163
+#if defined(OS_WIN) && !defined(NDEBUG)
+ return false;
+#elif defined(OS_MACOSX)
+ // TODO(justinlin): Browser window do not become active on Mac even when
+ // Activate() is called on them. Enable when/if it's possible to fix.
+ return false;
+#else
+ return true;
+#endif
+ }
+
+ // Open an extension popup via the chrome.browserAction.openPopup API.
+ void OpenExtensionPopupViaAPI() {
+ // Setup the notification observer to wait for the popup to finish loading.
+ content::WindowedNotificationObserver frame_observer(
+ content::NOTIFICATION_LOAD_COMPLETED_MAIN_FRAME,
+ content::NotificationService::AllSources());
+ // Show first popup in first window and expect it to have loaded.
+ ASSERT_TRUE(RunExtensionSubtest("browser_action/open_popup",
+ "open_popup_succeeds.html")) << message_;
+ frame_observer.Wait();
+ EXPECT_TRUE(BrowserActionTestUtil(browser()).HasPopup());
+ }
+};
+
+// Tests opening a popup using the chrome.browserAction.openPopup API. This test
+// opens a popup in the starting window, closes the popup, creates a new window
+// and opens a popup in the new window. Both popups should succeed in opening.
+IN_PROC_BROWSER_TEST_F(BrowserActionInteractiveTest, TestOpenPopup) {
+ if (!ShouldRunPopupTest())
+ return;
+
+ BrowserActionTestUtil browserActionBar(browser());
+ // Setup extension message listener to wait for javascript to finish running.
+ ExtensionTestMessageListener listener("ready", true);
+ {
+ OpenExtensionPopupViaAPI();
+ EXPECT_TRUE(browserActionBar.HasPopup());
+ browserActionBar.HidePopup();
+ }
+
+ EXPECT_TRUE(listener.WaitUntilSatisfied());
+ Browser* new_browser = NULL;
+ {
+ content::WindowedNotificationObserver frame_observer(
+ content::NOTIFICATION_LOAD_COMPLETED_MAIN_FRAME,
+ content::NotificationService::AllSources());
+ // Open a new window.
+ new_browser = chrome::FindBrowserWithWebContents(
+ browser()->OpenURL(content::OpenURLParams(
+ GURL("about:"), content::Referrer(), NEW_WINDOW,
+ ui::PAGE_TRANSITION_TYPED, false)));
+ // Hide all the buttons to test that it opens even when the browser action
+ // is in the overflow bucket.
+ ToolbarActionsModel::Get(profile())->SetVisibleIconCount(0);
+ frame_observer.Wait();
+ }
+
+ EXPECT_TRUE(new_browser != NULL);
+
+// Flaky on non-aura linux http://crbug.com/309749
+#if !(defined(OS_LINUX) && !defined(USE_AURA))
+ ResultCatcher catcher;
+ {
+ content::WindowedNotificationObserver frame_observer(
+ content::NOTIFICATION_LOAD_COMPLETED_MAIN_FRAME,
+ content::NotificationService::AllSources());
+ // Show second popup in new window.
+ listener.Reply("show another");
+ frame_observer.Wait();
+ EXPECT_TRUE(BrowserActionTestUtil(new_browser).HasPopup());
+ }
+ ASSERT_TRUE(catcher.GetNextResult()) << message_;
+#endif
+}
+
+// Tests opening a popup in an incognito window.
+IN_PROC_BROWSER_TEST_F(BrowserActionInteractiveTest, TestOpenPopupIncognito) {
+ if (!ShouldRunPopupTest())
+ return;
+
+ content::WindowedNotificationObserver frame_observer(
+ content::NOTIFICATION_LOAD_COMPLETED_MAIN_FRAME,
+ content::NotificationService::AllSources());
+ ASSERT_TRUE(RunExtensionSubtest("browser_action/open_popup",
+ "open_popup_succeeds.html",
+ kFlagEnableIncognito | kFlagUseIncognito))
+ << message_;
+ 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(USE_AURA))
+ // Starting window does not have a popup.
+ EXPECT_FALSE(BrowserActionTestUtil(browser()).HasPopup());
+#endif
+ // Incognito window should have a popup.
+ EXPECT_TRUE(BrowserActionTestUtil(BrowserList::GetInstance()->GetLastActive())
+ .HasPopup());
+}
+
+// Tests that an extension can open a popup in the last active incognito window
+// even from a background page with a non-incognito profile.
+// (crbug.com/448853)
+#if defined(OS_WIN)
+// Fails on XP: http://crbug.com/515717
+#define MAYBE_TestOpenPopupIncognitoFromBackground \
+ DISABLED_TestOpenPopupIncognitoFromBackground
+#else
+#define MAYBE_TestOpenPopupIncognitoFromBackground \
+ TestOpenPopupIncognitoFromBackground
+#endif
+IN_PROC_BROWSER_TEST_F(BrowserActionInteractiveTest,
+ MAYBE_TestOpenPopupIncognitoFromBackground) {
+ if (!ShouldRunPopupTest())
+ return;
+
+ const Extension* extension =
+ LoadExtensionIncognito(test_data_dir_.AppendASCII("browser_action").
+ AppendASCII("open_popup_background"));
+ ASSERT_TRUE(extension);
+ ExtensionTestMessageListener listener(false);
+ listener.set_extension_id(extension->id());
+
+ Browser* incognito_browser =
+ OpenURLOffTheRecord(profile(), GURL("chrome://newtab/"));
+ listener.WaitUntilSatisfied();
+ EXPECT_EQ(std::string("opened"), listener.message());
+ EXPECT_TRUE(BrowserActionTestUtil(incognito_browser).HasPopup());
+}
+
+#if defined(OS_LINUX)
+#define MAYBE_TestOpenPopupDoesNotCloseOtherPopups DISABLED_TestOpenPopupDoesNotCloseOtherPopups
+#else
+#define MAYBE_TestOpenPopupDoesNotCloseOtherPopups TestOpenPopupDoesNotCloseOtherPopups
+#endif
+// Tests if there is already a popup open (by a user click or otherwise), that
+// the openPopup API does not override it.
+IN_PROC_BROWSER_TEST_F(BrowserActionInteractiveTest,
+ MAYBE_TestOpenPopupDoesNotCloseOtherPopups) {
+ if (!ShouldRunPopupTest())
+ return;
+
+ // Load a first extension that can open a popup.
+ ASSERT_TRUE(LoadExtension(test_data_dir_.AppendASCII(
+ "browser_action/popup")));
+ const Extension* extension = GetSingleLoadedExtension();
+ ASSERT_TRUE(extension) << message_;
+
+ ExtensionTestMessageListener listener("ready", true);
+ // Load the test extension which will do nothing except notifyPass() to
+ // return control here.
+ ASSERT_TRUE(RunExtensionSubtest("browser_action/open_popup",
+ "open_popup_fails.html")) << message_;
+ EXPECT_TRUE(listener.WaitUntilSatisfied());
+
+ content::WindowedNotificationObserver frame_observer(
+ content::NOTIFICATION_LOAD_COMPLETED_MAIN_FRAME,
+ content::NotificationService::AllSources());
+ // Open popup in the first extension.
+ BrowserActionTestUtil(browser()).Press(0);
+ frame_observer.Wait();
+ EXPECT_TRUE(BrowserActionTestUtil(browser()).HasPopup());
+
+ ResultCatcher catcher;
+ // Return control to javascript to validate that opening a popup fails now.
+ listener.Reply("show another");
+ ASSERT_TRUE(catcher.GetNextResult()) << message_;
+}
+
+// Test that openPopup does not grant tab permissions like for browser action
+// clicks if the activeTab permission is set.
+IN_PROC_BROWSER_TEST_F(BrowserActionInteractiveTest,
+ TestOpenPopupDoesNotGrantTabPermissions) {
+ if (!ShouldRunPopupTest())
+ return;
+
+ OpenExtensionPopupViaAPI();
+ ExtensionService* service = extensions::ExtensionSystem::Get(
+ browser()->profile())->extension_service();
+ ASSERT_FALSE(
+ service->GetExtensionById(last_loaded_extension_id(), false)
+ ->permissions_data()
+ ->HasAPIPermissionForTab(
+ SessionTabHelper::IdForTab(
+ browser()->tab_strip_model()->GetActiveWebContents()),
+ APIPermission::kTab));
+}
+
+// Test that the extension popup is closed when the browser window is clicked.
+IN_PROC_BROWSER_TEST_F(BrowserActionInteractiveTest, BrowserClickClosesPopup1) {
+ if (!ShouldRunPopupTest())
+ return;
+
+ // Open an extension popup via the chrome.browserAction.openPopup API.
+ content::WindowedNotificationObserver frame_observer(
+ content::NOTIFICATION_LOAD_COMPLETED_MAIN_FRAME,
+ content::NotificationService::AllSources());
+ ASSERT_TRUE(RunExtensionSubtest("browser_action/open_popup",
+ "open_popup_succeeds.html")) << message_;
+ frame_observer.Wait();
+ EXPECT_TRUE(BrowserActionTestUtil(browser()).HasPopup());
+
+ // Click on the omnibox to close the extension popup.
+ ui_test_utils::ClickOnView(browser(), VIEW_ID_OMNIBOX);
+ EXPECT_FALSE(BrowserActionTestUtil(browser()).HasPopup());
+}
+
+// Test that the extension popup is closed when the browser window is clicked.
+IN_PROC_BROWSER_TEST_F(BrowserActionInteractiveTest, BrowserClickClosesPopup2) {
+ if (!ShouldRunPopupTest())
+ return;
+
+ // Load a first extension that can open a popup.
+ ASSERT_TRUE(LoadExtension(test_data_dir_.AppendASCII(
+ "browser_action/popup")));
+ const Extension* extension = GetSingleLoadedExtension();
+ ASSERT_TRUE(extension) << message_;
+
+ // Open an extension popup by clicking the browser action button.
+ content::WindowedNotificationObserver frame_observer(
+ content::NOTIFICATION_LOAD_COMPLETED_MAIN_FRAME,
+ content::NotificationService::AllSources());
+ BrowserActionTestUtil(browser()).Press(0);
+ frame_observer.Wait();
+ EXPECT_TRUE(BrowserActionTestUtil(browser()).HasPopup());
+
+ // Click on the omnibox to close the extension popup.
+ ui_test_utils::ClickOnView(browser(), VIEW_ID_OMNIBOX);
+ EXPECT_FALSE(BrowserActionTestUtil(browser()).HasPopup());
+}
+
+// Test that the extension popup is closed on browser tab switches.
+IN_PROC_BROWSER_TEST_F(BrowserActionInteractiveTest, TabSwitchClosesPopup) {
+ if (!ShouldRunPopupTest())
+ return;
+
+ // Add a second tab to the browser and open an extension popup.
+ chrome::NewTab(browser());
+ ASSERT_EQ(2, browser()->tab_strip_model()->count());
+ EXPECT_EQ(browser()->tab_strip_model()->GetWebContentsAt(1),
+ browser()->tab_strip_model()->GetActiveWebContents());
+ OpenExtensionPopupViaAPI();
+
+ content::WindowedNotificationObserver observer(
+ extensions::NOTIFICATION_EXTENSION_HOST_DESTROYED,
+ content::NotificationService::AllSources());
+ // Change active tabs, the extension popup should close.
+ browser()->tab_strip_model()->ActivateTabAt(0, true);
+ observer.Wait();
+
+ EXPECT_FALSE(BrowserActionTestUtil(browser()).HasPopup());
+}
+
+IN_PROC_BROWSER_TEST_F(BrowserActionInteractiveTest,
+ DeleteBrowserActionWithPopupOpen) {
+ if (!ShouldRunPopupTest())
+ return;
+
+ // First, we open a popup.
+ OpenExtensionPopupViaAPI();
+ BrowserActionTestUtil browser_action_test_util(browser());
+ EXPECT_TRUE(browser_action_test_util.HasPopup());
+
+ // Then, find the extension that created it.
+ content::WebContents* active_web_contents =
+ browser()->tab_strip_model()->GetActiveWebContents();
+ ASSERT_TRUE(active_web_contents);
+ GURL url = active_web_contents->GetLastCommittedURL();
+ const Extension* extension = ExtensionRegistry::Get(browser()->profile())->
+ enabled_extensions().GetExtensionOrAppByURL(url);
+ ASSERT_TRUE(extension);
+
+ // Finally, uninstall the extension, which causes the view to be deleted and
+ // the popup to go away. This should not crash.
+ UninstallExtension(extension->id());
+ EXPECT_FALSE(browser_action_test_util.HasPopup());
+}
+
+#if defined(TOOLKIT_VIEWS)
+// Test closing the browser while inspecting an extension popup with dev tools.
+IN_PROC_BROWSER_TEST_F(BrowserActionInteractiveTest, CloseBrowserWithDevTools) {
+ if (!ShouldRunPopupTest())
+ return;
+
+ // Load a first extension that can open a popup.
+ ASSERT_TRUE(LoadExtension(test_data_dir_.AppendASCII(
+ "browser_action/popup")));
+ const Extension* extension = GetSingleLoadedExtension();
+ ASSERT_TRUE(extension) << message_;
+
+ // Open an extension popup by clicking the browser action button.
+ content::WindowedNotificationObserver frame_observer(
+ content::NOTIFICATION_LOAD_COMPLETED_MAIN_FRAME,
+ content::NotificationService::AllSources());
+ BrowserActionTestUtil(browser()).InspectPopup(0);
+ frame_observer.Wait();
+ EXPECT_TRUE(BrowserActionTestUtil(browser()).HasPopup());
+
+ // Close the browser window, this should not cause a crash.
+ chrome::CloseWindow(browser());
+}
+#endif // TOOLKIT_VIEWS
+
+#if defined(OS_WIN)
+// Test that forcibly closing the browser and popup HWND does not cause a crash.
+// http://crbug.com/400646
+IN_PROC_BROWSER_TEST_F(BrowserActionInteractiveTest,
+ DISABLED_DestroyHWNDDoesNotCrash) {
+ if (!ShouldRunPopupTest())
+ return;
+
+ OpenExtensionPopupViaAPI();
+ BrowserActionTestUtil test_util(browser());
+ const gfx::NativeView view = test_util.GetPopupNativeView();
+ EXPECT_NE(static_cast<gfx::NativeView>(NULL), view);
+ const HWND hwnd = views::HWNDForNativeView(view);
+ EXPECT_EQ(hwnd,
+ views::HWNDForNativeView(browser()->window()->GetNativeWindow()));
+ EXPECT_EQ(TRUE, ::IsWindow(hwnd));
+
+ // Create a new browser window to prevent the message loop from terminating.
+ browser()->OpenURL(content::OpenURLParams(GURL("about:"), content::Referrer(),
+ NEW_WINDOW,
+ ui::PAGE_TRANSITION_TYPED, false));
+
+ // Forcibly closing the browser HWND should not cause a crash.
+ EXPECT_EQ(TRUE, ::CloseWindow(hwnd));
+ EXPECT_EQ(TRUE, ::DestroyWindow(hwnd));
+ EXPECT_EQ(FALSE, ::IsWindow(hwnd));
+}
+#endif // OS_WIN
+
+} // namespace
+} // namespace extensions
diff --git a/chromium/chrome/browser/extensions/api/extension_action/browser_action_unittest.cc b/chromium/chrome/browser/extensions/api/extension_action/browser_action_unittest.cc
new file mode 100644
index 00000000000..b69bbf3055c
--- /dev/null
+++ b/chromium/chrome/browser/extensions/api/extension_action/browser_action_unittest.cc
@@ -0,0 +1,39 @@
+// Copyright 2015 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/files/file_util.h"
+#include "chrome/browser/extensions/extension_service_test_with_install.h"
+#include "chrome/common/extensions/api/extension_action/action_info.h"
+
+namespace extensions {
+namespace {
+
+class BrowserActionUnitTest : public ExtensionServiceTestWithInstall {
+};
+
+TEST_F(BrowserActionUnitTest, MultiIcons) {
+ InitializeEmptyExtensionService();
+ base::FilePath path =
+ data_dir().AppendASCII("api_test/browser_action/multi_icons");
+ ASSERT_TRUE(base::PathExists(path));
+
+ const Extension* extension = PackAndInstallCRX(path, INSTALL_NEW);
+
+ EXPECT_EQ(0U, extension->install_warnings().size());
+ const ActionInfo* browser_action_info =
+ ActionInfo::GetBrowserActionInfo(extension);
+ ASSERT_TRUE(browser_action_info);
+
+ const ExtensionIconSet& icons = browser_action_info->default_icon;
+
+ // Extension can provide arbitrary sizes.
+ EXPECT_EQ(4u, icons.map().size());
+ EXPECT_EQ("icon19.png", icons.Get(19, ExtensionIconSet::MATCH_EXACTLY));
+ EXPECT_EQ("icon24.png", icons.Get(24, ExtensionIconSet::MATCH_EXACTLY));
+ EXPECT_EQ("icon24.png", icons.Get(31, ExtensionIconSet::MATCH_EXACTLY));
+ EXPECT_EQ("icon38.png", icons.Get(38, ExtensionIconSet::MATCH_EXACTLY));
+}
+
+} // namespace
+} // namespace extensions
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
new file mode 100644
index 00000000000..754d1542518
--- /dev/null
+++ b/chromium/chrome/browser/extensions/api/extension_action/extension_action_api.cc
@@ -0,0 +1,639 @@
+// 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 "chrome/browser/extensions/api/extension_action/extension_action_api.h"
+
+#include <stddef.h>
+#include <utility>
+
+#include "base/lazy_instance.h"
+#include "base/location.h"
+#include "base/macros.h"
+#include "base/memory/ptr_util.h"
+#include "base/single_thread_task_runner.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/threading/thread_task_runner_handle.h"
+#include "base/values.h"
+#include "chrome/browser/extensions/extension_action_manager.h"
+#include "chrome/browser/extensions/extension_action_runner.h"
+#include "chrome/browser/extensions/extension_tab_util.h"
+#include "chrome/browser/extensions/extension_util.h"
+#include "chrome/browser/extensions/tab_helper.h"
+#include "chrome/browser/profiles/profile.h"
+#include "chrome/browser/sessions/session_tab_helper.h"
+#include "chrome/browser/ui/browser.h"
+#include "chrome/browser/ui/browser_finder.h"
+#include "chrome/browser/ui/browser_window.h"
+#include "chrome/browser/ui/location_bar/location_bar.h"
+#include "chrome/browser/ui/tabs/tab_strip_model.h"
+#include "chrome/browser/ui/toolbar/toolbar_actions_bar.h"
+#include "chrome/common/extensions/api/extension_action/action_info.h"
+#include "content/public/browser/notification_service.h"
+#include "extensions/browser/event_router.h"
+#include "extensions/browser/extension_function_registry.h"
+#include "extensions/browser/extension_host.h"
+#include "extensions/browser/extension_registry.h"
+#include "extensions/browser/notification_types.h"
+#include "extensions/common/error_utils.h"
+#include "extensions/common/feature_switch.h"
+#include "extensions/common/image_util.h"
+#include "ui/gfx/image/image.h"
+#include "ui/gfx/image/image_skia.h"
+
+using content::WebContents;
+
+namespace extensions {
+
+namespace {
+
+// Whether the browser action is visible in the toolbar.
+const char kBrowserActionVisible[] = "browser_action_visible";
+
+// Errors.
+const char kNoExtensionActionError[] =
+ "This extension has no action specified.";
+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 kInvalidColorError[] =
+ "The color specification could not be parsed.";
+
+} // namespace
+
+//
+// ExtensionActionAPI::Observer
+//
+
+void ExtensionActionAPI::Observer::OnExtensionActionUpdated(
+ ExtensionAction* extension_action,
+ content::WebContents* web_contents,
+ content::BrowserContext* browser_context) {
+}
+
+void ExtensionActionAPI::Observer::OnExtensionActionVisibilityChanged(
+ const std::string& extension_id,
+ bool is_now_visible) {
+}
+
+void ExtensionActionAPI::Observer::OnPageActionsUpdated(
+ content::WebContents* web_contents) {
+}
+
+void ExtensionActionAPI::Observer::OnExtensionActionAPIShuttingDown() {
+}
+
+ExtensionActionAPI::Observer::~Observer() {
+}
+
+//
+// ExtensionActionAPI
+//
+
+static base::LazyInstance<BrowserContextKeyedAPIFactory<ExtensionActionAPI> >
+ g_factory = LAZY_INSTANCE_INITIALIZER;
+
+ExtensionActionAPI::ExtensionActionAPI(content::BrowserContext* context)
+ : browser_context_(context),
+ extension_prefs_(nullptr) {
+ ExtensionFunctionRegistry* registry =
+ ExtensionFunctionRegistry::GetInstance();
+
+ // Browser Actions
+ registry->RegisterFunction<BrowserActionSetIconFunction>();
+ registry->RegisterFunction<BrowserActionSetTitleFunction>();
+ registry->RegisterFunction<BrowserActionSetBadgeTextFunction>();
+ registry->RegisterFunction<BrowserActionSetBadgeBackgroundColorFunction>();
+ registry->RegisterFunction<BrowserActionSetPopupFunction>();
+ registry->RegisterFunction<BrowserActionGetTitleFunction>();
+ registry->RegisterFunction<BrowserActionGetBadgeTextFunction>();
+ registry->RegisterFunction<BrowserActionGetBadgeBackgroundColorFunction>();
+ registry->RegisterFunction<BrowserActionGetPopupFunction>();
+ registry->RegisterFunction<BrowserActionEnableFunction>();
+ registry->RegisterFunction<BrowserActionDisableFunction>();
+ registry->RegisterFunction<BrowserActionOpenPopupFunction>();
+
+ // Page Actions
+ registry->RegisterFunction<PageActionShowFunction>();
+ registry->RegisterFunction<PageActionHideFunction>();
+ registry->RegisterFunction<PageActionSetIconFunction>();
+ registry->RegisterFunction<PageActionSetTitleFunction>();
+ registry->RegisterFunction<PageActionSetPopupFunction>();
+ registry->RegisterFunction<PageActionGetTitleFunction>();
+ registry->RegisterFunction<PageActionGetPopupFunction>();
+}
+
+ExtensionActionAPI::~ExtensionActionAPI() {
+}
+
+// static
+BrowserContextKeyedAPIFactory<ExtensionActionAPI>*
+ExtensionActionAPI::GetFactoryInstance() {
+ return g_factory.Pointer();
+}
+
+// static
+ExtensionActionAPI* ExtensionActionAPI::Get(content::BrowserContext* context) {
+ return BrowserContextKeyedAPIFactory<ExtensionActionAPI>::Get(context);
+}
+
+void ExtensionActionAPI::AddObserver(Observer* observer) {
+ observers_.AddObserver(observer);
+}
+
+void ExtensionActionAPI::RemoveObserver(Observer* observer) {
+ observers_.RemoveObserver(observer);
+}
+
+bool ExtensionActionAPI::GetBrowserActionVisibility(
+ const std::string& extension_id) {
+ bool visible = false;
+ ExtensionPrefs* prefs = GetExtensionPrefs();
+ if (!prefs || !prefs->ReadPrefAsBoolean(extension_id,
+ kBrowserActionVisible,
+ &visible)) {
+ return true;
+ }
+ return visible;
+}
+
+void ExtensionActionAPI::SetBrowserActionVisibility(
+ const std::string& extension_id,
+ bool visible) {
+ if (GetBrowserActionVisibility(extension_id) == visible)
+ return;
+
+ GetExtensionPrefs()->UpdateExtensionPref(extension_id,
+ kBrowserActionVisible,
+ new base::FundamentalValue(visible));
+ FOR_EACH_OBSERVER(Observer, observers_, OnExtensionActionVisibilityChanged(
+ extension_id, visible));
+}
+
+bool ExtensionActionAPI::ShowExtensionActionPopup(
+ const Extension* extension,
+ Browser* browser,
+ bool grant_active_tab_permissions) {
+ ExtensionAction* extension_action =
+ ExtensionActionManager::Get(browser_context_)->GetExtensionAction(
+ *extension);
+ if (!extension_action)
+ return false;
+
+ if (extension_action->action_type() == ActionInfo::TYPE_PAGE &&
+ !FeatureSwitch::extension_action_redesign()->IsEnabled()) {
+ // We show page actions in the location bar unless the new toolbar is
+ // enabled.
+ return browser->window()->GetLocationBar()->ShowPageActionPopup(
+ extension, grant_active_tab_permissions);
+ }
+
+ // Don't support showing action popups in a popup window.
+ if (!browser->SupportsWindowFeature(Browser::FEATURE_TOOLBAR))
+ return false;
+
+ ToolbarActionsBar* toolbar_actions_bar =
+ browser->window()->GetToolbarActionsBar();
+ // ToolbarActionsBar could be null if, e.g., this is a popup window with no
+ // toolbar.
+ return toolbar_actions_bar &&
+ toolbar_actions_bar->ShowToolbarActionPopup(
+ extension->id(), grant_active_tab_permissions);
+}
+
+void ExtensionActionAPI::NotifyChange(ExtensionAction* extension_action,
+ content::WebContents* web_contents,
+ content::BrowserContext* context) {
+ FOR_EACH_OBSERVER(
+ Observer,
+ observers_,
+ OnExtensionActionUpdated(extension_action, web_contents, context));
+
+ if (extension_action->action_type() == ActionInfo::TYPE_PAGE)
+ NotifyPageActionsChanged(web_contents);
+}
+
+void ExtensionActionAPI::DispatchExtensionActionClicked(
+ const ExtensionAction& extension_action,
+ WebContents* web_contents) {
+ events::HistogramValue histogram_value = events::UNKNOWN;
+ const char* event_name = NULL;
+ switch (extension_action.action_type()) {
+ case ActionInfo::TYPE_BROWSER:
+ histogram_value = events::BROWSER_ACTION_ON_CLICKED;
+ event_name = "browserAction.onClicked";
+ break;
+ case ActionInfo::TYPE_PAGE:
+ histogram_value = events::PAGE_ACTION_ON_CLICKED;
+ event_name = "pageAction.onClicked";
+ break;
+ case ActionInfo::TYPE_SYSTEM_INDICATOR:
+ // The System Indicator handles its own clicks.
+ NOTREACHED();
+ break;
+ }
+
+ if (event_name) {
+ std::unique_ptr<base::ListValue> args(new base::ListValue());
+ args->Append(ExtensionTabUtil::CreateTabObject(web_contents)->ToValue());
+
+ DispatchEventToExtension(web_contents->GetBrowserContext(),
+ extension_action.extension_id(), histogram_value,
+ event_name, std::move(args));
+ }
+}
+
+void ExtensionActionAPI::ClearAllValuesForTab(
+ content::WebContents* web_contents) {
+ DCHECK(web_contents);
+ int tab_id = SessionTabHelper::IdForTab(web_contents);
+ content::BrowserContext* browser_context = web_contents->GetBrowserContext();
+ const ExtensionSet& enabled_extensions =
+ ExtensionRegistry::Get(browser_context_)->enabled_extensions();
+ ExtensionActionManager* action_manager =
+ ExtensionActionManager::Get(browser_context_);
+
+ for (ExtensionSet::const_iterator iter = enabled_extensions.begin();
+ iter != enabled_extensions.end(); ++iter) {
+ ExtensionAction* extension_action =
+ action_manager->GetExtensionAction(**iter);
+ if (extension_action) {
+ extension_action->ClearAllValuesForTab(tab_id);
+ NotifyChange(extension_action, web_contents, browser_context);
+ }
+ }
+}
+
+ExtensionPrefs* ExtensionActionAPI::GetExtensionPrefs() {
+ // This lazy initialization is more than just an optimization, because it
+ // allows tests to associate a new ExtensionPrefs with the browser context
+ // before we access it.
+ if (!extension_prefs_)
+ extension_prefs_ = ExtensionPrefs::Get(browser_context_);
+ return extension_prefs_;
+}
+
+void ExtensionActionAPI::DispatchEventToExtension(
+ content::BrowserContext* context,
+ const std::string& extension_id,
+ events::HistogramValue histogram_value,
+ const std::string& event_name,
+ std::unique_ptr<base::ListValue> event_args) {
+ if (!EventRouter::Get(context))
+ return;
+
+ std::unique_ptr<Event> event(
+ new Event(histogram_value, event_name, std::move(event_args)));
+ event->restrict_to_browser_context = context;
+ event->user_gesture = EventRouter::USER_GESTURE_ENABLED;
+ EventRouter::Get(context)
+ ->DispatchEventToExtension(extension_id, std::move(event));
+}
+
+void ExtensionActionAPI::NotifyPageActionsChanged(
+ content::WebContents* web_contents) {
+ Browser* browser = chrome::FindBrowserWithWebContents(web_contents);
+ if (!browser)
+ return;
+ LocationBar* location_bar =
+ browser->window() ? browser->window()->GetLocationBar() : NULL;
+ if (!location_bar)
+ return;
+ location_bar->UpdatePageActions();
+
+ FOR_EACH_OBSERVER(Observer, observers_, OnPageActionsUpdated(web_contents));
+}
+
+void ExtensionActionAPI::Shutdown() {
+ FOR_EACH_OBSERVER(Observer, observers_, OnExtensionActionAPIShuttingDown());
+}
+
+//
+// ExtensionActionFunction
+//
+
+ExtensionActionFunction::ExtensionActionFunction()
+ : details_(NULL),
+ tab_id_(ExtensionAction::kDefaultTabId),
+ contents_(NULL),
+ extension_action_(NULL) {
+}
+
+ExtensionActionFunction::~ExtensionActionFunction() {
+}
+
+bool ExtensionActionFunction::RunSync() {
+ ExtensionActionManager* manager = ExtensionActionManager::Get(GetProfile());
+ if (base::StartsWith(name(), "systemIndicator.",
+ base::CompareCase::INSENSITIVE_ASCII)) {
+ extension_action_ = manager->GetSystemIndicator(*extension());
+ } else {
+ extension_action_ = manager->GetBrowserAction(*extension());
+ if (!extension_action_) {
+ extension_action_ = manager->GetPageAction(*extension());
+ }
+ }
+ if (!extension_action_) {
+ // TODO(kalman): ideally the browserAction/pageAction APIs wouldn't event
+ // exist for extensions that don't have one declared. This should come as
+ // part of the Feature system.
+ error_ = kNoExtensionActionError;
+ return false;
+ }
+
+ // Populates the tab_id_ and details_ members.
+ EXTENSION_FUNCTION_VALIDATE(ExtractDataFromArguments());
+
+ // Find the WebContents that contains this tab id if one is required.
+ if (tab_id_ != ExtensionAction::kDefaultTabId) {
+ ExtensionTabUtil::GetTabById(tab_id_,
+ GetProfile(),
+ include_incognito(),
+ NULL,
+ NULL,
+ &contents_,
+ NULL);
+ if (!contents_) {
+ error_ = ErrorUtils::FormatErrorMessage(
+ kNoTabError, base::IntToString(tab_id_));
+ return false;
+ }
+ } else {
+ // Only browser actions and system indicators have a default tabId.
+ ActionInfo::Type action_type = extension_action_->action_type();
+ EXTENSION_FUNCTION_VALIDATE(
+ action_type == ActionInfo::TYPE_BROWSER ||
+ action_type == ActionInfo::TYPE_SYSTEM_INDICATOR);
+ }
+ return RunExtensionAction();
+}
+
+bool ExtensionActionFunction::ExtractDataFromArguments() {
+ // There may or may not be details (depends on the function).
+ // The tabId might appear in details (if it exists), as the first
+ // argument besides the action type (depends on the function), or be omitted
+ // entirely.
+ base::Value* first_arg = NULL;
+ if (!args_->Get(0, &first_arg))
+ return true;
+
+ switch (first_arg->GetType()) {
+ case base::Value::TYPE_INTEGER:
+ CHECK(first_arg->GetAsInteger(&tab_id_));
+ break;
+
+ case base::Value::TYPE_DICTIONARY: {
+ // Found the details argument.
+ details_ = static_cast<base::DictionaryValue*>(first_arg);
+ // Still need to check for the tabId within details.
+ base::Value* tab_id_value = NULL;
+ if (details_->Get("tabId", &tab_id_value)) {
+ switch (tab_id_value->GetType()) {
+ case base::Value::TYPE_NULL:
+ // OK; tabId is optional, leave it default.
+ return true;
+ case base::Value::TYPE_INTEGER:
+ CHECK(tab_id_value->GetAsInteger(&tab_id_));
+ return true;
+ default:
+ // Boom.
+ return false;
+ }
+ }
+ // Not found; tabId is optional, leave it default.
+ break;
+ }
+
+ case base::Value::TYPE_NULL:
+ // The tabId might be an optional argument.
+ break;
+
+ default:
+ return false;
+ }
+
+ return true;
+}
+
+void ExtensionActionFunction::NotifyChange() {
+ ExtensionActionAPI::Get(GetProfile())->NotifyChange(
+ extension_action_, contents_, GetProfile());
+}
+
+bool ExtensionActionFunction::SetVisible(bool visible) {
+ if (extension_action_->GetIsVisible(tab_id_) == visible)
+ return true;
+ extension_action_->SetIsVisible(tab_id_, visible);
+ NotifyChange();
+ return true;
+}
+
+bool ExtensionActionShowFunction::RunExtensionAction() {
+ return SetVisible(true);
+}
+
+bool ExtensionActionHideFunction::RunExtensionAction() {
+ return SetVisible(false);
+}
+
+bool ExtensionActionSetIconFunction::RunExtensionAction() {
+ EXTENSION_FUNCTION_VALIDATE(details_);
+
+ // setIcon can take a variant argument: either a dictionary of canvas
+ // ImageData, or an icon index.
+ base::DictionaryValue* canvas_set = NULL;
+ int icon_index;
+ if (details_->GetDictionary("imageData", &canvas_set)) {
+ gfx::ImageSkia icon;
+
+ EXTENSION_FUNCTION_VALIDATE(
+ ExtensionAction::ParseIconFromCanvasDictionary(*canvas_set, &icon));
+
+ if (icon.isNull()) {
+ error_ = "Icon invalid.";
+ return false;
+ }
+
+ extension_action_->SetIcon(tab_id_, gfx::Image(icon));
+ } else if (details_->GetInteger("iconIndex", &icon_index)) {
+ // Obsolete argument: ignore it.
+ return true;
+ } else {
+ EXTENSION_FUNCTION_VALIDATE(false);
+ }
+ NotifyChange();
+ return true;
+}
+
+bool ExtensionActionSetTitleFunction::RunExtensionAction() {
+ EXTENSION_FUNCTION_VALIDATE(details_);
+ std::string title;
+ EXTENSION_FUNCTION_VALIDATE(details_->GetString("title", &title));
+ extension_action_->SetTitle(tab_id_, title);
+ NotifyChange();
+ return true;
+}
+
+bool ExtensionActionSetPopupFunction::RunExtensionAction() {
+ EXTENSION_FUNCTION_VALIDATE(details_);
+ std::string popup_string;
+ EXTENSION_FUNCTION_VALIDATE(details_->GetString("popup", &popup_string));
+
+ GURL popup_url;
+ if (!popup_string.empty())
+ popup_url = extension()->GetResourceURL(popup_string);
+
+ extension_action_->SetPopupUrl(tab_id_, popup_url);
+ NotifyChange();
+ return true;
+}
+
+bool ExtensionActionSetBadgeTextFunction::RunExtensionAction() {
+ EXTENSION_FUNCTION_VALIDATE(details_);
+ std::string badge_text;
+ EXTENSION_FUNCTION_VALIDATE(details_->GetString("text", &badge_text));
+ extension_action_->SetBadgeText(tab_id_, badge_text);
+ NotifyChange();
+ return true;
+}
+
+bool ExtensionActionSetBadgeBackgroundColorFunction::RunExtensionAction() {
+ EXTENSION_FUNCTION_VALIDATE(details_);
+ base::Value* color_value = NULL;
+ EXTENSION_FUNCTION_VALIDATE(details_->Get("color", &color_value));
+ SkColor color = 0;
+ if (color_value->IsType(base::Value::TYPE_LIST)) {
+ base::ListValue* list = NULL;
+ EXTENSION_FUNCTION_VALIDATE(details_->GetList("color", &list));
+ EXTENSION_FUNCTION_VALIDATE(list->GetSize() == 4);
+
+ int color_array[4] = {0};
+ for (size_t i = 0; i < arraysize(color_array); ++i) {
+ EXTENSION_FUNCTION_VALIDATE(list->GetInteger(i, &color_array[i]));
+ }
+
+ color = SkColorSetARGB(color_array[3], color_array[0],
+ color_array[1], color_array[2]);
+ } else if (color_value->IsType(base::Value::TYPE_STRING)) {
+ std::string color_string;
+ EXTENSION_FUNCTION_VALIDATE(details_->GetString("color", &color_string));
+ if (!image_util::ParseCssColorString(color_string, &color)) {
+ error_ = kInvalidColorError;
+ return false;
+ }
+ }
+
+ extension_action_->SetBadgeBackgroundColor(tab_id_, color);
+ NotifyChange();
+ return true;
+}
+
+bool ExtensionActionGetTitleFunction::RunExtensionAction() {
+ SetResult(base::MakeUnique<base::StringValue>(
+ extension_action_->GetTitle(tab_id_)));
+ return true;
+}
+
+bool ExtensionActionGetPopupFunction::RunExtensionAction() {
+ SetResult(base::MakeUnique<base::StringValue>(
+ extension_action_->GetPopupUrl(tab_id_).spec()));
+ return true;
+}
+
+bool ExtensionActionGetBadgeTextFunction::RunExtensionAction() {
+ SetResult(base::MakeUnique<base::StringValue>(
+ extension_action_->GetBadgeText(tab_id_)));
+ return true;
+}
+
+bool ExtensionActionGetBadgeBackgroundColorFunction::RunExtensionAction() {
+ std::unique_ptr<base::ListValue> list(new base::ListValue());
+ SkColor color = extension_action_->GetBadgeBackgroundColor(tab_id_);
+ list->AppendInteger(static_cast<int>(SkColorGetR(color)));
+ list->AppendInteger(static_cast<int>(SkColorGetG(color)));
+ list->AppendInteger(static_cast<int>(SkColorGetB(color)));
+ list->AppendInteger(static_cast<int>(SkColorGetA(color)));
+ SetResult(std::move(list));
+ return true;
+}
+
+BrowserActionOpenPopupFunction::BrowserActionOpenPopupFunction()
+ : response_sent_(false) {
+}
+
+bool BrowserActionOpenPopupFunction::RunAsync() {
+ // We only allow the popup in the active window.
+ Profile* profile = GetProfile();
+ 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->HasOffTheRecordProfile()) {
+ browser =
+ chrome::FindLastActiveWithProfile(profile->GetOffTheRecordProfile());
+ }
+
+ // 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(GetProfile())->ShowExtensionActionPopup(
+ extension_.get(), browser, false)) {
+ error_ = kOpenPopupError;
+ return false;
+ }
+
+ // Even if this is for an incognito window, we want to use the normal profile.
+ // If the extension is spanning, then extension hosts are created with the
+ // original profile, and if it's split, then we know the api call came from
+ // the right profile.
+ registrar_.Add(this, NOTIFICATION_EXTENSION_HOST_DID_STOP_FIRST_LOAD,
+ content::Source<Profile>(profile));
+
+ // Set a timeout for waiting for the notification that the popup is loaded.
+ // Waiting is required so that the popup view can be retrieved by the custom
+ // bindings for the response callback. It's also needed to keep this function
+ // instance around until a notification is observed.
+ base::ThreadTaskRunnerHandle::Get()->PostDelayedTask(
+ FROM_HERE,
+ base::Bind(&BrowserActionOpenPopupFunction::OpenPopupTimedOut, this),
+ base::TimeDelta::FromSeconds(10));
+ return true;
+}
+
+void BrowserActionOpenPopupFunction::OpenPopupTimedOut() {
+ if (response_sent_)
+ return;
+
+ DVLOG(1) << "chrome.browserAction.openPopup did not show a popup.";
+ error_ = kOpenPopupError;
+ SendResponse(false);
+ response_sent_ = true;
+}
+
+void BrowserActionOpenPopupFunction::Observe(
+ int type,
+ const content::NotificationSource& source,
+ const content::NotificationDetails& details) {
+ DCHECK_EQ(NOTIFICATION_EXTENSION_HOST_DID_STOP_FIRST_LOAD, type);
+ if (response_sent_)
+ return;
+
+ ExtensionHost* host = content::Details<ExtensionHost>(details).ptr();
+ if (host->extension_host_type() != VIEW_TYPE_EXTENSION_POPUP ||
+ host->extension()->id() != extension_->id())
+ return;
+
+ SendResponse(true);
+ response_sent_ = true;
+ registrar_.RemoveAll();
+}
+
+} // namespace extensions
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
new file mode 100644
index 00000000000..070b6686b80
--- /dev/null
+++ b/chromium/chrome/browser/extensions/api/extension_action/extension_action_api.h
@@ -0,0 +1,450 @@
+// 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.
+
+#ifndef CHROME_BROWSER_EXTENSIONS_API_EXTENSION_ACTION_EXTENSION_ACTION_API_H_
+#define CHROME_BROWSER_EXTENSIONS_API_EXTENSION_ACTION_EXTENSION_ACTION_API_H_
+
+#include <string>
+
+#include "base/macros.h"
+#include "base/observer_list.h"
+#include "chrome/browser/extensions/chrome_extension_function.h"
+#include "chrome/browser/extensions/extension_action.h"
+#include "content/public/browser/notification_observer.h"
+#include "content/public/browser/notification_registrar.h"
+#include "extensions/browser/browser_context_keyed_api_factory.h"
+#include "extensions/browser/extension_event_histogram_value.h"
+#include "third_party/skia/include/core/SkColor.h"
+
+namespace base {
+class DictionaryValue;
+}
+
+namespace content {
+class BrowserContext;
+class WebContents;
+}
+
+namespace extensions {
+class ExtensionPrefs;
+
+class ExtensionActionAPI : public BrowserContextKeyedAPI {
+ public:
+ class Observer {
+ public:
+ // Called when there is a change to the given |extension_action|.
+ // |web_contents| is the web contents that was affected, and
+ // |browser_context| is the associated BrowserContext. (The latter is
+ // included because ExtensionActionAPI is shared between normal and
+ // incognito contexts, so |browser_context| may not equal
+ // |browser_context_|.)
+ virtual void OnExtensionActionUpdated(
+ ExtensionAction* extension_action,
+ content::WebContents* web_contents,
+ content::BrowserContext* browser_context);
+
+ // Called when there is a change to the extension action's visibility.
+ virtual void OnExtensionActionVisibilityChanged(
+ const std::string& extension_id,
+ bool is_now_visible);
+
+ // Called when the page actions have been refreshed do to a possible change
+ // in count or visibility.
+ virtual void OnPageActionsUpdated(content::WebContents* web_contents);
+
+ // Called when the ExtensionActionAPI is shutting down, giving observers a
+ // chance to unregister themselves if there is not a definitive lifecycle.
+ virtual void OnExtensionActionAPIShuttingDown();
+
+ protected:
+ virtual ~Observer();
+ };
+
+ explicit ExtensionActionAPI(content::BrowserContext* context);
+ ~ExtensionActionAPI() override;
+
+ // Convenience method to get the instance for a profile.
+ static ExtensionActionAPI* Get(content::BrowserContext* context);
+
+ static BrowserContextKeyedAPIFactory<ExtensionActionAPI>*
+ GetFactoryInstance();
+
+ // Add or remove observers.
+ void AddObserver(Observer* observer);
+ void RemoveObserver(Observer* observer);
+
+ bool GetBrowserActionVisibility(const std::string& extension_id);
+ void SetBrowserActionVisibility(const std::string& extension_id,
+ bool visible);
+
+ // Opens the popup for the given |extension| in the given |browser|'s window.
+ // If |grant_active_tab_permissions| is true, this grants the extension
+ // activeTab (so this should only be done if this is through a direct user
+ // action).
+ bool ShowExtensionActionPopup(const Extension* extension,
+ Browser* browser,
+ bool grant_active_tab_permissions);
+
+ // Notifies that there has been a change in the given |extension_action|.
+ void NotifyChange(ExtensionAction* extension_action,
+ content::WebContents* web_contents,
+ content::BrowserContext* browser_context);
+
+ // Dispatches the onClicked event for extension that owns the given action.
+ void DispatchExtensionActionClicked(const ExtensionAction& extension_action,
+ content::WebContents* web_contents);
+
+ // Clears the values for all ExtensionActions for the tab associated with the
+ // given |web_contents| (and signals that page actions changed).
+ void ClearAllValuesForTab(content::WebContents* web_contents);
+
+ // Notifies that the current set of page actions for |web_contents| has
+ // changed, and signals the browser to update.
+ void NotifyPageActionsChanged(content::WebContents* web_contents);
+
+ void set_prefs_for_testing(ExtensionPrefs* prefs) {
+ extension_prefs_ = prefs;
+ }
+
+ private:
+ friend class BrowserContextKeyedAPIFactory<ExtensionActionAPI>;
+
+ // Returns the associated extension prefs.
+ ExtensionPrefs* GetExtensionPrefs();
+
+ // The DispatchEvent methods forward events to the |context|'s event router.
+ void DispatchEventToExtension(content::BrowserContext* context,
+ const std::string& extension_id,
+ events::HistogramValue histogram_value,
+ const std::string& event_name,
+ std::unique_ptr<base::ListValue> event_args);
+
+ // BrowserContextKeyedAPI implementation.
+ void Shutdown() override;
+ static const char* service_name() { return "ExtensionActionAPI"; }
+ static const bool kServiceRedirectedInIncognito = true;
+
+ base::ObserverList<Observer> observers_;
+
+ content::BrowserContext* browser_context_;
+
+ ExtensionPrefs* extension_prefs_;
+
+ DISALLOW_COPY_AND_ASSIGN(ExtensionActionAPI);
+};
+
+// Implementation of the browserAction and pageAction APIs.
+//
+// Divergent behaviour between the two is minimal (pageAction has required
+// tabIds while browserAction's are optional, they have different internal
+// browser notification requirements, and not all functions are defined for all
+// APIs).
+class ExtensionActionFunction : public ChromeSyncExtensionFunction {
+ public:
+ static bool ParseCSSColorString(const std::string& color_string,
+ SkColor* result);
+
+ protected:
+ ExtensionActionFunction();
+ ~ExtensionActionFunction() override;
+ bool RunSync() override;
+ virtual bool RunExtensionAction() = 0;
+
+ bool ExtractDataFromArguments();
+ void NotifyChange();
+ bool SetVisible(bool visible);
+
+ // All the extension action APIs take a single argument called details that
+ // is a dictionary.
+ base::DictionaryValue* details_;
+
+ // The tab id the extension action function should apply to, if any, or
+ // kDefaultTabId if none was specified.
+ int tab_id_;
+
+ // WebContents for |tab_id_| if one exists.
+ content::WebContents* contents_;
+
+ // The extension action for the current extension.
+ ExtensionAction* extension_action_;
+};
+
+//
+// Implementations of each extension action API.
+//
+// pageAction and browserAction bindings are created for these by extending them
+// then declaring an EXTENSION_FUNCTION_NAME.
+//
+
+// show
+class ExtensionActionShowFunction : public ExtensionActionFunction {
+ protected:
+ ~ExtensionActionShowFunction() override {}
+ bool RunExtensionAction() override;
+};
+
+// hide
+class ExtensionActionHideFunction : public ExtensionActionFunction {
+ protected:
+ ~ExtensionActionHideFunction() override {}
+ bool RunExtensionAction() override;
+};
+
+// setIcon
+class ExtensionActionSetIconFunction : public ExtensionActionFunction {
+ protected:
+ ~ExtensionActionSetIconFunction() override {}
+ bool RunExtensionAction() override;
+};
+
+// setTitle
+class ExtensionActionSetTitleFunction : public ExtensionActionFunction {
+ protected:
+ ~ExtensionActionSetTitleFunction() override {}
+ bool RunExtensionAction() override;
+};
+
+// setPopup
+class ExtensionActionSetPopupFunction : public ExtensionActionFunction {
+ protected:
+ ~ExtensionActionSetPopupFunction() override {}
+ bool RunExtensionAction() override;
+};
+
+// setBadgeText
+class ExtensionActionSetBadgeTextFunction : public ExtensionActionFunction {
+ protected:
+ ~ExtensionActionSetBadgeTextFunction() override {}
+ bool RunExtensionAction() override;
+};
+
+// setBadgeBackgroundColor
+class ExtensionActionSetBadgeBackgroundColorFunction
+ : public ExtensionActionFunction {
+ protected:
+ ~ExtensionActionSetBadgeBackgroundColorFunction() override {}
+ bool RunExtensionAction() override;
+};
+
+// getTitle
+class ExtensionActionGetTitleFunction : public ExtensionActionFunction {
+ protected:
+ ~ExtensionActionGetTitleFunction() override {}
+ bool RunExtensionAction() override;
+};
+
+// getPopup
+class ExtensionActionGetPopupFunction : public ExtensionActionFunction {
+ protected:
+ ~ExtensionActionGetPopupFunction() override {}
+ bool RunExtensionAction() override;
+};
+
+// getBadgeText
+class ExtensionActionGetBadgeTextFunction : public ExtensionActionFunction {
+ protected:
+ ~ExtensionActionGetBadgeTextFunction() override {}
+ bool RunExtensionAction() override;
+};
+
+// getBadgeBackgroundColor
+class ExtensionActionGetBadgeBackgroundColorFunction
+ : public ExtensionActionFunction {
+ protected:
+ ~ExtensionActionGetBadgeBackgroundColorFunction() override {}
+ bool RunExtensionAction() override;
+};
+
+//
+// browserAction.* aliases for supported browserAction APIs.
+//
+
+class BrowserActionSetIconFunction : public ExtensionActionSetIconFunction {
+ public:
+ DECLARE_EXTENSION_FUNCTION("browserAction.setIcon", BROWSERACTION_SETICON)
+
+ protected:
+ ~BrowserActionSetIconFunction() override {}
+};
+
+class BrowserActionSetTitleFunction : public ExtensionActionSetTitleFunction {
+ public:
+ DECLARE_EXTENSION_FUNCTION("browserAction.setTitle", BROWSERACTION_SETTITLE)
+
+ protected:
+ ~BrowserActionSetTitleFunction() override {}
+};
+
+class BrowserActionSetPopupFunction : public ExtensionActionSetPopupFunction {
+ public:
+ DECLARE_EXTENSION_FUNCTION("browserAction.setPopup", BROWSERACTION_SETPOPUP)
+
+ protected:
+ ~BrowserActionSetPopupFunction() override {}
+};
+
+class BrowserActionGetTitleFunction : public ExtensionActionGetTitleFunction {
+ public:
+ DECLARE_EXTENSION_FUNCTION("browserAction.getTitle", BROWSERACTION_GETTITLE)
+
+ protected:
+ ~BrowserActionGetTitleFunction() override {}
+};
+
+class BrowserActionGetPopupFunction : public ExtensionActionGetPopupFunction {
+ public:
+ DECLARE_EXTENSION_FUNCTION("browserAction.getPopup", BROWSERACTION_GETPOPUP)
+
+ protected:
+ ~BrowserActionGetPopupFunction() override {}
+};
+
+class BrowserActionSetBadgeTextFunction
+ : public ExtensionActionSetBadgeTextFunction {
+ public:
+ DECLARE_EXTENSION_FUNCTION("browserAction.setBadgeText",
+ BROWSERACTION_SETBADGETEXT)
+
+ protected:
+ ~BrowserActionSetBadgeTextFunction() override {}
+};
+
+class BrowserActionSetBadgeBackgroundColorFunction
+ : public ExtensionActionSetBadgeBackgroundColorFunction {
+ public:
+ DECLARE_EXTENSION_FUNCTION("browserAction.setBadgeBackgroundColor",
+ BROWSERACTION_SETBADGEBACKGROUNDCOLOR)
+
+ protected:
+ ~BrowserActionSetBadgeBackgroundColorFunction() override {}
+};
+
+class BrowserActionGetBadgeTextFunction
+ : public ExtensionActionGetBadgeTextFunction {
+ public:
+ DECLARE_EXTENSION_FUNCTION("browserAction.getBadgeText",
+ BROWSERACTION_GETBADGETEXT)
+
+ protected:
+ ~BrowserActionGetBadgeTextFunction() override {}
+};
+
+class BrowserActionGetBadgeBackgroundColorFunction
+ : public ExtensionActionGetBadgeBackgroundColorFunction {
+ public:
+ DECLARE_EXTENSION_FUNCTION("browserAction.getBadgeBackgroundColor",
+ BROWSERACTION_GETBADGEBACKGROUNDCOLOR)
+
+ protected:
+ ~BrowserActionGetBadgeBackgroundColorFunction() override {}
+};
+
+class BrowserActionEnableFunction : public ExtensionActionShowFunction {
+ public:
+ DECLARE_EXTENSION_FUNCTION("browserAction.enable", BROWSERACTION_ENABLE)
+
+ protected:
+ ~BrowserActionEnableFunction() override {}
+};
+
+class BrowserActionDisableFunction : public ExtensionActionHideFunction {
+ public:
+ DECLARE_EXTENSION_FUNCTION("browserAction.disable", BROWSERACTION_DISABLE)
+
+ protected:
+ ~BrowserActionDisableFunction() override {}
+};
+
+class BrowserActionOpenPopupFunction : public ChromeAsyncExtensionFunction,
+ public content::NotificationObserver {
+ public:
+ DECLARE_EXTENSION_FUNCTION("browserAction.openPopup",
+ BROWSERACTION_OPEN_POPUP)
+ BrowserActionOpenPopupFunction();
+
+ private:
+ ~BrowserActionOpenPopupFunction() override {}
+
+ // ExtensionFunction:
+ bool RunAsync() override;
+
+ void Observe(int type,
+ const content::NotificationSource& source,
+ const content::NotificationDetails& details) override;
+ void OpenPopupTimedOut();
+
+ content::NotificationRegistrar registrar_;
+ bool response_sent_;
+
+ DISALLOW_COPY_AND_ASSIGN(BrowserActionOpenPopupFunction);
+};
+
+} // namespace extensions
+
+//
+// pageAction.* aliases for supported pageAction APIs.
+//
+
+class PageActionShowFunction : public extensions::ExtensionActionShowFunction {
+ public:
+ DECLARE_EXTENSION_FUNCTION("pageAction.show", PAGEACTION_SHOW)
+
+ protected:
+ ~PageActionShowFunction() override {}
+};
+
+class PageActionHideFunction : public extensions::ExtensionActionHideFunction {
+ public:
+ DECLARE_EXTENSION_FUNCTION("pageAction.hide", PAGEACTION_HIDE)
+
+ protected:
+ ~PageActionHideFunction() override {}
+};
+
+class PageActionSetIconFunction
+ : public extensions::ExtensionActionSetIconFunction {
+ public:
+ DECLARE_EXTENSION_FUNCTION("pageAction.setIcon", PAGEACTION_SETICON)
+
+ protected:
+ ~PageActionSetIconFunction() override {}
+};
+
+class PageActionSetTitleFunction
+ : public extensions::ExtensionActionSetTitleFunction {
+ public:
+ DECLARE_EXTENSION_FUNCTION("pageAction.setTitle", PAGEACTION_SETTITLE)
+
+ protected:
+ ~PageActionSetTitleFunction() override {}
+};
+
+class PageActionSetPopupFunction
+ : public extensions::ExtensionActionSetPopupFunction {
+ public:
+ DECLARE_EXTENSION_FUNCTION("pageAction.setPopup", PAGEACTION_SETPOPUP)
+
+ protected:
+ ~PageActionSetPopupFunction() override {}
+};
+
+class PageActionGetTitleFunction
+ : public extensions::ExtensionActionGetTitleFunction {
+ public:
+ DECLARE_EXTENSION_FUNCTION("pageAction.getTitle", PAGEACTION_GETTITLE)
+
+ protected:
+ ~PageActionGetTitleFunction() override {}
+};
+
+class PageActionGetPopupFunction
+ : public extensions::ExtensionActionGetPopupFunction {
+ public:
+ DECLARE_EXTENSION_FUNCTION("pageAction.getPopup", PAGEACTION_GETPOPUP)
+
+ protected:
+ ~PageActionGetPopupFunction() override {}
+};
+
+#endif // CHROME_BROWSER_EXTENSIONS_API_EXTENSION_ACTION_EXTENSION_ACTION_API_H_
diff --git a/chromium/chrome/browser/extensions/api/extension_action/extension_action_prefs_unittest.cc b/chromium/chrome/browser/extensions/api/extension_action/extension_action_prefs_unittest.cc
new file mode 100644
index 00000000000..80a80a9789d
--- /dev/null
+++ b/chromium/chrome/browser/extensions/api/extension_action/extension_action_prefs_unittest.cc
@@ -0,0 +1,66 @@
+// Copyright (c) 2013 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 <string>
+
+#include "base/logging.h"
+#include "base/macros.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/stringprintf.h"
+#include "chrome/browser/extensions/api/extension_action/extension_action_api.h"
+#include "chrome/browser/extensions/extension_prefs_unittest.h"
+#include "chrome/test/base/testing_profile.h"
+#include "extensions/common/extension.h"
+
+namespace extensions {
+
+// Tests force hiding browser actions.
+class ExtensionPrefsHidingBrowserActions : public ExtensionPrefsTest {
+ public:
+ ExtensionPrefsHidingBrowserActions() {}
+ ~ExtensionPrefsHidingBrowserActions() override {}
+
+ void Initialize() override {
+ profile_.reset(new TestingProfile());
+
+ // Install 5 extensions.
+ for (int i = 0; i < 5; i++) {
+ std::string name = "test" + base::IntToString(i);
+ extensions_.push_back(prefs_.AddExtension(name));
+ }
+
+ ExtensionActionAPI* action_api = ExtensionActionAPI::Get(profile_.get());
+ action_api->set_prefs_for_testing(prefs());
+ for (const scoped_refptr<const Extension>& extension : extensions_)
+ EXPECT_TRUE(action_api->GetBrowserActionVisibility(extension->id()));
+
+ action_api->SetBrowserActionVisibility(extensions_[0]->id(), false);
+ action_api->SetBrowserActionVisibility(extensions_[1]->id(), true);
+ }
+
+ void Verify() override {
+ ExtensionActionAPI* action_api = ExtensionActionAPI::Get(profile_.get());
+ action_api->set_prefs_for_testing(prefs());
+ // Make sure the one we hid is hidden.
+ EXPECT_FALSE(action_api->GetBrowserActionVisibility(extensions_[0]->id()));
+
+ // Make sure the other id's are not hidden.
+ ExtensionList::const_iterator iter = extensions_.begin() + 1;
+ for (; iter != extensions_.end(); ++iter) {
+ SCOPED_TRACE(base::StringPrintf("Loop %d ",
+ static_cast<int>(iter - extensions_.begin())));
+ EXPECT_TRUE(action_api->GetBrowserActionVisibility((*iter)->id()));
+ }
+ }
+
+ private:
+ std::unique_ptr<TestingProfile> profile_;
+ ExtensionList extensions_;
+
+ DISALLOW_COPY_AND_ASSIGN(ExtensionPrefsHidingBrowserActions);
+};
+
+TEST_F(ExtensionPrefsHidingBrowserActions, ForceHide) {}
+
+} // namespace extensions
diff --git a/chromium/chrome/browser/extensions/api/extension_action/extension_page_actions_api_constants.cc b/chromium/chrome/browser/extensions/api/extension_action/extension_page_actions_api_constants.cc
new file mode 100644
index 00000000000..8ad3dacee96
--- /dev/null
+++ b/chromium/chrome/browser/extensions/api/extension_action/extension_page_actions_api_constants.cc
@@ -0,0 +1,15 @@
+// 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 "chrome/browser/extensions/api/extension_action/extension_page_actions_api_constants.h"
+
+namespace extension_page_actions_api_constants {
+
+const char kTabIdKey[] = "tabId";
+const char kTabUrlKey[] = "tabUrl";
+const char kUrlKey[] = "url";
+const char kTitleKey[] = "title";
+const char kButtonKey[] = "button";
+
+} // namespace extension_page_actions_api_constants
diff --git a/chromium/chrome/browser/extensions/api/extension_action/extension_page_actions_api_constants.h b/chromium/chrome/browser/extensions/api/extension_action/extension_page_actions_api_constants.h
new file mode 100644
index 00000000000..c8cfcc60fcb
--- /dev/null
+++ b/chromium/chrome/browser/extensions/api/extension_action/extension_page_actions_api_constants.h
@@ -0,0 +1,21 @@
+// 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.
+
+// Constants used for the Page Actions API.
+
+#ifndef CHROME_BROWSER_EXTENSIONS_API_EXTENSION_ACTION_EXTENSION_PAGE_ACTIONS_API_CONSTANTS_H_
+#define CHROME_BROWSER_EXTENSIONS_API_EXTENSION_ACTION_EXTENSION_PAGE_ACTIONS_API_CONSTANTS_H_
+
+namespace extension_page_actions_api_constants {
+
+// Keys.
+extern const char kTabIdKey[];
+extern const char kTabUrlKey[];
+extern const char kUrlKey[];
+extern const char kTitleKey[];
+extern const char kButtonKey[];
+
+}; // namespace extension_page_actions_api_constants
+
+#endif // CHROME_BROWSER_EXTENSIONS_API_EXTENSION_ACTION_EXTENSION_PAGE_ACTIONS_API_CONSTANTS_H_
diff --git a/chromium/chrome/browser/extensions/api/extension_action/page_action_apitest.cc b/chromium/chrome/browser/extensions/api/extension_action/page_action_apitest.cc
new file mode 100644
index 00000000000..dbb9671ed83
--- /dev/null
+++ b/chromium/chrome/browser/extensions/api/extension_action/page_action_apitest.cc
@@ -0,0 +1,251 @@
+// 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 "chrome/browser/extensions/api/extension_action/extension_action_api.h"
+#include "chrome/browser/extensions/extension_action.h"
+#include "chrome/browser/extensions/extension_action_icon_factory.h"
+#include "chrome/browser/extensions/extension_action_manager.h"
+#include "chrome/browser/extensions/extension_action_runner.h"
+#include "chrome/browser/extensions/extension_action_test_util.h"
+#include "chrome/browser/extensions/extension_apitest.h"
+#include "chrome/browser/extensions/extension_service.h"
+#include "chrome/browser/extensions/extension_tab_util.h"
+#include "chrome/browser/profiles/profile.h"
+#include "chrome/browser/sessions/session_tab_helper.h"
+#include "chrome/browser/ui/browser.h"
+#include "chrome/browser/ui/browser_commands.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/browser/web_contents.h"
+#include "content/public/test/browser_test_utils.h"
+#include "extensions/browser/extension_system.h"
+#include "extensions/common/extension.h"
+#include "extensions/test/result_catcher.h"
+#include "net/test/embedded_test_server/embedded_test_server.h"
+
+using content::WebContents;
+
+namespace extensions {
+namespace {
+
+class PageActionApiTest : public ExtensionApiTest {
+ protected:
+ ExtensionAction* GetPageAction(const Extension& extension) {
+ return ExtensionActionManager::Get(browser()->profile())->
+ GetPageAction(extension);
+ }
+};
+
+IN_PROC_BROWSER_TEST_F(PageActionApiTest, Basic) {
+ ASSERT_TRUE(embedded_test_server()->Start());
+ ASSERT_TRUE(RunExtensionTest("page_action/basics")) << message_;
+ const Extension* extension = GetSingleLoadedExtension();
+ ASSERT_TRUE(extension) << message_;
+ {
+ // Tell the extension to update the page action state.
+ ResultCatcher catcher;
+ ui_test_utils::NavigateToURL(browser(),
+ GURL(extension->GetResourceURL("update.html")));
+ ASSERT_TRUE(catcher.GetNextResult());
+ }
+
+ // Test that we received the changes.
+ int tab_id = SessionTabHelper::FromWebContents(
+ browser()->tab_strip_model()->GetActiveWebContents())->session_id().id();
+ ExtensionAction* action = GetPageAction(*extension);
+ ASSERT_TRUE(action);
+ EXPECT_EQ("Modified", action->GetTitle(tab_id));
+
+ {
+ // Simulate the page action being clicked.
+ ResultCatcher catcher;
+ ExtensionActionRunner::GetForWebContents(
+ browser()->tab_strip_model()->GetActiveWebContents())
+ ->RunAction(extension, true);
+ EXPECT_TRUE(catcher.GetNextResult());
+ }
+
+ {
+ // Tell the extension to update the page action state again.
+ ResultCatcher catcher;
+ ui_test_utils::NavigateToURL(browser(),
+ GURL(extension->GetResourceURL("update2.html")));
+ ASSERT_TRUE(catcher.GetNextResult());
+ }
+
+ // We should not be creating icons asynchronously, so we don't need an
+ // observer.
+ ExtensionActionIconFactory icon_factory(profile(), extension, action, NULL);
+
+ // Test that we received the changes.
+ tab_id = SessionTabHelper::FromWebContents(
+ browser()->tab_strip_model()->GetActiveWebContents())->session_id().id();
+ EXPECT_FALSE(icon_factory.GetIcon(tab_id).IsEmpty());
+}
+
+// Test that calling chrome.pageAction.setPopup() can enable a popup.
+IN_PROC_BROWSER_TEST_F(PageActionApiTest, AddPopup) {
+ // Load the extension, which has no default popup.
+ ASSERT_TRUE(RunExtensionTest("page_action/add_popup")) << message_;
+ const Extension* extension = GetSingleLoadedExtension();
+ ASSERT_TRUE(extension) << message_;
+
+ int tab_id = ExtensionTabUtil::GetTabId(
+ browser()->tab_strip_model()->GetActiveWebContents());
+
+ ExtensionAction* page_action = GetPageAction(*extension);
+ ASSERT_TRUE(page_action)
+ << "Page action test extension should have a page action.";
+
+ ASSERT_FALSE(page_action->HasPopup(tab_id));
+
+ // Simulate the page action being clicked. The resulting event should
+ // install a page action popup.
+ {
+ ResultCatcher catcher;
+ ExtensionActionRunner::GetForWebContents(
+ browser()->tab_strip_model()->GetActiveWebContents())
+ ->RunAction(extension, true);
+ ASSERT_TRUE(catcher.GetNextResult());
+ }
+
+ ASSERT_TRUE(page_action->HasPopup(tab_id))
+ << "Clicking on the page action should have caused a popup to be added.";
+
+ ASSERT_STREQ("/a_popup.html",
+ page_action->GetPopupUrl(tab_id).path().c_str());
+
+ // Now change the popup from a_popup.html to a_second_popup.html .
+ // Load a page which removes the popup using chrome.pageAction.setPopup().
+ {
+ ResultCatcher catcher;
+ ui_test_utils::NavigateToURL(
+ browser(),
+ GURL(extension->GetResourceURL("change_popup.html")));
+ ASSERT_TRUE(catcher.GetNextResult());
+ }
+
+ ASSERT_TRUE(page_action->HasPopup(tab_id));
+ ASSERT_STREQ("/another_popup.html",
+ page_action->GetPopupUrl(tab_id).path().c_str());
+}
+
+// Test that calling chrome.pageAction.setPopup() can remove a popup.
+IN_PROC_BROWSER_TEST_F(PageActionApiTest, RemovePopup) {
+ // Load the extension, which has a page action with a default popup.
+ ASSERT_TRUE(RunExtensionTest("page_action/remove_popup")) << message_;
+ const Extension* extension = GetSingleLoadedExtension();
+ ASSERT_TRUE(extension) << message_;
+
+ int tab_id = ExtensionTabUtil::GetTabId(
+ browser()->tab_strip_model()->GetActiveWebContents());
+
+ ExtensionAction* page_action = GetPageAction(*extension);
+ ASSERT_TRUE(page_action)
+ << "Page action test extension should have a page action.";
+
+ ASSERT_TRUE(page_action->HasPopup(tab_id))
+ << "Expect a page action popup before the test removes it.";
+
+ // Load a page which removes the popup using chrome.pageAction.setPopup().
+ {
+ ResultCatcher catcher;
+ ui_test_utils::NavigateToURL(
+ browser(),
+ GURL(extension->GetResourceURL("remove_popup.html")));
+ ASSERT_TRUE(catcher.GetNextResult());
+ }
+
+ ASSERT_FALSE(page_action->HasPopup(tab_id))
+ << "Page action popup should have been removed.";
+}
+
+// Tests popups in page actions.
+// Flaky on the trybots. See http://crbug.com/96725.
+IN_PROC_BROWSER_TEST_F(PageActionApiTest, DISABLED_ShowPageActionPopup) {
+ ASSERT_TRUE(RunExtensionTest("page_action/popup")) << message_;
+ const Extension* extension = GetSingleLoadedExtension();
+ ASSERT_TRUE(extension) << message_;
+
+ ASSERT_TRUE(WaitForPageActionVisibilityChangeTo(1));
+
+ {
+ ResultCatcher catcher;
+ ExtensionActionAPI::Get(browser()->profile())->ShowExtensionActionPopup(
+ extension, browser(), true);
+ ASSERT_TRUE(catcher.GetNextResult());
+ }
+}
+
+// Test http://crbug.com/57333: that two page action extensions using the same
+// icon for the page action icon and the extension icon do not crash.
+IN_PROC_BROWSER_TEST_F(PageActionApiTest, TestCrash57333) {
+ // Load extension A.
+ ASSERT_TRUE(LoadExtension(test_data_dir_.AppendASCII("page_action")
+ .AppendASCII("crash_57333")
+ .AppendASCII("Extension1")));
+ // Load extension B.
+ ASSERT_TRUE(LoadExtension(test_data_dir_.AppendASCII("page_action")
+ .AppendASCII("crash_57333")
+ .AppendASCII("Extension2")));
+}
+
+IN_PROC_BROWSER_TEST_F(PageActionApiTest, Getters) {
+ ASSERT_TRUE(RunExtensionTest("page_action/getters")) << message_;
+ const Extension* extension = GetSingleLoadedExtension();
+ ASSERT_TRUE(extension) << message_;
+
+ ResultCatcher catcher;
+ ui_test_utils::NavigateToURL(browser(),
+ GURL(extension->GetResourceURL("update.html")));
+ ASSERT_TRUE(catcher.GetNextResult());
+}
+
+// Verify triggering page action.
+IN_PROC_BROWSER_TEST_F(PageActionApiTest, TestTriggerPageAction) {
+ ASSERT_TRUE(embedded_test_server()->Start());
+
+ ASSERT_TRUE(RunExtensionTest("trigger_actions/page_action")) << message_;
+ const Extension* extension = GetSingleLoadedExtension();
+ ASSERT_TRUE(extension) << message_;
+
+ // Page action icon is displayed when a tab is created.
+ ui_test_utils::NavigateToURL(browser(),
+ embedded_test_server()->GetURL("/simple.html"));
+ chrome::NewTab(browser());
+ browser()->tab_strip_model()->ActivateTabAt(0, true);
+
+ // Give the extension time to show the page action on the tab.
+ WaitForPageActionVisibilityChangeTo(1);
+
+ ExtensionAction* page_action = GetPageAction(*extension);
+ ASSERT_TRUE(page_action);
+
+ WebContents* tab =
+ browser()->tab_strip_model()->GetActiveWebContents();
+ ASSERT_TRUE(tab);
+
+ EXPECT_TRUE(page_action->GetIsVisible(ExtensionTabUtil::GetTabId(tab)));
+
+ {
+ // Simulate the page action being clicked.
+ ResultCatcher catcher;
+ ExtensionActionRunner::GetForWebContents(
+ browser()->tab_strip_model()->GetActiveWebContents())
+ ->RunAction(extension, true);
+ EXPECT_TRUE(catcher.GetNextResult());
+ }
+
+ // 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");
+}
+
+} // namespace
+} // namespace extensions