• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
31 namespace extensions {
32 
PendingRequest()33 ActiveScriptController::PendingRequest::PendingRequest() :
34     page_id(-1) {
35 }
36 
PendingRequest(const base::Closure & closure,int page_id)37 ActiveScriptController::PendingRequest::PendingRequest(
38     const base::Closure& closure,
39     int page_id)
40     : closure(closure),
41       page_id(page_id) {
42 }
43 
~PendingRequest()44 ActiveScriptController::PendingRequest::~PendingRequest() {
45 }
46 
ActiveScriptController(content::WebContents * web_contents)47 ActiveScriptController::ActiveScriptController(
48     content::WebContents* web_contents)
49     : content::WebContentsObserver(web_contents),
50       enabled_(FeatureSwitch::scripts_require_action()->IsEnabled()) {
51   CHECK(web_contents);
52 }
53 
~ActiveScriptController()54 ActiveScriptController::~ActiveScriptController() {
55   LogUMA();
56 }
57 
58 // static
GetForWebContents(content::WebContents * web_contents)59 ActiveScriptController* 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 
RequiresUserConsentForScriptInjection(const Extension * extension)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 
RequestScriptInjection(const Extension * extension,int page_id,const base::Closure & callback)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 
OnActiveTabPermissionGranted(const Extension * extension)107 void ActiveScriptController::OnActiveTabPermissionGranted(
108     const Extension* extension) {
109   RunPendingForExtension(extension);
110 }
111 
OnAdInjectionDetected(const std::set<std::string> & ad_injectors)112 void 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 
GetActionForExtension(const Extension * extension)130 ExtensionAction* 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 
OnClicked(const Extension * extension)158 LocationBarController::Action ActiveScriptController::OnClicked(
159     const Extension* extension) {
160   DCHECK(ContainsKey(pending_requests_, extension->id()));
161   RunPendingForExtension(extension);
162   return LocationBarController::ACTION_NONE;
163 }
164 
OnNavigated()165 void ActiveScriptController::OnNavigated() {
166   LogUMA();
167   permitted_extensions_.clear();
168   pending_requests_.clear();
169 }
170 
OnExtensionUnloaded(const Extension * extension)171 void 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 
RunPendingForExtension(const Extension * extension)177 void 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 
OnRequestContentScriptPermission(const std::string & extension_id,int page_id,int request_id)222 void 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 
GrantContentScriptPermission(int request_id)262 void 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 
OnMessageReceived(const IPC::Message & message)272 bool 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 
LogUMA() const282 void 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