Chromium Code Reviews| 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()->active_permissions() | |
| 48 ->ShouldWarnAllHosts(); | |
|
not at google - send to devlin
2014/06/27 23:24:33
don't you need to check withheld permissions as we
Devlin
2014/06/30 17:06:09
No.
This method is the yak shave for being able t
| |
| 49 } | |
| 50 | |
| 51 } // namespace | |
| 52 | |
| 33 ActiveScriptController::PendingRequest::PendingRequest() : | 53 ActiveScriptController::PendingRequest::PendingRequest() : |
| 34 page_id(-1) { | 54 page_id(-1) { |
| 35 } | 55 } |
| 36 | 56 |
| 37 ActiveScriptController::PendingRequest::PendingRequest( | 57 ActiveScriptController::PendingRequest::PendingRequest( |
| 38 const base::Closure& closure, | 58 const base::Closure& closure, |
| 39 int page_id) | 59 int page_id) |
| 40 : closure(closure), | 60 : closure(closure), |
| 41 page_id(page_id) { | 61 page_id(page_id) { |
| 42 } | 62 } |
| (...skipping 20 matching lines...) Expand all Loading... | |
| 63 TabHelper* tab_helper = TabHelper::FromWebContents(web_contents); | 83 TabHelper* tab_helper = TabHelper::FromWebContents(web_contents); |
| 64 if (!tab_helper) | 84 if (!tab_helper) |
| 65 return NULL; | 85 return NULL; |
| 66 LocationBarController* location_bar_controller = | 86 LocationBarController* location_bar_controller = |
| 67 tab_helper->location_bar_controller(); | 87 tab_helper->location_bar_controller(); |
| 68 // This should never be NULL. | 88 // This should never be NULL. |
| 69 DCHECK(location_bar_controller); | 89 DCHECK(location_bar_controller); |
| 70 return location_bar_controller->active_script_controller(); | 90 return location_bar_controller->active_script_controller(); |
| 71 } | 91 } |
| 72 | 92 |
| 73 bool ActiveScriptController::RequiresUserConsentForScriptInjection( | |
| 74 const Extension* extension) { | |
| 75 CHECK(extension); | |
| 76 if (!extension->permissions_data()->RequiresActionForScriptExecution( | |
| 77 extension, | |
| 78 SessionID::IdForTab(web_contents()), | |
| 79 web_contents()->GetVisibleURL()) || | |
| 80 util::AllowedScriptingOnAllUrls(extension->id(), | |
| 81 web_contents()->GetBrowserContext())) { | |
| 82 return false; | |
| 83 } | |
| 84 | |
| 85 // If the feature is not enabled, we automatically allow all extensions to | |
| 86 // run scripts. | |
| 87 if (!enabled_) | |
| 88 permitted_extensions_.insert(extension->id()); | |
| 89 | |
| 90 return permitted_extensions_.count(extension->id()) == 0; | |
| 91 } | |
| 92 | |
| 93 void ActiveScriptController::RequestScriptInjection( | |
| 94 const Extension* extension, | |
| 95 int page_id, | |
| 96 const base::Closure& callback) { | |
| 97 CHECK(extension); | |
| 98 PendingRequestList& list = pending_requests_[extension->id()]; | |
| 99 list.push_back(PendingRequest(callback, page_id)); | |
| 100 | |
| 101 // If this was the first entry, notify the location bar that there's a new | |
| 102 // icon. | |
| 103 if (list.size() == 1u) | |
| 104 LocationBarController::NotifyChange(web_contents()); | |
| 105 } | |
| 106 | |
| 107 void ActiveScriptController::OnActiveTabPermissionGranted( | 93 void ActiveScriptController::OnActiveTabPermissionGranted( |
| 108 const Extension* extension) { | 94 const Extension* extension) { |
| 109 RunPendingForExtension(extension); | 95 RunPendingForExtension(extension); |
| 110 } | 96 } |
| 111 | 97 |
| 112 void ActiveScriptController::OnAdInjectionDetected( | 98 void ActiveScriptController::OnAdInjectionDetected( |
| 113 const std::set<std::string>& ad_injectors) { | 99 const std::set<std::string>& ad_injectors) { |
| 114 // We're only interested in data if there are ad injectors detected. | 100 // We're only interested in data if there are ad injectors detected. |
| 115 if (ad_injectors.empty()) | 101 if (ad_injectors.empty()) |
| 116 return; | 102 return; |
| (...skipping 50 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 167 permitted_extensions_.clear(); | 153 permitted_extensions_.clear(); |
| 168 pending_requests_.clear(); | 154 pending_requests_.clear(); |
| 169 } | 155 } |
| 170 | 156 |
| 171 void ActiveScriptController::OnExtensionUnloaded(const Extension* extension) { | 157 void ActiveScriptController::OnExtensionUnloaded(const Extension* extension) { |
| 172 PendingRequestMap::iterator iter = pending_requests_.find(extension->id()); | 158 PendingRequestMap::iterator iter = pending_requests_.find(extension->id()); |
| 173 if (iter != pending_requests_.end()) | 159 if (iter != pending_requests_.end()) |
| 174 pending_requests_.erase(iter); | 160 pending_requests_.erase(iter); |
| 175 } | 161 } |
| 176 | 162 |
| 163 PermissionsData::AccessType | |
| 164 ActiveScriptController::RequiresUserConsentForScriptInjection( | |
| 165 const Extension* extension, | |
| 166 extension_misc::InjectedScriptType type) { | |
| 167 CHECK(extension); | |
| 168 | |
| 169 // If the feature is not enabled, we automatically allow all extensions to | |
| 170 // run scripts. | |
| 171 if (!enabled_) | |
| 172 permitted_extensions_.insert(extension->id()); | |
| 173 | |
| 174 // Allow the extension if it's been explicitly granted permission. | |
| 175 if (permitted_extensions_.count(extension->id()) > 0) | |
| 176 return PermissionsData::ALLOW_ACCESS; | |
| 177 | |
| 178 PermissionsData::AccessType access = PermissionsData::DENY_ACCESS; | |
| 179 GURL url = web_contents()->GetVisibleURL(); | |
| 180 int tab_id = SessionID::IdForTab(web_contents()); | |
| 181 switch (type) { | |
| 182 case extension_misc::CONTENT_SCRIPT: | |
| 183 access = extension->permissions_data() | |
| 184 ->CanRunContentScriptOnPageWithUserConsent( | |
| 185 extension, url, url, tab_id, -1, NULL); | |
| 186 break; | |
| 187 case extension_misc::PROGRAMMATIC_SCRIPT: | |
| 188 access = extension->permissions_data()->CanAccessPageWithUserConsent( | |
| 189 extension, url, url, tab_id, -1, NULL); | |
| 190 } | |
|
not at google - send to devlin
2014/06/27 23:24:33
nit: I'd prefer to phrase this more succinctly wit
not at google - send to devlin
2014/06/30 14:37:03
I mean "without declaring |access|"
Devlin
2014/06/30 17:06:09
Sure, why not.
| |
| 191 | |
| 192 return access; | |
| 193 } | |
| 194 | |
| 195 void ActiveScriptController::RequestScriptInjection( | |
| 196 const Extension* extension, | |
| 197 int page_id, | |
| 198 const base::Closure& callback) { | |
| 199 CHECK(extension); | |
| 200 PendingRequestList& list = pending_requests_[extension->id()]; | |
| 201 list.push_back(PendingRequest(callback, page_id)); | |
| 202 | |
| 203 // If this was the first entry, notify the location bar that there's a new | |
| 204 // icon. | |
| 205 if (list.size() == 1u) | |
| 206 LocationBarController::NotifyChange(web_contents()); | |
| 207 } | |
| 208 | |
| 177 void ActiveScriptController::RunPendingForExtension( | 209 void ActiveScriptController::RunPendingForExtension( |
| 178 const Extension* extension) { | 210 const Extension* extension) { |
| 179 DCHECK(extension); | 211 DCHECK(extension); |
| 180 PendingRequestMap::iterator iter = | |
| 181 pending_requests_.find(extension->id()); | |
| 182 if (iter == pending_requests_.end()) | |
| 183 return; | |
| 184 | 212 |
| 185 content::NavigationEntry* visible_entry = | 213 content::NavigationEntry* visible_entry = |
| 186 web_contents()->GetController().GetVisibleEntry(); | 214 web_contents()->GetController().GetVisibleEntry(); |
| 187 // Refuse to run if there's no visible entry, because we have no idea of | 215 // Refuse to run if there's no visible entry, because we have no idea of |
| 188 // determining if it's the proper page. This should rarely, if ever, happen. | 216 // determining if it's the proper page. This should rarely, if ever, happen. |
| 189 if (!visible_entry) | 217 if (!visible_entry) |
| 190 return; | 218 return; |
| 191 | 219 |
| 192 int page_id = visible_entry->GetPageID(); | 220 int page_id = visible_entry->GetPageID(); |
| 193 | 221 |
| 194 // We add this to the list of permitted extensions and erase pending entries | 222 // We add this to the list of permitted extensions and erase pending entries |
| 195 // *before* running them to guard against the crazy case where running the | 223 // *before* running them to guard against the crazy case where running the |
| 196 // callbacks adds more entries. | 224 // callbacks adds more entries. |
| 197 permitted_extensions_.insert(extension->id()); | 225 permitted_extensions_.insert(extension->id()); |
| 226 | |
| 227 PendingRequestMap::iterator iter = | |
| 228 pending_requests_.find(extension->id()); | |
| 229 if (iter == pending_requests_.end()) | |
| 230 return; | |
| 231 | |
| 198 PendingRequestList requests; | 232 PendingRequestList requests; |
| 199 iter->second.swap(requests); | 233 iter->second.swap(requests); |
| 200 pending_requests_.erase(extension->id()); | 234 pending_requests_.erase(extension->id()); |
| 201 | 235 |
| 202 // Clicking to run the extension counts as granting it permission to run on | 236 // Clicking to run the extension counts as granting it permission to run on |
| 203 // the given tab. | 237 // the given tab. |
| 204 // The extension may already have active tab at this point, but granting | 238 // The extension may already have active tab at this point, but granting |
| 205 // it twice is essentially a no-op. | 239 // it twice is essentially a no-op. |
| 206 TabHelper::FromWebContents(web_contents())-> | 240 TabHelper::FromWebContents(web_contents())-> |
| 207 active_tab_permission_granter()->GrantIfRequested(extension); | 241 active_tab_permission_granter()->GrantIfRequested(extension); |
| 208 | 242 |
| 209 // Run all pending injections for the given extension. | 243 // Run all pending injections for the given extension. |
| 210 for (PendingRequestList::iterator request = requests.begin(); | 244 for (PendingRequestList::iterator request = requests.begin(); |
| 211 request != requests.end(); | 245 request != requests.end(); |
| 212 ++request) { | 246 ++request) { |
| 213 // Only run if it's on the proper page. | 247 // Only run if it's on the proper page. |
| 214 if (request->page_id == page_id) | 248 if (request->page_id == page_id) |
| 215 request->closure.Run(); | 249 request->closure.Run(); |
| 216 } | 250 } |
| 217 | 251 |
| 218 // Inform the location bar that the action is now gone. | 252 // Inform the location bar that the action is now gone. |
| 219 LocationBarController::NotifyChange(web_contents()); | 253 LocationBarController::NotifyChange(web_contents()); |
| 220 } | 254 } |
| 221 | 255 |
| 222 void ActiveScriptController::OnRequestScriptInjectionPermission( | 256 void ActiveScriptController::OnRequestScriptInjectionPermission( |
| 223 const std::string& extension_id, | 257 const std::string& extension_id, |
| 258 extension_misc::InjectedScriptType script_type, | |
| 224 int page_id, | 259 int page_id, |
| 225 int request_id) { | 260 int request_id) { |
| 226 if (!Extension::IdIsValid(extension_id)) { | 261 if (!Extension::IdIsValid(extension_id)) { |
| 227 NOTREACHED() << "'" << extension_id << "' is not a valid id."; | 262 NOTREACHED() << "'" << extension_id << "' is not a valid id."; |
| 228 return; | 263 return; |
| 229 } | 264 } |
| 230 | 265 |
| 231 const Extension* extension = | 266 const Extension* extension = |
| 232 ExtensionRegistry::Get(web_contents()->GetBrowserContext()) | 267 ExtensionRegistry::Get(web_contents()->GetBrowserContext()) |
| 233 ->enabled_extensions().GetByID(extension_id); | 268 ->enabled_extensions().GetByID(extension_id); |
| 234 // We shouldn't allow extensions which are no longer enabled to run any | 269 // We shouldn't allow extensions which are no longer enabled to run any |
| 235 // scripts. Ignore the request. | 270 // scripts. Ignore the request. |
| 236 if (!extension) | 271 if (!extension) |
| 237 return; | 272 return; |
| 238 | 273 |
| 239 // If the request id is -1, that signals that the content script has already | 274 // If the request id is -1, that signals that the content script has already |
| 240 // ran (because this feature is not enabled). Add the extension to the list of | 275 // ran (because this feature is not enabled). Add the extension to the list of |
| 241 // permitted extensions (for metrics), and return immediately. | 276 // permitted extensions (for metrics), and return immediately. |
| 242 if (request_id == -1) { | 277 if (request_id == -1) { |
| 243 DCHECK(!enabled_); | 278 if (ShouldRecordExtension(extension)) { |
| 244 permitted_extensions_.insert(extension->id()); | 279 DCHECK(!enabled_); |
| 280 permitted_extensions_.insert(extension->id()); | |
| 281 } | |
| 245 return; | 282 return; |
| 246 } | 283 } |
| 247 | 284 |
| 248 if (RequiresUserConsentForScriptInjection(extension)) { | 285 switch (RequiresUserConsentForScriptInjection(extension, script_type)) { |
| 249 // This base::Unretained() is safe, because the callback is only invoked by | 286 case PermissionsData::ALLOW_ACCESS: |
| 250 // this object. | 287 PermitScriptInjection(request_id); |
| 251 RequestScriptInjection( | 288 break; |
| 252 extension, | 289 case PermissionsData::REQUEST_ACCESS: |
| 253 page_id, | 290 // This base::Unretained() is safe, because the callback is only invoked |
| 254 base::Bind(&ActiveScriptController::PermitScriptInjection, | 291 // by this object. |
| 255 base::Unretained(this), | 292 RequestScriptInjection( |
| 256 request_id)); | 293 extension, |
| 257 } else { | 294 page_id, |
| 258 PermitScriptInjection(request_id); | 295 base::Bind(&ActiveScriptController::PermitScriptInjection, |
| 296 base::Unretained(this), | |
| 297 request_id)); | |
| 298 break; | |
| 299 case PermissionsData::DENY_ACCESS: | |
| 300 // We should usually only get a "deny access" if the page changed (as the | |
| 301 // renderer wouldn't have requested permission if the answer was always | |
| 302 // "no"). Just let the request fizzle and die. | |
| 303 break; | |
| 259 } | 304 } |
| 260 } | 305 } |
| 261 | 306 |
| 262 void ActiveScriptController::PermitScriptInjection(int request_id) { | 307 void ActiveScriptController::PermitScriptInjection(int request_id) { |
|
not at google - send to devlin
2014/06/30 14:37:03
the reason this works on the browser side is becau
Devlin
2014/06/30 17:06:09
Commented to clear it up.
| |
| 263 content::RenderViewHost* render_view_host = | 308 content::RenderViewHost* render_view_host = |
| 264 web_contents()->GetRenderViewHost(); | 309 web_contents()->GetRenderViewHost(); |
| 265 if (render_view_host) { | 310 if (render_view_host) { |
| 266 render_view_host->Send(new ExtensionMsg_PermitScriptInjection( | 311 render_view_host->Send(new ExtensionMsg_PermitScriptInjection( |
| 267 render_view_host->GetRoutingID(), request_id)); | 312 render_view_host->GetRoutingID(), request_id)); |
| 268 } | 313 } |
| 269 } | 314 } |
| 270 | 315 |
| 271 bool ActiveScriptController::OnMessageReceived(const IPC::Message& message) { | 316 bool ActiveScriptController::OnMessageReceived(const IPC::Message& message) { |
| 272 bool handled = true; | 317 bool handled = true; |
| (...skipping 16 matching lines...) Expand all Loading... | |
| 289 UMA_HISTOGRAM_COUNTS_100( | 334 UMA_HISTOGRAM_COUNTS_100( |
| 290 "Extensions.ActiveScriptController.PermittedExtensions", | 335 "Extensions.ActiveScriptController.PermittedExtensions", |
| 291 permitted_extensions_.size()); | 336 permitted_extensions_.size()); |
| 292 UMA_HISTOGRAM_COUNTS_100( | 337 UMA_HISTOGRAM_COUNTS_100( |
| 293 "Extensions.ActiveScriptController.DeniedExtensions", | 338 "Extensions.ActiveScriptController.DeniedExtensions", |
| 294 pending_requests_.size()); | 339 pending_requests_.size()); |
| 295 } | 340 } |
| 296 } | 341 } |
| 297 | 342 |
| 298 } // namespace extensions | 343 } // namespace extensions |
| OLD | NEW |