[Extensions OOPI] Clean up script injection for OOPI more
[chromium-blink-merge.git] / chrome / browser / extensions / active_script_controller.cc
blobf4ef4dea74e431774d9b671e9746b38967e6dea3
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"
7 #include "base/bind.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) {
46 CHECK(web_contents);
47 extension_registry_observer_.Add(ExtensionRegistry::Get(browser_context_));
50 ActiveScriptController::~ActiveScriptController() {
51 LogUMA();
54 // static
55 ActiveScriptController* ActiveScriptController::GetForWebContents(
56 content::WebContents* web_contents) {
57 if (!web_contents)
58 return NULL;
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())
72 return;
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(),
96 url.GetOrigin());
98 if (withheld_permissions->scriptable_hosts().MatchesURL(url)) {
99 new_scriptable_hosts.AddOrigin(UserScript::ValidUserScriptSchemes(),
100 url.GetOrigin());
103 scoped_refptr<PermissionSet> new_permissions =
104 new PermissionSet(APIPermissionSet(),
105 ManifestPermissionSet(),
106 new_explicit_hosts,
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) {
133 CHECK(extension);
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());
141 switch (type) {
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);
150 NOTREACHED();
151 return PermissionsData::ACCESS_DENIED;
154 void ActiveScriptController::RequestScriptInjection(
155 const Extension* extension,
156 const base::Closure& callback) {
157 CHECK(extension);
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
162 // to run.
163 if (list.size() == 1u)
164 NotifyChange(extension);
166 was_used_on_page_ = true;
169 void ActiveScriptController::RunPendingForExtension(
170 const Extension* extension) {
171 DCHECK(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.
177 if (!visible_entry)
178 return;
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())
187 return;
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
194 // the given tab.
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();
203 ++request) {
204 request->Run();
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,
215 int64 request_id) {
216 if (!crx_file::id_util::IdIsValid(extension_id)) {
217 NOTREACHED() << "'" << extension_id << "' is not a valid id.";
218 return;
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.
226 if (!extension)
227 return;
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(
234 extension,
235 extension->permissions_data()->active_permissions().get())) {
236 permitted_extensions_.insert(extension->id());
238 return;
241 ++num_page_requests_;
243 switch (RequiresUserConsentForScriptInjection(extension, script_type)) {
244 case PermissionsData::ACCESS_ALLOWED:
245 PermitScriptInjection(request_id);
246 break;
247 case PermissionsData::ACCESS_WITHHELD:
248 // This base::Unretained() is safe, because the callback is only invoked
249 // by this object.
250 RequestScriptInjection(
251 extension,
252 base::Bind(&ActiveScriptController::PermitScriptInjection,
253 base::Unretained(this),
254 request_id));
255 break;
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.
260 break;
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|.
272 request_id));
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) {
307 bool handled = true;
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()
313 return handled;
316 void ActiveScriptController::DidNavigateMainFrame(
317 const content::LoadCommittedDetails& details,
318 const content::FrameNavigateParams& params) {
319 if (details.is_in_page)
320 return;
322 LogUMA();
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