summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--chrome/app/generated_resources.grd16
-rw-r--r--chrome/browser/extensions/api/web_request/web_request_apitest.cc17
-rw-r--r--chrome/browser/extensions/extension_action_runner.cc78
-rw-r--r--chrome/browser/extensions/extension_action_runner.h30
-rw-r--r--chrome/browser/extensions/extension_action_runner_browsertest.cc107
-rw-r--r--chrome/browser/extensions/extension_action_runner_unittest.cc12
-rw-r--r--chrome/browser/ui/cocoa/extensions/browser_actions_controller.mm51
-rw-r--r--chrome/browser/ui/cocoa/extensions/extension_message_bubble_bridge.h1
-rw-r--r--chrome/browser/ui/cocoa/extensions/extension_message_bubble_bridge.mm5
-rw-r--r--chrome/browser/ui/extensions/blocked_action_bubble_delegate.cc53
-rw-r--r--chrome/browser/ui/extensions/blocked_action_bubble_delegate.h38
-rw-r--r--chrome/browser/ui/extensions/extension_action_view_controller.cc2
-rw-r--r--chrome/browser/ui/extensions/extension_toolbar_icon_surfacing_bubble_delegate.cc4
-rw-r--r--chrome/browser/ui/extensions/extension_toolbar_icon_surfacing_bubble_delegate.h1
-rw-r--r--chrome/browser/ui/toolbar/test_toolbar_actions_bar_bubble_delegate.cc1
-rw-r--r--chrome/browser/ui/toolbar/toolbar_actions_bar.cc12
-rw-r--r--chrome/browser/ui/toolbar/toolbar_actions_bar.h13
-rw-r--r--chrome/browser/ui/toolbar/toolbar_actions_bar_bubble_delegate.h6
-rw-r--r--chrome/browser/ui/toolbar/toolbar_actions_bar_delegate.h5
-rw-r--r--chrome/browser/ui/views/toolbar/browser_actions_container.cc19
-rw-r--r--chrome/browser/ui/views/toolbar/browser_actions_container.h2
-rw-r--r--chrome/chrome_browser_ui.gypi2
-rw-r--r--chrome/test/data/extensions/blocked_actions/content_scripts/manifest.json11
-rw-r--r--chrome/test/data/extensions/blocked_actions/content_scripts/script.js9
24 files changed, 470 insertions, 25 deletions
diff --git a/chrome/app/generated_resources.grd b/chrome/app/generated_resources.grd
index 222cbd5..518cf5f 100644
--- a/chrome/app/generated_resources.grd
+++ b/chrome/app/generated_resources.grd
@@ -3618,8 +3618,8 @@ Even if you have downloaded files from this website before, the website might ha
<ph name="EXTENSION_NAME">$1<ex>Adblock</ex></ph> was installed remotely
</message>
- <!-- Extension toolbar bubble -->
<if expr="enable_extensions">
+ <!-- Extension toolbar bubble -->
<message name="IDS_EXTENSION_TOOLBAR_BUBBLE_HEADING" desc="Heading of the bubble to tell users that all extensions are now visible in the toolbar (the extension toolbar info bubble).">
All your extensions are here
</message>
@@ -3629,6 +3629,20 @@ Even if you have downloaded files from this website before, the website might ha
<message name="IDS_EXTENSION_TOOLBAR_BUBBLE_OK" desc="Button label for the extension toolbar info bubble.">
Got it
</message>
+
+ <!-- Extension blocked action bubble -->
+ <message name="IDS_EXTENSION_BLOCKED_ACTION_BUBBLE_HEADING" desc="Heading of the bubble to tell users that in order to run an extension, they'll need to refresh the page.">
+ Refresh required
+ </message>
+ <message name="IDS_EXTENSION_BLOCKED_ACTION_BUBBLE_CONTENT" desc="The content of the bubble to tell users that in order to run an extension, they'll need to refresh the page.">
+ In order to run this extension, you need to refresh the page. You can run this extension automatically on this site by right-clicking on the extension icon.
+ </message>
+ <message name="IDS_EXTENSION_BLOCKED_ACTION_BUBBLE_OK_BUTTON" desc="The text of the button to proceed with the page refresh for running an extension.">
+ OK, refresh
+ </message>
+ <message name="IDS_EXTENSION_BLOCKED_ACTION_BUBBLE_CANCEL_BUTTON" desc="The text of the button to close the bubble and not refresh the page or run the extension.">
+ Never mind
+ </message>
</if>
<!-- Extension install prompt -->
diff --git a/chrome/browser/extensions/api/web_request/web_request_apitest.cc b/chrome/browser/extensions/api/web_request/web_request_apitest.cc
index 15458a5..8522337 100644
--- a/chrome/browser/extensions/api/web_request/web_request_apitest.cc
+++ b/chrome/browser/extensions/api/web_request/web_request_apitest.cc
@@ -527,10 +527,22 @@ IN_PROC_BROWSER_TEST_F(ExtensionWebRequestApiTest,
// Grant activeTab permission, and perform another XHR. The extension should
// receive the event.
EXPECT_EQ(BLOCKED_ACTION_WEB_REQUEST, runner->GetBlockedActions(extension));
+ runner->set_default_bubble_close_action_for_testing(
+ make_scoped_ptr(new ToolbarActionsBarBubbleDelegate::CloseAction(
+ ToolbarActionsBarBubbleDelegate::CLOSE_EXECUTE)));
runner->RunAction(extension, true);
+ base::RunLoop().RunUntilIdle();
+ EXPECT_TRUE(content::WaitForLoadStop(web_contents));
+ // The runner will have refreshed the page...
EXPECT_EQ(BLOCKED_ACTION_NONE, runner->GetBlockedActions(extension));
+ int xhr_count = GetWebRequestCountFromBackgroundPage(extension, profile());
+ // ... which means that we should have a non-zero xhr count.
+ EXPECT_GT(xhr_count, 0);
+ // And the extension should receive future events.
PerformXhrInPage(web_contents, kHost, port, kXhrPath);
- EXPECT_EQ(1, GetWebRequestCountFromBackgroundPage(extension, profile()));
+ ++xhr_count;
+ EXPECT_EQ(xhr_count,
+ GetWebRequestCountFromBackgroundPage(extension, profile()));
// If we revoke the extension's tab permissions, it should no longer receive
// webRequest events.
@@ -540,7 +552,8 @@ IN_PROC_BROWSER_TEST_F(ExtensionWebRequestApiTest,
granter->RevokeForTesting();
base::RunLoop().RunUntilIdle();
PerformXhrInPage(web_contents, kHost, port, kXhrPath);
- EXPECT_EQ(1, GetWebRequestCountFromBackgroundPage(extension, profile()));
+ EXPECT_EQ(xhr_count,
+ GetWebRequestCountFromBackgroundPage(extension, profile()));
EXPECT_EQ(BLOCKED_ACTION_WEB_REQUEST, runner->GetBlockedActions(extension));
}
diff --git a/chrome/browser/extensions/extension_action_runner.cc b/chrome/browser/extensions/extension_action_runner.cc
index 8fa7f01..8be229b 100644
--- a/chrome/browser/extensions/extension_action_runner.cc
+++ b/chrome/browser/extensions/extension_action_runner.cc
@@ -4,6 +4,7 @@
#include "chrome/browser/extensions/extension_action_runner.h"
+#include "base/auto_reset.h"
#include "base/bind.h"
#include "base/bind_helpers.h"
#include "base/memory/scoped_ptr.h"
@@ -17,6 +18,10 @@
#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_finder.h"
+#include "chrome/browser/ui/browser_window.h"
+#include "chrome/browser/ui/extensions/blocked_action_bubble_delegate.h"
+#include "chrome/browser/ui/toolbar/toolbar_actions_bar.h"
#include "chrome/common/extensions/api/extension_action/action_info.h"
#include "components/crx_file/id_util.h"
#include "content/public/browser/navigation_controller.h"
@@ -35,6 +40,13 @@
namespace extensions {
+namespace {
+
+// The blocked actions that require a page refresh to run.
+const int kRefreshRequiredActionsMask =
+ BLOCKED_ACTION_WEB_REQUEST | BLOCKED_ACTION_SCRIPT_AT_START;
+}
+
ExtensionActionRunner::PendingScript::PendingScript(
UserScript::RunLocation run_location,
const base::Closure& permit_script)
@@ -50,7 +62,9 @@ ExtensionActionRunner::ExtensionActionRunner(content::WebContents* web_contents)
num_page_requests_(0),
browser_context_(web_contents->GetBrowserContext()),
was_used_on_page_(false),
- extension_registry_observer_(this) {
+ ignore_active_tab_granted_(false),
+ extension_registry_observer_(this),
+ weak_factory_(this) {
CHECK(web_contents);
extension_registry_observer_.Add(ExtensionRegistry::Get(browser_context_));
}
@@ -71,15 +85,19 @@ ExtensionActionRunner* ExtensionActionRunner::GetForWebContents(
ExtensionAction::ShowAction ExtensionActionRunner::RunAction(
const Extension* extension,
bool grant_tab_permissions) {
- bool has_pending_scripts = WantsToRun(extension);
if (grant_tab_permissions) {
+ int blocked = GetBlockedActions(extension);
+ if ((blocked & kRefreshRequiredActionsMask) != 0) {
+ ShowBlockedActionBubble(extension);
+ return ExtensionAction::ACTION_NONE;
+ }
TabHelper::FromWebContents(web_contents())
->active_tab_permission_granter()
->GrantIfRequested(extension);
// If the extension had blocked actions, granting active tab will have
// run the extension. Don't execute further since clicking should run
// blocked actions *or* the normal extension action, not both.
- if (has_pending_scripts)
+ if (blocked != BLOCKED_ACTION_NONE)
return ExtensionAction::ACTION_NONE;
}
@@ -123,7 +141,7 @@ void ExtensionActionRunner::RunBlockedActions(const Extension* extension) {
void ExtensionActionRunner::OnActiveTabPermissionGranted(
const Extension* extension) {
- if (WantsToRun(extension))
+ if (!ignore_active_tab_granted_ && WantsToRun(extension))
RunBlockedActions(extension);
}
@@ -162,6 +180,14 @@ bool ExtensionActionRunner::WantsToRun(const Extension* extension) {
return GetBlockedActions(extension) != BLOCKED_ACTION_NONE;
}
+void ExtensionActionRunner::RunForTesting(const Extension* extension) {
+ if (WantsToRun(extension)) {
+ TabHelper::FromWebContents(web_contents())
+ ->active_tab_permission_granter()
+ ->GrantIfRequested(extension);
+ }
+}
+
PermissionsData::AccessType
ExtensionActionRunner::RequiresUserConsentForScriptInjection(
const Extension* extension,
@@ -312,6 +338,49 @@ void ExtensionActionRunner::LogUMA() const {
}
}
+void ExtensionActionRunner::ShowBlockedActionBubble(
+ const Extension* extension) {
+ Browser* browser = chrome::FindBrowserWithWebContents(web_contents());
+ ToolbarActionsBar* toolbar_actions_bar =
+ browser ? browser->window()->GetToolbarActionsBar() : nullptr;
+ if (toolbar_actions_bar) {
+ auto callback =
+ base::Bind(&ExtensionActionRunner::OnBlockedActionBubbleClosed,
+ weak_factory_.GetWeakPtr(), extension->id());
+ if (default_bubble_close_action_for_testing_) {
+ base::MessageLoop::current()->PostTask(
+ FROM_HERE,
+ base::Bind(callback, *default_bubble_close_action_for_testing_));
+ } else {
+ toolbar_actions_bar->ShowToolbarActionBubble(make_scoped_ptr(
+ new BlockedActionBubbleDelegate(callback, extension->id())));
+ }
+ }
+}
+
+void ExtensionActionRunner::OnBlockedActionBubbleClosed(
+ const std::string& extension_id,
+ ToolbarActionsBarBubbleDelegate::CloseAction action) {
+ // If the user agreed to refresh the page, do so.
+ if (action == ToolbarActionsBarBubbleDelegate::CLOSE_EXECUTE) {
+ const Extension* extension = ExtensionRegistry::Get(browser_context_)
+ ->enabled_extensions()
+ .GetByID(extension_id);
+ if (!extension)
+ return;
+ {
+ // Ignore the active tab permission being granted because we don't want
+ // to run scripts right before we refresh the page.
+ base::AutoReset<bool> ignore_active_tab(&ignore_active_tab_granted_,
+ true);
+ TabHelper::FromWebContents(web_contents())
+ ->active_tab_permission_granter()
+ ->GrantIfRequested(extension);
+ }
+ web_contents()->GetController().Reload(false);
+ }
+}
+
bool ExtensionActionRunner::OnMessageReceived(
const IPC::Message& message,
content::RenderFrameHost* render_frame_host) {
@@ -336,6 +405,7 @@ void ExtensionActionRunner::DidNavigateMainFrame(
pending_scripts_.clear();
web_request_blocked_.clear();
was_used_on_page_ = false;
+ weak_factory_.InvalidateWeakPtrs();
}
void ExtensionActionRunner::OnExtensionUnloaded(
diff --git a/chrome/browser/extensions/extension_action_runner.h b/chrome/browser/extensions/extension_action_runner.h
index d985a28..d260ee0 100644
--- a/chrome/browser/extensions/extension_action_runner.h
+++ b/chrome/browser/extensions/extension_action_runner.h
@@ -15,8 +15,10 @@
#include "base/callback.h"
#include "base/compiler_specific.h"
#include "base/macros.h"
+#include "base/memory/weak_ptr.h"
#include "base/scoped_observer.h"
#include "chrome/browser/extensions/extension_action.h"
+#include "chrome/browser/ui/toolbar/toolbar_actions_bar_bubble_delegate.h"
#include "content/public/browser/web_contents_observer.h"
#include "extensions/browser/blocked_action_type.h"
#include "extensions/browser/extension_registry_observer.h"
@@ -76,8 +78,17 @@ class ExtensionActionRunner : public content::WebContentsObserver,
// Returns true if the given |extension| has any blocked actions.
bool WantsToRun(const Extension* extension);
+ // Runs any blocked actions the extension has, but does not handle any page
+ // refreshes for document_start/webRequest.
+ void RunForTesting(const Extension* extension);
+
int num_page_requests() const { return num_page_requests_; }
+ void set_default_bubble_close_action_for_testing(
+ scoped_ptr<ToolbarActionsBarBubbleDelegate::CloseAction> action) {
+ default_bubble_close_action_for_testing_ = std::move(action);
+ }
+
#if defined(UNIT_TEST)
// Only used in tests.
PermissionsData::AccessType RequiresUserConsentForScriptInjectionForTesting(
@@ -141,6 +152,15 @@ class ExtensionActionRunner : public content::WebContentsObserver,
// Log metrics.
void LogUMA() const;
+ // Shows the bubble to prompt the user to refresh the page to run the blocked
+ // actions for the given |extension|.
+ void ShowBlockedActionBubble(const Extension* extension);
+
+ // Called when the blocked actions bubble is closed.
+ void OnBlockedActionBubbleClosed(
+ const std::string& extension_id,
+ ToolbarActionsBarBubbleDelegate::CloseAction action);
+
// content::WebContentsObserver implementation.
bool OnMessageReceived(const IPC::Message& message,
content::RenderFrameHost* render_frame_host) override;
@@ -177,9 +197,19 @@ class ExtensionActionRunner : public content::WebContentsObserver,
// should incorporate more fully with ActiveTab.
std::set<std::string> permitted_extensions_;
+ // If true, ignore active tab being granted rather than running pending
+ // actions.
+ bool ignore_active_tab_granted_;
+
+ // If non-null, the bubble action to simulate for testing.
+ scoped_ptr<ToolbarActionsBarBubbleDelegate::CloseAction>
+ default_bubble_close_action_for_testing_;
+
ScopedObserver<ExtensionRegistry, ExtensionRegistryObserver>
extension_registry_observer_;
+ base::WeakPtrFactory<ExtensionActionRunner> weak_factory_;
+
DISALLOW_COPY_AND_ASSIGN(ExtensionActionRunner);
};
diff --git a/chrome/browser/extensions/extension_action_runner_browsertest.cc b/chrome/browser/extensions/extension_action_runner_browsertest.cc
index f55091d..5428c3c 100644
--- a/chrome/browser/extensions/extension_action_runner_browsertest.cc
+++ b/chrome/browser/extensions/extension_action_runner_browsertest.cc
@@ -20,6 +20,8 @@
#include "chrome/browser/ui/browser.h"
#include "chrome/browser/ui/tabs/tab_strip_model.h"
#include "chrome/test/base/ui_test_utils.h"
+#include "content/public/browser/navigation_entry.h"
+#include "content/public/browser/web_contents.h"
#include "content/public/test/browser_test_utils.h"
#include "extensions/common/feature_switch.h"
#include "extensions/common/switches.h"
@@ -63,6 +65,22 @@ bool RunAllPendingInRenderer(content::WebContents* web_contents) {
return content::ExecuteScript(web_contents, "1 == 1;");
}
+// For use with blocked actions browsertests that put the result in
+// window.localStorage. Returns the result or "undefined" if the result is not
+// set.
+std::string GetValue(content::WebContents* web_contents) {
+ std::string out;
+ if (!content::ExecuteScriptAndExtractString(
+ web_contents,
+ "var res = window.localStorage.getItem('extResult') || 'undefined';"
+ "window.localStorage.removeItem('extResult');"
+ "window.domAutomationController.send(res);",
+ &out)) {
+ out = "Failed to inject script";
+ }
+ return out;
+}
+
} // namespace
class ExtensionActionRunnerBrowserTest : public ExtensionBrowserTest {
@@ -119,7 +137,7 @@ const Extension* ExtensionActionRunnerBrowserTest::CreateExtension(
" {"
" \"matches\": [\"%s\"],"
" \"js\": [\"script.js\"],"
- " \"run_at\": \"document_start\""
+ " \"run_at\": \"document_end\""
" }"
"]",
permission_scheme);
@@ -427,6 +445,93 @@ IN_PROC_BROWSER_TEST_F(ExtensionActionRunnerBrowserTest,
EXPECT_FALSE(inject_success_listener.was_satisfied());
}
+IN_PROC_BROWSER_TEST_F(ExtensionActionRunnerBrowserTest,
+ BlockedActionBrowserTest) {
+ // Load an extension that wants to run on every page at document start, and
+ // load a test page.
+ ASSERT_TRUE(embedded_test_server()->Start());
+ const GURL url = embedded_test_server()->GetURL("/simple.html");
+ const Extension* extension = LoadExtension(
+ test_data_dir_.AppendASCII("blocked_actions/content_scripts"));
+ ASSERT_TRUE(extension);
+ ui_test_utils::NavigateToURL(browser(), url);
+ content::WebContents* web_contents =
+ browser()->tab_strip_model()->GetActiveWebContents();
+ EXPECT_TRUE(content::WaitForLoadStop(web_contents));
+
+ // The extension should want to run on the page, and should not have
+ // injected.
+ ExtensionActionRunner* runner =
+ ExtensionActionRunner::GetForWebContents(web_contents);
+ ASSERT_TRUE(runner);
+ EXPECT_TRUE(runner->WantsToRun(extension));
+ EXPECT_EQ("undefined", GetValue(web_contents));
+
+ // Wire up the runner to automatically accept the bubble to prompt for page
+ // refresh.
+ runner->set_default_bubble_close_action_for_testing(
+ make_scoped_ptr(new ToolbarActionsBarBubbleDelegate::CloseAction(
+ ToolbarActionsBarBubbleDelegate::CLOSE_EXECUTE)));
+
+ content::NavigationEntry* entry =
+ web_contents->GetController().GetLastCommittedEntry();
+ ASSERT_TRUE(entry);
+ const int first_nav_id = entry->GetUniqueID();
+
+ // Run the extension action, which should cause a page refresh (since we
+ // automatically accepted the bubble prompting us), and the extension should
+ // have injected at document start.
+ runner->RunAction(extension, true);
+ base::RunLoop().RunUntilIdle();
+ EXPECT_TRUE(content::WaitForLoadStop(web_contents));
+ entry = web_contents->GetController().GetLastCommittedEntry();
+ ASSERT_TRUE(entry);
+ // Confirm that we refreshed the page.
+ EXPECT_GE(entry->GetUniqueID(), first_nav_id);
+ EXPECT_EQ("success", GetValue(web_contents));
+ EXPECT_FALSE(runner->WantsToRun(extension));
+
+ // Revoke permission and reload to try different bubble options.
+ ActiveTabPermissionGranter* active_tab_granter =
+ TabHelper::FromWebContents(web_contents)->active_tab_permission_granter();
+ ASSERT_TRUE(active_tab_granter);
+ active_tab_granter->RevokeForTesting();
+ web_contents->GetController().Reload(true);
+ EXPECT_TRUE(content::WaitForLoadStop(web_contents));
+
+ // The extension should again want to run. Automatically dismiss the bubble
+ // that pops up prompting for page refresh.
+ EXPECT_TRUE(runner->WantsToRun(extension));
+ EXPECT_EQ("undefined", GetValue(web_contents));
+ const int next_nav_id =
+ web_contents->GetController().GetLastCommittedEntry()->GetUniqueID();
+ runner->set_default_bubble_close_action_for_testing(
+ make_scoped_ptr(new ToolbarActionsBarBubbleDelegate::CloseAction(
+ ToolbarActionsBarBubbleDelegate::CLOSE_DISMISS_USER_ACTION)));
+
+ // Try running the extension. Nothing should happen, because the user
+ // didn't agree to refresh the page. The extension should still want to run.
+ runner->RunAction(extension, true);
+ base::RunLoop().RunUntilIdle();
+ EXPECT_TRUE(content::WaitForLoadStop(web_contents));
+ EXPECT_EQ("undefined", GetValue(web_contents));
+ EXPECT_EQ(
+ next_nav_id,
+ web_contents->GetController().GetLastCommittedEntry()->GetUniqueID());
+
+ // Repeat with a dismissal from bubble deactivation - same story.
+ runner->set_default_bubble_close_action_for_testing(
+ make_scoped_ptr(new ToolbarActionsBarBubbleDelegate::CloseAction(
+ ToolbarActionsBarBubbleDelegate::CLOSE_DISMISS_DEACTIVATION)));
+ runner->RunAction(extension, true);
+ base::RunLoop().RunUntilIdle();
+ EXPECT_TRUE(content::WaitForLoadStop(web_contents));
+ EXPECT_EQ("undefined", GetValue(web_contents));
+ EXPECT_EQ(
+ next_nav_id,
+ web_contents->GetController().GetLastCommittedEntry()->GetUniqueID());
+}
+
// A version of the test with the flag off, in order to test that everything
// still works as expected.
class FlagOffExtensionActionRunnerBrowserTest
diff --git a/chrome/browser/extensions/extension_action_runner_unittest.cc b/chrome/browser/extensions/extension_action_runner_unittest.cc
index 2aaff8c..edacb84 100644
--- a/chrome/browser/extensions/extension_action_runner_unittest.cc
+++ b/chrome/browser/extensions/extension_action_runner_unittest.cc
@@ -209,7 +209,7 @@ TEST_F(ExtensionActionRunnerUnitTest, RequestPermissionAndExecute) {
EXPECT_EQ(0u, GetExecutionCountForExtension(extension->id()));
// Click to accept the extension executing.
- runner()->RunAction(extension, true);
+ runner()->RunForTesting(extension);
// The extension should execute, and the extension shouldn't want to run.
EXPECT_EQ(1u, GetExecutionCountForExtension(extension->id()));
@@ -234,7 +234,7 @@ TEST_F(ExtensionActionRunnerUnitTest, RequestPermissionAndExecute) {
// Grant access.
RequestInjection(extension);
- runner()->RunAction(extension, true);
+ runner()->RunForTesting(extension);
EXPECT_EQ(2u, GetExecutionCountForExtension(extension->id()));
EXPECT_FALSE(runner()->WantsToRun(extension));
@@ -266,7 +266,7 @@ TEST_F(ExtensionActionRunnerUnitTest, PendingInjectionsRemovedAtNavigation) {
// Request and accept a new injection.
RequestInjection(extension);
- runner()->RunAction(extension, true);
+ runner()->RunForTesting(extension);
// The extension should only have executed once, even though a grand total
// of two executions were requested.
@@ -290,7 +290,7 @@ TEST_F(ExtensionActionRunnerUnitTest, MultiplePendingInjection) {
EXPECT_EQ(0u, GetExecutionCountForExtension(extension->id()));
- runner()->RunAction(extension, true);
+ runner()->RunForTesting(extension);
// All pending injections should have executed.
EXPECT_EQ(kNumInjections, GetExecutionCountForExtension(extension->id()));
@@ -388,7 +388,7 @@ TEST_F(ExtensionActionRunnerUnitTest, TestAlwaysRun) {
// Allow the extension to always run on this origin.
ScriptingPermissionsModifier modifier(profile(), extension);
modifier.GrantHostPermission(web_contents()->GetLastCommittedURL());
- runner()->RunAction(extension, true);
+ runner()->RunForTesting(extension);
// The extension should execute, and the extension shouldn't want to run.
EXPECT_EQ(1u, GetExecutionCountForExtension(extension->id()));
@@ -444,7 +444,7 @@ TEST_F(ExtensionActionRunnerUnitTest, TestDifferentScriptRunLocations) {
EXPECT_EQ(BLOCKED_ACTION_SCRIPT_AT_START | BLOCKED_ACTION_SCRIPT_OTHER,
runner()->GetBlockedActions(extension));
- runner()->RunAction(extension, true);
+ runner()->RunForTesting(extension);
EXPECT_EQ(BLOCKED_ACTION_NONE, runner()->GetBlockedActions(extension));
}
diff --git a/chrome/browser/ui/cocoa/extensions/browser_actions_controller.mm b/chrome/browser/ui/cocoa/extensions/browser_actions_controller.mm
index 3f97173..d6b7bb5 100644
--- a/chrome/browser/ui/cocoa/extensions/browser_actions_controller.mm
+++ b/chrome/browser/ui/cocoa/extensions/browser_actions_controller.mm
@@ -167,11 +167,11 @@ const CGFloat kBrowserActionBubbleYOffset = 3.0;
// Returns the associated ToolbarController.
- (ToolbarController*)toolbarController;
-// Creates a message bubble anchored to the given |anchorAction|, or the app
-// menu if no |anchorAction| is null.
+// Creates a message bubble with the given |delegate| that is anchored to the
+// given |anchorView|.
- (ToolbarActionsBarBubbleMac*)createMessageBubble:
(scoped_ptr<ToolbarActionsBarBubbleDelegate>)delegate
- anchorToSelf:(BOOL)anchorToSelf;
+ anchorView:(NSView*)anchorView;
// Called when the window for the active bubble is closing, and sets the active
// bubble to nil.
@@ -223,6 +223,8 @@ class ToolbarActionsBarBridge : public ToolbarActionsBarDelegate {
void ShowExtensionMessageBubble(
scoped_ptr<extensions::ExtensionMessageBubbleController> controller,
ToolbarActionViewController* anchor_action) override;
+ void ShowToolbarActionBubble(
+ scoped_ptr<ToolbarActionsBarBubbleDelegate> bubble) override;
// The owning BrowserActionsController; weak.
BrowserActionsController* controller_;
@@ -297,6 +299,19 @@ int ToolbarActionsBarBridge::GetChevronWidth() const {
void ToolbarActionsBarBridge::ShowExtensionMessageBubble(
scoped_ptr<extensions::ExtensionMessageBubbleController> bubble_controller,
ToolbarActionViewController* anchor_action) {
+ NSView* anchorView = nil;
+ BOOL anchoredToAction = NO;
+ if (anchor_action) {
+ BrowserActionButton* actionButton =
+ [controller_ buttonForId:anchor_action->GetId()];
+ if (actionButton && [actionButton superview]) {
+ anchorView = actionButton;
+ anchoredToAction = YES;
+ }
+ }
+ if (!anchorView)
+ anchorView = [[controller_ toolbarController] appMenuButton];
+
// This goop is a by-product of needing to wire together abstract classes,
// C++/Cocoa bridges, and ExtensionMessageBubbleController's somewhat strange
// Show() interface. It's ugly, but it's pretty confined, so it's probably
@@ -305,14 +320,32 @@ void ToolbarActionsBarBridge::ShowExtensionMessageBubble(
bubble_controller.get();
scoped_ptr<ExtensionMessageBubbleBridge> bridge(
new ExtensionMessageBubbleBridge(std::move(bubble_controller),
- anchor_action != nullptr));
+ anchoredToAction));
ToolbarActionsBarBubbleMac* bubble =
[controller_ createMessageBubble:std::move(bridge)
- anchorToSelf:anchor_action != nil];
+ anchorView:anchorView];
weak_controller->OnShown();
[bubble showWindow:nil];
}
+void ToolbarActionsBarBridge::ShowToolbarActionBubble(
+ scoped_ptr<ToolbarActionsBarBubbleDelegate> bubble) {
+ NSView* anchorView = nil;
+ if (!bubble->GetAnchorActionId().empty()) {
+ BrowserActionButton* button =
+ [controller_ buttonForId:bubble->GetAnchorActionId()];
+ anchorView = button && [button superview] ? button :
+ [[controller_ toolbarController] appMenuButton];
+ } else {
+ anchorView = [controller_ containerView];
+ }
+
+ ToolbarActionsBarBubbleMac* bubbleView =
+ [controller_ createMessageBubble:std::move(bubble)
+ anchorView:anchorView];
+ [bubbleView showWindow:nil];
+}
+
} // namespace
@implementation BrowserActionsController
@@ -826,7 +859,8 @@ void ToolbarActionsBarBridge::ShowExtensionMessageBubble(
scoped_ptr<ToolbarActionsBarBubbleDelegate> delegate(
new ExtensionToolbarIconSurfacingBubbleDelegate(browser_->profile()));
ToolbarActionsBarBubbleMac* bubble =
- [self createMessageBubble:std::move(delegate) anchorToSelf:YES];
+ [self createMessageBubble:std::move(delegate)
+ anchorView:containerView_];
[bubble showWindow:nil];
}
[containerView_ setTrackingEnabled:NO];
@@ -1042,10 +1076,9 @@ void ToolbarActionsBarBridge::ShowExtensionMessageBubble(
- (ToolbarActionsBarBubbleMac*)createMessageBubble:
(scoped_ptr<ToolbarActionsBarBubbleDelegate>)delegate
- anchorToSelf:(BOOL)anchorToSelf {
+ anchorView:(NSView*)anchorView {
+ DCHECK(anchorView);
DCHECK_GE([buttons_ count], 0u);
- NSView* anchorView =
- anchorToSelf ? containerView_ : [[self toolbarController] appMenuButton];
NSPoint anchor = [self popupPointForView:anchorView
withBounds:[anchorView bounds]];
diff --git a/chrome/browser/ui/cocoa/extensions/extension_message_bubble_bridge.h b/chrome/browser/ui/cocoa/extensions/extension_message_bubble_bridge.h
index 5a86977..ab925a4 100644
--- a/chrome/browser/ui/cocoa/extensions/extension_message_bubble_bridge.h
+++ b/chrome/browser/ui/cocoa/extensions/extension_message_bubble_bridge.h
@@ -32,6 +32,7 @@ class ExtensionMessageBubbleBridge : public ToolbarActionsBarBubbleDelegate {
base::string16 GetActionButtonText() override;
base::string16 GetDismissButtonText() override;
base::string16 GetLearnMoreButtonText() override;
+ std::string GetAnchorActionId() override;
void OnBubbleShown() override;
void OnBubbleClosed(CloseAction action) override;
diff --git a/chrome/browser/ui/cocoa/extensions/extension_message_bubble_bridge.mm b/chrome/browser/ui/cocoa/extensions/extension_message_bubble_bridge.mm
index f7cb1f5..ae34ee2 100644
--- a/chrome/browser/ui/cocoa/extensions/extension_message_bubble_bridge.mm
+++ b/chrome/browser/ui/cocoa/extensions/extension_message_bubble_bridge.mm
@@ -44,6 +44,11 @@ base::string16 ExtensionMessageBubbleBridge::GetLearnMoreButtonText() {
return controller_->delegate()->GetLearnMoreLabel();
}
+std::string ExtensionMessageBubbleBridge::GetAnchorActionId() {
+ return controller_->GetExtensionIdList().size() == 1u ?
+ controller_->GetExtensionIdList()[0] : std::string();
+}
+
void ExtensionMessageBubbleBridge::OnBubbleShown() {
}
diff --git a/chrome/browser/ui/extensions/blocked_action_bubble_delegate.cc b/chrome/browser/ui/extensions/blocked_action_bubble_delegate.cc
new file mode 100644
index 0000000..288ffef
--- /dev/null
+++ b/chrome/browser/ui/extensions/blocked_action_bubble_delegate.cc
@@ -0,0 +1,53 @@
+// Copyright 2016 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/ui/extensions/blocked_action_bubble_delegate.h"
+
+#include "base/callback_helpers.h"
+#include "base/strings/string16.h"
+#include "grit/generated_resources.h"
+#include "ui/base/l10n/l10n_util.h"
+
+BlockedActionBubbleDelegate::BlockedActionBubbleDelegate(
+ const base::Callback<void(CloseAction)>& callback,
+ const std::string& extension_id)
+ : callback_(callback), extension_id_(extension_id) {}
+
+BlockedActionBubbleDelegate::~BlockedActionBubbleDelegate() {}
+
+base::string16 BlockedActionBubbleDelegate::GetHeadingText() {
+ return l10n_util::GetStringUTF16(IDS_EXTENSION_BLOCKED_ACTION_BUBBLE_HEADING);
+}
+
+base::string16 BlockedActionBubbleDelegate::GetBodyText() {
+ return l10n_util::GetStringUTF16(IDS_EXTENSION_BLOCKED_ACTION_BUBBLE_CONTENT);
+}
+
+base::string16 BlockedActionBubbleDelegate::GetItemListText() {
+ return base::string16(); // No item list.
+}
+
+base::string16 BlockedActionBubbleDelegate::GetActionButtonText() {
+ return l10n_util::GetStringUTF16(
+ IDS_EXTENSION_BLOCKED_ACTION_BUBBLE_OK_BUTTON);
+}
+
+base::string16 BlockedActionBubbleDelegate::GetDismissButtonText() {
+ return l10n_util::GetStringUTF16(
+ IDS_EXTENSION_BLOCKED_ACTION_BUBBLE_CANCEL_BUTTON);
+}
+
+base::string16 BlockedActionBubbleDelegate::GetLearnMoreButtonText() {
+ return base::string16(); // No learn more link.
+}
+
+std::string BlockedActionBubbleDelegate::GetAnchorActionId() {
+ return extension_id_;
+}
+
+void BlockedActionBubbleDelegate::OnBubbleShown() {}
+
+void BlockedActionBubbleDelegate::OnBubbleClosed(CloseAction action) {
+ base::ResetAndReturn(&callback_).Run(action);
+}
diff --git a/chrome/browser/ui/extensions/blocked_action_bubble_delegate.h b/chrome/browser/ui/extensions/blocked_action_bubble_delegate.h
new file mode 100644
index 0000000..8e2200e
--- /dev/null
+++ b/chrome/browser/ui/extensions/blocked_action_bubble_delegate.h
@@ -0,0 +1,38 @@
+// Copyright 2016 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_UI_EXTENSIONS_BLOCKED_ACTION_BUBBLE_DELEGATE_H_
+#define CHROME_BROWSER_UI_EXTENSIONS_BLOCKED_ACTION_BUBBLE_DELEGATE_H_
+
+#include "base/callback.h"
+#include "base/macros.h"
+#include "chrome/browser/ui/toolbar/toolbar_actions_bar_bubble_delegate.h"
+
+// The delegate for the bubble to ask the user if they want to refresh the page
+// in order to run any blocked actions the extension may have.
+class BlockedActionBubbleDelegate : public ToolbarActionsBarBubbleDelegate {
+ public:
+ BlockedActionBubbleDelegate(const base::Callback<void(CloseAction)>& callback,
+ const std::string& extension_id);
+ ~BlockedActionBubbleDelegate() override;
+
+ private:
+ // ToolbarActionsBarBubbleDelegate:
+ base::string16 GetHeadingText() override;
+ base::string16 GetBodyText() override;
+ base::string16 GetItemListText() override;
+ base::string16 GetActionButtonText() override;
+ base::string16 GetDismissButtonText() override;
+ base::string16 GetLearnMoreButtonText() override;
+ std::string GetAnchorActionId() override;
+ void OnBubbleShown() override;
+ void OnBubbleClosed(CloseAction action) override;
+
+ base::Callback<void(CloseAction)> callback_;
+ std::string extension_id_;
+
+ DISALLOW_COPY_AND_ASSIGN(BlockedActionBubbleDelegate);
+};
+
+#endif // CHROME_BROWSER_UI_EXTENSIONS_BLOCKED_ACTION_BUBBLE_DELEGATE_H_
diff --git a/chrome/browser/ui/extensions/extension_action_view_controller.cc b/chrome/browser/ui/extensions/extension_action_view_controller.cc
index bdbb532..4b9f123 100644
--- a/chrome/browser/ui/extensions/extension_action_view_controller.cc
+++ b/chrome/browser/ui/extensions/extension_action_view_controller.cc
@@ -216,7 +216,7 @@ bool ExtensionActionViewController::ExecuteAction(PopupShowAction show_action,
if (action_runner->RunAction(extension(), grant_tab_permissions) ==
ExtensionAction::ACTION_SHOW_POPUP) {
GURL popup_url = extension_action_->GetPopupUrl(
- SessionTabHelper::IdForTab(view_delegate_->GetCurrentWebContents()));
+ SessionTabHelper::IdForTab(web_contents));
return GetPreferredPopupViewController()
->TriggerPopupWithUrl(show_action, popup_url, grant_tab_permissions);
}
diff --git a/chrome/browser/ui/extensions/extension_toolbar_icon_surfacing_bubble_delegate.cc b/chrome/browser/ui/extensions/extension_toolbar_icon_surfacing_bubble_delegate.cc
index a2c8ffc..1151155 100644
--- a/chrome/browser/ui/extensions/extension_toolbar_icon_surfacing_bubble_delegate.cc
+++ b/chrome/browser/ui/extensions/extension_toolbar_icon_surfacing_bubble_delegate.cc
@@ -91,6 +91,10 @@ ExtensionToolbarIconSurfacingBubbleDelegate::GetLearnMoreButtonText() {
return base::string16(); // No learn more link.
}
+std::string ExtensionToolbarIconSurfacingBubbleDelegate::GetAnchorActionId() {
+ return std::string(); // Point to the whole set of actions.
+}
+
void ExtensionToolbarIconSurfacingBubbleDelegate::OnBubbleShown() {
// Record the last time the bubble was shown.
profile_->GetPrefs()->SetInt64(
diff --git a/chrome/browser/ui/extensions/extension_toolbar_icon_surfacing_bubble_delegate.h b/chrome/browser/ui/extensions/extension_toolbar_icon_surfacing_bubble_delegate.h
index 527201a..85e4caf 100644
--- a/chrome/browser/ui/extensions/extension_toolbar_icon_surfacing_bubble_delegate.h
+++ b/chrome/browser/ui/extensions/extension_toolbar_icon_surfacing_bubble_delegate.h
@@ -29,6 +29,7 @@ class ExtensionToolbarIconSurfacingBubbleDelegate
base::string16 GetActionButtonText() override;
base::string16 GetDismissButtonText() override;
base::string16 GetLearnMoreButtonText() override;
+ std::string GetAnchorActionId() override;
void OnBubbleShown() override;
void OnBubbleClosed(CloseAction action) override;
diff --git a/chrome/browser/ui/toolbar/test_toolbar_actions_bar_bubble_delegate.cc b/chrome/browser/ui/toolbar/test_toolbar_actions_bar_bubble_delegate.cc
index 957ad6f..fa13f07 100644
--- a/chrome/browser/ui/toolbar/test_toolbar_actions_bar_bubble_delegate.cc
+++ b/chrome/browser/ui/toolbar/test_toolbar_actions_bar_bubble_delegate.cc
@@ -23,6 +23,7 @@ class TestToolbarActionsBarBubbleDelegate::DelegateImpl
base::string16 GetLearnMoreButtonText() override {
return parent_->learn_more_;
}
+ std::string GetAnchorActionId() override { return std::string(); }
void OnBubbleShown() override {
CHECK(!parent_->shown_);
parent_->shown_ = true;
diff --git a/chrome/browser/ui/toolbar/toolbar_actions_bar.cc b/chrome/browser/ui/toolbar/toolbar_actions_bar.cc
index 546acd4..cb62c0c 100644
--- a/chrome/browser/ui/toolbar/toolbar_actions_bar.cc
+++ b/chrome/browser/ui/toolbar/toolbar_actions_bar.cc
@@ -493,6 +493,8 @@ void ToolbarActionsBar::OnAnimationEnded() {
// message bubble, or to show a popup.
if (pending_extension_bubble_controller_) {
MaybeShowExtensionBubble(std::move(pending_extension_bubble_controller_));
+ } else if (pending_toolbar_bubble_controller_) {
+ ShowToolbarActionBubble(std::move(pending_toolbar_bubble_controller_));
} else if (!popped_out_closure_.is_null()) {
popped_out_closure_.Run();
popped_out_closure_.Reset();
@@ -575,6 +577,16 @@ void ToolbarActionsBar::RemoveObserver(ToolbarActionsBarObserver* observer) {
observers_.RemoveObserver(observer);
}
+void ToolbarActionsBar::ShowToolbarActionBubble(
+ scoped_ptr<ToolbarActionsBarBubbleDelegate> bubble) {
+ DCHECK(bubble->GetAnchorActionId().empty() ||
+ GetActionForId(bubble->GetAnchorActionId()));
+ if (delegate_->IsAnimating())
+ pending_toolbar_bubble_controller_ = std::move(bubble);
+ else
+ delegate_->ShowToolbarActionBubble(std::move(bubble));
+}
+
void ToolbarActionsBar::MaybeShowExtensionBubble(
scoped_ptr<extensions::ExtensionMessageBubbleController> controller) {
controller->HighlightExtensionsIfNecessary(); // Safe to call multiple times.
diff --git a/chrome/browser/ui/toolbar/toolbar_actions_bar.h b/chrome/browser/ui/toolbar/toolbar_actions_bar.h
index 1c04dae..871db82 100644
--- a/chrome/browser/ui/toolbar/toolbar_actions_bar.h
+++ b/chrome/browser/ui/toolbar/toolbar_actions_bar.h
@@ -211,6 +211,10 @@ class ToolbarActionsBar : public ToolbarActionsModel::Observer {
void AddObserver(ToolbarActionsBarObserver* observer);
void RemoveObserver(ToolbarActionsBarObserver* observer);
+ // Displays the given |bubble| once the toolbar is no longer animating.
+ void ShowToolbarActionBubble(
+ scoped_ptr<ToolbarActionsBarBubbleDelegate> bubble);
+
// Returns the underlying toolbar actions, but does not order them. Primarily
// for use in testing.
const std::vector<ToolbarActionViewController*>& toolbar_actions_unordered()
@@ -336,10 +340,17 @@ class ToolbarActionsBar : public ToolbarActionsModel::Observer {
// it is fully popped out.
base::Closure popped_out_closure_;
- // The controller of the bubble to show once animation finishes, if any.
+ // The controller of the extension message bubble to show once animation
+ // finishes, if any. This has priority over
+ // |pending_toolbar_bubble_controller_|.
scoped_ptr<extensions::ExtensionMessageBubbleController>
pending_extension_bubble_controller_;
+ // The controller for the toolbar action bubble to show once animation
+ // finishes, if any.
+ scoped_ptr<ToolbarActionsBarBubbleDelegate>
+ pending_toolbar_bubble_controller_;
+
base::ObserverList<ToolbarActionsBarObserver> observers_;
base::WeakPtrFactory<ToolbarActionsBar> weak_ptr_factory_;
diff --git a/chrome/browser/ui/toolbar/toolbar_actions_bar_bubble_delegate.h b/chrome/browser/ui/toolbar/toolbar_actions_bar_bubble_delegate.h
index 508371e..d07dc71 100644
--- a/chrome/browser/ui/toolbar/toolbar_actions_bar_bubble_delegate.h
+++ b/chrome/browser/ui/toolbar/toolbar_actions_bar_bubble_delegate.h
@@ -5,6 +5,8 @@
#ifndef CHROME_BROWSER_UI_TOOLBAR_TOOLBAR_ACTIONS_BAR_BUBBLE_DELEGATE_H_
#define CHROME_BROWSER_UI_TOOLBAR_TOOLBAR_ACTIONS_BAR_BUBBLE_DELEGATE_H_
+#include <string>
+
#include "base/strings/string16.h"
// A delegate for a generic bubble that hangs off the toolbar actions bar.
@@ -43,6 +45,10 @@ class ToolbarActionsBarBubbleDelegate {
// string, no button will be added.
virtual base::string16 GetLearnMoreButtonText() = 0;
+ // Returns the id of the action to point to, or the empty string if the
+ // bubble should point to the center of the actions container.
+ virtual std::string GetAnchorActionId() = 0;
+
// Called when the bubble is shown.
virtual void OnBubbleShown() = 0;
diff --git a/chrome/browser/ui/toolbar/toolbar_actions_bar_delegate.h b/chrome/browser/ui/toolbar/toolbar_actions_bar_delegate.h
index 584a4b3..4c31025 100644
--- a/chrome/browser/ui/toolbar/toolbar_actions_bar_delegate.h
+++ b/chrome/browser/ui/toolbar/toolbar_actions_bar_delegate.h
@@ -12,6 +12,7 @@
#include "ui/gfx/geometry/size.h"
class ToolbarActionViewController;
+class ToolbarActionsBarBubbleDelegate;
namespace extensions {
class ExtensionMessageBubbleController;
@@ -70,6 +71,10 @@ class ToolbarActionsBarDelegate {
virtual void ShowExtensionMessageBubble(
scoped_ptr<extensions::ExtensionMessageBubbleController> controller,
ToolbarActionViewController* anchor_action) = 0;
+
+ // Shows the given |bubble| if no other bubbles are showing.
+ virtual void ShowToolbarActionBubble(
+ scoped_ptr<ToolbarActionsBarBubbleDelegate> bubble) = 0;
};
#endif // CHROME_BROWSER_UI_TOOLBAR_TOOLBAR_ACTIONS_BAR_DELEGATE_H_
diff --git a/chrome/browser/ui/views/toolbar/browser_actions_container.cc b/chrome/browser/ui/views/toolbar/browser_actions_container.cc
index 5ac758e..2d704d4 100644
--- a/chrome/browser/ui/views/toolbar/browser_actions_container.cc
+++ b/chrome/browser/ui/views/toolbar/browser_actions_container.cc
@@ -319,6 +319,25 @@ int BrowserActionsContainer::GetChevronWidth() const {
chevron_->GetPreferredSize().width() + GetChevronSpacing() : 0;
}
+void BrowserActionsContainer::ShowToolbarActionBubble(
+ scoped_ptr<ToolbarActionsBarBubbleDelegate> controller) {
+ // The container shouldn't be asked to show a bubble if it's animating.
+ DCHECK(!animating());
+ views::View* anchor_view = nullptr;
+ if (!controller->GetAnchorActionId().empty()) {
+ ToolbarActionView* action_view =
+ GetViewForId(controller->GetAnchorActionId());
+ anchor_view =
+ action_view->visible() ? action_view : GetOverflowReferenceView();
+ } else {
+ anchor_view = this;
+ }
+ ToolbarActionsBarBubbleViews* bubble =
+ new ToolbarActionsBarBubbleViews(anchor_view, std::move(controller));
+ views::BubbleDelegateView::CreateBubble(bubble);
+ bubble->Show();
+}
+
void BrowserActionsContainer::ShowExtensionMessageBubble(
scoped_ptr<extensions::ExtensionMessageBubbleController> controller,
ToolbarActionViewController* anchor_action) {
diff --git a/chrome/browser/ui/views/toolbar/browser_actions_container.h b/chrome/browser/ui/views/toolbar/browser_actions_container.h
index 14517aa..f77f7cb 100644
--- a/chrome/browser/ui/views/toolbar/browser_actions_container.h
+++ b/chrome/browser/ui/views/toolbar/browser_actions_container.h
@@ -239,6 +239,8 @@ class BrowserActionsContainer : public views::View,
bool IsAnimating() const override;
void StopAnimating() override;
int GetChevronWidth() const override;
+ void ShowToolbarActionBubble(
+ scoped_ptr<ToolbarActionsBarBubbleDelegate> controller) override;
void ShowExtensionMessageBubble(
scoped_ptr<extensions::ExtensionMessageBubbleController> controller,
ToolbarActionViewController* anchor_action) override;
diff --git a/chrome/chrome_browser_ui.gypi b/chrome/chrome_browser_ui.gypi
index 3239cda..75fa4985 100644
--- a/chrome/chrome_browser_ui.gypi
+++ b/chrome/chrome_browser_ui.gypi
@@ -2769,6 +2769,8 @@
'browser/ui/extensions/extension_message_bubble_factory.h',
'browser/ui/extensions/extension_toolbar_icon_surfacing_bubble_delegate.cc',
'browser/ui/extensions/extension_toolbar_icon_surfacing_bubble_delegate.h',
+ 'browser/ui/extensions/blocked_action_bubble_delegate.cc',
+ 'browser/ui/extensions/blocked_action_bubble_delegate.h',
'browser/ui/extensions/hosted_app_browser_controller.cc',
'browser/ui/extensions/hosted_app_browser_controller.h',
'browser/ui/extensions/icon_with_badge_image_source.cc',
diff --git a/chrome/test/data/extensions/blocked_actions/content_scripts/manifest.json b/chrome/test/data/extensions/blocked_actions/content_scripts/manifest.json
new file mode 100644
index 0000000..70d666b
--- /dev/null
+++ b/chrome/test/data/extensions/blocked_actions/content_scripts/manifest.json
@@ -0,0 +1,11 @@
+{
+ "name": "Blocked Actions Doc Start",
+ "description": "An extension that wants to run at document start on all urls",
+ "version": "0.1",
+ "manifest_version": 2,
+ "content_scripts": [{
+ "matches": ["<all_urls>"],
+ "run_at": "document_start",
+ "js": ["script.js"]
+ }]
+}
diff --git a/chrome/test/data/extensions/blocked_actions/content_scripts/script.js b/chrome/test/data/extensions/blocked_actions/content_scripts/script.js
new file mode 100644
index 0000000..c54b92c
--- /dev/null
+++ b/chrome/test/data/extensions/blocked_actions/content_scripts/script.js
@@ -0,0 +1,9 @@
+// Copyright 2016 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.
+
+// If the script was really injected at document_start, then document.body will
+// be null. If it's not null, then we didn't inject at document_start.
+// Store the result in window.localStorage so that it's accessible from the
+// main world script context in the browsertest.
+window.localStorage.setItem('extResult', document.body ? 'failure' : 'success');