OLD | NEW |
1 // Copyright 2014 The Chromium Authors. All rights reserved. | 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 | 2 // Use of this source code is governed by a BSD-style license that can be |
3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
4 | 4 |
5 #include "chrome/browser/extensions/active_script_controller.h" | 5 #include "chrome/browser/extensions/active_script_controller.h" |
6 | 6 |
7 #include "base/bind.h" | 7 #include "base/bind.h" |
8 #include "base/bind_helpers.h" | 8 #include "base/bind_helpers.h" |
9 #include "base/memory/scoped_ptr.h" | 9 #include "base/memory/scoped_ptr.h" |
10 #include "base/metrics/histogram.h" | 10 #include "base/metrics/histogram.h" |
11 #include "base/stl_util.h" | 11 #include "base/stl_util.h" |
12 #include "chrome/browser/extensions/active_tab_permission_granter.h" | 12 #include "chrome/browser/extensions/active_tab_permission_granter.h" |
13 #include "chrome/browser/extensions/extension_action.h" | 13 #include "chrome/browser/extensions/extension_action.h" |
14 #include "chrome/browser/extensions/extension_util.h" | 14 #include "chrome/browser/extensions/extension_util.h" |
15 #include "chrome/browser/extensions/location_bar_controller.h" | 15 #include "chrome/browser/extensions/location_bar_controller.h" |
16 #include "chrome/browser/extensions/tab_helper.h" | 16 #include "chrome/browser/extensions/tab_helper.h" |
17 #include "chrome/browser/sessions/session_id.h" | 17 #include "chrome/browser/sessions/session_id.h" |
18 #include "chrome/common/extensions/api/extension_action/action_info.h" | 18 #include "chrome/common/extensions/api/extension_action/action_info.h" |
19 #include "content/public/browser/navigation_controller.h" | 19 #include "content/public/browser/navigation_controller.h" |
20 #include "content/public/browser/navigation_entry.h" | 20 #include "content/public/browser/navigation_entry.h" |
21 #include "content/public/browser/render_view_host.h" | 21 #include "content/public/browser/render_view_host.h" |
22 #include "content/public/browser/web_contents.h" | 22 #include "content/public/browser/web_contents.h" |
23 #include "extensions/browser/extension_registry.h" | 23 #include "extensions/browser/extension_registry.h" |
24 #include "extensions/common/extension.h" | 24 #include "extensions/common/extension.h" |
25 #include "extensions/common/extension_messages.h" | 25 #include "extensions/common/extension_messages.h" |
26 #include "extensions/common/extension_set.h" | 26 #include "extensions/common/extension_set.h" |
27 #include "extensions/common/feature_switch.h" | 27 #include "extensions/common/feature_switch.h" |
| 28 #include "extensions/common/manifest.h" |
28 #include "extensions/common/permissions/permissions_data.h" | 29 #include "extensions/common/permissions/permissions_data.h" |
29 #include "ipc/ipc_message_macros.h" | 30 #include "ipc/ipc_message_macros.h" |
30 | 31 |
31 namespace extensions { | 32 namespace extensions { |
32 | 33 |
| 34 namespace { |
| 35 |
| 36 // Returns true if the extension should be regarded as a "permitted" extension |
| 37 // for the case of metrics. We need this because we only actually withhold |
| 38 // permissions if the switch is enabled, but want to record metrics in all |
| 39 // cases. |
| 40 // "ExtensionWouldHaveHadHostPermissionsWithheldIfSwitchWasOn()" would be |
| 41 // more accurate, but too long. |
| 42 bool ShouldRecordExtension(const Extension* extension) { |
| 43 return extension->ShouldDisplayInExtensionSettings() && |
| 44 !Manifest::IsPolicyLocation(extension->location()) && |
| 45 !Manifest::IsComponentLocation(extension->location()) && |
| 46 !PermissionsData::CanExecuteScriptEverywhere(extension) && |
| 47 extension->permissions_data() |
| 48 ->active_permissions() |
| 49 ->ShouldWarnAllHosts(); |
| 50 } |
| 51 |
| 52 } // namespace |
| 53 |
33 ActiveScriptController::ActiveScriptController( | 54 ActiveScriptController::ActiveScriptController( |
34 content::WebContents* web_contents) | 55 content::WebContents* web_contents) |
35 : content::WebContentsObserver(web_contents), | 56 : content::WebContentsObserver(web_contents), |
36 enabled_(FeatureSwitch::scripts_require_action()->IsEnabled()) { | 57 enabled_(FeatureSwitch::scripts_require_action()->IsEnabled()) { |
37 CHECK(web_contents); | 58 CHECK(web_contents); |
38 } | 59 } |
39 | 60 |
40 ActiveScriptController::~ActiveScriptController() { | 61 ActiveScriptController::~ActiveScriptController() { |
41 LogUMA(); | 62 LogUMA(); |
42 } | 63 } |
43 | 64 |
44 // static | 65 // static |
45 ActiveScriptController* ActiveScriptController::GetForWebContents( | 66 ActiveScriptController* ActiveScriptController::GetForWebContents( |
46 content::WebContents* web_contents) { | 67 content::WebContents* web_contents) { |
47 if (!web_contents) | 68 if (!web_contents) |
48 return NULL; | 69 return NULL; |
49 TabHelper* tab_helper = TabHelper::FromWebContents(web_contents); | 70 TabHelper* tab_helper = TabHelper::FromWebContents(web_contents); |
50 if (!tab_helper) | 71 if (!tab_helper) |
51 return NULL; | 72 return NULL; |
52 LocationBarController* location_bar_controller = | 73 LocationBarController* location_bar_controller = |
53 tab_helper->location_bar_controller(); | 74 tab_helper->location_bar_controller(); |
54 // This should never be NULL. | 75 // This should never be NULL. |
55 DCHECK(location_bar_controller); | 76 DCHECK(location_bar_controller); |
56 return location_bar_controller->active_script_controller(); | 77 return location_bar_controller->active_script_controller(); |
57 } | 78 } |
58 | 79 |
59 bool ActiveScriptController::RequiresUserConsentForScriptInjection( | |
60 const Extension* extension) { | |
61 CHECK(extension); | |
62 if (!extension->permissions_data()->RequiresActionForScriptExecution( | |
63 extension, | |
64 SessionID::IdForTab(web_contents()), | |
65 web_contents()->GetVisibleURL()) || | |
66 util::AllowedScriptingOnAllUrls(extension->id(), | |
67 web_contents()->GetBrowserContext())) { | |
68 return false; | |
69 } | |
70 | |
71 // If the feature is not enabled, we automatically allow all extensions to | |
72 // run scripts. | |
73 if (!enabled_) | |
74 permitted_extensions_.insert(extension->id()); | |
75 | |
76 return permitted_extensions_.count(extension->id()) == 0; | |
77 } | |
78 | |
79 void ActiveScriptController::RequestScriptInjection( | |
80 const Extension* extension, | |
81 const base::Closure& callback) { | |
82 CHECK(extension); | |
83 PendingRequestList& list = pending_requests_[extension->id()]; | |
84 list.push_back(callback); | |
85 | |
86 // If this was the first entry, notify the location bar that there's a new | |
87 // icon. | |
88 if (list.size() == 1u) | |
89 LocationBarController::NotifyChange(web_contents()); | |
90 } | |
91 | |
92 void ActiveScriptController::OnActiveTabPermissionGranted( | 80 void ActiveScriptController::OnActiveTabPermissionGranted( |
93 const Extension* extension) { | 81 const Extension* extension) { |
94 RunPendingForExtension(extension); | 82 RunPendingForExtension(extension); |
95 } | 83 } |
96 | 84 |
97 void ActiveScriptController::OnAdInjectionDetected( | 85 void ActiveScriptController::OnAdInjectionDetected( |
98 const std::set<std::string>& ad_injectors) { | 86 const std::set<std::string>& ad_injectors) { |
99 // We're only interested in data if there are ad injectors detected. | 87 // We're only interested in data if there are ad injectors detected. |
100 if (ad_injectors.empty()) | 88 if (ad_injectors.empty()) |
101 return; | 89 return; |
(...skipping 50 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
152 permitted_extensions_.clear(); | 140 permitted_extensions_.clear(); |
153 pending_requests_.clear(); | 141 pending_requests_.clear(); |
154 } | 142 } |
155 | 143 |
156 void ActiveScriptController::OnExtensionUnloaded(const Extension* extension) { | 144 void ActiveScriptController::OnExtensionUnloaded(const Extension* extension) { |
157 PendingRequestMap::iterator iter = pending_requests_.find(extension->id()); | 145 PendingRequestMap::iterator iter = pending_requests_.find(extension->id()); |
158 if (iter != pending_requests_.end()) | 146 if (iter != pending_requests_.end()) |
159 pending_requests_.erase(iter); | 147 pending_requests_.erase(iter); |
160 } | 148 } |
161 | 149 |
| 150 PermissionsData::AccessType |
| 151 ActiveScriptController::RequiresUserConsentForScriptInjection( |
| 152 const Extension* extension, |
| 153 UserScript::InjectionType type) { |
| 154 CHECK(extension); |
| 155 |
| 156 // If the feature is not enabled, we automatically allow all extensions to |
| 157 // run scripts. |
| 158 if (!enabled_) |
| 159 permitted_extensions_.insert(extension->id()); |
| 160 |
| 161 // Allow the extension if it's been explicitly granted permission. |
| 162 if (permitted_extensions_.count(extension->id()) > 0) |
| 163 return PermissionsData::ACCESS_ALLOWED; |
| 164 |
| 165 GURL url = web_contents()->GetVisibleURL(); |
| 166 int tab_id = SessionID::IdForTab(web_contents()); |
| 167 switch (type) { |
| 168 case UserScript::CONTENT_SCRIPT: |
| 169 return extension->permissions_data()->GetContentScriptAccess( |
| 170 extension, url, url, tab_id, -1, NULL); |
| 171 case UserScript::PROGRAMMATIC_SCRIPT: |
| 172 return extension->permissions_data()->GetPageAccess( |
| 173 extension, url, url, tab_id, -1, NULL); |
| 174 } |
| 175 |
| 176 NOTREACHED(); |
| 177 return PermissionsData::ACCESS_DENIED; |
| 178 } |
| 179 |
| 180 void ActiveScriptController::RequestScriptInjection( |
| 181 const Extension* extension, |
| 182 const base::Closure& callback) { |
| 183 CHECK(extension); |
| 184 PendingRequestList& list = pending_requests_[extension->id()]; |
| 185 list.push_back(callback); |
| 186 |
| 187 // If this was the first entry, notify the location bar that there's a new |
| 188 // icon. |
| 189 if (list.size() == 1u) |
| 190 LocationBarController::NotifyChange(web_contents()); |
| 191 } |
| 192 |
162 void ActiveScriptController::RunPendingForExtension( | 193 void ActiveScriptController::RunPendingForExtension( |
163 const Extension* extension) { | 194 const Extension* extension) { |
164 DCHECK(extension); | 195 DCHECK(extension); |
165 PendingRequestMap::iterator iter = | |
166 pending_requests_.find(extension->id()); | |
167 if (iter == pending_requests_.end()) | |
168 return; | |
169 | 196 |
170 content::NavigationEntry* visible_entry = | 197 content::NavigationEntry* visible_entry = |
171 web_contents()->GetController().GetVisibleEntry(); | 198 web_contents()->GetController().GetVisibleEntry(); |
172 // Refuse to run if there's no visible entry, because we have no idea of | 199 // Refuse to run if there's no visible entry, because we have no idea of |
173 // determining if it's the proper page. This should rarely, if ever, happen. | 200 // determining if it's the proper page. This should rarely, if ever, happen. |
174 if (!visible_entry) | 201 if (!visible_entry) |
175 return; | 202 return; |
176 | 203 |
177 // We add this to the list of permitted extensions and erase pending entries | 204 // We add this to the list of permitted extensions and erase pending entries |
178 // *before* running them to guard against the crazy case where running the | 205 // *before* running them to guard against the crazy case where running the |
179 // callbacks adds more entries. | 206 // callbacks adds more entries. |
180 permitted_extensions_.insert(extension->id()); | 207 permitted_extensions_.insert(extension->id()); |
| 208 |
| 209 PendingRequestMap::iterator iter = pending_requests_.find(extension->id()); |
| 210 if (iter == pending_requests_.end()) |
| 211 return; |
| 212 |
181 PendingRequestList requests; | 213 PendingRequestList requests; |
182 iter->second.swap(requests); | 214 iter->second.swap(requests); |
183 pending_requests_.erase(extension->id()); | 215 pending_requests_.erase(extension->id()); |
184 | 216 |
185 // Clicking to run the extension counts as granting it permission to run on | 217 // Clicking to run the extension counts as granting it permission to run on |
186 // the given tab. | 218 // the given tab. |
187 // The extension may already have active tab at this point, but granting | 219 // The extension may already have active tab at this point, but granting |
188 // it twice is essentially a no-op. | 220 // it twice is essentially a no-op. |
189 TabHelper::FromWebContents(web_contents())-> | 221 TabHelper::FromWebContents(web_contents())-> |
190 active_tab_permission_granter()->GrantIfRequested(extension); | 222 active_tab_permission_granter()->GrantIfRequested(extension); |
191 | 223 |
192 // Run all pending injections for the given extension. | 224 // Run all pending injections for the given extension. |
193 for (PendingRequestList::iterator request = requests.begin(); | 225 for (PendingRequestList::iterator request = requests.begin(); |
194 request != requests.end(); | 226 request != requests.end(); |
195 ++request) { | 227 ++request) { |
196 request->Run(); | 228 request->Run(); |
197 } | 229 } |
198 | 230 |
199 // Inform the location bar that the action is now gone. | 231 // Inform the location bar that the action is now gone. |
200 LocationBarController::NotifyChange(web_contents()); | 232 LocationBarController::NotifyChange(web_contents()); |
201 } | 233 } |
202 | 234 |
203 void ActiveScriptController::OnRequestScriptInjectionPermission( | 235 void ActiveScriptController::OnRequestScriptInjectionPermission( |
204 const std::string& extension_id, | 236 const std::string& extension_id, |
| 237 UserScript::InjectionType script_type, |
205 int64 request_id) { | 238 int64 request_id) { |
206 if (!Extension::IdIsValid(extension_id)) { | 239 if (!Extension::IdIsValid(extension_id)) { |
207 NOTREACHED() << "'" << extension_id << "' is not a valid id."; | 240 NOTREACHED() << "'" << extension_id << "' is not a valid id."; |
208 return; | 241 return; |
209 } | 242 } |
210 | 243 |
211 const Extension* extension = | 244 const Extension* extension = |
212 ExtensionRegistry::Get(web_contents()->GetBrowserContext()) | 245 ExtensionRegistry::Get(web_contents()->GetBrowserContext()) |
213 ->enabled_extensions().GetByID(extension_id); | 246 ->enabled_extensions().GetByID(extension_id); |
214 // We shouldn't allow extensions which are no longer enabled to run any | 247 // We shouldn't allow extensions which are no longer enabled to run any |
215 // scripts. Ignore the request. | 248 // scripts. Ignore the request. |
216 if (!extension) | 249 if (!extension) |
217 return; | 250 return; |
218 | 251 |
219 // If the request id is -1, that signals that the content script has already | 252 // If the request id is -1, that signals that the content script has already |
220 // ran (because this feature is not enabled). Add the extension to the list of | 253 // ran (because this feature is not enabled). Add the extension to the list of |
221 // permitted extensions (for metrics), and return immediately. | 254 // permitted extensions (for metrics), and return immediately. |
222 if (request_id == -1) { | 255 if (request_id == -1) { |
223 DCHECK(!enabled_); | 256 if (ShouldRecordExtension(extension)) { |
224 permitted_extensions_.insert(extension->id()); | 257 DCHECK(!enabled_); |
| 258 permitted_extensions_.insert(extension->id()); |
| 259 } |
225 return; | 260 return; |
226 } | 261 } |
227 | 262 |
228 if (RequiresUserConsentForScriptInjection(extension)) { | 263 switch (RequiresUserConsentForScriptInjection(extension, script_type)) { |
229 // This base::Unretained() is safe, because the callback is only invoked by | 264 case PermissionsData::ACCESS_ALLOWED: |
230 // this object. | 265 PermitScriptInjection(request_id); |
231 RequestScriptInjection( | 266 break; |
232 extension, | 267 case PermissionsData::ACCESS_WITHHELD: |
233 base::Bind(&ActiveScriptController::PermitScriptInjection, | 268 // This base::Unretained() is safe, because the callback is only invoked |
234 base::Unretained(this), | 269 // by this object. |
235 request_id)); | 270 RequestScriptInjection( |
236 } else { | 271 extension, |
237 PermitScriptInjection(request_id); | 272 base::Bind(&ActiveScriptController::PermitScriptInjection, |
| 273 base::Unretained(this), |
| 274 request_id)); |
| 275 break; |
| 276 case PermissionsData::ACCESS_DENIED: |
| 277 // We should usually only get a "deny access" if the page changed (as the |
| 278 // renderer wouldn't have requested permission if the answer was always |
| 279 // "no"). Just let the request fizzle and die. |
| 280 break; |
238 } | 281 } |
239 } | 282 } |
240 | 283 |
241 void ActiveScriptController::PermitScriptInjection(int64 request_id) { | 284 void ActiveScriptController::PermitScriptInjection(int64 request_id) { |
| 285 // This only sends the response to the renderer - the process of adding the |
| 286 // extension to the list of |permitted_extensions_| is done elsewhere. |
242 content::RenderViewHost* render_view_host = | 287 content::RenderViewHost* render_view_host = |
243 web_contents()->GetRenderViewHost(); | 288 web_contents()->GetRenderViewHost(); |
244 if (render_view_host) { | 289 if (render_view_host) { |
245 render_view_host->Send(new ExtensionMsg_PermitScriptInjection( | 290 render_view_host->Send(new ExtensionMsg_PermitScriptInjection( |
246 render_view_host->GetRoutingID(), request_id)); | 291 render_view_host->GetRoutingID(), request_id)); |
247 } | 292 } |
248 } | 293 } |
249 | 294 |
250 bool ActiveScriptController::OnMessageReceived(const IPC::Message& message) { | 295 bool ActiveScriptController::OnMessageReceived(const IPC::Message& message) { |
251 bool handled = true; | 296 bool handled = true; |
(...skipping 16 matching lines...) Expand all Loading... |
268 UMA_HISTOGRAM_COUNTS_100( | 313 UMA_HISTOGRAM_COUNTS_100( |
269 "Extensions.ActiveScriptController.PermittedExtensions", | 314 "Extensions.ActiveScriptController.PermittedExtensions", |
270 permitted_extensions_.size()); | 315 permitted_extensions_.size()); |
271 UMA_HISTOGRAM_COUNTS_100( | 316 UMA_HISTOGRAM_COUNTS_100( |
272 "Extensions.ActiveScriptController.DeniedExtensions", | 317 "Extensions.ActiveScriptController.DeniedExtensions", |
273 pending_requests_.size()); | 318 pending_requests_.size()); |
274 } | 319 } |
275 } | 320 } |
276 | 321 |
277 } // namespace extensions | 322 } // namespace extensions |
OLD | NEW |