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. 4 5#include "chrome/browser/extensions/active_script_controller.h" 6 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/extension_action.h" 14#include "chrome/browser/extensions/extension_util.h" 15#include "chrome/browser/extensions/location_bar_controller.h" 16#include "chrome/browser/extensions/tab_helper.h" 17#include "chrome/browser/sessions/session_id.h" 18#include "chrome/common/extensions/api/extension_action/action_info.h" 19#include "content/public/browser/navigation_controller.h" 20#include "content/public/browser/navigation_entry.h" 21#include "content/public/browser/render_view_host.h" 22#include "content/public/browser/web_contents.h" 23#include "extensions/browser/extension_registry.h" 24#include "extensions/common/extension.h" 25#include "extensions/common/extension_messages.h" 26#include "extensions/common/extension_set.h" 27#include "extensions/common/feature_switch.h" 28#include "extensions/common/permissions/permissions_data.h" 29#include "ipc/ipc_message_macros.h" 30 31namespace extensions { 32 33ActiveScriptController::PendingRequest::PendingRequest() : 34 page_id(-1) { 35} 36 37ActiveScriptController::PendingRequest::PendingRequest( 38 const base::Closure& closure, 39 int page_id) 40 : closure(closure), 41 page_id(page_id) { 42} 43 44ActiveScriptController::PendingRequest::~PendingRequest() { 45} 46 47ActiveScriptController::ActiveScriptController( 48 content::WebContents* web_contents) 49 : content::WebContentsObserver(web_contents), 50 enabled_(FeatureSwitch::scripts_require_action()->IsEnabled()) { 51 CHECK(web_contents); 52} 53 54ActiveScriptController::~ActiveScriptController() { 55 LogUMA(); 56} 57 58// static 59ActiveScriptController* ActiveScriptController::GetForWebContents( 60 content::WebContents* web_contents) { 61 if (!web_contents) 62 return NULL; 63 TabHelper* tab_helper = TabHelper::FromWebContents(web_contents); 64 if (!tab_helper) 65 return NULL; 66 LocationBarController* location_bar_controller = 67 tab_helper->location_bar_controller(); 68 // This should never be NULL. 69 DCHECK(location_bar_controller); 70 return location_bar_controller->active_script_controller(); 71} 72 73bool 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 93void 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 107void ActiveScriptController::OnActiveTabPermissionGranted( 108 const Extension* extension) { 109 RunPendingForExtension(extension); 110} 111 112void ActiveScriptController::OnAdInjectionDetected( 113 const std::set<std::string>& ad_injectors) { 114 // We're only interested in data if there are ad injectors detected. 115 if (ad_injectors.empty()) 116 return; 117 118 size_t num_preventable_ad_injectors = 119 base::STLSetIntersection<std::set<std::string> >( 120 ad_injectors, permitted_extensions_).size(); 121 122 UMA_HISTOGRAM_COUNTS_100( 123 "Extensions.ActiveScriptController.PreventableAdInjectors", 124 num_preventable_ad_injectors); 125 UMA_HISTOGRAM_COUNTS_100( 126 "Extensions.ActiveScriptController.UnpreventableAdInjectors", 127 ad_injectors.size() - num_preventable_ad_injectors); 128} 129 130ExtensionAction* ActiveScriptController::GetActionForExtension( 131 const Extension* extension) { 132 if (!enabled_ || pending_requests_.count(extension->id()) == 0) 133 return NULL; // No action for this extension. 134 135 ActiveScriptMap::iterator existing = 136 active_script_actions_.find(extension->id()); 137 if (existing != active_script_actions_.end()) 138 return existing->second.get(); 139 140 linked_ptr<ExtensionAction> action(new ExtensionAction( 141 extension->id(), ActionInfo::TYPE_PAGE, ActionInfo())); 142 action->SetTitle(ExtensionAction::kDefaultTabId, extension->name()); 143 action->SetIsVisible(ExtensionAction::kDefaultTabId, true); 144 145 const ActionInfo* action_info = ActionInfo::GetPageActionInfo(extension); 146 if (!action_info) 147 action_info = ActionInfo::GetBrowserActionInfo(extension); 148 149 if (action_info && !action_info->default_icon.empty()) { 150 action->set_default_icon( 151 make_scoped_ptr(new ExtensionIconSet(action_info->default_icon))); 152 } 153 154 active_script_actions_[extension->id()] = action; 155 return action.get(); 156} 157 158LocationBarController::Action ActiveScriptController::OnClicked( 159 const Extension* extension) { 160 DCHECK(ContainsKey(pending_requests_, extension->id())); 161 RunPendingForExtension(extension); 162 return LocationBarController::ACTION_NONE; 163} 164 165void ActiveScriptController::OnNavigated() { 166 LogUMA(); 167 permitted_extensions_.clear(); 168 pending_requests_.clear(); 169} 170 171void ActiveScriptController::OnExtensionUnloaded(const Extension* extension) { 172 PendingRequestMap::iterator iter = pending_requests_.find(extension->id()); 173 if (iter != pending_requests_.end()) 174 pending_requests_.erase(iter); 175} 176 177void ActiveScriptController::RunPendingForExtension( 178 const Extension* extension) { 179 DCHECK(extension); 180 PendingRequestMap::iterator iter = 181 pending_requests_.find(extension->id()); 182 if (iter == pending_requests_.end()) 183 return; 184 185 content::NavigationEntry* visible_entry = 186 web_contents()->GetController().GetVisibleEntry(); 187 // 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. 189 if (!visible_entry) 190 return; 191 192 int page_id = visible_entry->GetPageID(); 193 194 // 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 196 // callbacks adds more entries. 197 permitted_extensions_.insert(extension->id()); 198 PendingRequestList requests; 199 iter->second.swap(requests); 200 pending_requests_.erase(extension->id()); 201 202 // Clicking to run the extension counts as granting it permission to run on 203 // the given tab. 204 // The extension may already have active tab at this point, but granting 205 // it twice is essentially a no-op. 206 TabHelper::FromWebContents(web_contents())-> 207 active_tab_permission_granter()->GrantIfRequested(extension); 208 209 // Run all pending injections for the given extension. 210 for (PendingRequestList::iterator request = requests.begin(); 211 request != requests.end(); 212 ++request) { 213 // Only run if it's on the proper page. 214 if (request->page_id == page_id) 215 request->closure.Run(); 216 } 217 218 // Inform the location bar that the action is now gone. 219 LocationBarController::NotifyChange(web_contents()); 220} 221 222void ActiveScriptController::OnRequestContentScriptPermission( 223 const std::string& extension_id, 224 int page_id, 225 int request_id) { 226 if (!Extension::IdIsValid(extension_id)) { 227 NOTREACHED() << "'" << extension_id << "' is not a valid id."; 228 return; 229 } 230 231 const Extension* extension = 232 ExtensionRegistry::Get(web_contents()->GetBrowserContext()) 233 ->enabled_extensions().GetByID(extension_id); 234 // We shouldn't allow extensions which are no longer enabled to run any 235 // scripts. Ignore the request. 236 if (!extension) 237 return; 238 239 // 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 241 // permitted extensions (for metrics), and return immediately. 242 if (request_id == -1) { 243 DCHECK(!enabled_); 244 permitted_extensions_.insert(extension->id()); 245 return; 246 } 247 248 if (RequiresUserConsentForScriptInjection(extension)) { 249 // This base::Unretained() is safe, because the callback is only invoked by 250 // this object. 251 RequestScriptInjection( 252 extension, 253 page_id, 254 base::Bind(&ActiveScriptController::GrantContentScriptPermission, 255 base::Unretained(this), 256 request_id)); 257 } else { 258 GrantContentScriptPermission(request_id); 259 } 260} 261 262void ActiveScriptController::GrantContentScriptPermission(int request_id) { 263 content::RenderViewHost* render_view_host = 264 web_contents()->GetRenderViewHost(); 265 if (render_view_host) { 266 render_view_host->Send(new ExtensionMsg_GrantContentScriptPermission( 267 render_view_host->GetRoutingID(), 268 request_id)); 269 } 270} 271 272bool ActiveScriptController::OnMessageReceived(const IPC::Message& message) { 273 bool handled = true; 274 IPC_BEGIN_MESSAGE_MAP(ActiveScriptController, message) 275 IPC_MESSAGE_HANDLER(ExtensionHostMsg_RequestContentScriptPermission, 276 OnRequestContentScriptPermission) 277 IPC_MESSAGE_UNHANDLED(handled = false) 278 IPC_END_MESSAGE_MAP() 279 return handled; 280} 281 282void ActiveScriptController::LogUMA() const { 283 UMA_HISTOGRAM_COUNTS_100( 284 "Extensions.ActiveScriptController.ShownActiveScriptsOnPage", 285 pending_requests_.size()); 286 287 // We only log the permitted extensions metric if the feature is enabled, 288 // because otherwise the data will be boring (100% allowed). 289 if (enabled_) { 290 UMA_HISTOGRAM_COUNTS_100( 291 "Extensions.ActiveScriptController.PermittedExtensions", 292 permitted_extensions_.size()); 293 UMA_HISTOGRAM_COUNTS_100( 294 "Extensions.ActiveScriptController.DeniedExtensions", 295 pending_requests_.size()); 296 } 297} 298 299} // namespace extensions 300