// Copyright 2014 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "chrome/browser/extensions/active_script_controller.h" #include "base/bind.h" #include "base/bind_helpers.h" #include "base/memory/scoped_ptr.h" #include "base/metrics/histogram.h" #include "base/stl_util.h" #include "chrome/browser/extensions/active_tab_permission_granter.h" #include "chrome/browser/extensions/extension_action.h" #include "chrome/browser/extensions/extension_util.h" #include "chrome/browser/extensions/location_bar_controller.h" #include "chrome/browser/extensions/tab_helper.h" #include "chrome/browser/sessions/session_id.h" #include "chrome/common/extensions/api/extension_action/action_info.h" #include "content/public/browser/navigation_controller.h" #include "content/public/browser/navigation_entry.h" #include "content/public/browser/render_view_host.h" #include "content/public/browser/web_contents.h" #include "extensions/browser/extension_registry.h" #include "extensions/common/extension.h" #include "extensions/common/extension_messages.h" #include "extensions/common/extension_set.h" #include "extensions/common/feature_switch.h" #include "extensions/common/permissions/permissions_data.h" #include "ipc/ipc_message_macros.h" namespace extensions { ActiveScriptController::PendingRequest::PendingRequest() : page_id(-1) { } ActiveScriptController::PendingRequest::PendingRequest( const base::Closure& closure, int page_id) : closure(closure), page_id(page_id) { } ActiveScriptController::PendingRequest::~PendingRequest() { } ActiveScriptController::ActiveScriptController( content::WebContents* web_contents) : content::WebContentsObserver(web_contents), enabled_(FeatureSwitch::scripts_require_action()->IsEnabled()) { CHECK(web_contents); } ActiveScriptController::~ActiveScriptController() { LogUMA(); } // static ActiveScriptController* ActiveScriptController::GetForWebContents( content::WebContents* web_contents) { if (!web_contents) return NULL; TabHelper* tab_helper = TabHelper::FromWebContents(web_contents); if (!tab_helper) return NULL; LocationBarController* location_bar_controller = tab_helper->location_bar_controller(); // This should never be NULL. DCHECK(location_bar_controller); return location_bar_controller->active_script_controller(); } bool ActiveScriptController::RequiresUserConsentForScriptInjection( const Extension* extension) { CHECK(extension); if (!extension->permissions_data()->RequiresActionForScriptExecution( extension, SessionID::IdForTab(web_contents()), web_contents()->GetVisibleURL()) || util::AllowedScriptingOnAllUrls(extension->id(), web_contents()->GetBrowserContext())) { return false; } // If the feature is not enabled, we automatically allow all extensions to // run scripts. if (!enabled_) permitted_extensions_.insert(extension->id()); return permitted_extensions_.count(extension->id()) == 0; } void ActiveScriptController::RequestScriptInjection( const Extension* extension, int page_id, const base::Closure& callback) { CHECK(extension); PendingRequestList& list = pending_requests_[extension->id()]; list.push_back(PendingRequest(callback, page_id)); // If this was the first entry, notify the location bar that there's a new // icon. if (list.size() == 1u) LocationBarController::NotifyChange(web_contents()); } void ActiveScriptController::OnActiveTabPermissionGranted( const Extension* extension) { RunPendingForExtension(extension); } void ActiveScriptController::OnAdInjectionDetected( const std::set<std::string>& ad_injectors) { // We're only interested in data if there are ad injectors detected. if (ad_injectors.empty()) return; size_t num_preventable_ad_injectors = base::STLSetIntersection<std::set<std::string> >( ad_injectors, permitted_extensions_).size(); UMA_HISTOGRAM_COUNTS_100( "Extensions.ActiveScriptController.PreventableAdInjectors", num_preventable_ad_injectors); UMA_HISTOGRAM_COUNTS_100( "Extensions.ActiveScriptController.UnpreventableAdInjectors", ad_injectors.size() - num_preventable_ad_injectors); } ExtensionAction* ActiveScriptController::GetActionForExtension( const Extension* extension) { if (!enabled_ || pending_requests_.count(extension->id()) == 0) return NULL; // No action for this extension. ActiveScriptMap::iterator existing = active_script_actions_.find(extension->id()); if (existing != active_script_actions_.end()) return existing->second.get(); linked_ptr<ExtensionAction> action(new ExtensionAction( extension->id(), ActionInfo::TYPE_PAGE, ActionInfo())); action->SetTitle(ExtensionAction::kDefaultTabId, extension->name()); action->SetIsVisible(ExtensionAction::kDefaultTabId, true); const ActionInfo* action_info = ActionInfo::GetPageActionInfo(extension); if (!action_info) action_info = ActionInfo::GetBrowserActionInfo(extension); if (action_info && !action_info->default_icon.empty()) { action->set_default_icon( make_scoped_ptr(new ExtensionIconSet(action_info->default_icon))); } active_script_actions_[extension->id()] = action; return action.get(); } LocationBarController::Action ActiveScriptController::OnClicked( const Extension* extension) { DCHECK(ContainsKey(pending_requests_, extension->id())); RunPendingForExtension(extension); return LocationBarController::ACTION_NONE; } void ActiveScriptController::OnNavigated() { LogUMA(); permitted_extensions_.clear(); pending_requests_.clear(); } void ActiveScriptController::OnExtensionUnloaded(const Extension* extension) { PendingRequestMap::iterator iter = pending_requests_.find(extension->id()); if (iter != pending_requests_.end()) pending_requests_.erase(iter); } void ActiveScriptController::RunPendingForExtension( const Extension* extension) { DCHECK(extension); PendingRequestMap::iterator iter = pending_requests_.find(extension->id()); if (iter == pending_requests_.end()) return; content::NavigationEntry* visible_entry = web_contents()->GetController().GetVisibleEntry(); // Refuse to run if there's no visible entry, because we have no idea of // determining if it's the proper page. This should rarely, if ever, happen. if (!visible_entry) return; int page_id = visible_entry->GetPageID(); // We add this to the list of permitted extensions and erase pending entries // *before* running them to guard against the crazy case where running the // callbacks adds more entries. permitted_extensions_.insert(extension->id()); PendingRequestList requests; iter->second.swap(requests); pending_requests_.erase(extension->id()); // Clicking to run the extension counts as granting it permission to run on // the given tab. // The extension may already have active tab at this point, but granting // it twice is essentially a no-op. TabHelper::FromWebContents(web_contents())-> active_tab_permission_granter()->GrantIfRequested(extension); // Run all pending injections for the given extension. for (PendingRequestList::iterator request = requests.begin(); request != requests.end(); ++request) { // Only run if it's on the proper page. if (request->page_id == page_id) request->closure.Run(); } // Inform the location bar that the action is now gone. LocationBarController::NotifyChange(web_contents()); } void ActiveScriptController::OnRequestContentScriptPermission( const std::string& extension_id, int page_id, int request_id) { if (!Extension::IdIsValid(extension_id)) { NOTREACHED() << "'" << extension_id << "' is not a valid id."; return; } const Extension* extension = ExtensionRegistry::Get(web_contents()->GetBrowserContext()) ->enabled_extensions().GetByID(extension_id); // We shouldn't allow extensions which are no longer enabled to run any // scripts. Ignore the request. if (!extension) return; // If the request id is -1, that signals that the content script has already // ran (because this feature is not enabled). Add the extension to the list of // permitted extensions (for metrics), and return immediately. if (request_id == -1) { DCHECK(!enabled_); permitted_extensions_.insert(extension->id()); return; } if (RequiresUserConsentForScriptInjection(extension)) { // This base::Unretained() is safe, because the callback is only invoked by // this object. RequestScriptInjection( extension, page_id, base::Bind(&ActiveScriptController::GrantContentScriptPermission, base::Unretained(this), request_id)); } else { GrantContentScriptPermission(request_id); } } void ActiveScriptController::GrantContentScriptPermission(int request_id) { content::RenderViewHost* render_view_host = web_contents()->GetRenderViewHost(); if (render_view_host) { render_view_host->Send(new ExtensionMsg_GrantContentScriptPermission( render_view_host->GetRoutingID(), request_id)); } } bool ActiveScriptController::OnMessageReceived(const IPC::Message& message) { bool handled = true; IPC_BEGIN_MESSAGE_MAP(ActiveScriptController, message) IPC_MESSAGE_HANDLER(ExtensionHostMsg_RequestContentScriptPermission, OnRequestContentScriptPermission) IPC_MESSAGE_UNHANDLED(handled = false) IPC_END_MESSAGE_MAP() return handled; } void ActiveScriptController::LogUMA() const { UMA_HISTOGRAM_COUNTS_100( "Extensions.ActiveScriptController.ShownActiveScriptsOnPage", pending_requests_.size()); // We only log the permitted extensions metric if the feature is enabled, // because otherwise the data will be boring (100% allowed). if (enabled_) { UMA_HISTOGRAM_COUNTS_100( "Extensions.ActiveScriptController.PermittedExtensions", permitted_extensions_.size()); UMA_HISTOGRAM_COUNTS_100( "Extensions.ActiveScriptController.DeniedExtensions", pending_requests_.size()); } } } // namespace extensions