Commit ededa7cf authored by Karan Bhatia's avatar Karan Bhatia Committed by Commit Bot

Click To Script: Prompt user to refresh page when toggling access through context menu.

Currently when a user grants access to the current page using the extension
action context menu, we run the blocked actions. But the extension may have
blocked actions which can't be run at this stage. Prompt the user to refresh the
page in this case, and grant permissions once the user accepts the refresh
bubble.

BUG=876791

Change-Id: I2516fe40050110ee2844670489796b421ad4fdb9
Reviewed-on: https://chromium-review.googlesource.com/1196225
Commit-Queue: Karan Bhatia <karandeepb@chromium.org>
Reviewed-by: default avatarDevlin <rdevlin.cronin@chromium.org>
Cr-Commit-Position: refs/heads/master@{#588953}
parent b91187c6
...@@ -19,6 +19,7 @@ ...@@ -19,6 +19,7 @@
#include "chrome/browser/extensions/extension_action.h" #include "chrome/browser/extensions/extension_action.h"
#include "chrome/browser/extensions/extension_action_manager.h" #include "chrome/browser/extensions/extension_action_manager.h"
#include "chrome/browser/extensions/permissions_updater.h" #include "chrome/browser/extensions/permissions_updater.h"
#include "chrome/browser/extensions/scripting_permissions_modifier.h"
#include "chrome/browser/extensions/tab_helper.h" #include "chrome/browser/extensions/tab_helper.h"
#include "chrome/browser/profiles/profile.h" #include "chrome/browser/profiles/profile.h"
#include "chrome/browser/sessions/session_tab_helper.h" #include "chrome/browser/sessions/session_tab_helper.h"
...@@ -41,6 +42,7 @@ ...@@ -41,6 +42,7 @@
#include "extensions/common/permissions/permission_set.h" #include "extensions/common/permissions/permission_set.h"
#include "extensions/common/permissions/permissions_data.h" #include "extensions/common/permissions/permissions_data.h"
#include "ipc/ipc_message_macros.h" #include "ipc/ipc_message_macros.h"
#include "url/origin.h"
namespace extensions { namespace extensions {
...@@ -93,7 +95,11 @@ ExtensionAction::ShowAction ExtensionActionRunner::RunAction( ...@@ -93,7 +95,11 @@ ExtensionAction::ShowAction ExtensionActionRunner::RunAction(
if (grant_tab_permissions) { if (grant_tab_permissions) {
int blocked = GetBlockedActions(extension); int blocked = GetBlockedActions(extension);
if ((blocked & kRefreshRequiredActionsMask) != 0) { if ((blocked & kRefreshRequiredActionsMask) != 0) {
ShowBlockedActionBubble(extension); ShowBlockedActionBubble(
extension,
base::Bind(
&ExtensionActionRunner::OnBlockedActionBubbleForRunActionClosed,
weak_factory_.GetWeakPtr(), extension->id()));
return ExtensionAction::ACTION_NONE; return ExtensionAction::ACTION_NONE;
} }
TabHelper::FromWebContents(web_contents()) TabHelper::FromWebContents(web_contents())
...@@ -125,24 +131,35 @@ ExtensionAction::ShowAction ExtensionActionRunner::RunAction( ...@@ -125,24 +131,35 @@ ExtensionAction::ShowAction ExtensionActionRunner::RunAction(
return ExtensionAction::ACTION_NONE; return ExtensionAction::ACTION_NONE;
} }
void ExtensionActionRunner::RunBlockedActions(const Extension* extension) { void ExtensionActionRunner::HandlePageAccessModified(const Extension* extension,
DCHECK(base::ContainsKey(pending_scripts_, extension->id()) || PageAccess current_access,
web_request_blocked_.count(extension->id()) != 0); PageAccess new_access) {
DCHECK_NE(current_access, new_access);
// Clicking to run the extension counts as granting it permission to run on // If we are restricting page access, just change permissions.
// the given tab. if (new_access == PageAccess::RUN_ON_CLICK) {
// The extension may already have active tab at this point, but granting UpdatePageAccessSettings(extension, current_access, new_access);
// it twice is essentially a no-op. return;
TabHelper::FromWebContents(web_contents()) }
->active_tab_permission_granter()
->GrantIfRequested(extension);
RunPendingScriptsForExtension(extension); int blocked_actions = GetBlockedActions(extension);
web_request_blocked_.erase(extension->id());
// Refresh the page if there are pending actions which mandate a refresh.
if (blocked_actions & kRefreshRequiredActionsMask) {
// TODO(devlin): The bubble text should make it clear that permissions are
// granted only after the user accepts the refresh.
ShowBlockedActionBubble(
extension, base::Bind(&ExtensionActionRunner::
OnBlockedActionBubbleForPageAccessGrantClosed,
weak_factory_.GetWeakPtr(), extension->id(),
web_contents()->GetLastCommittedURL(),
current_access, new_access));
return;
}
// The extension ran, so we need to tell the ExtensionActionAPI that we no UpdatePageAccessSettings(extension, current_access, new_access);
// longer want to act. if (blocked_actions)
NotifyChange(extension); RunBlockedActions(extension);
} }
void ExtensionActionRunner::OnActiveTabPermissionGranted( void ExtensionActionRunner::OnActiveTabPermissionGranted(
...@@ -349,14 +366,13 @@ void ExtensionActionRunner::LogUMA() const { ...@@ -349,14 +366,13 @@ void ExtensionActionRunner::LogUMA() const {
} }
void ExtensionActionRunner::ShowBlockedActionBubble( void ExtensionActionRunner::ShowBlockedActionBubble(
const Extension* extension) { const Extension* extension,
const base::Callback<void(ToolbarActionsBarBubbleDelegate::CloseAction)>&
callback) {
Browser* browser = chrome::FindBrowserWithWebContents(web_contents()); Browser* browser = chrome::FindBrowserWithWebContents(web_contents());
ToolbarActionsBar* toolbar_actions_bar = ToolbarActionsBar* toolbar_actions_bar =
browser ? browser->window()->GetToolbarActionsBar() : nullptr; browser ? browser->window()->GetToolbarActionsBar() : nullptr;
if (toolbar_actions_bar) { if (toolbar_actions_bar) {
auto callback =
base::Bind(&ExtensionActionRunner::OnBlockedActionBubbleClosed,
weak_factory_.GetWeakPtr(), extension->id());
if (default_bubble_close_action_for_testing_) { if (default_bubble_close_action_for_testing_) {
base::ThreadTaskRunnerHandle::Get()->PostTask( base::ThreadTaskRunnerHandle::Get()->PostTask(
FROM_HERE, FROM_HERE,
...@@ -369,7 +385,7 @@ void ExtensionActionRunner::ShowBlockedActionBubble( ...@@ -369,7 +385,7 @@ void ExtensionActionRunner::ShowBlockedActionBubble(
} }
} }
void ExtensionActionRunner::OnBlockedActionBubbleClosed( void ExtensionActionRunner::OnBlockedActionBubbleForRunActionClosed(
const std::string& extension_id, const std::string& extension_id,
ToolbarActionsBarBubbleDelegate::CloseAction action) { ToolbarActionsBarBubbleDelegate::CloseAction action) {
// If the user agreed to refresh the page, do so. // If the user agreed to refresh the page, do so.
...@@ -392,6 +408,82 @@ void ExtensionActionRunner::OnBlockedActionBubbleClosed( ...@@ -392,6 +408,82 @@ void ExtensionActionRunner::OnBlockedActionBubbleClosed(
} }
} }
void ExtensionActionRunner::OnBlockedActionBubbleForPageAccessGrantClosed(
const std::string& extension_id,
const GURL& page_url,
PageAccess current_access,
PageAccess new_access,
ToolbarActionsBarBubbleDelegate::CloseAction action) {
DCHECK(new_access == PageAccess::RUN_ON_SITE ||
new_access == PageAccess::RUN_ON_ALL_SITES);
DCHECK_EQ(PageAccess::RUN_ON_CLICK, current_access);
// Don't change permissions if the user chose to not refresh the page.
if (action != ToolbarActionsBarBubbleDelegate::CLOSE_EXECUTE)
return;
// If the web contents have navigated to a different origin, do nothing.
if (!url::IsSameOriginWith(page_url, web_contents()->GetLastCommittedURL()))
return;
const Extension* extension = ExtensionRegistry::Get(browser_context_)
->enabled_extensions()
.GetByID(extension_id);
if (!extension)
return;
UpdatePageAccessSettings(extension, current_access, new_access);
web_contents()->GetController().Reload(content::ReloadType::NORMAL, false);
}
void ExtensionActionRunner::UpdatePageAccessSettings(const Extension* extension,
PageAccess current_access,
PageAccess new_access) {
DCHECK_NE(current_access, new_access);
const GURL& url = web_contents()->GetLastCommittedURL();
ScriptingPermissionsModifier modifier(browser_context_, extension);
DCHECK(modifier.CanAffectExtension());
switch (new_access) {
case PageAccess::RUN_ON_CLICK:
if (current_access == PageAccess::RUN_ON_ALL_SITES)
modifier.SetWithholdHostPermissions(true);
if (modifier.HasGrantedHostPermission(url))
modifier.RemoveGrantedHostPermission(url);
break;
case PageAccess::RUN_ON_SITE:
if (current_access == PageAccess::RUN_ON_ALL_SITES)
modifier.SetWithholdHostPermissions(true);
if (!modifier.HasGrantedHostPermission(url))
modifier.GrantHostPermission(url);
break;
case PageAccess::RUN_ON_ALL_SITES:
modifier.SetWithholdHostPermissions(false);
break;
}
}
void ExtensionActionRunner::RunBlockedActions(const Extension* extension) {
DCHECK(base::ContainsKey(pending_scripts_, extension->id()) ||
web_request_blocked_.count(extension->id()) != 0);
// Clicking to run the extension counts as granting it permission to run on
// the given tab.
// The extension may already have active tab at this point, but granting
// it twice is essentially a no-op.
TabHelper::FromWebContents(web_contents())
->active_tab_permission_granter()
->GrantIfRequested(extension);
RunPendingScriptsForExtension(extension);
web_request_blocked_.erase(extension->id());
// The extension ran, so we need to tell the ExtensionActionAPI that we no
// longer want to act.
NotifyChange(extension);
}
bool ExtensionActionRunner::OnMessageReceived( bool ExtensionActionRunner::OnMessageReceived(
const IPC::Message& message, const IPC::Message& message,
content::RenderFrameHost* render_frame_host) { content::RenderFrameHost* render_frame_host) {
......
...@@ -43,6 +43,12 @@ class ExtensionRegistry; ...@@ -43,6 +43,12 @@ class ExtensionRegistry;
class ExtensionActionRunner : public content::WebContentsObserver, class ExtensionActionRunner : public content::WebContentsObserver,
public ExtensionRegistryObserver { public ExtensionRegistryObserver {
public: public:
enum class PageAccess {
RUN_ON_CLICK,
RUN_ON_SITE,
RUN_ON_ALL_SITES,
};
class TestObserver { class TestObserver {
public: public:
virtual void OnBlockedActionAdded() = 0; virtual void OnBlockedActionAdded() = 0;
...@@ -64,9 +70,11 @@ class ExtensionActionRunner : public content::WebContentsObserver, ...@@ -64,9 +70,11 @@ class ExtensionActionRunner : public content::WebContentsObserver,
ExtensionAction::ShowAction RunAction(const Extension* extension, ExtensionAction::ShowAction RunAction(const Extension* extension,
bool grant_tab_permissions); bool grant_tab_permissions);
// Runs any actions that were blocked for the given |extension|. As a // Notifies the ExtensionActionRunner that the page access for |extension| has
// requirement, this will grant activeTab permission to the extension. // changed.
void RunBlockedActions(const Extension* extension); void HandlePageAccessModified(const Extension* extension,
PageAccess current_access,
PageAccess new_access);
// Notifies the ExtensionActionRunner that an extension has been granted // Notifies the ExtensionActionRunner that an extension has been granted
// active tab permissions. This will run any pending injections for that // active tab permissions. This will run any pending injections for that
...@@ -164,14 +172,38 @@ class ExtensionActionRunner : public content::WebContentsObserver, ...@@ -164,14 +172,38 @@ class ExtensionActionRunner : public content::WebContentsObserver,
void LogUMA() const; void LogUMA() const;
// Shows the bubble to prompt the user to refresh the page to run the blocked // Shows the bubble to prompt the user to refresh the page to run the blocked
// actions for the given |extension|. // actions for the given |extension|. |callback| is invoked when the bubble is
void ShowBlockedActionBubble(const Extension* extension); // closed.
void ShowBlockedActionBubble(
const Extension* extension,
const base::Callback<void(ToolbarActionsBarBubbleDelegate::CloseAction)>&
callback);
// Called when the blocked actions bubble invoked to run the extension action
// is closed.
void OnBlockedActionBubbleForRunActionClosed(
const std::string& extension_id,
ToolbarActionsBarBubbleDelegate::CloseAction action);
// Called when the blocked actions bubble is closed. // Called when the blocked actions bubble invoked for the page access grant is
void OnBlockedActionBubbleClosed( // closed.
void OnBlockedActionBubbleForPageAccessGrantClosed(
const std::string& extension_id, const std::string& extension_id,
const GURL& page_url,
PageAccess current_access,
PageAccess new_access,
ToolbarActionsBarBubbleDelegate::CloseAction action); ToolbarActionsBarBubbleDelegate::CloseAction action);
// Handles permission changes necessary for page access modification of the
// |extension|.
void UpdatePageAccessSettings(const Extension* extension,
PageAccess current_access,
PageAccess new_access);
// Runs any actions that were blocked for the given |extension|. As a
// requirement, this will grant activeTab permission to the extension.
void RunBlockedActions(const Extension* extension);
// content::WebContentsObserver implementation. // content::WebContentsObserver implementation.
bool OnMessageReceived(const IPC::Message& message, bool OnMessageReceived(const IPC::Message& message,
content::RenderFrameHost* render_frame_host) override; content::RenderFrameHost* render_frame_host) override;
......
...@@ -424,36 +424,25 @@ void ExtensionContextMenuModel::HandlePageAccessCommand( ...@@ -424,36 +424,25 @@ void ExtensionContextMenuModel::HandlePageAccessCommand(
if (command_id == current_access) if (command_id == current_access)
return; return;
const GURL& url = web_contents->GetLastCommittedURL(); auto convert_page_access = [](int command_id) {
ScriptingPermissionsModifier modifier(profile_, extension); switch (command_id) {
DCHECK(modifier.CanAffectExtension()); case PAGE_ACCESS_RUN_ON_CLICK:
switch (command_id) { return ExtensionActionRunner::PageAccess::RUN_ON_CLICK;
case PAGE_ACCESS_RUN_ON_CLICK: case PAGE_ACCESS_RUN_ON_SITE:
if (current_access == PAGE_ACCESS_RUN_ON_ALL_SITES) return ExtensionActionRunner::PageAccess::RUN_ON_SITE;
modifier.SetWithholdHostPermissions(true); case PAGE_ACCESS_RUN_ON_ALL_SITES:
if (modifier.HasGrantedHostPermission(url)) return ExtensionActionRunner::PageAccess::RUN_ON_ALL_SITES;
modifier.RemoveGrantedHostPermission(url); }
break; NOTREACHED();
case PAGE_ACCESS_RUN_ON_SITE: return ExtensionActionRunner::PageAccess::RUN_ON_CLICK;
if (current_access == PAGE_ACCESS_RUN_ON_ALL_SITES) };
modifier.SetWithholdHostPermissions(true);
if (!modifier.HasGrantedHostPermission(url)) ExtensionActionRunner* runner =
modifier.GrantHostPermission(url); ExtensionActionRunner::GetForWebContents(web_contents);
break; if (runner)
case PAGE_ACCESS_RUN_ON_ALL_SITES: runner->HandlePageAccessModified(extension,
modifier.SetWithholdHostPermissions(false); convert_page_access(current_access),
break; convert_page_access(command_id));
default:
NOTREACHED();
}
if (command_id == PAGE_ACCESS_RUN_ON_SITE ||
command_id == PAGE_ACCESS_RUN_ON_ALL_SITES) {
ExtensionActionRunner* runner =
ExtensionActionRunner::GetForWebContents(web_contents);
if (runner && runner->WantsToRun(extension))
runner->RunBlockedActions(extension);
}
} }
content::WebContents* ExtensionContextMenuModel::GetActiveWebContents() const { content::WebContents* ExtensionContextMenuModel::GetActiveWebContents() const {
......
...@@ -84,9 +84,20 @@ TEST_P(ToolbarActionsBarUnitTest, ExtensionActionBlockedActions) { ...@@ -84,9 +84,20 @@ TEST_P(ToolbarActionsBarUnitTest, ExtensionActionBlockedActions) {
extensions_features::kRuntimeHostPermissions); extensions_features::kRuntimeHostPermissions);
scoped_refptr<const extensions::Extension> browser_action_ext = scoped_refptr<const extensions::Extension> browser_action_ext =
CreateAndAddExtension( extensions::ExtensionBuilder("browser action")
"browser action", .SetAction(extensions::ExtensionBuilder::ActionType::BROWSER_ACTION)
extensions::ExtensionBuilder::ActionType::BROWSER_ACTION); .SetLocation(extensions::Manifest::INTERNAL)
.AddPermission("https://www.google.com/*")
.Build();
extensions::ExtensionService* service =
extensions::ExtensionSystem::Get(profile())->extension_service();
service->GrantPermissions(browser_action_ext.get());
service->AddExtension(browser_action_ext.get());
extensions::ScriptingPermissionsModifier permissions_modifier_browser_ext(
profile(), browser_action_ext);
permissions_modifier_browser_ext.SetWithholdHostPermissions(true);
ASSERT_EQ(1u, toolbar_actions_bar()->GetIconCount()); ASSERT_EQ(1u, toolbar_actions_bar()->GetIconCount());
AddTab(browser(), GURL("https://www.google.com/")); AddTab(browser(), GURL("https://www.google.com/"));
...@@ -117,7 +128,7 @@ TEST_P(ToolbarActionsBarUnitTest, ExtensionActionBlockedActions) { ...@@ -117,7 +128,7 @@ TEST_P(ToolbarActionsBarUnitTest, ExtensionActionBlockedActions) {
EXPECT_FALSE(image_source->paint_page_action_decoration()); EXPECT_FALSE(image_source->paint_page_action_decoration());
EXPECT_TRUE(image_source->paint_blocked_actions_decoration()); EXPECT_TRUE(image_source->paint_blocked_actions_decoration());
action_runner->RunBlockedActions(browser_action_ext.get()); action_runner->RunForTesting(browser_action_ext.get());
image_source = image_source =
browser_action->GetIconImageSourceForTesting(web_contents, size); browser_action->GetIconImageSourceForTesting(web_contents, size);
EXPECT_FALSE(image_source->grayscale()); EXPECT_FALSE(image_source->grayscale());
...@@ -125,8 +136,17 @@ TEST_P(ToolbarActionsBarUnitTest, ExtensionActionBlockedActions) { ...@@ -125,8 +136,17 @@ TEST_P(ToolbarActionsBarUnitTest, ExtensionActionBlockedActions) {
EXPECT_FALSE(image_source->paint_blocked_actions_decoration()); EXPECT_FALSE(image_source->paint_blocked_actions_decoration());
scoped_refptr<const extensions::Extension> page_action_ext = scoped_refptr<const extensions::Extension> page_action_ext =
CreateAndAddExtension( extensions::ExtensionBuilder("page action")
"page action", extensions::ExtensionBuilder::ActionType::PAGE_ACTION); .SetAction(extensions::ExtensionBuilder::ActionType::PAGE_ACTION)
.SetLocation(extensions::Manifest::INTERNAL)
.AddPermission("https://www.google.com/*")
.Build();
service->GrantPermissions(page_action_ext.get());
service->AddExtension(page_action_ext.get());
extensions::ScriptingPermissionsModifier permissions_modifier_page_action(
profile(), page_action_ext);
permissions_modifier_page_action.SetWithholdHostPermissions(true);
ASSERT_EQ(2u, toolbar_actions_bar()->GetIconCount()); ASSERT_EQ(2u, toolbar_actions_bar()->GetIconCount());
ExtensionActionViewController* page_action = ExtensionActionViewController* page_action =
static_cast<ExtensionActionViewController*>( static_cast<ExtensionActionViewController*>(
...@@ -167,9 +187,9 @@ TEST_P(ToolbarActionsBarUnitTest, ExtensionActionBlockedActions) { ...@@ -167,9 +187,9 @@ TEST_P(ToolbarActionsBarUnitTest, ExtensionActionBlockedActions) {
web_contents, false); web_contents, false);
toolbar_model()->SetVisibleIconCount(2u); toolbar_model()->SetVisibleIconCount(2u);
action_runner->RunBlockedActions(page_action_ext.get()); action_runner->RunForTesting(page_action_ext.get());
image_source = page_action->GetIconImageSourceForTesting(web_contents, size); image_source = page_action->GetIconImageSourceForTesting(web_contents, size);
EXPECT_TRUE(image_source->grayscale()); EXPECT_FALSE(image_source->grayscale());
EXPECT_FALSE(image_source->paint_page_action_decoration()); EXPECT_FALSE(image_source->paint_page_action_decoration());
EXPECT_FALSE(image_source->paint_blocked_actions_decoration()); EXPECT_FALSE(image_source->paint_blocked_actions_decoration());
} }
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment