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.
5 #include "chrome/browser/extensions/active_script_controller.h"
8 #include "base/bind_helpers.h"
9 #include "base/memory/scoped_ptr.h"
10 #include "base/metrics/histogram.h"
11 #include "base/stl_util.h"
12 #include "chrome/browser/extensions/active_tab_permission_granter.h"
13 #include "chrome/browser/extensions/api/extension_action/extension_action_api.h"
14 #include "chrome/browser/extensions/extension_action.h"
15 #include "chrome/browser/extensions/extension_action_manager.h"
16 #include "chrome/browser/extensions/permissions_updater.h"
17 #include "chrome/browser/extensions/tab_helper.h"
18 #include "chrome/browser/profiles/profile.h"
19 #include "chrome/browser/sessions/session_tab_helper.h"
20 #include "chrome/common/extensions/api/extension_action/action_info.h"
21 #include "components/crx_file/id_util.h"
22 #include "content/public/browser/navigation_controller.h"
23 #include "content/public/browser/navigation_details.h"
24 #include "content/public/browser/navigation_entry.h"
25 #include "content/public/browser/render_view_host.h"
26 #include "content/public/browser/web_contents.h"
27 #include "extensions/browser/extension_registry.h"
28 #include "extensions/common/extension.h"
29 #include "extensions/common/extension_messages.h"
30 #include "extensions/common/extension_set.h"
31 #include "extensions/common/feature_switch.h"
32 #include "extensions/common/manifest.h"
33 #include "extensions/common/permissions/permission_set.h"
34 #include "extensions/common/permissions/permissions_data.h"
35 #include "ipc/ipc_message_macros.h"
37 namespace extensions
{
39 ActiveScriptController::ActiveScriptController(
40 content::WebContents
* web_contents
)
41 : content::WebContentsObserver(web_contents
),
42 num_page_requests_(0),
43 browser_context_(web_contents
->GetBrowserContext()),
44 was_used_on_page_(false),
45 extension_registry_observer_(this) {
47 extension_registry_observer_
.Add(ExtensionRegistry::Get(browser_context_
));
50 ActiveScriptController::~ActiveScriptController() {
55 ActiveScriptController
* ActiveScriptController::GetForWebContents(
56 content::WebContents
* web_contents
) {
59 TabHelper
* tab_helper
= TabHelper::FromWebContents(web_contents
);
60 return tab_helper
? tab_helper
->active_script_controller() : NULL
;
63 void ActiveScriptController::OnActiveTabPermissionGranted(
64 const Extension
* extension
) {
65 RunPendingForExtension(extension
);
68 void ActiveScriptController::OnAdInjectionDetected(
69 const std::set
<std::string
>& ad_injectors
) {
70 // We're only interested in data if there are ad injectors detected.
71 if (ad_injectors
.empty())
74 size_t num_preventable_ad_injectors
=
75 base::STLSetIntersection
<std::set
<std::string
> >(
76 ad_injectors
, permitted_extensions_
).size();
78 UMA_HISTOGRAM_COUNTS_100(
79 "Extensions.ActiveScriptController.PreventableAdInjectors",
80 num_preventable_ad_injectors
);
81 UMA_HISTOGRAM_COUNTS_100(
82 "Extensions.ActiveScriptController.UnpreventableAdInjectors",
83 ad_injectors
.size() - num_preventable_ad_injectors
);
86 void ActiveScriptController::AlwaysRunOnVisibleOrigin(
87 const Extension
* extension
) {
88 const GURL
& url
= web_contents()->GetVisibleURL();
89 URLPatternSet new_explicit_hosts
;
90 URLPatternSet new_scriptable_hosts
;
92 scoped_refptr
<const PermissionSet
> withheld_permissions
=
93 extension
->permissions_data()->withheld_permissions();
94 if (withheld_permissions
->explicit_hosts().MatchesURL(url
)) {
95 new_explicit_hosts
.AddOrigin(UserScript::ValidUserScriptSchemes(),
98 if (withheld_permissions
->scriptable_hosts().MatchesURL(url
)) {
99 new_scriptable_hosts
.AddOrigin(UserScript::ValidUserScriptSchemes(),
103 scoped_refptr
<PermissionSet
> new_permissions
=
104 new PermissionSet(APIPermissionSet(),
105 ManifestPermissionSet(),
107 new_scriptable_hosts
);
109 // Update permissions for the session. This adds |new_permissions| to active
110 // permissions and granted permissions.
111 // TODO(devlin): Make sure that the permission is removed from
112 // withheld_permissions if appropriate.
113 PermissionsUpdater(browser_context_
).AddPermissions(extension
,
114 new_permissions
.get());
116 // Allow current tab to run injection.
117 OnClicked(extension
);
120 void ActiveScriptController::OnClicked(const Extension
* extension
) {
121 DCHECK(ContainsKey(pending_requests_
, extension
->id()));
122 RunPendingForExtension(extension
);
125 bool ActiveScriptController::WantsToRun(const Extension
* extension
) {
126 return pending_requests_
.count(extension
->id()) > 0;
129 PermissionsData::AccessType
130 ActiveScriptController::RequiresUserConsentForScriptInjection(
131 const Extension
* extension
,
132 UserScript::InjectionType type
) {
135 // Allow the extension if it's been explicitly granted permission.
136 if (permitted_extensions_
.count(extension
->id()) > 0)
137 return PermissionsData::ACCESS_ALLOWED
;
139 GURL url
= web_contents()->GetVisibleURL();
140 int tab_id
= SessionTabHelper::IdForTab(web_contents());
142 case UserScript::CONTENT_SCRIPT
:
143 return extension
->permissions_data()->GetContentScriptAccess(
144 extension
, url
, tab_id
, -1, NULL
);
145 case UserScript::PROGRAMMATIC_SCRIPT
:
146 return extension
->permissions_data()->GetPageAccess(
147 extension
, url
, tab_id
, -1, NULL
);
151 return PermissionsData::ACCESS_DENIED
;
154 void ActiveScriptController::RequestScriptInjection(
155 const Extension
* extension
,
156 const base::Closure
& callback
) {
158 PendingRequestList
& list
= pending_requests_
[extension
->id()];
159 list
.push_back(callback
);
161 // If this was the first entry, we need to notify that a new extension wants
163 if (list
.size() == 1u)
164 NotifyChange(extension
);
166 was_used_on_page_
= true;
169 void ActiveScriptController::RunPendingForExtension(
170 const Extension
* extension
) {
173 content::NavigationEntry
* visible_entry
=
174 web_contents()->GetController().GetVisibleEntry();
175 // Refuse to run if there's no visible entry, because we have no idea of
176 // determining if it's the proper page. This should rarely, if ever, happen.
180 // We add this to the list of permitted extensions and erase pending entries
181 // *before* running them to guard against the crazy case where running the
182 // callbacks adds more entries.
183 permitted_extensions_
.insert(extension
->id());
185 PendingRequestMap::iterator iter
= pending_requests_
.find(extension
->id());
186 if (iter
== pending_requests_
.end())
189 PendingRequestList requests
;
190 iter
->second
.swap(requests
);
191 pending_requests_
.erase(extension
->id());
193 // Clicking to run the extension counts as granting it permission to run on
195 // The extension may already have active tab at this point, but granting
196 // it twice is essentially a no-op.
197 TabHelper::FromWebContents(web_contents())->
198 active_tab_permission_granter()->GrantIfRequested(extension
);
200 // Run all pending injections for the given extension.
201 for (PendingRequestList::iterator request
= requests
.begin();
202 request
!= requests
.end();
207 // The extension ran, so we need to update the ExtensionActionAPI that we no
208 // longer want to act.
209 NotifyChange(extension
);
212 void ActiveScriptController::OnRequestScriptInjectionPermission(
213 const std::string
& extension_id
,
214 UserScript::InjectionType script_type
,
216 if (!crx_file::id_util::IdIsValid(extension_id
)) {
217 NOTREACHED() << "'" << extension_id
<< "' is not a valid id.";
221 const Extension
* extension
=
222 ExtensionRegistry::Get(browser_context_
)
223 ->enabled_extensions().GetByID(extension_id
);
224 // We shouldn't allow extensions which are no longer enabled to run any
225 // scripts. Ignore the request.
229 // If the request id is -1, that signals that the content script has already
230 // ran (because this feature is not enabled). Add the extension to the list of
231 // permitted extensions (for metrics), and return immediately.
232 if (request_id
== -1) {
233 if (PermissionsData::ScriptsMayRequireActionForExtension(
235 extension
->permissions_data()->active_permissions().get())) {
236 permitted_extensions_
.insert(extension
->id());
241 ++num_page_requests_
;
243 switch (RequiresUserConsentForScriptInjection(extension
, script_type
)) {
244 case PermissionsData::ACCESS_ALLOWED
:
245 PermitScriptInjection(request_id
);
247 case PermissionsData::ACCESS_WITHHELD
:
248 // This base::Unretained() is safe, because the callback is only invoked
250 RequestScriptInjection(
252 base::Bind(&ActiveScriptController::PermitScriptInjection
,
253 base::Unretained(this),
256 case PermissionsData::ACCESS_DENIED
:
257 // We should usually only get a "deny access" if the page changed (as the
258 // renderer wouldn't have requested permission if the answer was always
259 // "no"). Just let the request fizzle and die.
264 void ActiveScriptController::PermitScriptInjection(int64 request_id
) {
265 // This only sends the response to the renderer - the process of adding the
266 // extension to the list of |permitted_extensions_| is done elsewhere.
267 // TODO(devlin): Instead of sending this to all frames, we should include the
268 // routing_id in the permission request message, and send only to the proper
269 // frame (sending it to all frames doesn't hurt, but isn't as efficient).
270 web_contents()->SendToAllFrames(new ExtensionMsg_PermitScriptInjection(
271 MSG_ROUTING_NONE
, // Routing id is set by the |web_contents|.
275 void ActiveScriptController::NotifyChange(const Extension
* extension
) {
276 ExtensionActionAPI
* extension_action_api
=
277 ExtensionActionAPI::Get(browser_context_
);
278 ExtensionAction
* extension_action
=
279 ExtensionActionManager::Get(browser_context_
)->
280 GetExtensionAction(*extension
);
281 // If the extension has an action, we need to notify that it's updated.
282 if (extension_action
) {
283 extension_action_api
->NotifyChange(
284 extension_action
, web_contents(), browser_context_
);
287 // We also notify that page actions may have changed.
288 extension_action_api
->NotifyPageActionsChanged(web_contents());
291 void ActiveScriptController::LogUMA() const {
292 // We only log the permitted extensions metric if the feature was used at all
293 // on the page, because otherwise the data will be boring.
294 if (was_used_on_page_
) {
295 UMA_HISTOGRAM_COUNTS_100(
296 "Extensions.ActiveScriptController.PermittedExtensions",
297 permitted_extensions_
.size());
298 UMA_HISTOGRAM_COUNTS_100(
299 "Extensions.ActiveScriptController.DeniedExtensions",
300 pending_requests_
.size());
304 bool ActiveScriptController::OnMessageReceived(
305 const IPC::Message
& message
,
306 content::RenderFrameHost
* render_frame_host
) {
308 IPC_BEGIN_MESSAGE_MAP(ActiveScriptController
, message
)
309 IPC_MESSAGE_HANDLER(ExtensionHostMsg_RequestScriptInjectionPermission
,
310 OnRequestScriptInjectionPermission
)
311 IPC_MESSAGE_UNHANDLED(handled
= false)
312 IPC_END_MESSAGE_MAP()
316 void ActiveScriptController::DidNavigateMainFrame(
317 const content::LoadCommittedDetails
& details
,
318 const content::FrameNavigateParams
& params
) {
319 if (details
.is_in_page
)
323 num_page_requests_
= 0;
324 permitted_extensions_
.clear();
325 pending_requests_
.clear();
326 was_used_on_page_
= false;
329 void ActiveScriptController::OnExtensionUnloaded(
330 content::BrowserContext
* browser_context
,
331 const Extension
* extension
,
332 UnloadedExtensionInfo::Reason reason
) {
333 PendingRequestMap::iterator iter
= pending_requests_
.find(extension
->id());
334 if (iter
!= pending_requests_
.end()) {
335 pending_requests_
.erase(iter
);
336 ExtensionActionAPI::Get(browser_context_
)->
337 NotifyPageActionsChanged(web_contents());
341 } // namespace extensions