diff options
author | Allan Sandfeld Jensen <allan.jensen@theqtcompany.com> | 2016-08-01 12:59:39 +0200 |
---|---|---|
committer | Allan Sandfeld Jensen <allan.jensen@qt.io> | 2016-08-04 12:40:43 +0000 |
commit | 28b1110370900897ab652cb420c371fab8857ad4 (patch) | |
tree | 41b32127d23b0df4f2add2a27e12dc87bddb260e /chromium/chrome/browser/extensions/api/extension_action | |
parent | 399c965b6064c440ddcf4015f5f8e9d131c7a0a6 (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')
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(¶ms); + 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 |