[email protected] | 39ef0a7c5 | 2014-05-11 01:40:00 | [diff] [blame] | 1 | // Copyright 2014 The Chromium Authors. All rights reserved. |
| 2 | // Use of this source code is governed by a BSD-style license that can be |
| 3 | // found in the LICENSE file. |
| 4 | |
rdevlin.cronin | 8408b4f9 | 2016-03-15 19:14:14 | [diff] [blame] | 5 | #include "chrome/browser/extensions/extension_action_runner.h" |
[email protected] | 39ef0a7c5 | 2014-05-11 01:40:00 | [diff] [blame] | 6 | |
dcheng | c963c714 | 2016-04-08 03:55:22 | [diff] [blame] | 7 | #include <memory> |
| 8 | |
rdevlin.cronin | 4a78c48b | 2016-03-24 00:02:29 | [diff] [blame] | 9 | #include "base/auto_reset.h" |
[email protected] | ac02ac5 | 2014-05-20 01:11:26 | [diff] [blame] | 10 | #include "base/bind.h" |
| 11 | #include "base/bind_helpers.h" |
fdoray | 283082bd | 2016-06-02 20:18:46 | [diff] [blame^] | 12 | #include "base/location.h" |
dcheng | c963c714 | 2016-04-08 03:55:22 | [diff] [blame] | 13 | #include "base/memory/ptr_util.h" |
[email protected] | 39ef0a7c5 | 2014-05-11 01:40:00 | [diff] [blame] | 14 | #include "base/metrics/histogram.h" |
fdoray | 283082bd | 2016-06-02 20:18:46 | [diff] [blame^] | 15 | #include "base/single_thread_task_runner.h" |
[email protected] | 39ef0a7c5 | 2014-05-11 01:40:00 | [diff] [blame] | 16 | #include "base/stl_util.h" |
fdoray | 283082bd | 2016-06-02 20:18:46 | [diff] [blame^] | 17 | #include "base/threading/thread_task_runner_handle.h" |
[email protected] | 78cd68e | 2014-05-22 20:33:52 | [diff] [blame] | 18 | #include "chrome/browser/extensions/active_tab_permission_granter.h" |
rdevlin.cronin | aeffd18 | 2014-08-26 17:04:00 | [diff] [blame] | 19 | #include "chrome/browser/extensions/api/extension_action/extension_action_api.h" |
[email protected] | 39ef0a7c5 | 2014-05-11 01:40:00 | [diff] [blame] | 20 | #include "chrome/browser/extensions/extension_action.h" |
[email protected] | 6a24a039 | 2014-08-12 21:31:33 | [diff] [blame] | 21 | #include "chrome/browser/extensions/extension_action_manager.h" |
[email protected] | e167058 | 2014-08-15 23:05:41 | [diff] [blame] | 22 | #include "chrome/browser/extensions/permissions_updater.h" |
[email protected] | eac223a | 2014-05-13 17:39:57 | [diff] [blame] | 23 | #include "chrome/browser/extensions/tab_helper.h" |
[email protected] | 6a24a039 | 2014-08-12 21:31:33 | [diff] [blame] | 24 | #include "chrome/browser/profiles/profile.h" |
[email protected] | e3f90c60 | 2014-08-18 12:41:59 | [diff] [blame] | 25 | #include "chrome/browser/sessions/session_tab_helper.h" |
rdevlin.cronin | 4a78c48b | 2016-03-24 00:02:29 | [diff] [blame] | 26 | #include "chrome/browser/ui/browser_finder.h" |
| 27 | #include "chrome/browser/ui/browser_window.h" |
| 28 | #include "chrome/browser/ui/extensions/blocked_action_bubble_delegate.h" |
| 29 | #include "chrome/browser/ui/toolbar/toolbar_actions_bar.h" |
[email protected] | 39ef0a7c5 | 2014-05-11 01:40:00 | [diff] [blame] | 30 | #include "chrome/common/extensions/api/extension_action/action_info.h" |
[email protected] | fdd2837 | 2014-08-21 02:27:26 | [diff] [blame] | 31 | #include "components/crx_file/id_util.h" |
[email protected] | 39ef0a7c5 | 2014-05-11 01:40:00 | [diff] [blame] | 32 | #include "content/public/browser/navigation_controller.h" |
rdevlin.cronin | 5094223 | 2014-08-27 17:40:56 | [diff] [blame] | 33 | #include "content/public/browser/navigation_details.h" |
[email protected] | 39ef0a7c5 | 2014-05-11 01:40:00 | [diff] [blame] | 34 | #include "content/public/browser/navigation_entry.h" |
[email protected] | ac02ac5 | 2014-05-20 01:11:26 | [diff] [blame] | 35 | #include "content/public/browser/render_view_host.h" |
[email protected] | 39ef0a7c5 | 2014-05-11 01:40:00 | [diff] [blame] | 36 | #include "content/public/browser/web_contents.h" |
| 37 | #include "extensions/browser/extension_registry.h" |
| 38 | #include "extensions/common/extension.h" |
| 39 | #include "extensions/common/extension_messages.h" |
| 40 | #include "extensions/common/extension_set.h" |
[email protected] | 23a8536 | 2014-07-07 23:26:19 | [diff] [blame] | 41 | #include "extensions/common/manifest.h" |
[email protected] | e167058 | 2014-08-15 23:05:41 | [diff] [blame] | 42 | #include "extensions/common/permissions/permission_set.h" |
[email protected] | 39ef0a7c5 | 2014-05-11 01:40:00 | [diff] [blame] | 43 | #include "extensions/common/permissions/permissions_data.h" |
| 44 | #include "ipc/ipc_message_macros.h" |
| 45 | |
| 46 | namespace extensions { |
| 47 | |
rdevlin.cronin | 4a78c48b | 2016-03-24 00:02:29 | [diff] [blame] | 48 | namespace { |
| 49 | |
| 50 | // The blocked actions that require a page refresh to run. |
| 51 | const int kRefreshRequiredActionsMask = |
| 52 | BLOCKED_ACTION_WEB_REQUEST | BLOCKED_ACTION_SCRIPT_AT_START; |
| 53 | } |
| 54 | |
rdevlin.cronin | 8408b4f9 | 2016-03-15 19:14:14 | [diff] [blame] | 55 | ExtensionActionRunner::PendingScript::PendingScript( |
rdevlin.cronin | 8d034e5 | 2016-02-02 22:46:32 | [diff] [blame] | 56 | UserScript::RunLocation run_location, |
| 57 | const base::Closure& permit_script) |
| 58 | : run_location(run_location), permit_script(permit_script) {} |
| 59 | |
rdevlin.cronin | 8408b4f9 | 2016-03-15 19:14:14 | [diff] [blame] | 60 | ExtensionActionRunner::PendingScript::PendingScript( |
vmpstr | b8aacbe | 2016-02-26 02:00:48 | [diff] [blame] | 61 | const PendingScript& other) = default; |
| 62 | |
rdevlin.cronin | 8408b4f9 | 2016-03-15 19:14:14 | [diff] [blame] | 63 | ExtensionActionRunner::PendingScript::~PendingScript() {} |
rdevlin.cronin | 8d034e5 | 2016-02-02 22:46:32 | [diff] [blame] | 64 | |
rdevlin.cronin | 8408b4f9 | 2016-03-15 19:14:14 | [diff] [blame] | 65 | ExtensionActionRunner::ExtensionActionRunner(content::WebContents* web_contents) |
[email protected] | 39ef0a7c5 | 2014-05-11 01:40:00 | [diff] [blame] | 66 | : content::WebContentsObserver(web_contents), |
rdevlin.cronin | 1f87703 | 2015-02-20 00:12:42 | [diff] [blame] | 67 | num_page_requests_(0), |
rdevlin.cronin | 699ca6ff | 2014-09-29 23:59:57 | [diff] [blame] | 68 | browser_context_(web_contents->GetBrowserContext()), |
rdevlin.cronin | b8dffe56 | 2015-02-07 00:58:01 | [diff] [blame] | 69 | was_used_on_page_(false), |
rdevlin.cronin | 4a78c48b | 2016-03-24 00:02:29 | [diff] [blame] | 70 | ignore_active_tab_granted_(false), |
| 71 | extension_registry_observer_(this), |
| 72 | weak_factory_(this) { |
[email protected] | ac02ac5 | 2014-05-20 01:11:26 | [diff] [blame] | 73 | CHECK(web_contents); |
rdevlin.cronin | 699ca6ff | 2014-09-29 23:59:57 | [diff] [blame] | 74 | extension_registry_observer_.Add(ExtensionRegistry::Get(browser_context_)); |
[email protected] | 39ef0a7c5 | 2014-05-11 01:40:00 | [diff] [blame] | 75 | } |
| 76 | |
rdevlin.cronin | 8408b4f9 | 2016-03-15 19:14:14 | [diff] [blame] | 77 | ExtensionActionRunner::~ExtensionActionRunner() { |
[email protected] | 39ef0a7c5 | 2014-05-11 01:40:00 | [diff] [blame] | 78 | LogUMA(); |
| 79 | } |
| 80 | |
[email protected] | eac223a | 2014-05-13 17:39:57 | [diff] [blame] | 81 | // static |
rdevlin.cronin | 8408b4f9 | 2016-03-15 19:14:14 | [diff] [blame] | 82 | ExtensionActionRunner* ExtensionActionRunner::GetForWebContents( |
[email protected] | eac223a | 2014-05-13 17:39:57 | [diff] [blame] | 83 | content::WebContents* web_contents) { |
| 84 | if (!web_contents) |
| 85 | return NULL; |
| 86 | TabHelper* tab_helper = TabHelper::FromWebContents(web_contents); |
rdevlin.cronin | 8408b4f9 | 2016-03-15 19:14:14 | [diff] [blame] | 87 | return tab_helper ? tab_helper->extension_action_runner() : NULL; |
[email protected] | eac223a | 2014-05-13 17:39:57 | [diff] [blame] | 88 | } |
| 89 | |
rdevlin.cronin | 343fd10 | 2016-03-17 00:24:54 | [diff] [blame] | 90 | ExtensionAction::ShowAction ExtensionActionRunner::RunAction( |
| 91 | const Extension* extension, |
| 92 | bool grant_tab_permissions) { |
rdevlin.cronin | 343fd10 | 2016-03-17 00:24:54 | [diff] [blame] | 93 | if (grant_tab_permissions) { |
rdevlin.cronin | 4a78c48b | 2016-03-24 00:02:29 | [diff] [blame] | 94 | int blocked = GetBlockedActions(extension); |
| 95 | if ((blocked & kRefreshRequiredActionsMask) != 0) { |
| 96 | ShowBlockedActionBubble(extension); |
| 97 | return ExtensionAction::ACTION_NONE; |
| 98 | } |
rdevlin.cronin | 343fd10 | 2016-03-17 00:24:54 | [diff] [blame] | 99 | TabHelper::FromWebContents(web_contents()) |
| 100 | ->active_tab_permission_granter() |
| 101 | ->GrantIfRequested(extension); |
| 102 | // If the extension had blocked actions, granting active tab will have |
| 103 | // run the extension. Don't execute further since clicking should run |
| 104 | // blocked actions *or* the normal extension action, not both. |
rdevlin.cronin | 4a78c48b | 2016-03-24 00:02:29 | [diff] [blame] | 105 | if (blocked != BLOCKED_ACTION_NONE) |
rdevlin.cronin | 343fd10 | 2016-03-17 00:24:54 | [diff] [blame] | 106 | return ExtensionAction::ACTION_NONE; |
| 107 | } |
| 108 | |
| 109 | ExtensionAction* extension_action = |
| 110 | ExtensionActionManager::Get(browser_context_) |
| 111 | ->GetExtensionAction(*extension); |
| 112 | |
| 113 | // Anything that gets here should have a page or browser action. |
| 114 | DCHECK(extension_action); |
| 115 | int tab_id = SessionTabHelper::IdForTab(web_contents()); |
| 116 | if (!extension_action->GetIsVisible(tab_id)) |
| 117 | return ExtensionAction::ACTION_NONE; |
| 118 | |
| 119 | if (extension_action->HasPopup(tab_id)) |
| 120 | return ExtensionAction::ACTION_SHOW_POPUP; |
| 121 | |
| 122 | ExtensionActionAPI::Get(browser_context_) |
| 123 | ->DispatchExtensionActionClicked(*extension_action, web_contents()); |
| 124 | return ExtensionAction::ACTION_NONE; |
[email protected] | 11814f5 | 2014-05-23 06:50:35 | [diff] [blame] | 125 | } |
| 126 | |
rdevlin.cronin | 343fd10 | 2016-03-17 00:24:54 | [diff] [blame] | 127 | void ExtensionActionRunner::RunBlockedActions(const Extension* extension) { |
rdevlin.cronin | acb74592 | 2016-02-17 20:37:44 | [diff] [blame] | 128 | DCHECK(ContainsKey(pending_scripts_, extension->id()) || |
| 129 | web_request_blocked_.count(extension->id()) != 0); |
| 130 | |
| 131 | // Clicking to run the extension counts as granting it permission to run on |
| 132 | // the given tab. |
| 133 | // The extension may already have active tab at this point, but granting |
| 134 | // it twice is essentially a no-op. |
| 135 | TabHelper::FromWebContents(web_contents()) |
| 136 | ->active_tab_permission_granter() |
| 137 | ->GrantIfRequested(extension); |
| 138 | |
| 139 | RunPendingScriptsForExtension(extension); |
| 140 | web_request_blocked_.erase(extension->id()); |
| 141 | |
| 142 | // The extension ran, so we need to tell the ExtensionActionAPI that we no |
| 143 | // longer want to act. |
| 144 | NotifyChange(extension); |
rdevlin.cronin | e9c7112 | 2014-08-25 23:47:21 | [diff] [blame] | 145 | } |
| 146 | |
rdevlin.cronin | 343fd10 | 2016-03-17 00:24:54 | [diff] [blame] | 147 | void ExtensionActionRunner::OnActiveTabPermissionGranted( |
| 148 | const Extension* extension) { |
rdevlin.cronin | 4a78c48b | 2016-03-24 00:02:29 | [diff] [blame] | 149 | if (!ignore_active_tab_granted_ && WantsToRun(extension)) |
rdevlin.cronin | 343fd10 | 2016-03-17 00:24:54 | [diff] [blame] | 150 | RunBlockedActions(extension); |
| 151 | } |
| 152 | |
rdevlin.cronin | 8408b4f9 | 2016-03-15 19:14:14 | [diff] [blame] | 153 | void ExtensionActionRunner::OnWebRequestBlocked(const Extension* extension) { |
rdevlin.cronin | 8d034e5 | 2016-02-02 22:46:32 | [diff] [blame] | 154 | web_request_blocked_.insert(extension->id()); |
| 155 | } |
| 156 | |
rdevlin.cronin | 8408b4f9 | 2016-03-15 19:14:14 | [diff] [blame] | 157 | int ExtensionActionRunner::GetBlockedActions(const Extension* extension) { |
rdevlin.cronin | 8d034e5 | 2016-02-02 22:46:32 | [diff] [blame] | 158 | int blocked_actions = BLOCKED_ACTION_NONE; |
| 159 | if (web_request_blocked_.count(extension->id()) != 0) |
| 160 | blocked_actions |= BLOCKED_ACTION_WEB_REQUEST; |
| 161 | auto iter = pending_scripts_.find(extension->id()); |
| 162 | if (iter != pending_scripts_.end()) { |
| 163 | for (const PendingScript& script : iter->second) { |
| 164 | switch (script.run_location) { |
| 165 | case UserScript::DOCUMENT_START: |
| 166 | blocked_actions |= BLOCKED_ACTION_SCRIPT_AT_START; |
| 167 | break; |
| 168 | case UserScript::DOCUMENT_END: |
| 169 | case UserScript::DOCUMENT_IDLE: |
| 170 | case UserScript::BROWSER_DRIVEN: |
| 171 | blocked_actions |= BLOCKED_ACTION_SCRIPT_OTHER; |
| 172 | break; |
| 173 | case UserScript::UNDEFINED: |
| 174 | case UserScript::RUN_DEFERRED: |
| 175 | case UserScript::RUN_LOCATION_LAST: |
| 176 | NOTREACHED(); |
| 177 | } |
| 178 | } |
| 179 | } |
| 180 | |
| 181 | return blocked_actions; |
| 182 | } |
| 183 | |
rdevlin.cronin | 8408b4f9 | 2016-03-15 19:14:14 | [diff] [blame] | 184 | bool ExtensionActionRunner::WantsToRun(const Extension* extension) { |
rdevlin.cronin | 8d034e5 | 2016-02-02 22:46:32 | [diff] [blame] | 185 | return GetBlockedActions(extension) != BLOCKED_ACTION_NONE; |
[email protected] | e167058 | 2014-08-15 23:05:41 | [diff] [blame] | 186 | } |
| 187 | |
rdevlin.cronin | 4a78c48b | 2016-03-24 00:02:29 | [diff] [blame] | 188 | void ExtensionActionRunner::RunForTesting(const Extension* extension) { |
| 189 | if (WantsToRun(extension)) { |
| 190 | TabHelper::FromWebContents(web_contents()) |
| 191 | ->active_tab_permission_granter() |
| 192 | ->GrantIfRequested(extension); |
| 193 | } |
| 194 | } |
| 195 | |
[email protected] | 23a8536 | 2014-07-07 23:26:19 | [diff] [blame] | 196 | PermissionsData::AccessType |
rdevlin.cronin | 8408b4f9 | 2016-03-15 19:14:14 | [diff] [blame] | 197 | ExtensionActionRunner::RequiresUserConsentForScriptInjection( |
[email protected] | 23a8536 | 2014-07-07 23:26:19 | [diff] [blame] | 198 | const Extension* extension, |
| 199 | UserScript::InjectionType type) { |
| 200 | CHECK(extension); |
| 201 | |
[email protected] | 23a8536 | 2014-07-07 23:26:19 | [diff] [blame] | 202 | // Allow the extension if it's been explicitly granted permission. |
| 203 | if (permitted_extensions_.count(extension->id()) > 0) |
| 204 | return PermissionsData::ACCESS_ALLOWED; |
| 205 | |
| 206 | GURL url = web_contents()->GetVisibleURL(); |
[email protected] | e3f90c60 | 2014-08-18 12:41:59 | [diff] [blame] | 207 | int tab_id = SessionTabHelper::IdForTab(web_contents()); |
[email protected] | 23a8536 | 2014-07-07 23:26:19 | [diff] [blame] | 208 | switch (type) { |
| 209 | case UserScript::CONTENT_SCRIPT: |
| 210 | return extension->permissions_data()->GetContentScriptAccess( |
rob | 889ceeb | 2016-01-25 19:41:08 | [diff] [blame] | 211 | extension, url, tab_id, nullptr); |
[email protected] | 23a8536 | 2014-07-07 23:26:19 | [diff] [blame] | 212 | case UserScript::PROGRAMMATIC_SCRIPT: |
rob | 889ceeb | 2016-01-25 19:41:08 | [diff] [blame] | 213 | return extension->permissions_data()->GetPageAccess(extension, url, |
| 214 | tab_id, nullptr); |
[email protected] | 23a8536 | 2014-07-07 23:26:19 | [diff] [blame] | 215 | } |
| 216 | |
| 217 | NOTREACHED(); |
| 218 | return PermissionsData::ACCESS_DENIED; |
| 219 | } |
| 220 | |
rdevlin.cronin | 8408b4f9 | 2016-03-15 19:14:14 | [diff] [blame] | 221 | void ExtensionActionRunner::RequestScriptInjection( |
[email protected] | 23a8536 | 2014-07-07 23:26:19 | [diff] [blame] | 222 | const Extension* extension, |
rdevlin.cronin | 8d034e5 | 2016-02-02 22:46:32 | [diff] [blame] | 223 | UserScript::RunLocation run_location, |
[email protected] | 23a8536 | 2014-07-07 23:26:19 | [diff] [blame] | 224 | const base::Closure& callback) { |
| 225 | CHECK(extension); |
rdevlin.cronin | 8d034e5 | 2016-02-02 22:46:32 | [diff] [blame] | 226 | PendingScriptList& list = pending_scripts_[extension->id()]; |
| 227 | list.push_back(PendingScript(run_location, callback)); |
[email protected] | 23a8536 | 2014-07-07 23:26:19 | [diff] [blame] | 228 | |
rdevlin.cronin | 699ca6ff | 2014-09-29 23:59:57 | [diff] [blame] | 229 | // If this was the first entry, we need to notify that a new extension wants |
| 230 | // to run. |
| 231 | if (list.size() == 1u) |
| 232 | NotifyChange(extension); |
rdevlin.cronin | b8dffe56 | 2015-02-07 00:58:01 | [diff] [blame] | 233 | |
| 234 | was_used_on_page_ = true; |
[email protected] | 23a8536 | 2014-07-07 23:26:19 | [diff] [blame] | 235 | } |
| 236 | |
rdevlin.cronin | 8408b4f9 | 2016-03-15 19:14:14 | [diff] [blame] | 237 | void ExtensionActionRunner::RunPendingScriptsForExtension( |
[email protected] | 11814f5 | 2014-05-23 06:50:35 | [diff] [blame] | 238 | const Extension* extension) { |
[email protected] | ac02ac5 | 2014-05-20 01:11:26 | [diff] [blame] | 239 | DCHECK(extension); |
[email protected] | ac02ac5 | 2014-05-20 01:11:26 | [diff] [blame] | 240 | |
| 241 | content::NavigationEntry* visible_entry = |
| 242 | web_contents()->GetController().GetVisibleEntry(); |
| 243 | // Refuse to run if there's no visible entry, because we have no idea of |
| 244 | // determining if it's the proper page. This should rarely, if ever, happen. |
| 245 | if (!visible_entry) |
[email protected] | 11814f5 | 2014-05-23 06:50:35 | [diff] [blame] | 246 | return; |
[email protected] | ac02ac5 | 2014-05-20 01:11:26 | [diff] [blame] | 247 | |
[email protected] | ac02ac5 | 2014-05-20 01:11:26 | [diff] [blame] | 248 | // We add this to the list of permitted extensions and erase pending entries |
| 249 | // *before* running them to guard against the crazy case where running the |
| 250 | // callbacks adds more entries. |
| 251 | permitted_extensions_.insert(extension->id()); |
[email protected] | 23a8536 | 2014-07-07 23:26:19 | [diff] [blame] | 252 | |
rdevlin.cronin | 8d034e5 | 2016-02-02 22:46:32 | [diff] [blame] | 253 | PendingScriptMap::iterator iter = pending_scripts_.find(extension->id()); |
| 254 | if (iter == pending_scripts_.end()) |
[email protected] | 23a8536 | 2014-07-07 23:26:19 | [diff] [blame] | 255 | return; |
| 256 | |
rdevlin.cronin | 8d034e5 | 2016-02-02 22:46:32 | [diff] [blame] | 257 | PendingScriptList scripts; |
| 258 | iter->second.swap(scripts); |
| 259 | pending_scripts_.erase(extension->id()); |
[email protected] | ac02ac5 | 2014-05-20 01:11:26 | [diff] [blame] | 260 | |
| 261 | // Run all pending injections for the given extension. |
rdevlin.cronin | 8d034e5 | 2016-02-02 22:46:32 | [diff] [blame] | 262 | for (PendingScript& pending_script : scripts) |
| 263 | pending_script.permit_script.Run(); |
[email protected] | ac02ac5 | 2014-05-20 01:11:26 | [diff] [blame] | 264 | } |
| 265 | |
rdevlin.cronin | 8408b4f9 | 2016-03-15 19:14:14 | [diff] [blame] | 266 | void ExtensionActionRunner::OnRequestScriptInjectionPermission( |
[email protected] | ac02ac5 | 2014-05-20 01:11:26 | [diff] [blame] | 267 | const std::string& extension_id, |
[email protected] | 23a8536 | 2014-07-07 23:26:19 | [diff] [blame] | 268 | UserScript::InjectionType script_type, |
rdevlin.cronin | 8d034e5 | 2016-02-02 22:46:32 | [diff] [blame] | 269 | UserScript::RunLocation run_location, |
avi | a2f4804a | 2015-12-24 23:11:13 | [diff] [blame] | 270 | int64_t request_id) { |
[email protected] | fdd2837 | 2014-08-21 02:27:26 | [diff] [blame] | 271 | if (!crx_file::id_util::IdIsValid(extension_id)) { |
[email protected] | ac02ac5 | 2014-05-20 01:11:26 | [diff] [blame] | 272 | NOTREACHED() << "'" << extension_id << "' is not a valid id."; |
| 273 | return; |
| 274 | } |
| 275 | |
rdevlin.cronin | 8408b4f9 | 2016-03-15 19:14:14 | [diff] [blame] | 276 | const Extension* extension = ExtensionRegistry::Get(browser_context_) |
| 277 | ->enabled_extensions() |
| 278 | .GetByID(extension_id); |
[email protected] | ac02ac5 | 2014-05-20 01:11:26 | [diff] [blame] | 279 | // We shouldn't allow extensions which are no longer enabled to run any |
| 280 | // scripts. Ignore the request. |
| 281 | if (!extension) |
| 282 | return; |
| 283 | |
rdevlin.cronin | 1f87703 | 2015-02-20 00:12:42 | [diff] [blame] | 284 | ++num_page_requests_; |
| 285 | |
[email protected] | 23a8536 | 2014-07-07 23:26:19 | [diff] [blame] | 286 | switch (RequiresUserConsentForScriptInjection(extension, script_type)) { |
| 287 | case PermissionsData::ACCESS_ALLOWED: |
| 288 | PermitScriptInjection(request_id); |
| 289 | break; |
| 290 | case PermissionsData::ACCESS_WITHHELD: |
| 291 | // This base::Unretained() is safe, because the callback is only invoked |
| 292 | // by this object. |
| 293 | RequestScriptInjection( |
rdevlin.cronin | 8d034e5 | 2016-02-02 22:46:32 | [diff] [blame] | 294 | extension, run_location, |
rdevlin.cronin | 8408b4f9 | 2016-03-15 19:14:14 | [diff] [blame] | 295 | base::Bind(&ExtensionActionRunner::PermitScriptInjection, |
rdevlin.cronin | 8d034e5 | 2016-02-02 22:46:32 | [diff] [blame] | 296 | base::Unretained(this), request_id)); |
[email protected] | 23a8536 | 2014-07-07 23:26:19 | [diff] [blame] | 297 | break; |
| 298 | case PermissionsData::ACCESS_DENIED: |
| 299 | // We should usually only get a "deny access" if the page changed (as the |
| 300 | // renderer wouldn't have requested permission if the answer was always |
| 301 | // "no"). Just let the request fizzle and die. |
| 302 | break; |
[email protected] | 0d8d697 | 2014-06-03 22:41:02 | [diff] [blame] | 303 | } |
| 304 | } |
| 305 | |
rdevlin.cronin | 8408b4f9 | 2016-03-15 19:14:14 | [diff] [blame] | 306 | void ExtensionActionRunner::PermitScriptInjection(int64_t request_id) { |
[email protected] | 23a8536 | 2014-07-07 23:26:19 | [diff] [blame] | 307 | // This only sends the response to the renderer - the process of adding the |
| 308 | // extension to the list of |permitted_extensions_| is done elsewhere. |
rdevlin.cronin | 4bb32d7 | 2015-06-02 21:55:01 | [diff] [blame] | 309 | // TODO(devlin): Instead of sending this to all frames, we should include the |
| 310 | // routing_id in the permission request message, and send only to the proper |
| 311 | // frame (sending it to all frames doesn't hurt, but isn't as efficient). |
| 312 | web_contents()->SendToAllFrames(new ExtensionMsg_PermitScriptInjection( |
| 313 | MSG_ROUTING_NONE, // Routing id is set by the |web_contents|. |
| 314 | request_id)); |
[email protected] | 39ef0a7c5 | 2014-05-11 01:40:00 | [diff] [blame] | 315 | } |
| 316 | |
rdevlin.cronin | 8408b4f9 | 2016-03-15 19:14:14 | [diff] [blame] | 317 | void ExtensionActionRunner::NotifyChange(const Extension* extension) { |
rdevlin.cronin | 699ca6ff | 2014-09-29 23:59:57 | [diff] [blame] | 318 | ExtensionActionAPI* extension_action_api = |
| 319 | ExtensionActionAPI::Get(browser_context_); |
| 320 | ExtensionAction* extension_action = |
rdevlin.cronin | 8408b4f9 | 2016-03-15 19:14:14 | [diff] [blame] | 321 | ExtensionActionManager::Get(browser_context_) |
| 322 | ->GetExtensionAction(*extension); |
rdevlin.cronin | 699ca6ff | 2014-09-29 23:59:57 | [diff] [blame] | 323 | // If the extension has an action, we need to notify that it's updated. |
| 324 | if (extension_action) { |
rdevlin.cronin | 8408b4f9 | 2016-03-15 19:14:14 | [diff] [blame] | 325 | extension_action_api->NotifyChange(extension_action, web_contents(), |
| 326 | browser_context_); |
rdevlin.cronin | 699ca6ff | 2014-09-29 23:59:57 | [diff] [blame] | 327 | } |
| 328 | |
| 329 | // We also notify that page actions may have changed. |
| 330 | extension_action_api->NotifyPageActionsChanged(web_contents()); |
| 331 | } |
| 332 | |
rdevlin.cronin | 8408b4f9 | 2016-03-15 19:14:14 | [diff] [blame] | 333 | void ExtensionActionRunner::LogUMA() const { |
rdevlin.cronin | b8dffe56 | 2015-02-07 00:58:01 | [diff] [blame] | 334 | // We only log the permitted extensions metric if the feature was used at all |
| 335 | // on the page, because otherwise the data will be boring. |
| 336 | if (was_used_on_page_) { |
[email protected] | ac02ac5 | 2014-05-20 01:11:26 | [diff] [blame] | 337 | UMA_HISTOGRAM_COUNTS_100( |
| 338 | "Extensions.ActiveScriptController.PermittedExtensions", |
| 339 | permitted_extensions_.size()); |
| 340 | UMA_HISTOGRAM_COUNTS_100( |
| 341 | "Extensions.ActiveScriptController.DeniedExtensions", |
rdevlin.cronin | 8d034e5 | 2016-02-02 22:46:32 | [diff] [blame] | 342 | pending_scripts_.size()); |
[email protected] | ac02ac5 | 2014-05-20 01:11:26 | [diff] [blame] | 343 | } |
[email protected] | 39ef0a7c5 | 2014-05-11 01:40:00 | [diff] [blame] | 344 | } |
| 345 | |
rdevlin.cronin | 4a78c48b | 2016-03-24 00:02:29 | [diff] [blame] | 346 | void ExtensionActionRunner::ShowBlockedActionBubble( |
| 347 | const Extension* extension) { |
| 348 | Browser* browser = chrome::FindBrowserWithWebContents(web_contents()); |
| 349 | ToolbarActionsBar* toolbar_actions_bar = |
| 350 | browser ? browser->window()->GetToolbarActionsBar() : nullptr; |
| 351 | if (toolbar_actions_bar) { |
| 352 | auto callback = |
| 353 | base::Bind(&ExtensionActionRunner::OnBlockedActionBubbleClosed, |
| 354 | weak_factory_.GetWeakPtr(), extension->id()); |
| 355 | if (default_bubble_close_action_for_testing_) { |
fdoray | 283082bd | 2016-06-02 20:18:46 | [diff] [blame^] | 356 | base::ThreadTaskRunnerHandle::Get()->PostTask( |
rdevlin.cronin | 4a78c48b | 2016-03-24 00:02:29 | [diff] [blame] | 357 | FROM_HERE, |
| 358 | base::Bind(callback, *default_bubble_close_action_for_testing_)); |
| 359 | } else { |
dcheng | c963c714 | 2016-04-08 03:55:22 | [diff] [blame] | 360 | toolbar_actions_bar->ShowToolbarActionBubble(base::WrapUnique( |
rdevlin.cronin | 4a78c48b | 2016-03-24 00:02:29 | [diff] [blame] | 361 | new BlockedActionBubbleDelegate(callback, extension->id()))); |
| 362 | } |
| 363 | } |
| 364 | } |
| 365 | |
| 366 | void ExtensionActionRunner::OnBlockedActionBubbleClosed( |
| 367 | const std::string& extension_id, |
| 368 | ToolbarActionsBarBubbleDelegate::CloseAction action) { |
| 369 | // If the user agreed to refresh the page, do so. |
| 370 | if (action == ToolbarActionsBarBubbleDelegate::CLOSE_EXECUTE) { |
| 371 | const Extension* extension = ExtensionRegistry::Get(browser_context_) |
| 372 | ->enabled_extensions() |
| 373 | .GetByID(extension_id); |
| 374 | if (!extension) |
| 375 | return; |
| 376 | { |
| 377 | // Ignore the active tab permission being granted because we don't want |
| 378 | // to run scripts right before we refresh the page. |
| 379 | base::AutoReset<bool> ignore_active_tab(&ignore_active_tab_granted_, |
| 380 | true); |
| 381 | TabHelper::FromWebContents(web_contents()) |
| 382 | ->active_tab_permission_granter() |
| 383 | ->GrantIfRequested(extension); |
| 384 | } |
| 385 | web_contents()->GetController().Reload(false); |
| 386 | } |
| 387 | } |
| 388 | |
rdevlin.cronin | 8408b4f9 | 2016-03-15 19:14:14 | [diff] [blame] | 389 | bool ExtensionActionRunner::OnMessageReceived( |
rdevlin.cronin | 45dca7f | 2015-06-08 19:47:03 | [diff] [blame] | 390 | const IPC::Message& message, |
| 391 | content::RenderFrameHost* render_frame_host) { |
rdevlin.cronin | 6e7e5edc | 2014-08-29 16:23:23 | [diff] [blame] | 392 | bool handled = true; |
rdevlin.cronin | 8408b4f9 | 2016-03-15 19:14:14 | [diff] [blame] | 393 | IPC_BEGIN_MESSAGE_MAP(ExtensionActionRunner, message) |
rdevlin.cronin | 6e7e5edc | 2014-08-29 16:23:23 | [diff] [blame] | 394 | IPC_MESSAGE_HANDLER(ExtensionHostMsg_RequestScriptInjectionPermission, |
| 395 | OnRequestScriptInjectionPermission) |
| 396 | IPC_MESSAGE_UNHANDLED(handled = false) |
| 397 | IPC_END_MESSAGE_MAP() |
| 398 | return handled; |
| 399 | } |
| 400 | |
rdevlin.cronin | 8408b4f9 | 2016-03-15 19:14:14 | [diff] [blame] | 401 | void ExtensionActionRunner::DidNavigateMainFrame( |
rdevlin.cronin | 5094223 | 2014-08-27 17:40:56 | [diff] [blame] | 402 | const content::LoadCommittedDetails& details, |
| 403 | const content::FrameNavigateParams& params) { |
| 404 | if (details.is_in_page) |
| 405 | return; |
| 406 | |
| 407 | LogUMA(); |
rdevlin.cronin | 1f87703 | 2015-02-20 00:12:42 | [diff] [blame] | 408 | num_page_requests_ = 0; |
rdevlin.cronin | 5094223 | 2014-08-27 17:40:56 | [diff] [blame] | 409 | permitted_extensions_.clear(); |
rdevlin.cronin | 8d034e5 | 2016-02-02 22:46:32 | [diff] [blame] | 410 | pending_scripts_.clear(); |
| 411 | web_request_blocked_.clear(); |
rdevlin.cronin | b8dffe56 | 2015-02-07 00:58:01 | [diff] [blame] | 412 | was_used_on_page_ = false; |
rdevlin.cronin | 4a78c48b | 2016-03-24 00:02:29 | [diff] [blame] | 413 | weak_factory_.InvalidateWeakPtrs(); |
rdevlin.cronin | 5094223 | 2014-08-27 17:40:56 | [diff] [blame] | 414 | } |
| 415 | |
rdevlin.cronin | 8408b4f9 | 2016-03-15 19:14:14 | [diff] [blame] | 416 | void ExtensionActionRunner::OnExtensionUnloaded( |
rdevlin.cronin | 6e7e5edc | 2014-08-29 16:23:23 | [diff] [blame] | 417 | content::BrowserContext* browser_context, |
| 418 | const Extension* extension, |
| 419 | UnloadedExtensionInfo::Reason reason) { |
rdevlin.cronin | 8d034e5 | 2016-02-02 22:46:32 | [diff] [blame] | 420 | PendingScriptMap::iterator iter = pending_scripts_.find(extension->id()); |
| 421 | if (iter != pending_scripts_.end()) { |
| 422 | pending_scripts_.erase(iter); |
rdevlin.cronin | 8408b4f9 | 2016-03-15 19:14:14 | [diff] [blame] | 423 | ExtensionActionAPI::Get(browser_context_) |
| 424 | ->NotifyPageActionsChanged(web_contents()); |
rdevlin.cronin | 6e7e5edc | 2014-08-29 16:23:23 | [diff] [blame] | 425 | } |
| 426 | } |
| 427 | |
[email protected] | 39ef0a7c5 | 2014-05-11 01:40:00 | [diff] [blame] | 428 | } // namespace extensions |