[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> |
Karan Bhatia | 1c72c69 | 2018-10-04 00:40:51 | [diff] [blame] | 8 | #include <tuple> |
dcheng | c963c714 | 2016-04-08 03:55:22 | [diff] [blame] | 9 | |
rdevlin.cronin | 4a78c48b | 2016-03-24 00:02:29 | [diff] [blame] | 10 | #include "base/auto_reset.h" |
[email protected] | ac02ac5 | 2014-05-20 01:11:26 | [diff] [blame] | 11 | #include "base/bind.h" |
| 12 | #include "base/bind_helpers.h" |
fdoray | 283082bd | 2016-06-02 20:18:46 | [diff] [blame] | 13 | #include "base/location.h" |
asvitkine | aa06031 | 2016-09-01 22:44:13 | [diff] [blame] | 14 | #include "base/metrics/histogram_macros.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" |
Kelvin Jiang | caf6c3f | 2019-08-13 21:49:50 | [diff] [blame] | 22 | #include "chrome/browser/extensions/extension_tab_util.h" |
[email protected] | e167058 | 2014-08-15 23:05:41 | [diff] [blame] | 23 | #include "chrome/browser/extensions/permissions_updater.h" |
Karan Bhatia | ededa7cf | 2018-09-05 19:00:30 | [diff] [blame] | 24 | #include "chrome/browser/extensions/scripting_permissions_modifier.h" |
[email protected] | eac223a | 2014-05-13 17:39:57 | [diff] [blame] | 25 | #include "chrome/browser/extensions/tab_helper.h" |
[email protected] | 6a24a039 | 2014-08-12 21:31:33 | [diff] [blame] | 26 | #include "chrome/browser/profiles/profile.h" |
[email protected] | e3f90c60 | 2014-08-18 12:41:59 | [diff] [blame] | 27 | #include "chrome/browser/sessions/session_tab_helper.h" |
rdevlin.cronin | 4a78c48b | 2016-03-24 00:02:29 | [diff] [blame] | 28 | #include "chrome/browser/ui/browser_finder.h" |
| 29 | #include "chrome/browser/ui/browser_window.h" |
| 30 | #include "chrome/browser/ui/extensions/blocked_action_bubble_delegate.h" |
| 31 | #include "chrome/browser/ui/toolbar/toolbar_actions_bar.h" |
[email protected] | 39ef0a7c5 | 2014-05-11 01:40:00 | [diff] [blame] | 32 | #include "chrome/common/extensions/api/extension_action/action_info.h" |
[email protected] | fdd2837 | 2014-08-21 02:27:26 | [diff] [blame] | 33 | #include "components/crx_file/id_util.h" |
[email protected] | 39ef0a7c5 | 2014-05-11 01:40:00 | [diff] [blame] | 34 | #include "content/public/browser/navigation_controller.h" |
| 35 | #include "content/public/browser/navigation_entry.h" |
jam | 29737c4 | 2017-02-01 16:26:08 | [diff] [blame] | 36 | #include "content/public/browser/navigation_handle.h" |
[email protected] | ac02ac5 | 2014-05-20 01:11:26 | [diff] [blame] | 37 | #include "content/public/browser/render_view_host.h" |
[email protected] | 39ef0a7c5 | 2014-05-11 01:40:00 | [diff] [blame] | 38 | #include "content/public/browser/web_contents.h" |
Kelvin Jiang | caf6c3f | 2019-08-13 21:49:50 | [diff] [blame] | 39 | #include "extensions/browser/api/declarative_net_request/action_tracker.h" |
| 40 | #include "extensions/browser/api/declarative_net_request/rules_monitor_service.h" |
[email protected] | 39ef0a7c5 | 2014-05-11 01:40:00 | [diff] [blame] | 41 | #include "extensions/common/extension.h" |
| 42 | #include "extensions/common/extension_messages.h" |
| 43 | #include "extensions/common/extension_set.h" |
[email protected] | 23a8536 | 2014-07-07 23:26:19 | [diff] [blame] | 44 | #include "extensions/common/manifest.h" |
[email protected] | e167058 | 2014-08-15 23:05:41 | [diff] [blame] | 45 | #include "extensions/common/permissions/permission_set.h" |
[email protected] | 39ef0a7c5 | 2014-05-11 01:40:00 | [diff] [blame] | 46 | #include "extensions/common/permissions/permissions_data.h" |
| 47 | #include "ipc/ipc_message_macros.h" |
Karan Bhatia | ededa7cf | 2018-09-05 19:00:30 | [diff] [blame] | 48 | #include "url/origin.h" |
[email protected] | 39ef0a7c5 | 2014-05-11 01:40:00 | [diff] [blame] | 49 | |
| 50 | namespace extensions { |
| 51 | |
rdevlin.cronin | 4a78c48b | 2016-03-24 00:02:29 | [diff] [blame] | 52 | namespace { |
| 53 | |
| 54 | // The blocked actions that require a page refresh to run. |
| 55 | const int kRefreshRequiredActionsMask = |
| 56 | BLOCKED_ACTION_WEB_REQUEST | BLOCKED_ACTION_SCRIPT_AT_START; |
| 57 | } |
| 58 | |
rdevlin.cronin | 8408b4f9 | 2016-03-15 19:14:14 | [diff] [blame] | 59 | ExtensionActionRunner::PendingScript::PendingScript( |
rdevlin.cronin | 8d034e5 | 2016-02-02 22:46:32 | [diff] [blame] | 60 | UserScript::RunLocation run_location, |
| 61 | const base::Closure& permit_script) |
| 62 | : run_location(run_location), permit_script(permit_script) {} |
| 63 | |
rdevlin.cronin | 8408b4f9 | 2016-03-15 19:14:14 | [diff] [blame] | 64 | ExtensionActionRunner::PendingScript::PendingScript( |
vmpstr | b8aacbe | 2016-02-26 02:00:48 | [diff] [blame] | 65 | const PendingScript& other) = default; |
| 66 | |
rdevlin.cronin | 8408b4f9 | 2016-03-15 19:14:14 | [diff] [blame] | 67 | ExtensionActionRunner::PendingScript::~PendingScript() {} |
rdevlin.cronin | 8d034e5 | 2016-02-02 22:46:32 | [diff] [blame] | 68 | |
rdevlin.cronin | 8408b4f9 | 2016-03-15 19:14:14 | [diff] [blame] | 69 | ExtensionActionRunner::ExtensionActionRunner(content::WebContents* web_contents) |
[email protected] | 39ef0a7c5 | 2014-05-11 01:40:00 | [diff] [blame] | 70 | : content::WebContentsObserver(web_contents), |
rdevlin.cronin | 1f87703 | 2015-02-20 00:12:42 | [diff] [blame] | 71 | num_page_requests_(0), |
rdevlin.cronin | 699ca6ff | 2014-09-29 23:59:57 | [diff] [blame] | 72 | browser_context_(web_contents->GetBrowserContext()), |
rdevlin.cronin | b8dffe56 | 2015-02-07 00:58:01 | [diff] [blame] | 73 | was_used_on_page_(false), |
rdevlin.cronin | 4a78c48b | 2016-03-24 00:02:29 | [diff] [blame] | 74 | ignore_active_tab_granted_(false), |
Evan Stade | 75872a6 | 2019-09-06 21:17:38 | [diff] [blame] | 75 | test_observer_(nullptr) { |
[email protected] | ac02ac5 | 2014-05-20 01:11:26 | [diff] [blame] | 76 | CHECK(web_contents); |
rdevlin.cronin | 699ca6ff | 2014-09-29 23:59:57 | [diff] [blame] | 77 | extension_registry_observer_.Add(ExtensionRegistry::Get(browser_context_)); |
[email protected] | 39ef0a7c5 | 2014-05-11 01:40:00 | [diff] [blame] | 78 | } |
| 79 | |
rdevlin.cronin | 8408b4f9 | 2016-03-15 19:14:14 | [diff] [blame] | 80 | ExtensionActionRunner::~ExtensionActionRunner() { |
[email protected] | 39ef0a7c5 | 2014-05-11 01:40:00 | [diff] [blame] | 81 | LogUMA(); |
| 82 | } |
| 83 | |
[email protected] | eac223a | 2014-05-13 17:39:57 | [diff] [blame] | 84 | // static |
rdevlin.cronin | 8408b4f9 | 2016-03-15 19:14:14 | [diff] [blame] | 85 | ExtensionActionRunner* ExtensionActionRunner::GetForWebContents( |
[email protected] | eac223a | 2014-05-13 17:39:57 | [diff] [blame] | 86 | content::WebContents* web_contents) { |
| 87 | if (!web_contents) |
| 88 | return NULL; |
| 89 | TabHelper* tab_helper = TabHelper::FromWebContents(web_contents); |
rdevlin.cronin | 8408b4f9 | 2016-03-15 19:14:14 | [diff] [blame] | 90 | return tab_helper ? tab_helper->extension_action_runner() : NULL; |
[email protected] | eac223a | 2014-05-13 17:39:57 | [diff] [blame] | 91 | } |
| 92 | |
rdevlin.cronin | 343fd10 | 2016-03-17 00:24:54 | [diff] [blame] | 93 | ExtensionAction::ShowAction ExtensionActionRunner::RunAction( |
| 94 | const Extension* extension, |
| 95 | bool grant_tab_permissions) { |
rdevlin.cronin | 343fd10 | 2016-03-17 00:24:54 | [diff] [blame] | 96 | if (grant_tab_permissions) { |
rdevlin.cronin | 4a78c48b | 2016-03-24 00:02:29 | [diff] [blame] | 97 | int blocked = GetBlockedActions(extension); |
| 98 | if ((blocked & kRefreshRequiredActionsMask) != 0) { |
Karan Bhatia | ededa7cf | 2018-09-05 19:00:30 | [diff] [blame] | 99 | ShowBlockedActionBubble( |
| 100 | extension, |
| 101 | base::Bind( |
| 102 | &ExtensionActionRunner::OnBlockedActionBubbleForRunActionClosed, |
| 103 | weak_factory_.GetWeakPtr(), extension->id())); |
rdevlin.cronin | 4a78c48b | 2016-03-24 00:02:29 | [diff] [blame] | 104 | return ExtensionAction::ACTION_NONE; |
| 105 | } |
rdevlin.cronin | 343fd10 | 2016-03-17 00:24:54 | [diff] [blame] | 106 | TabHelper::FromWebContents(web_contents()) |
| 107 | ->active_tab_permission_granter() |
| 108 | ->GrantIfRequested(extension); |
| 109 | // If the extension had blocked actions, granting active tab will have |
| 110 | // run the extension. Don't execute further since clicking should run |
| 111 | // blocked actions *or* the normal extension action, not both. |
rdevlin.cronin | 4a78c48b | 2016-03-24 00:02:29 | [diff] [blame] | 112 | if (blocked != BLOCKED_ACTION_NONE) |
rdevlin.cronin | 343fd10 | 2016-03-17 00:24:54 | [diff] [blame] | 113 | return ExtensionAction::ACTION_NONE; |
| 114 | } |
| 115 | |
| 116 | ExtensionAction* extension_action = |
| 117 | ExtensionActionManager::Get(browser_context_) |
| 118 | ->GetExtensionAction(*extension); |
| 119 | |
| 120 | // Anything that gets here should have a page or browser action. |
| 121 | DCHECK(extension_action); |
Mikel Astiz | 2f127c42 | 2018-04-05 19:10:27 | [diff] [blame] | 122 | int tab_id = SessionTabHelper::IdForTab(web_contents()).id(); |
rdevlin.cronin | 343fd10 | 2016-03-17 00:24:54 | [diff] [blame] | 123 | if (!extension_action->GetIsVisible(tab_id)) |
| 124 | return ExtensionAction::ACTION_NONE; |
| 125 | |
| 126 | if (extension_action->HasPopup(tab_id)) |
| 127 | return ExtensionAction::ACTION_SHOW_POPUP; |
| 128 | |
| 129 | ExtensionActionAPI::Get(browser_context_) |
isandrk | 1296202 | 2017-05-04 15:27:33 | [diff] [blame] | 130 | ->DispatchExtensionActionClicked(*extension_action, web_contents(), |
| 131 | extension); |
rdevlin.cronin | 343fd10 | 2016-03-17 00:24:54 | [diff] [blame] | 132 | return ExtensionAction::ACTION_NONE; |
[email protected] | 11814f5 | 2014-05-23 06:50:35 | [diff] [blame] | 133 | } |
| 134 | |
Karan Bhatia | ededa7cf | 2018-09-05 19:00:30 | [diff] [blame] | 135 | void ExtensionActionRunner::HandlePageAccessModified(const Extension* extension, |
| 136 | PageAccess current_access, |
| 137 | PageAccess new_access) { |
| 138 | DCHECK_NE(current_access, new_access); |
rdevlin.cronin | acb74592 | 2016-02-17 20:37:44 | [diff] [blame] | 139 | |
Karan Bhatia | ededa7cf | 2018-09-05 19:00:30 | [diff] [blame] | 140 | // If we are restricting page access, just change permissions. |
| 141 | if (new_access == PageAccess::RUN_ON_CLICK) { |
| 142 | UpdatePageAccessSettings(extension, current_access, new_access); |
| 143 | return; |
| 144 | } |
rdevlin.cronin | acb74592 | 2016-02-17 20:37:44 | [diff] [blame] | 145 | |
Karan Bhatia | ededa7cf | 2018-09-05 19:00:30 | [diff] [blame] | 146 | int blocked_actions = GetBlockedActions(extension); |
rdevlin.cronin | acb74592 | 2016-02-17 20:37:44 | [diff] [blame] | 147 | |
Karan Bhatia | ededa7cf | 2018-09-05 19:00:30 | [diff] [blame] | 148 | // Refresh the page if there are pending actions which mandate a refresh. |
| 149 | if (blocked_actions & kRefreshRequiredActionsMask) { |
| 150 | // TODO(devlin): The bubble text should make it clear that permissions are |
| 151 | // granted only after the user accepts the refresh. |
| 152 | ShowBlockedActionBubble( |
| 153 | extension, base::Bind(&ExtensionActionRunner:: |
| 154 | OnBlockedActionBubbleForPageAccessGrantClosed, |
| 155 | weak_factory_.GetWeakPtr(), extension->id(), |
| 156 | web_contents()->GetLastCommittedURL(), |
| 157 | current_access, new_access)); |
| 158 | return; |
| 159 | } |
| 160 | |
| 161 | UpdatePageAccessSettings(extension, current_access, new_access); |
| 162 | if (blocked_actions) |
| 163 | RunBlockedActions(extension); |
rdevlin.cronin | e9c7112 | 2014-08-25 23:47:21 | [diff] [blame] | 164 | } |
| 165 | |
rdevlin.cronin | 343fd10 | 2016-03-17 00:24:54 | [diff] [blame] | 166 | void ExtensionActionRunner::OnActiveTabPermissionGranted( |
| 167 | const Extension* extension) { |
rdevlin.cronin | 4a78c48b | 2016-03-24 00:02:29 | [diff] [blame] | 168 | if (!ignore_active_tab_granted_ && WantsToRun(extension)) |
rdevlin.cronin | 343fd10 | 2016-03-17 00:24:54 | [diff] [blame] | 169 | RunBlockedActions(extension); |
| 170 | } |
| 171 | |
rdevlin.cronin | 8408b4f9 | 2016-03-15 19:14:14 | [diff] [blame] | 172 | void ExtensionActionRunner::OnWebRequestBlocked(const Extension* extension) { |
Karan Bhatia | 1c72c69 | 2018-10-04 00:40:51 | [diff] [blame] | 173 | bool inserted = false; |
| 174 | std::tie(std::ignore, inserted) = |
| 175 | web_request_blocked_.insert(extension->id()); |
| 176 | if (inserted) |
| 177 | NotifyChange(extension); |
| 178 | |
Devlin Cronin | 5c5abd24 | 2018-08-30 22:49:50 | [diff] [blame] | 179 | if (test_observer_) |
| 180 | test_observer_->OnBlockedActionAdded(); |
rdevlin.cronin | 8d034e5 | 2016-02-02 22:46:32 | [diff] [blame] | 181 | } |
| 182 | |
rdevlin.cronin | 8408b4f9 | 2016-03-15 19:14:14 | [diff] [blame] | 183 | int ExtensionActionRunner::GetBlockedActions(const Extension* extension) { |
rdevlin.cronin | 8d034e5 | 2016-02-02 22:46:32 | [diff] [blame] | 184 | int blocked_actions = BLOCKED_ACTION_NONE; |
| 185 | if (web_request_blocked_.count(extension->id()) != 0) |
| 186 | blocked_actions |= BLOCKED_ACTION_WEB_REQUEST; |
| 187 | auto iter = pending_scripts_.find(extension->id()); |
| 188 | if (iter != pending_scripts_.end()) { |
| 189 | for (const PendingScript& script : iter->second) { |
| 190 | switch (script.run_location) { |
| 191 | case UserScript::DOCUMENT_START: |
| 192 | blocked_actions |= BLOCKED_ACTION_SCRIPT_AT_START; |
| 193 | break; |
| 194 | case UserScript::DOCUMENT_END: |
| 195 | case UserScript::DOCUMENT_IDLE: |
| 196 | case UserScript::BROWSER_DRIVEN: |
| 197 | blocked_actions |= BLOCKED_ACTION_SCRIPT_OTHER; |
| 198 | break; |
| 199 | case UserScript::UNDEFINED: |
| 200 | case UserScript::RUN_DEFERRED: |
| 201 | case UserScript::RUN_LOCATION_LAST: |
| 202 | NOTREACHED(); |
| 203 | } |
| 204 | } |
| 205 | } |
| 206 | |
| 207 | return blocked_actions; |
| 208 | } |
| 209 | |
rdevlin.cronin | 8408b4f9 | 2016-03-15 19:14:14 | [diff] [blame] | 210 | bool ExtensionActionRunner::WantsToRun(const Extension* extension) { |
rdevlin.cronin | 8d034e5 | 2016-02-02 22:46:32 | [diff] [blame] | 211 | return GetBlockedActions(extension) != BLOCKED_ACTION_NONE; |
[email protected] | e167058 | 2014-08-15 23:05:41 | [diff] [blame] | 212 | } |
| 213 | |
rdevlin.cronin | 4a78c48b | 2016-03-24 00:02:29 | [diff] [blame] | 214 | void ExtensionActionRunner::RunForTesting(const Extension* extension) { |
| 215 | if (WantsToRun(extension)) { |
| 216 | TabHelper::FromWebContents(web_contents()) |
| 217 | ->active_tab_permission_granter() |
| 218 | ->GrantIfRequested(extension); |
| 219 | } |
| 220 | } |
| 221 | |
Devlin Cronin | 3e532b8 | 2018-05-03 21:27:19 | [diff] [blame] | 222 | PermissionsData::PageAccess |
rdevlin.cronin | 8408b4f9 | 2016-03-15 19:14:14 | [diff] [blame] | 223 | ExtensionActionRunner::RequiresUserConsentForScriptInjection( |
[email protected] | 23a8536 | 2014-07-07 23:26:19 | [diff] [blame] | 224 | const Extension* extension, |
| 225 | UserScript::InjectionType type) { |
| 226 | CHECK(extension); |
| 227 | |
[email protected] | 23a8536 | 2014-07-07 23:26:19 | [diff] [blame] | 228 | // Allow the extension if it's been explicitly granted permission. |
| 229 | if (permitted_extensions_.count(extension->id()) > 0) |
Devlin Cronin | 3e532b8 | 2018-05-03 21:27:19 | [diff] [blame] | 230 | return PermissionsData::PageAccess::kAllowed; |
[email protected] | 23a8536 | 2014-07-07 23:26:19 | [diff] [blame] | 231 | |
| 232 | GURL url = web_contents()->GetVisibleURL(); |
Mikel Astiz | 2f127c42 | 2018-04-05 19:10:27 | [diff] [blame] | 233 | int tab_id = SessionTabHelper::IdForTab(web_contents()).id(); |
[email protected] | 23a8536 | 2014-07-07 23:26:19 | [diff] [blame] | 234 | switch (type) { |
| 235 | case UserScript::CONTENT_SCRIPT: |
Devlin Cronin | 5cb43783 | 2018-05-17 20:14:41 | [diff] [blame] | 236 | return extension->permissions_data()->GetContentScriptAccess(url, tab_id, |
| 237 | nullptr); |
[email protected] | 23a8536 | 2014-07-07 23:26:19 | [diff] [blame] | 238 | case UserScript::PROGRAMMATIC_SCRIPT: |
Devlin Cronin | 5cb43783 | 2018-05-17 20:14:41 | [diff] [blame] | 239 | return extension->permissions_data()->GetPageAccess(url, tab_id, nullptr); |
[email protected] | 23a8536 | 2014-07-07 23:26:19 | [diff] [blame] | 240 | } |
| 241 | |
| 242 | NOTREACHED(); |
Devlin Cronin | 3e532b8 | 2018-05-03 21:27:19 | [diff] [blame] | 243 | return PermissionsData::PageAccess::kDenied; |
[email protected] | 23a8536 | 2014-07-07 23:26:19 | [diff] [blame] | 244 | } |
| 245 | |
rdevlin.cronin | 8408b4f9 | 2016-03-15 19:14:14 | [diff] [blame] | 246 | void ExtensionActionRunner::RequestScriptInjection( |
[email protected] | 23a8536 | 2014-07-07 23:26:19 | [diff] [blame] | 247 | const Extension* extension, |
rdevlin.cronin | 8d034e5 | 2016-02-02 22:46:32 | [diff] [blame] | 248 | UserScript::RunLocation run_location, |
[email protected] | 23a8536 | 2014-07-07 23:26:19 | [diff] [blame] | 249 | const base::Closure& callback) { |
| 250 | CHECK(extension); |
rdevlin.cronin | 8d034e5 | 2016-02-02 22:46:32 | [diff] [blame] | 251 | PendingScriptList& list = pending_scripts_[extension->id()]; |
| 252 | list.push_back(PendingScript(run_location, callback)); |
[email protected] | 23a8536 | 2014-07-07 23:26:19 | [diff] [blame] | 253 | |
rdevlin.cronin | 699ca6ff | 2014-09-29 23:59:57 | [diff] [blame] | 254 | // If this was the first entry, we need to notify that a new extension wants |
| 255 | // to run. |
| 256 | if (list.size() == 1u) |
| 257 | NotifyChange(extension); |
rdevlin.cronin | b8dffe56 | 2015-02-07 00:58:01 | [diff] [blame] | 258 | |
| 259 | was_used_on_page_ = true; |
Devlin Cronin | 5c5abd24 | 2018-08-30 22:49:50 | [diff] [blame] | 260 | |
| 261 | if (test_observer_) |
| 262 | test_observer_->OnBlockedActionAdded(); |
[email protected] | 23a8536 | 2014-07-07 23:26:19 | [diff] [blame] | 263 | } |
| 264 | |
rdevlin.cronin | 8408b4f9 | 2016-03-15 19:14:14 | [diff] [blame] | 265 | void ExtensionActionRunner::RunPendingScriptsForExtension( |
[email protected] | 11814f5 | 2014-05-23 06:50:35 | [diff] [blame] | 266 | const Extension* extension) { |
[email protected] | ac02ac5 | 2014-05-20 01:11:26 | [diff] [blame] | 267 | DCHECK(extension); |
[email protected] | ac02ac5 | 2014-05-20 01:11:26 | [diff] [blame] | 268 | |
| 269 | content::NavigationEntry* visible_entry = |
| 270 | web_contents()->GetController().GetVisibleEntry(); |
| 271 | // Refuse to run if there's no visible entry, because we have no idea of |
| 272 | // determining if it's the proper page. This should rarely, if ever, happen. |
| 273 | if (!visible_entry) |
[email protected] | 11814f5 | 2014-05-23 06:50:35 | [diff] [blame] | 274 | return; |
[email protected] | ac02ac5 | 2014-05-20 01:11:26 | [diff] [blame] | 275 | |
[email protected] | ac02ac5 | 2014-05-20 01:11:26 | [diff] [blame] | 276 | // We add this to the list of permitted extensions and erase pending entries |
| 277 | // *before* running them to guard against the crazy case where running the |
| 278 | // callbacks adds more entries. |
| 279 | permitted_extensions_.insert(extension->id()); |
[email protected] | 23a8536 | 2014-07-07 23:26:19 | [diff] [blame] | 280 | |
jdoerrie | 13cd648c8 | 2018-10-02 21:21:02 | [diff] [blame] | 281 | auto iter = pending_scripts_.find(extension->id()); |
rdevlin.cronin | 8d034e5 | 2016-02-02 22:46:32 | [diff] [blame] | 282 | if (iter == pending_scripts_.end()) |
[email protected] | 23a8536 | 2014-07-07 23:26:19 | [diff] [blame] | 283 | return; |
| 284 | |
rdevlin.cronin | 8d034e5 | 2016-02-02 22:46:32 | [diff] [blame] | 285 | PendingScriptList scripts; |
| 286 | iter->second.swap(scripts); |
| 287 | pending_scripts_.erase(extension->id()); |
[email protected] | ac02ac5 | 2014-05-20 01:11:26 | [diff] [blame] | 288 | |
| 289 | // Run all pending injections for the given extension. |
rdevlin.cronin | 8d034e5 | 2016-02-02 22:46:32 | [diff] [blame] | 290 | for (PendingScript& pending_script : scripts) |
| 291 | pending_script.permit_script.Run(); |
[email protected] | ac02ac5 | 2014-05-20 01:11:26 | [diff] [blame] | 292 | } |
| 293 | |
rdevlin.cronin | 8408b4f9 | 2016-03-15 19:14:14 | [diff] [blame] | 294 | void ExtensionActionRunner::OnRequestScriptInjectionPermission( |
[email protected] | ac02ac5 | 2014-05-20 01:11:26 | [diff] [blame] | 295 | const std::string& extension_id, |
[email protected] | 23a8536 | 2014-07-07 23:26:19 | [diff] [blame] | 296 | UserScript::InjectionType script_type, |
rdevlin.cronin | 8d034e5 | 2016-02-02 22:46:32 | [diff] [blame] | 297 | UserScript::RunLocation run_location, |
avi | a2f4804a | 2015-12-24 23:11:13 | [diff] [blame] | 298 | int64_t request_id) { |
[email protected] | fdd2837 | 2014-08-21 02:27:26 | [diff] [blame] | 299 | if (!crx_file::id_util::IdIsValid(extension_id)) { |
[email protected] | ac02ac5 | 2014-05-20 01:11:26 | [diff] [blame] | 300 | NOTREACHED() << "'" << extension_id << "' is not a valid id."; |
| 301 | return; |
| 302 | } |
| 303 | |
rdevlin.cronin | 8408b4f9 | 2016-03-15 19:14:14 | [diff] [blame] | 304 | const Extension* extension = ExtensionRegistry::Get(browser_context_) |
| 305 | ->enabled_extensions() |
| 306 | .GetByID(extension_id); |
[email protected] | ac02ac5 | 2014-05-20 01:11:26 | [diff] [blame] | 307 | // We shouldn't allow extensions which are no longer enabled to run any |
| 308 | // scripts. Ignore the request. |
| 309 | if (!extension) |
| 310 | return; |
| 311 | |
rdevlin.cronin | 1f87703 | 2015-02-20 00:12:42 | [diff] [blame] | 312 | ++num_page_requests_; |
| 313 | |
[email protected] | 23a8536 | 2014-07-07 23:26:19 | [diff] [blame] | 314 | switch (RequiresUserConsentForScriptInjection(extension, script_type)) { |
Devlin Cronin | 3e532b8 | 2018-05-03 21:27:19 | [diff] [blame] | 315 | case PermissionsData::PageAccess::kAllowed: |
[email protected] | 23a8536 | 2014-07-07 23:26:19 | [diff] [blame] | 316 | PermitScriptInjection(request_id); |
| 317 | break; |
Devlin Cronin | 3e532b8 | 2018-05-03 21:27:19 | [diff] [blame] | 318 | case PermissionsData::PageAccess::kWithheld: |
[email protected] | 23a8536 | 2014-07-07 23:26:19 | [diff] [blame] | 319 | // This base::Unretained() is safe, because the callback is only invoked |
| 320 | // by this object. |
| 321 | RequestScriptInjection( |
rdevlin.cronin | 8d034e5 | 2016-02-02 22:46:32 | [diff] [blame] | 322 | extension, run_location, |
rdevlin.cronin | 8408b4f9 | 2016-03-15 19:14:14 | [diff] [blame] | 323 | base::Bind(&ExtensionActionRunner::PermitScriptInjection, |
rdevlin.cronin | 8d034e5 | 2016-02-02 22:46:32 | [diff] [blame] | 324 | base::Unretained(this), request_id)); |
[email protected] | 23a8536 | 2014-07-07 23:26:19 | [diff] [blame] | 325 | break; |
Devlin Cronin | 3e532b8 | 2018-05-03 21:27:19 | [diff] [blame] | 326 | case PermissionsData::PageAccess::kDenied: |
[email protected] | 23a8536 | 2014-07-07 23:26:19 | [diff] [blame] | 327 | // We should usually only get a "deny access" if the page changed (as the |
| 328 | // renderer wouldn't have requested permission if the answer was always |
| 329 | // "no"). Just let the request fizzle and die. |
| 330 | break; |
[email protected] | 0d8d697 | 2014-06-03 22:41:02 | [diff] [blame] | 331 | } |
| 332 | } |
| 333 | |
rdevlin.cronin | 8408b4f9 | 2016-03-15 19:14:14 | [diff] [blame] | 334 | void ExtensionActionRunner::PermitScriptInjection(int64_t request_id) { |
[email protected] | 23a8536 | 2014-07-07 23:26:19 | [diff] [blame] | 335 | // This only sends the response to the renderer - the process of adding the |
| 336 | // extension to the list of |permitted_extensions_| is done elsewhere. |
rdevlin.cronin | 4bb32d7 | 2015-06-02 21:55:01 | [diff] [blame] | 337 | // TODO(devlin): Instead of sending this to all frames, we should include the |
| 338 | // routing_id in the permission request message, and send only to the proper |
| 339 | // frame (sending it to all frames doesn't hurt, but isn't as efficient). |
| 340 | web_contents()->SendToAllFrames(new ExtensionMsg_PermitScriptInjection( |
| 341 | MSG_ROUTING_NONE, // Routing id is set by the |web_contents|. |
| 342 | request_id)); |
[email protected] | 39ef0a7c5 | 2014-05-11 01:40:00 | [diff] [blame] | 343 | } |
| 344 | |
rdevlin.cronin | 8408b4f9 | 2016-03-15 19:14:14 | [diff] [blame] | 345 | void ExtensionActionRunner::NotifyChange(const Extension* extension) { |
rdevlin.cronin | 699ca6ff | 2014-09-29 23:59:57 | [diff] [blame] | 346 | ExtensionActionAPI* extension_action_api = |
| 347 | ExtensionActionAPI::Get(browser_context_); |
| 348 | ExtensionAction* extension_action = |
rdevlin.cronin | 8408b4f9 | 2016-03-15 19:14:14 | [diff] [blame] | 349 | ExtensionActionManager::Get(browser_context_) |
| 350 | ->GetExtensionAction(*extension); |
rdevlin.cronin | 699ca6ff | 2014-09-29 23:59:57 | [diff] [blame] | 351 | // If the extension has an action, we need to notify that it's updated. |
| 352 | if (extension_action) { |
rdevlin.cronin | 8408b4f9 | 2016-03-15 19:14:14 | [diff] [blame] | 353 | extension_action_api->NotifyChange(extension_action, web_contents(), |
| 354 | browser_context_); |
rdevlin.cronin | 699ca6ff | 2014-09-29 23:59:57 | [diff] [blame] | 355 | } |
rdevlin.cronin | 699ca6ff | 2014-09-29 23:59:57 | [diff] [blame] | 356 | } |
| 357 | |
rdevlin.cronin | 8408b4f9 | 2016-03-15 19:14:14 | [diff] [blame] | 358 | void ExtensionActionRunner::LogUMA() const { |
rdevlin.cronin | b8dffe56 | 2015-02-07 00:58:01 | [diff] [blame] | 359 | // We only log the permitted extensions metric if the feature was used at all |
| 360 | // on the page, because otherwise the data will be boring. |
| 361 | if (was_used_on_page_) { |
[email protected] | ac02ac5 | 2014-05-20 01:11:26 | [diff] [blame] | 362 | UMA_HISTOGRAM_COUNTS_100( |
| 363 | "Extensions.ActiveScriptController.PermittedExtensions", |
| 364 | permitted_extensions_.size()); |
| 365 | UMA_HISTOGRAM_COUNTS_100( |
| 366 | "Extensions.ActiveScriptController.DeniedExtensions", |
rdevlin.cronin | 8d034e5 | 2016-02-02 22:46:32 | [diff] [blame] | 367 | pending_scripts_.size()); |
[email protected] | ac02ac5 | 2014-05-20 01:11:26 | [diff] [blame] | 368 | } |
[email protected] | 39ef0a7c5 | 2014-05-11 01:40:00 | [diff] [blame] | 369 | } |
| 370 | |
rdevlin.cronin | 4a78c48b | 2016-03-24 00:02:29 | [diff] [blame] | 371 | void ExtensionActionRunner::ShowBlockedActionBubble( |
Karan Bhatia | ededa7cf | 2018-09-05 19:00:30 | [diff] [blame] | 372 | const Extension* extension, |
| 373 | const base::Callback<void(ToolbarActionsBarBubbleDelegate::CloseAction)>& |
| 374 | callback) { |
rdevlin.cronin | 4a78c48b | 2016-03-24 00:02:29 | [diff] [blame] | 375 | Browser* browser = chrome::FindBrowserWithWebContents(web_contents()); |
Peter Boström | ee16999c | 2019-06-14 21:03:23 | [diff] [blame] | 376 | ExtensionsContainer* const extensions_container = |
| 377 | browser ? browser->window()->GetExtensionsContainer() : nullptr; |
| 378 | if (!extensions_container) |
| 379 | return; |
| 380 | if (default_bubble_close_action_for_testing_) { |
| 381 | base::ThreadTaskRunnerHandle::Get()->PostTask( |
| 382 | FROM_HERE, |
| 383 | base::BindOnce(callback, *default_bubble_close_action_for_testing_)); |
| 384 | } else { |
| 385 | extensions_container->ShowToolbarActionBubble( |
| 386 | std::make_unique<BlockedActionBubbleDelegate>(callback, |
| 387 | extension->id())); |
rdevlin.cronin | 4a78c48b | 2016-03-24 00:02:29 | [diff] [blame] | 388 | } |
| 389 | } |
| 390 | |
Karan Bhatia | ededa7cf | 2018-09-05 19:00:30 | [diff] [blame] | 391 | void ExtensionActionRunner::OnBlockedActionBubbleForRunActionClosed( |
rdevlin.cronin | 4a78c48b | 2016-03-24 00:02:29 | [diff] [blame] | 392 | const std::string& extension_id, |
| 393 | ToolbarActionsBarBubbleDelegate::CloseAction action) { |
| 394 | // If the user agreed to refresh the page, do so. |
| 395 | if (action == ToolbarActionsBarBubbleDelegate::CLOSE_EXECUTE) { |
| 396 | const Extension* extension = ExtensionRegistry::Get(browser_context_) |
| 397 | ->enabled_extensions() |
| 398 | .GetByID(extension_id); |
| 399 | if (!extension) |
| 400 | return; |
| 401 | { |
| 402 | // Ignore the active tab permission being granted because we don't want |
| 403 | // to run scripts right before we refresh the page. |
| 404 | base::AutoReset<bool> ignore_active_tab(&ignore_active_tab_granted_, |
| 405 | true); |
| 406 | TabHelper::FromWebContents(web_contents()) |
| 407 | ->active_tab_permission_granter() |
| 408 | ->GrantIfRequested(extension); |
| 409 | } |
toyoshim | 6142d96f | 2016-12-19 09:07:25 | [diff] [blame] | 410 | web_contents()->GetController().Reload(content::ReloadType::NORMAL, false); |
rdevlin.cronin | 4a78c48b | 2016-03-24 00:02:29 | [diff] [blame] | 411 | } |
| 412 | } |
| 413 | |
Karan Bhatia | ededa7cf | 2018-09-05 19:00:30 | [diff] [blame] | 414 | void ExtensionActionRunner::OnBlockedActionBubbleForPageAccessGrantClosed( |
| 415 | const std::string& extension_id, |
| 416 | const GURL& page_url, |
| 417 | PageAccess current_access, |
| 418 | PageAccess new_access, |
| 419 | ToolbarActionsBarBubbleDelegate::CloseAction action) { |
| 420 | DCHECK(new_access == PageAccess::RUN_ON_SITE || |
| 421 | new_access == PageAccess::RUN_ON_ALL_SITES); |
| 422 | DCHECK_EQ(PageAccess::RUN_ON_CLICK, current_access); |
| 423 | |
| 424 | // Don't change permissions if the user chose to not refresh the page. |
| 425 | if (action != ToolbarActionsBarBubbleDelegate::CLOSE_EXECUTE) |
| 426 | return; |
| 427 | |
| 428 | // If the web contents have navigated to a different origin, do nothing. |
| 429 | if (!url::IsSameOriginWith(page_url, web_contents()->GetLastCommittedURL())) |
| 430 | return; |
| 431 | |
| 432 | const Extension* extension = ExtensionRegistry::Get(browser_context_) |
| 433 | ->enabled_extensions() |
| 434 | .GetByID(extension_id); |
| 435 | if (!extension) |
| 436 | return; |
| 437 | |
| 438 | UpdatePageAccessSettings(extension, current_access, new_access); |
| 439 | web_contents()->GetController().Reload(content::ReloadType::NORMAL, false); |
| 440 | } |
| 441 | |
| 442 | void ExtensionActionRunner::UpdatePageAccessSettings(const Extension* extension, |
| 443 | PageAccess current_access, |
| 444 | PageAccess new_access) { |
| 445 | DCHECK_NE(current_access, new_access); |
| 446 | |
| 447 | const GURL& url = web_contents()->GetLastCommittedURL(); |
| 448 | ScriptingPermissionsModifier modifier(browser_context_, extension); |
| 449 | DCHECK(modifier.CanAffectExtension()); |
| 450 | |
| 451 | switch (new_access) { |
| 452 | case PageAccess::RUN_ON_CLICK: |
Devlin Cronin | d4d8bfc | 2018-09-13 17:39:39 | [diff] [blame] | 453 | // Note: SetWithholdHostPermissions() is a no-op if host permissions are |
| 454 | // already being withheld. |
| 455 | modifier.SetWithholdHostPermissions(true); |
Karan Bhatia | ededa7cf | 2018-09-05 19:00:30 | [diff] [blame] | 456 | if (modifier.HasGrantedHostPermission(url)) |
| 457 | modifier.RemoveGrantedHostPermission(url); |
| 458 | break; |
| 459 | case PageAccess::RUN_ON_SITE: |
Devlin Cronin | d4d8bfc | 2018-09-13 17:39:39 | [diff] [blame] | 460 | // Note: SetWithholdHostPermissions() is a no-op if host permissions are |
| 461 | // already being withheld. |
| 462 | modifier.SetWithholdHostPermissions(true); |
Karan Bhatia | ededa7cf | 2018-09-05 19:00:30 | [diff] [blame] | 463 | if (!modifier.HasGrantedHostPermission(url)) |
| 464 | modifier.GrantHostPermission(url); |
| 465 | break; |
| 466 | case PageAccess::RUN_ON_ALL_SITES: |
| 467 | modifier.SetWithholdHostPermissions(false); |
| 468 | break; |
| 469 | } |
| 470 | } |
| 471 | |
| 472 | void ExtensionActionRunner::RunBlockedActions(const Extension* extension) { |
Jan Wilken Dörrie | ade7922 | 2019-06-06 19:01:12 | [diff] [blame] | 473 | DCHECK(base::Contains(pending_scripts_, extension->id()) || |
Karan Bhatia | ededa7cf | 2018-09-05 19:00:30 | [diff] [blame] | 474 | web_request_blocked_.count(extension->id()) != 0); |
| 475 | |
| 476 | // Clicking to run the extension counts as granting it permission to run on |
| 477 | // the given tab. |
| 478 | // The extension may already have active tab at this point, but granting |
| 479 | // it twice is essentially a no-op. |
| 480 | TabHelper::FromWebContents(web_contents()) |
| 481 | ->active_tab_permission_granter() |
| 482 | ->GrantIfRequested(extension); |
| 483 | |
| 484 | RunPendingScriptsForExtension(extension); |
| 485 | web_request_blocked_.erase(extension->id()); |
| 486 | |
| 487 | // The extension ran, so we need to tell the ExtensionActionAPI that we no |
| 488 | // longer want to act. |
| 489 | NotifyChange(extension); |
| 490 | } |
| 491 | |
rdevlin.cronin | 8408b4f9 | 2016-03-15 19:14:14 | [diff] [blame] | 492 | bool ExtensionActionRunner::OnMessageReceived( |
rdevlin.cronin | 45dca7f | 2015-06-08 19:47:03 | [diff] [blame] | 493 | const IPC::Message& message, |
| 494 | content::RenderFrameHost* render_frame_host) { |
rdevlin.cronin | 6e7e5edc | 2014-08-29 16:23:23 | [diff] [blame] | 495 | bool handled = true; |
rdevlin.cronin | 8408b4f9 | 2016-03-15 19:14:14 | [diff] [blame] | 496 | IPC_BEGIN_MESSAGE_MAP(ExtensionActionRunner, message) |
rdevlin.cronin | 6e7e5edc | 2014-08-29 16:23:23 | [diff] [blame] | 497 | IPC_MESSAGE_HANDLER(ExtensionHostMsg_RequestScriptInjectionPermission, |
| 498 | OnRequestScriptInjectionPermission) |
| 499 | IPC_MESSAGE_UNHANDLED(handled = false) |
| 500 | IPC_END_MESSAGE_MAP() |
| 501 | return handled; |
| 502 | } |
| 503 | |
jam | 29737c4 | 2017-02-01 16:26:08 | [diff] [blame] | 504 | void ExtensionActionRunner::DidFinishNavigation( |
| 505 | content::NavigationHandle* navigation_handle) { |
| 506 | if (!navigation_handle->IsInMainFrame() || |
| 507 | !navigation_handle->HasCommitted() || |
eugenebut | a11672fb | 2017-03-07 17:13:51 | [diff] [blame] | 508 | navigation_handle->IsSameDocument()) { |
rdevlin.cronin | 5094223 | 2014-08-27 17:40:56 | [diff] [blame] | 509 | return; |
jam | 29737c4 | 2017-02-01 16:26:08 | [diff] [blame] | 510 | } |
rdevlin.cronin | 5094223 | 2014-08-27 17:40:56 | [diff] [blame] | 511 | |
| 512 | LogUMA(); |
rdevlin.cronin | 1f87703 | 2015-02-20 00:12:42 | [diff] [blame] | 513 | num_page_requests_ = 0; |
rdevlin.cronin | 5094223 | 2014-08-27 17:40:56 | [diff] [blame] | 514 | permitted_extensions_.clear(); |
rdevlin.cronin | 8d034e5 | 2016-02-02 22:46:32 | [diff] [blame] | 515 | pending_scripts_.clear(); |
| 516 | web_request_blocked_.clear(); |
rdevlin.cronin | b8dffe56 | 2015-02-07 00:58:01 | [diff] [blame] | 517 | was_used_on_page_ = false; |
rdevlin.cronin | 4a78c48b | 2016-03-24 00:02:29 | [diff] [blame] | 518 | weak_factory_.InvalidateWeakPtrs(); |
Devlin Cronin | 1d634324 | 2018-06-05 02:16:41 | [diff] [blame] | 519 | |
| 520 | // Note: This needs to be called *after* the maps have been updated, so that |
| 521 | // when the UI updates, this object returns the proper result for "wants to |
| 522 | // run". |
| 523 | ExtensionActionAPI::Get(browser_context_) |
| 524 | ->ClearAllValuesForTab(web_contents()); |
Kelvin Jiang | caf6c3f | 2019-08-13 21:49:50 | [diff] [blame] | 525 | |
| 526 | declarative_net_request::RulesMonitorService* rules_monitor_service = |
| 527 | declarative_net_request::RulesMonitorService::Get(browser_context_); |
| 528 | |
| 529 | // |rules_monitor_service| can be null for some unit tests. |
| 530 | if (rules_monitor_service) { |
| 531 | declarative_net_request::ActionTracker& action_tracker = |
| 532 | rules_monitor_service->ruleset_manager()->action_tracker(); |
| 533 | |
| 534 | int tab_id = ExtensionTabUtil::GetTabId(web_contents()); |
| 535 | action_tracker.ResetActionCountForTab(tab_id); |
| 536 | } |
Devlin Cronin | 1d634324 | 2018-06-05 02:16:41 | [diff] [blame] | 537 | } |
| 538 | |
| 539 | void ExtensionActionRunner::WebContentsDestroyed() { |
| 540 | ExtensionActionAPI::Get(browser_context_) |
| 541 | ->ClearAllValuesForTab(web_contents()); |
Kelvin Jiang | caf6c3f | 2019-08-13 21:49:50 | [diff] [blame] | 542 | |
| 543 | declarative_net_request::RulesMonitorService* rules_monitor_service = |
| 544 | declarative_net_request::RulesMonitorService::Get(browser_context_); |
| 545 | |
| 546 | // |rules_monitor_service| can be null for some unit tests. |
| 547 | if (rules_monitor_service) { |
| 548 | declarative_net_request::ActionTracker& action_tracker = |
| 549 | rules_monitor_service->ruleset_manager()->action_tracker(); |
| 550 | |
| 551 | int tab_id = ExtensionTabUtil::GetTabId(web_contents()); |
| 552 | action_tracker.ClearTabData(tab_id); |
| 553 | } |
rdevlin.cronin | 5094223 | 2014-08-27 17:40:56 | [diff] [blame] | 554 | } |
| 555 | |
rdevlin.cronin | 8408b4f9 | 2016-03-15 19:14:14 | [diff] [blame] | 556 | void ExtensionActionRunner::OnExtensionUnloaded( |
rdevlin.cronin | 6e7e5edc | 2014-08-29 16:23:23 | [diff] [blame] | 557 | content::BrowserContext* browser_context, |
| 558 | const Extension* extension, |
limasdf | 0deef204 | 2017-05-03 19:17:17 | [diff] [blame] | 559 | UnloadedExtensionReason reason) { |
jdoerrie | 13cd648c8 | 2018-10-02 21:21:02 | [diff] [blame] | 560 | auto iter = pending_scripts_.find(extension->id()); |
rdevlin.cronin | 8d034e5 | 2016-02-02 22:46:32 | [diff] [blame] | 561 | if (iter != pending_scripts_.end()) { |
| 562 | pending_scripts_.erase(iter); |
Devlin Cronin | e5fc7555 | 2019-04-25 02:19:06 | [diff] [blame] | 563 | NotifyChange(extension); |
rdevlin.cronin | 6e7e5edc | 2014-08-29 16:23:23 | [diff] [blame] | 564 | } |
| 565 | } |
| 566 | |
[email protected] | 39ef0a7c5 | 2014-05-11 01:40:00 | [diff] [blame] | 567 | } // namespace extensions |