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