blob: 80c7fbdc5e93c015b6465199dcad964694ca4a81 [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
5#include "chrome/browser/extensions/active_script_controller.h"
6
[email protected]ac02ac52014-05-20 01:11:267#include "base/bind.h"
8#include "base/bind_helpers.h"
9#include "base/memory/scoped_ptr.h"
[email protected]39ef0a7c52014-05-11 01:40:0010#include "base/metrics/histogram.h"
11#include "base/stl_util.h"
[email protected]78cd68e2014-05-22 20:33:5212#include "chrome/browser/extensions/active_tab_permission_granter.h"
rdevlin.croninaeffd182014-08-26 17:04:0013#include "chrome/browser/extensions/api/extension_action/extension_action_api.h"
[email protected]39ef0a7c52014-05-11 01:40:0014#include "chrome/browser/extensions/extension_action.h"
[email protected]6a24a0392014-08-12 21:31:3315#include "chrome/browser/extensions/extension_action_manager.h"
[email protected]b33c8c22014-05-29 19:51:0816#include "chrome/browser/extensions/extension_util.h"
[email protected]e1670582014-08-15 23:05:4117#include "chrome/browser/extensions/location_bar_controller.h"
18#include "chrome/browser/extensions/permissions_updater.h"
[email protected]eac223a2014-05-13 17:39:5719#include "chrome/browser/extensions/tab_helper.h"
[email protected]6a24a0392014-08-12 21:31:3320#include "chrome/browser/profiles/profile.h"
[email protected]e3f90c602014-08-18 12:41:5921#include "chrome/browser/sessions/session_tab_helper.h"
[email protected]39ef0a7c52014-05-11 01:40:0022#include "chrome/common/extensions/api/extension_action/action_info.h"
[email protected]fdd28372014-08-21 02:27:2623#include "components/crx_file/id_util.h"
[email protected]39ef0a7c52014-05-11 01:40:0024#include "content/public/browser/navigation_controller.h"
rdevlin.cronin50942232014-08-27 17:40:5625#include "content/public/browser/navigation_details.h"
[email protected]39ef0a7c52014-05-11 01:40:0026#include "content/public/browser/navigation_entry.h"
[email protected]ac02ac52014-05-20 01:11:2627#include "content/public/browser/render_view_host.h"
[email protected]39ef0a7c52014-05-11 01:40:0028#include "content/public/browser/web_contents.h"
29#include "extensions/browser/extension_registry.h"
30#include "extensions/common/extension.h"
31#include "extensions/common/extension_messages.h"
32#include "extensions/common/extension_set.h"
33#include "extensions/common/feature_switch.h"
[email protected]23a85362014-07-07 23:26:1934#include "extensions/common/manifest.h"
[email protected]e1670582014-08-15 23:05:4135#include "extensions/common/permissions/permission_set.h"
[email protected]39ef0a7c52014-05-11 01:40:0036#include "extensions/common/permissions/permissions_data.h"
37#include "ipc/ipc_message_macros.h"
38
39namespace extensions {
40
[email protected]23a85362014-07-07 23:26:1941namespace {
42
43// Returns true if the extension should be regarded as a "permitted" extension
44// for the case of metrics. We need this because we only actually withhold
45// permissions if the switch is enabled, but want to record metrics in all
46// cases.
47// "ExtensionWouldHaveHadHostPermissionsWithheldIfSwitchWasOn()" would be
48// more accurate, but too long.
49bool ShouldRecordExtension(const Extension* extension) {
50 return extension->ShouldDisplayInExtensionSettings() &&
51 !Manifest::IsPolicyLocation(extension->location()) &&
52 !Manifest::IsComponentLocation(extension->location()) &&
53 !PermissionsData::CanExecuteScriptEverywhere(extension) &&
54 extension->permissions_data()
55 ->active_permissions()
56 ->ShouldWarnAllHosts();
57}
58
59} // namespace
60
[email protected]39ef0a7c52014-05-11 01:40:0061ActiveScriptController::ActiveScriptController(
62 content::WebContents* web_contents)
63 : content::WebContentsObserver(web_contents),
64 enabled_(FeatureSwitch::scripts_require_action()->IsEnabled()) {
[email protected]ac02ac52014-05-20 01:11:2665 CHECK(web_contents);
[email protected]39ef0a7c52014-05-11 01:40:0066}
67
68ActiveScriptController::~ActiveScriptController() {
69 LogUMA();
70}
71
[email protected]eac223a2014-05-13 17:39:5772// static
73ActiveScriptController* ActiveScriptController::GetForWebContents(
74 content::WebContents* web_contents) {
75 if (!web_contents)
76 return NULL;
77 TabHelper* tab_helper = TabHelper::FromWebContents(web_contents);
78 if (!tab_helper)
79 return NULL;
80 LocationBarController* location_bar_controller =
81 tab_helper->location_bar_controller();
82 // This should never be NULL.
83 DCHECK(location_bar_controller);
84 return location_bar_controller->active_script_controller();
85}
86
[email protected]11814f52014-05-23 06:50:3587void ActiveScriptController::OnActiveTabPermissionGranted(
88 const Extension* extension) {
89 RunPendingForExtension(extension);
90}
91
[email protected]39ef0a7c52014-05-11 01:40:0092void ActiveScriptController::OnAdInjectionDetected(
[email protected]0d8d6972014-06-03 22:41:0293 const std::set<std::string>& ad_injectors) {
[email protected]c0a3cd92014-05-22 12:23:4294 // We're only interested in data if there are ad injectors detected.
95 if (ad_injectors.empty())
96 return;
97
[email protected]39ef0a7c52014-05-11 01:40:0098 size_t num_preventable_ad_injectors =
99 base::STLSetIntersection<std::set<std::string> >(
[email protected]ac02ac52014-05-20 01:11:26100 ad_injectors, permitted_extensions_).size();
[email protected]39ef0a7c52014-05-11 01:40:00101
102 UMA_HISTOGRAM_COUNTS_100(
103 "Extensions.ActiveScriptController.PreventableAdInjectors",
104 num_preventable_ad_injectors);
105 UMA_HISTOGRAM_COUNTS_100(
[email protected]ac02ac52014-05-20 01:11:26106 "Extensions.ActiveScriptController.UnpreventableAdInjectors",
[email protected]39ef0a7c52014-05-11 01:40:00107 ad_injectors.size() - num_preventable_ad_injectors);
108}
109
[email protected]e1670582014-08-15 23:05:41110void ActiveScriptController::AlwaysRunOnVisibleOrigin(
111 const Extension* extension) {
112 const GURL& url = web_contents()->GetVisibleURL();
113 URLPatternSet new_explicit_hosts;
114 URLPatternSet new_scriptable_hosts;
115
116 scoped_refptr<const PermissionSet> withheld_permissions =
117 extension->permissions_data()->withheld_permissions();
118 if (withheld_permissions->explicit_hosts().MatchesURL(url)) {
119 new_explicit_hosts.AddOrigin(UserScript::ValidUserScriptSchemes(),
120 url.GetOrigin());
121 }
122 if (withheld_permissions->scriptable_hosts().MatchesURL(url)) {
123 new_scriptable_hosts.AddOrigin(UserScript::ValidUserScriptSchemes(),
124 url.GetOrigin());
125 }
126
127 scoped_refptr<PermissionSet> new_permissions =
128 new PermissionSet(APIPermissionSet(),
129 ManifestPermissionSet(),
130 new_explicit_hosts,
131 new_scriptable_hosts);
132
133 // Update permissions for the session. This adds |new_permissions| to active
134 // permissions and granted permissions.
135 // TODO(devlin): Make sure that the permission is removed from
136 // withheld_permissions if appropriate.
137 PermissionsUpdater(web_contents()->GetBrowserContext())
138 .AddPermissions(extension, new_permissions.get());
139
140 // Allow current tab to run injection.
141 OnClicked(extension);
142}
143
rdevlin.cronine9c71122014-08-25 23:47:21144void ActiveScriptController::OnClicked(const Extension* extension) {
145 DCHECK(ContainsKey(pending_requests_, extension->id()));
146 RunPendingForExtension(extension);
147}
148
[email protected]e1670582014-08-15 23:05:41149bool ActiveScriptController::HasActiveScriptAction(const Extension* extension) {
[email protected]903cf842014-08-22 01:15:28150 return enabled_ && pending_requests_.count(extension->id()) > 0;
[email protected]e1670582014-08-15 23:05:41151}
152
[email protected]39ef0a7c52014-05-11 01:40:00153ExtensionAction* ActiveScriptController::GetActionForExtension(
154 const Extension* extension) {
[email protected]ac02ac52014-05-20 01:11:26155 if (!enabled_ || pending_requests_.count(extension->id()) == 0)
[email protected]39ef0a7c52014-05-11 01:40:00156 return NULL; // No action for this extension.
157
158 ActiveScriptMap::iterator existing =
159 active_script_actions_.find(extension->id());
160 if (existing != active_script_actions_.end())
161 return existing->second.get();
162
[email protected]6a24a0392014-08-12 21:31:33163 linked_ptr<ExtensionAction> action(ExtensionActionManager::Get(
164 Profile::FromBrowserContext(web_contents()->GetBrowserContext()))
165 ->GetBestFitAction(*extension, ActionInfo::TYPE_PAGE).release());
[email protected]39ef0a7c52014-05-11 01:40:00166 action->SetIsVisible(ExtensionAction::kDefaultTabId, true);
167
[email protected]39ef0a7c52014-05-11 01:40:00168 active_script_actions_[extension->id()] = action;
169 return action.get();
170}
171
[email protected]0d8d6972014-06-03 22:41:02172void ActiveScriptController::OnExtensionUnloaded(const Extension* extension) {
173 PendingRequestMap::iterator iter = pending_requests_.find(extension->id());
174 if (iter != pending_requests_.end())
175 pending_requests_.erase(iter);
176}
177
[email protected]23a85362014-07-07 23:26:19178PermissionsData::AccessType
179ActiveScriptController::RequiresUserConsentForScriptInjection(
180 const Extension* extension,
181 UserScript::InjectionType type) {
182 CHECK(extension);
183
184 // If the feature is not enabled, we automatically allow all extensions to
185 // run scripts.
186 if (!enabled_)
187 permitted_extensions_.insert(extension->id());
188
189 // Allow the extension if it's been explicitly granted permission.
190 if (permitted_extensions_.count(extension->id()) > 0)
191 return PermissionsData::ACCESS_ALLOWED;
192
193 GURL url = web_contents()->GetVisibleURL();
[email protected]e3f90c602014-08-18 12:41:59194 int tab_id = SessionTabHelper::IdForTab(web_contents());
[email protected]23a85362014-07-07 23:26:19195 switch (type) {
196 case UserScript::CONTENT_SCRIPT:
197 return extension->permissions_data()->GetContentScriptAccess(
198 extension, url, url, tab_id, -1, NULL);
199 case UserScript::PROGRAMMATIC_SCRIPT:
200 return extension->permissions_data()->GetPageAccess(
201 extension, url, url, tab_id, -1, NULL);
202 }
203
204 NOTREACHED();
205 return PermissionsData::ACCESS_DENIED;
206}
207
208void ActiveScriptController::RequestScriptInjection(
209 const Extension* extension,
210 const base::Closure& callback) {
211 CHECK(extension);
212 PendingRequestList& list = pending_requests_[extension->id()];
213 list.push_back(callback);
214
215 // If this was the first entry, notify the location bar that there's a new
216 // icon.
rdevlin.croninaeffd182014-08-26 17:04:00217 if (list.size() == 1u) {
218 ExtensionActionAPI::Get(web_contents()->GetBrowserContext())->
219 NotifyPageActionsChanged(web_contents());
220 }
[email protected]23a85362014-07-07 23:26:19221}
222
[email protected]11814f52014-05-23 06:50:35223void ActiveScriptController::RunPendingForExtension(
224 const Extension* extension) {
[email protected]ac02ac52014-05-20 01:11:26225 DCHECK(extension);
[email protected]ac02ac52014-05-20 01:11:26226
227 content::NavigationEntry* visible_entry =
228 web_contents()->GetController().GetVisibleEntry();
229 // Refuse to run if there's no visible entry, because we have no idea of
230 // determining if it's the proper page. This should rarely, if ever, happen.
231 if (!visible_entry)
[email protected]11814f52014-05-23 06:50:35232 return;
[email protected]ac02ac52014-05-20 01:11:26233
[email protected]ac02ac52014-05-20 01:11:26234 // We add this to the list of permitted extensions and erase pending entries
235 // *before* running them to guard against the crazy case where running the
236 // callbacks adds more entries.
237 permitted_extensions_.insert(extension->id());
[email protected]23a85362014-07-07 23:26:19238
239 PendingRequestMap::iterator iter = pending_requests_.find(extension->id());
240 if (iter == pending_requests_.end())
241 return;
242
[email protected]ac02ac52014-05-20 01:11:26243 PendingRequestList requests;
244 iter->second.swap(requests);
245 pending_requests_.erase(extension->id());
246
[email protected]78cd68e2014-05-22 20:33:52247 // Clicking to run the extension counts as granting it permission to run on
248 // the given tab.
[email protected]11814f52014-05-23 06:50:35249 // The extension may already have active tab at this point, but granting
250 // it twice is essentially a no-op.
[email protected]78cd68e2014-05-22 20:33:52251 TabHelper::FromWebContents(web_contents())->
252 active_tab_permission_granter()->GrantIfRequested(extension);
253
[email protected]ac02ac52014-05-20 01:11:26254 // Run all pending injections for the given extension.
255 for (PendingRequestList::iterator request = requests.begin();
256 request != requests.end();
257 ++request) {
[email protected]d2056002014-07-03 06:18:06258 request->Run();
[email protected]ac02ac52014-05-20 01:11:26259 }
260
261 // Inform the location bar that the action is now gone.
rdevlin.croninaeffd182014-08-26 17:04:00262 ExtensionActionAPI::Get(web_contents()->GetBrowserContext())->
263 NotifyPageActionsChanged(web_contents());
[email protected]ac02ac52014-05-20 01:11:26264}
265
[email protected]ac2f89372014-06-23 21:44:25266void ActiveScriptController::OnRequestScriptInjectionPermission(
[email protected]ac02ac52014-05-20 01:11:26267 const std::string& extension_id,
[email protected]23a85362014-07-07 23:26:19268 UserScript::InjectionType script_type,
[email protected]d2056002014-07-03 06:18:06269 int64 request_id) {
[email protected]fdd28372014-08-21 02:27:26270 if (!crx_file::id_util::IdIsValid(extension_id)) {
[email protected]ac02ac52014-05-20 01:11:26271 NOTREACHED() << "'" << extension_id << "' is not a valid id.";
272 return;
273 }
274
275 const Extension* extension =
276 ExtensionRegistry::Get(web_contents()->GetBrowserContext())
277 ->enabled_extensions().GetByID(extension_id);
278 // We shouldn't allow extensions which are no longer enabled to run any
279 // scripts. Ignore the request.
280 if (!extension)
281 return;
282
[email protected]0d8d6972014-06-03 22:41:02283 // If the request id is -1, that signals that the content script has already
284 // ran (because this feature is not enabled). Add the extension to the list of
285 // permitted extensions (for metrics), and return immediately.
286 if (request_id == -1) {
[email protected]23a85362014-07-07 23:26:19287 if (ShouldRecordExtension(extension)) {
288 DCHECK(!enabled_);
289 permitted_extensions_.insert(extension->id());
290 }
[email protected]0d8d6972014-06-03 22:41:02291 return;
292 }
293
[email protected]23a85362014-07-07 23:26:19294 switch (RequiresUserConsentForScriptInjection(extension, script_type)) {
295 case PermissionsData::ACCESS_ALLOWED:
296 PermitScriptInjection(request_id);
297 break;
298 case PermissionsData::ACCESS_WITHHELD:
299 // This base::Unretained() is safe, because the callback is only invoked
300 // by this object.
301 RequestScriptInjection(
302 extension,
303 base::Bind(&ActiveScriptController::PermitScriptInjection,
304 base::Unretained(this),
305 request_id));
306 break;
307 case PermissionsData::ACCESS_DENIED:
308 // We should usually only get a "deny access" if the page changed (as the
309 // renderer wouldn't have requested permission if the answer was always
310 // "no"). Just let the request fizzle and die.
311 break;
[email protected]0d8d6972014-06-03 22:41:02312 }
313}
314
[email protected]d2056002014-07-03 06:18:06315void ActiveScriptController::PermitScriptInjection(int64 request_id) {
[email protected]23a85362014-07-07 23:26:19316 // This only sends the response to the renderer - the process of adding the
317 // extension to the list of |permitted_extensions_| is done elsewhere.
[email protected]0d8d6972014-06-03 22:41:02318 content::RenderViewHost* render_view_host =
319 web_contents()->GetRenderViewHost();
320 if (render_view_host) {
[email protected]ac2f89372014-06-23 21:44:25321 render_view_host->Send(new ExtensionMsg_PermitScriptInjection(
322 render_view_host->GetRoutingID(), request_id));
[email protected]0d8d6972014-06-03 22:41:02323 }
[email protected]39ef0a7c52014-05-11 01:40:00324}
325
326bool ActiveScriptController::OnMessageReceived(const IPC::Message& message) {
327 bool handled = true;
328 IPC_BEGIN_MESSAGE_MAP(ActiveScriptController, message)
[email protected]ac2f89372014-06-23 21:44:25329 IPC_MESSAGE_HANDLER(ExtensionHostMsg_RequestScriptInjectionPermission,
330 OnRequestScriptInjectionPermission)
[email protected]39ef0a7c52014-05-11 01:40:00331 IPC_MESSAGE_UNHANDLED(handled = false)
332 IPC_END_MESSAGE_MAP()
333 return handled;
334}
335
[email protected]39ef0a7c52014-05-11 01:40:00336void ActiveScriptController::LogUMA() const {
337 UMA_HISTOGRAM_COUNTS_100(
338 "Extensions.ActiveScriptController.ShownActiveScriptsOnPage",
[email protected]ac02ac52014-05-20 01:11:26339 pending_requests_.size());
340
341 // We only log the permitted extensions metric if the feature is enabled,
342 // because otherwise the data will be boring (100% allowed).
343 if (enabled_) {
344 UMA_HISTOGRAM_COUNTS_100(
345 "Extensions.ActiveScriptController.PermittedExtensions",
346 permitted_extensions_.size());
347 UMA_HISTOGRAM_COUNTS_100(
348 "Extensions.ActiveScriptController.DeniedExtensions",
349 pending_requests_.size());
350 }
[email protected]39ef0a7c52014-05-11 01:40:00351}
352
rdevlin.cronin50942232014-08-27 17:40:56353void ActiveScriptController::DidNavigateMainFrame(
354 const content::LoadCommittedDetails& details,
355 const content::FrameNavigateParams& params) {
356 if (details.is_in_page)
357 return;
358
359 LogUMA();
360 permitted_extensions_.clear();
361 pending_requests_.clear();
362}
363
[email protected]39ef0a7c52014-05-11 01:40:00364} // namespace extensions