blob: 55e711669ad0e1f893a1ee1d3cd661ffb9b21c47 [file] [log] [blame]
[email protected]39ef0a7c52014-05-11 01:40:001// 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.cronin8408b4f92016-03-15 19:14:145#include "chrome/browser/extensions/extension_action_runner.h"
[email protected]39ef0a7c52014-05-11 01:40:006
dchengc963c7142016-04-08 03:55:227#include <memory>
Karan Bhatia1c72c692018-10-04 00:40:518#include <tuple>
dchengc963c7142016-04-08 03:55:229
rdevlin.cronin4a78c48b2016-03-24 00:02:2910#include "base/auto_reset.h"
[email protected]ac02ac52014-05-20 01:11:2611#include "base/bind.h"
12#include "base/bind_helpers.h"
fdoray283082bd2016-06-02 20:18:4613#include "base/location.h"
asvitkineaa060312016-09-01 22:44:1314#include "base/metrics/histogram_macros.h"
fdoray283082bd2016-06-02 20:18:4615#include "base/single_thread_task_runner.h"
[email protected]39ef0a7c52014-05-11 01:40:0016#include "base/stl_util.h"
fdoray283082bd2016-06-02 20:18:4617#include "base/threading/thread_task_runner_handle.h"
[email protected]78cd68e2014-05-22 20:33:5218#include "chrome/browser/extensions/active_tab_permission_granter.h"
rdevlin.croninaeffd182014-08-26 17:04:0019#include "chrome/browser/extensions/api/extension_action/extension_action_api.h"
[email protected]39ef0a7c52014-05-11 01:40:0020#include "chrome/browser/extensions/extension_action.h"
[email protected]6a24a0392014-08-12 21:31:3321#include "chrome/browser/extensions/extension_action_manager.h"
Kelvin Jiangcaf6c3f2019-08-13 21:49:5022#include "chrome/browser/extensions/extension_tab_util.h"
[email protected]e1670582014-08-15 23:05:4123#include "chrome/browser/extensions/permissions_updater.h"
Karan Bhatiaededa7cf2018-09-05 19:00:3024#include "chrome/browser/extensions/scripting_permissions_modifier.h"
[email protected]eac223a2014-05-13 17:39:5725#include "chrome/browser/extensions/tab_helper.h"
[email protected]6a24a0392014-08-12 21:31:3326#include "chrome/browser/profiles/profile.h"
[email protected]e3f90c602014-08-18 12:41:5927#include "chrome/browser/sessions/session_tab_helper.h"
rdevlin.cronin4a78c48b2016-03-24 00:02:2928#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]39ef0a7c52014-05-11 01:40:0032#include "chrome/common/extensions/api/extension_action/action_info.h"
[email protected]fdd28372014-08-21 02:27:2633#include "components/crx_file/id_util.h"
[email protected]39ef0a7c52014-05-11 01:40:0034#include "content/public/browser/navigation_controller.h"
35#include "content/public/browser/navigation_entry.h"
jam29737c42017-02-01 16:26:0836#include "content/public/browser/navigation_handle.h"
[email protected]ac02ac52014-05-20 01:11:2637#include "content/public/browser/render_view_host.h"
[email protected]39ef0a7c52014-05-11 01:40:0038#include "content/public/browser/web_contents.h"
Kelvin Jiangcaf6c3f2019-08-13 21:49:5039#include "extensions/browser/api/declarative_net_request/action_tracker.h"
40#include "extensions/browser/api/declarative_net_request/rules_monitor_service.h"
[email protected]39ef0a7c52014-05-11 01:40:0041#include "extensions/common/extension.h"
42#include "extensions/common/extension_messages.h"
43#include "extensions/common/extension_set.h"
[email protected]23a85362014-07-07 23:26:1944#include "extensions/common/manifest.h"
[email protected]e1670582014-08-15 23:05:4145#include "extensions/common/permissions/permission_set.h"
[email protected]39ef0a7c52014-05-11 01:40:0046#include "extensions/common/permissions/permissions_data.h"
47#include "ipc/ipc_message_macros.h"
Karan Bhatiaededa7cf2018-09-05 19:00:3048#include "url/origin.h"
[email protected]39ef0a7c52014-05-11 01:40:0049
50namespace extensions {
51
rdevlin.cronin4a78c48b2016-03-24 00:02:2952namespace {
53
54// The blocked actions that require a page refresh to run.
55const int kRefreshRequiredActionsMask =
56 BLOCKED_ACTION_WEB_REQUEST | BLOCKED_ACTION_SCRIPT_AT_START;
57}
58
rdevlin.cronin8408b4f92016-03-15 19:14:1459ExtensionActionRunner::PendingScript::PendingScript(
rdevlin.cronin8d034e52016-02-02 22:46:3260 UserScript::RunLocation run_location,
61 const base::Closure& permit_script)
62 : run_location(run_location), permit_script(permit_script) {}
63
rdevlin.cronin8408b4f92016-03-15 19:14:1464ExtensionActionRunner::PendingScript::PendingScript(
vmpstrb8aacbe2016-02-26 02:00:4865 const PendingScript& other) = default;
66
rdevlin.cronin8408b4f92016-03-15 19:14:1467ExtensionActionRunner::PendingScript::~PendingScript() {}
rdevlin.cronin8d034e52016-02-02 22:46:3268
rdevlin.cronin8408b4f92016-03-15 19:14:1469ExtensionActionRunner::ExtensionActionRunner(content::WebContents* web_contents)
[email protected]39ef0a7c52014-05-11 01:40:0070 : content::WebContentsObserver(web_contents),
rdevlin.cronin1f877032015-02-20 00:12:4271 num_page_requests_(0),
rdevlin.cronin699ca6ff2014-09-29 23:59:5772 browser_context_(web_contents->GetBrowserContext()),
rdevlin.croninb8dffe562015-02-07 00:58:0173 was_used_on_page_(false),
rdevlin.cronin4a78c48b2016-03-24 00:02:2974 ignore_active_tab_granted_(false),
Evan Stade75872a62019-09-06 21:17:3875 test_observer_(nullptr) {
[email protected]ac02ac52014-05-20 01:11:2676 CHECK(web_contents);
rdevlin.cronin699ca6ff2014-09-29 23:59:5777 extension_registry_observer_.Add(ExtensionRegistry::Get(browser_context_));
[email protected]39ef0a7c52014-05-11 01:40:0078}
79
rdevlin.cronin8408b4f92016-03-15 19:14:1480ExtensionActionRunner::~ExtensionActionRunner() {
[email protected]39ef0a7c52014-05-11 01:40:0081 LogUMA();
82}
83
[email protected]eac223a2014-05-13 17:39:5784// static
rdevlin.cronin8408b4f92016-03-15 19:14:1485ExtensionActionRunner* ExtensionActionRunner::GetForWebContents(
[email protected]eac223a2014-05-13 17:39:5786 content::WebContents* web_contents) {
87 if (!web_contents)
88 return NULL;
89 TabHelper* tab_helper = TabHelper::FromWebContents(web_contents);
rdevlin.cronin8408b4f92016-03-15 19:14:1490 return tab_helper ? tab_helper->extension_action_runner() : NULL;
[email protected]eac223a2014-05-13 17:39:5791}
92
rdevlin.cronin343fd102016-03-17 00:24:5493ExtensionAction::ShowAction ExtensionActionRunner::RunAction(
94 const Extension* extension,
95 bool grant_tab_permissions) {
rdevlin.cronin343fd102016-03-17 00:24:5496 if (grant_tab_permissions) {
rdevlin.cronin4a78c48b2016-03-24 00:02:2997 int blocked = GetBlockedActions(extension);
98 if ((blocked & kRefreshRequiredActionsMask) != 0) {
Karan Bhatiaededa7cf2018-09-05 19:00:3099 ShowBlockedActionBubble(
100 extension,
101 base::Bind(
102 &ExtensionActionRunner::OnBlockedActionBubbleForRunActionClosed,
103 weak_factory_.GetWeakPtr(), extension->id()));
rdevlin.cronin4a78c48b2016-03-24 00:02:29104 return ExtensionAction::ACTION_NONE;
105 }
rdevlin.cronin343fd102016-03-17 00:24:54106 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.cronin4a78c48b2016-03-24 00:02:29112 if (blocked != BLOCKED_ACTION_NONE)
rdevlin.cronin343fd102016-03-17 00:24:54113 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 Astiz2f127c422018-04-05 19:10:27122 int tab_id = SessionTabHelper::IdForTab(web_contents()).id();
rdevlin.cronin343fd102016-03-17 00:24:54123 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_)
isandrk12962022017-05-04 15:27:33130 ->DispatchExtensionActionClicked(*extension_action, web_contents(),
131 extension);
rdevlin.cronin343fd102016-03-17 00:24:54132 return ExtensionAction::ACTION_NONE;
[email protected]11814f52014-05-23 06:50:35133}
134
Karan Bhatiaededa7cf2018-09-05 19:00:30135void ExtensionActionRunner::HandlePageAccessModified(const Extension* extension,
136 PageAccess current_access,
137 PageAccess new_access) {
138 DCHECK_NE(current_access, new_access);
rdevlin.croninacb745922016-02-17 20:37:44139
Karan Bhatiaededa7cf2018-09-05 19:00:30140 // 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.croninacb745922016-02-17 20:37:44145
Karan Bhatiaededa7cf2018-09-05 19:00:30146 int blocked_actions = GetBlockedActions(extension);
rdevlin.croninacb745922016-02-17 20:37:44147
Karan Bhatiaededa7cf2018-09-05 19:00:30148 // 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.cronine9c71122014-08-25 23:47:21164}
165
rdevlin.cronin343fd102016-03-17 00:24:54166void ExtensionActionRunner::OnActiveTabPermissionGranted(
167 const Extension* extension) {
rdevlin.cronin4a78c48b2016-03-24 00:02:29168 if (!ignore_active_tab_granted_ && WantsToRun(extension))
rdevlin.cronin343fd102016-03-17 00:24:54169 RunBlockedActions(extension);
170}
171
rdevlin.cronin8408b4f92016-03-15 19:14:14172void ExtensionActionRunner::OnWebRequestBlocked(const Extension* extension) {
Karan Bhatia1c72c692018-10-04 00:40:51173 bool inserted = false;
174 std::tie(std::ignore, inserted) =
175 web_request_blocked_.insert(extension->id());
176 if (inserted)
177 NotifyChange(extension);
178
Devlin Cronin5c5abd242018-08-30 22:49:50179 if (test_observer_)
180 test_observer_->OnBlockedActionAdded();
rdevlin.cronin8d034e52016-02-02 22:46:32181}
182
rdevlin.cronin8408b4f92016-03-15 19:14:14183int ExtensionActionRunner::GetBlockedActions(const Extension* extension) {
rdevlin.cronin8d034e52016-02-02 22:46:32184 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.cronin8408b4f92016-03-15 19:14:14210bool ExtensionActionRunner::WantsToRun(const Extension* extension) {
rdevlin.cronin8d034e52016-02-02 22:46:32211 return GetBlockedActions(extension) != BLOCKED_ACTION_NONE;
[email protected]e1670582014-08-15 23:05:41212}
213
rdevlin.cronin4a78c48b2016-03-24 00:02:29214void 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 Cronin3e532b82018-05-03 21:27:19222PermissionsData::PageAccess
rdevlin.cronin8408b4f92016-03-15 19:14:14223ExtensionActionRunner::RequiresUserConsentForScriptInjection(
[email protected]23a85362014-07-07 23:26:19224 const Extension* extension,
225 UserScript::InjectionType type) {
226 CHECK(extension);
227
[email protected]23a85362014-07-07 23:26:19228 // Allow the extension if it's been explicitly granted permission.
229 if (permitted_extensions_.count(extension->id()) > 0)
Devlin Cronin3e532b82018-05-03 21:27:19230 return PermissionsData::PageAccess::kAllowed;
[email protected]23a85362014-07-07 23:26:19231
232 GURL url = web_contents()->GetVisibleURL();
Mikel Astiz2f127c422018-04-05 19:10:27233 int tab_id = SessionTabHelper::IdForTab(web_contents()).id();
[email protected]23a85362014-07-07 23:26:19234 switch (type) {
235 case UserScript::CONTENT_SCRIPT:
Devlin Cronin5cb437832018-05-17 20:14:41236 return extension->permissions_data()->GetContentScriptAccess(url, tab_id,
237 nullptr);
[email protected]23a85362014-07-07 23:26:19238 case UserScript::PROGRAMMATIC_SCRIPT:
Devlin Cronin5cb437832018-05-17 20:14:41239 return extension->permissions_data()->GetPageAccess(url, tab_id, nullptr);
[email protected]23a85362014-07-07 23:26:19240 }
241
242 NOTREACHED();
Devlin Cronin3e532b82018-05-03 21:27:19243 return PermissionsData::PageAccess::kDenied;
[email protected]23a85362014-07-07 23:26:19244}
245
rdevlin.cronin8408b4f92016-03-15 19:14:14246void ExtensionActionRunner::RequestScriptInjection(
[email protected]23a85362014-07-07 23:26:19247 const Extension* extension,
rdevlin.cronin8d034e52016-02-02 22:46:32248 UserScript::RunLocation run_location,
[email protected]23a85362014-07-07 23:26:19249 const base::Closure& callback) {
250 CHECK(extension);
rdevlin.cronin8d034e52016-02-02 22:46:32251 PendingScriptList& list = pending_scripts_[extension->id()];
252 list.push_back(PendingScript(run_location, callback));
[email protected]23a85362014-07-07 23:26:19253
rdevlin.cronin699ca6ff2014-09-29 23:59:57254 // 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.croninb8dffe562015-02-07 00:58:01258
259 was_used_on_page_ = true;
Devlin Cronin5c5abd242018-08-30 22:49:50260
261 if (test_observer_)
262 test_observer_->OnBlockedActionAdded();
[email protected]23a85362014-07-07 23:26:19263}
264
rdevlin.cronin8408b4f92016-03-15 19:14:14265void ExtensionActionRunner::RunPendingScriptsForExtension(
[email protected]11814f52014-05-23 06:50:35266 const Extension* extension) {
[email protected]ac02ac52014-05-20 01:11:26267 DCHECK(extension);
[email protected]ac02ac52014-05-20 01:11:26268
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]11814f52014-05-23 06:50:35274 return;
[email protected]ac02ac52014-05-20 01:11:26275
[email protected]ac02ac52014-05-20 01:11:26276 // 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]23a85362014-07-07 23:26:19280
jdoerrie13cd648c82018-10-02 21:21:02281 auto iter = pending_scripts_.find(extension->id());
rdevlin.cronin8d034e52016-02-02 22:46:32282 if (iter == pending_scripts_.end())
[email protected]23a85362014-07-07 23:26:19283 return;
284
rdevlin.cronin8d034e52016-02-02 22:46:32285 PendingScriptList scripts;
286 iter->second.swap(scripts);
287 pending_scripts_.erase(extension->id());
[email protected]ac02ac52014-05-20 01:11:26288
289 // Run all pending injections for the given extension.
rdevlin.cronin8d034e52016-02-02 22:46:32290 for (PendingScript& pending_script : scripts)
291 pending_script.permit_script.Run();
[email protected]ac02ac52014-05-20 01:11:26292}
293
rdevlin.cronin8408b4f92016-03-15 19:14:14294void ExtensionActionRunner::OnRequestScriptInjectionPermission(
[email protected]ac02ac52014-05-20 01:11:26295 const std::string& extension_id,
[email protected]23a85362014-07-07 23:26:19296 UserScript::InjectionType script_type,
rdevlin.cronin8d034e52016-02-02 22:46:32297 UserScript::RunLocation run_location,
avia2f4804a2015-12-24 23:11:13298 int64_t request_id) {
[email protected]fdd28372014-08-21 02:27:26299 if (!crx_file::id_util::IdIsValid(extension_id)) {
[email protected]ac02ac52014-05-20 01:11:26300 NOTREACHED() << "'" << extension_id << "' is not a valid id.";
301 return;
302 }
303
rdevlin.cronin8408b4f92016-03-15 19:14:14304 const Extension* extension = ExtensionRegistry::Get(browser_context_)
305 ->enabled_extensions()
306 .GetByID(extension_id);
[email protected]ac02ac52014-05-20 01:11:26307 // 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.cronin1f877032015-02-20 00:12:42312 ++num_page_requests_;
313
[email protected]23a85362014-07-07 23:26:19314 switch (RequiresUserConsentForScriptInjection(extension, script_type)) {
Devlin Cronin3e532b82018-05-03 21:27:19315 case PermissionsData::PageAccess::kAllowed:
[email protected]23a85362014-07-07 23:26:19316 PermitScriptInjection(request_id);
317 break;
Devlin Cronin3e532b82018-05-03 21:27:19318 case PermissionsData::PageAccess::kWithheld:
[email protected]23a85362014-07-07 23:26:19319 // This base::Unretained() is safe, because the callback is only invoked
320 // by this object.
321 RequestScriptInjection(
rdevlin.cronin8d034e52016-02-02 22:46:32322 extension, run_location,
rdevlin.cronin8408b4f92016-03-15 19:14:14323 base::Bind(&ExtensionActionRunner::PermitScriptInjection,
rdevlin.cronin8d034e52016-02-02 22:46:32324 base::Unretained(this), request_id));
[email protected]23a85362014-07-07 23:26:19325 break;
Devlin Cronin3e532b82018-05-03 21:27:19326 case PermissionsData::PageAccess::kDenied:
[email protected]23a85362014-07-07 23:26:19327 // 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]0d8d6972014-06-03 22:41:02331 }
332}
333
rdevlin.cronin8408b4f92016-03-15 19:14:14334void ExtensionActionRunner::PermitScriptInjection(int64_t request_id) {
[email protected]23a85362014-07-07 23:26:19335 // 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.cronin4bb32d72015-06-02 21:55:01337 // 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]39ef0a7c52014-05-11 01:40:00343}
344
rdevlin.cronin8408b4f92016-03-15 19:14:14345void ExtensionActionRunner::NotifyChange(const Extension* extension) {
rdevlin.cronin699ca6ff2014-09-29 23:59:57346 ExtensionActionAPI* extension_action_api =
347 ExtensionActionAPI::Get(browser_context_);
348 ExtensionAction* extension_action =
rdevlin.cronin8408b4f92016-03-15 19:14:14349 ExtensionActionManager::Get(browser_context_)
350 ->GetExtensionAction(*extension);
rdevlin.cronin699ca6ff2014-09-29 23:59:57351 // If the extension has an action, we need to notify that it's updated.
352 if (extension_action) {
rdevlin.cronin8408b4f92016-03-15 19:14:14353 extension_action_api->NotifyChange(extension_action, web_contents(),
354 browser_context_);
rdevlin.cronin699ca6ff2014-09-29 23:59:57355 }
rdevlin.cronin699ca6ff2014-09-29 23:59:57356}
357
rdevlin.cronin8408b4f92016-03-15 19:14:14358void ExtensionActionRunner::LogUMA() const {
rdevlin.croninb8dffe562015-02-07 00:58:01359 // 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]ac02ac52014-05-20 01:11:26362 UMA_HISTOGRAM_COUNTS_100(
363 "Extensions.ActiveScriptController.PermittedExtensions",
364 permitted_extensions_.size());
365 UMA_HISTOGRAM_COUNTS_100(
366 "Extensions.ActiveScriptController.DeniedExtensions",
rdevlin.cronin8d034e52016-02-02 22:46:32367 pending_scripts_.size());
[email protected]ac02ac52014-05-20 01:11:26368 }
[email protected]39ef0a7c52014-05-11 01:40:00369}
370
rdevlin.cronin4a78c48b2016-03-24 00:02:29371void ExtensionActionRunner::ShowBlockedActionBubble(
Karan Bhatiaededa7cf2018-09-05 19:00:30372 const Extension* extension,
373 const base::Callback<void(ToolbarActionsBarBubbleDelegate::CloseAction)>&
374 callback) {
rdevlin.cronin4a78c48b2016-03-24 00:02:29375 Browser* browser = chrome::FindBrowserWithWebContents(web_contents());
Peter Boströmee16999c2019-06-14 21:03:23376 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.cronin4a78c48b2016-03-24 00:02:29388 }
389}
390
Karan Bhatiaededa7cf2018-09-05 19:00:30391void ExtensionActionRunner::OnBlockedActionBubbleForRunActionClosed(
rdevlin.cronin4a78c48b2016-03-24 00:02:29392 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 }
toyoshim6142d96f2016-12-19 09:07:25410 web_contents()->GetController().Reload(content::ReloadType::NORMAL, false);
rdevlin.cronin4a78c48b2016-03-24 00:02:29411 }
412}
413
Karan Bhatiaededa7cf2018-09-05 19:00:30414void 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
442void 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 Cronind4d8bfc2018-09-13 17:39:39453 // Note: SetWithholdHostPermissions() is a no-op if host permissions are
454 // already being withheld.
455 modifier.SetWithholdHostPermissions(true);
Karan Bhatiaededa7cf2018-09-05 19:00:30456 if (modifier.HasGrantedHostPermission(url))
457 modifier.RemoveGrantedHostPermission(url);
458 break;
459 case PageAccess::RUN_ON_SITE:
Devlin Cronind4d8bfc2018-09-13 17:39:39460 // Note: SetWithholdHostPermissions() is a no-op if host permissions are
461 // already being withheld.
462 modifier.SetWithholdHostPermissions(true);
Karan Bhatiaededa7cf2018-09-05 19:00:30463 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
472void ExtensionActionRunner::RunBlockedActions(const Extension* extension) {
Jan Wilken Dörrieade79222019-06-06 19:01:12473 DCHECK(base::Contains(pending_scripts_, extension->id()) ||
Karan Bhatiaededa7cf2018-09-05 19:00:30474 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.cronin8408b4f92016-03-15 19:14:14492bool ExtensionActionRunner::OnMessageReceived(
rdevlin.cronin45dca7f2015-06-08 19:47:03493 const IPC::Message& message,
494 content::RenderFrameHost* render_frame_host) {
rdevlin.cronin6e7e5edc2014-08-29 16:23:23495 bool handled = true;
rdevlin.cronin8408b4f92016-03-15 19:14:14496 IPC_BEGIN_MESSAGE_MAP(ExtensionActionRunner, message)
rdevlin.cronin6e7e5edc2014-08-29 16:23:23497 IPC_MESSAGE_HANDLER(ExtensionHostMsg_RequestScriptInjectionPermission,
498 OnRequestScriptInjectionPermission)
499 IPC_MESSAGE_UNHANDLED(handled = false)
500 IPC_END_MESSAGE_MAP()
501 return handled;
502}
503
jam29737c42017-02-01 16:26:08504void ExtensionActionRunner::DidFinishNavigation(
505 content::NavigationHandle* navigation_handle) {
506 if (!navigation_handle->IsInMainFrame() ||
507 !navigation_handle->HasCommitted() ||
eugenebuta11672fb2017-03-07 17:13:51508 navigation_handle->IsSameDocument()) {
rdevlin.cronin50942232014-08-27 17:40:56509 return;
jam29737c42017-02-01 16:26:08510 }
rdevlin.cronin50942232014-08-27 17:40:56511
512 LogUMA();
rdevlin.cronin1f877032015-02-20 00:12:42513 num_page_requests_ = 0;
rdevlin.cronin50942232014-08-27 17:40:56514 permitted_extensions_.clear();
rdevlin.cronin8d034e52016-02-02 22:46:32515 pending_scripts_.clear();
516 web_request_blocked_.clear();
rdevlin.croninb8dffe562015-02-07 00:58:01517 was_used_on_page_ = false;
rdevlin.cronin4a78c48b2016-03-24 00:02:29518 weak_factory_.InvalidateWeakPtrs();
Devlin Cronin1d6343242018-06-05 02:16:41519
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 Jiangcaf6c3f2019-08-13 21:49:50525
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 Cronin1d6343242018-06-05 02:16:41537}
538
539void ExtensionActionRunner::WebContentsDestroyed() {
540 ExtensionActionAPI::Get(browser_context_)
541 ->ClearAllValuesForTab(web_contents());
Kelvin Jiangcaf6c3f2019-08-13 21:49:50542
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.cronin50942232014-08-27 17:40:56554}
555
rdevlin.cronin8408b4f92016-03-15 19:14:14556void ExtensionActionRunner::OnExtensionUnloaded(
rdevlin.cronin6e7e5edc2014-08-29 16:23:23557 content::BrowserContext* browser_context,
558 const Extension* extension,
limasdf0deef2042017-05-03 19:17:17559 UnloadedExtensionReason reason) {
jdoerrie13cd648c82018-10-02 21:21:02560 auto iter = pending_scripts_.find(extension->id());
rdevlin.cronin8d034e52016-02-02 22:46:32561 if (iter != pending_scripts_.end()) {
562 pending_scripts_.erase(iter);
Devlin Cronine5fc75552019-04-25 02:19:06563 NotifyChange(extension);
rdevlin.cronin6e7e5edc2014-08-29 16:23:23564 }
565}
566
[email protected]39ef0a7c52014-05-11 01:40:00567} // namespace extensions