permission_queue_controller.cc revision 116680a4aac90f2aa7413d9095a592090648e557
1// Copyright 2013 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/content_settings/permission_queue_controller.h"
6
7#include "base/prefs/pref_service.h"
8#include "chrome/browser/chrome_notification_types.h"
9#include "chrome/browser/content_settings/host_content_settings_map.h"
10#include "chrome/browser/content_settings/permission_context_uma_util.h"
11#include "chrome/browser/geolocation/geolocation_infobar_delegate.h"
12#include "chrome/browser/infobars/infobar_service.h"
13#include "chrome/browser/media/midi_permission_infobar_delegate.h"
14#include "chrome/browser/profiles/profile.h"
15#include "chrome/browser/services/gcm/push_messaging_infobar_delegate.h"
16#include "chrome/browser/tab_contents/tab_util.h"
17#include "chrome/common/content_settings.h"
18#include "chrome/common/pref_names.h"
19#include "components/infobars/core/infobar.h"
20#include "content/public/browser/browser_thread.h"
21#include "content/public/browser/notification_details.h"
22#include "content/public/browser/notification_source.h"
23#include "content/public/browser/notification_types.h"
24#include "content/public/browser/web_contents.h"
25#include "content/public/common/url_constants.h"
26
27#if defined(OS_ANDROID)
28#include "chrome/browser/media/protected_media_identifier_infobar_delegate.h"
29#endif
30
31namespace {
32
33InfoBarService* GetInfoBarService(const PermissionRequestID& id) {
34  content::WebContents* web_contents =
35      tab_util::GetWebContentsByID(id.render_process_id(), id.render_view_id());
36  return web_contents ? InfoBarService::FromWebContents(web_contents) : NULL;
37}
38
39}
40
41
42class PermissionQueueController::PendingInfobarRequest {
43 public:
44  PendingInfobarRequest(ContentSettingsType type,
45                        const PermissionRequestID& id,
46                        const GURL& requesting_frame,
47                        const GURL& embedder,
48                        const std::string& accept_button_label,
49                        PermissionDecidedCallback callback);
50  ~PendingInfobarRequest();
51
52  bool IsForPair(const GURL& requesting_frame,
53                 const GURL& embedder) const;
54
55  const PermissionRequestID& id() const { return id_; }
56  const GURL& requesting_frame() const { return requesting_frame_; }
57  bool has_infobar() const { return !!infobar_; }
58  infobars::InfoBar* infobar() { return infobar_; }
59
60  void RunCallback(bool allowed);
61  void CreateInfoBar(PermissionQueueController* controller,
62                     const std::string& display_languages);
63
64 private:
65  ContentSettingsType type_;
66  PermissionRequestID id_;
67  GURL requesting_frame_;
68  GURL embedder_;
69  std::string accept_button_label_;
70  PermissionDecidedCallback callback_;
71  infobars::InfoBar* infobar_;
72
73  // Purposefully do not disable copying, as this is stored in STL containers.
74};
75
76PermissionQueueController::PendingInfobarRequest::PendingInfobarRequest(
77    ContentSettingsType type,
78    const PermissionRequestID& id,
79    const GURL& requesting_frame,
80    const GURL& embedder,
81    const std::string& accept_button_label,
82    PermissionDecidedCallback callback)
83    : type_(type),
84      id_(id),
85      requesting_frame_(requesting_frame),
86      embedder_(embedder),
87      accept_button_label_(accept_button_label),
88      callback_(callback),
89      infobar_(NULL) {
90}
91
92PermissionQueueController::PendingInfobarRequest::~PendingInfobarRequest() {
93}
94
95bool PermissionQueueController::PendingInfobarRequest::IsForPair(
96    const GURL& requesting_frame,
97    const GURL& embedder) const {
98  return (requesting_frame_ == requesting_frame) && (embedder_ == embedder);
99}
100
101void PermissionQueueController::PendingInfobarRequest::RunCallback(
102    bool allowed) {
103  callback_.Run(allowed);
104}
105
106void PermissionQueueController::PendingInfobarRequest::CreateInfoBar(
107    PermissionQueueController* controller,
108    const std::string& display_languages) {
109  switch (type_) {
110    case CONTENT_SETTINGS_TYPE_GEOLOCATION:
111      infobar_ = GeolocationInfoBarDelegate::Create(
112          GetInfoBarService(id_), controller, id_, requesting_frame_,
113          display_languages, accept_button_label_);
114      break;
115    case CONTENT_SETTINGS_TYPE_MIDI_SYSEX:
116      infobar_ = MidiPermissionInfoBarDelegate::Create(
117          GetInfoBarService(id_), controller, id_, requesting_frame_,
118          display_languages, type_);
119      break;
120    case CONTENT_SETTINGS_TYPE_PUSH_MESSAGING:
121      infobar_ = gcm::PushMessagingInfoBarDelegate::Create(
122          GetInfoBarService(id_), controller, id_, requesting_frame_,
123          display_languages, type_);
124      break;
125#if defined(OS_ANDROID)
126    case CONTENT_SETTINGS_TYPE_PROTECTED_MEDIA_IDENTIFIER:
127      infobar_ = ProtectedMediaIdentifierInfoBarDelegate::Create(
128          GetInfoBarService(id_), controller, id_, requesting_frame_,
129          display_languages);
130      break;
131#endif
132    default:
133      NOTREACHED();
134      break;
135  }
136}
137
138
139PermissionQueueController::PermissionQueueController(Profile* profile,
140                                                     ContentSettingsType type)
141    : profile_(profile),
142      type_(type),
143      in_shutdown_(false) {
144}
145
146PermissionQueueController::~PermissionQueueController() {
147  // Cancel all outstanding requests.
148  in_shutdown_ = true;
149  while (!pending_infobar_requests_.empty())
150    CancelInfoBarRequest(pending_infobar_requests_.front().id());
151}
152
153void PermissionQueueController::CreateInfoBarRequest(
154    const PermissionRequestID& id,
155    const GURL& requesting_frame,
156    const GURL& embedder,
157    const std::string& accept_button_label,
158    PermissionDecidedCallback callback) {
159  DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
160
161  if (requesting_frame.SchemeIs(content::kChromeUIScheme) ||
162      embedder.SchemeIs(content::kChromeUIScheme))
163    return;
164
165  pending_infobar_requests_.push_back(PendingInfobarRequest(
166      type_, id, requesting_frame, embedder,
167      accept_button_label, callback));
168  if (!AlreadyShowingInfoBarForTab(id))
169    ShowQueuedInfoBarForTab(id);
170}
171
172void PermissionQueueController::CancelInfoBarRequest(
173    const PermissionRequestID& id) {
174  DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
175
176  for (PendingInfobarRequests::iterator i(pending_infobar_requests_.begin());
177       i != pending_infobar_requests_.end(); ++i) {
178    if (i->id().Equals(id)) {
179      if (i->has_infobar())
180        GetInfoBarService(id)->RemoveInfoBar(i->infobar());
181      else
182        pending_infobar_requests_.erase(i);
183      return;
184    }
185  }
186}
187
188void PermissionQueueController::OnPermissionSet(
189    const PermissionRequestID& id,
190    const GURL& requesting_frame,
191    const GURL& embedder,
192    bool update_content_setting,
193    bool allowed) {
194  DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
195
196  // TODO(miguelg): move the permission persistence to
197  // PermissionContextBase once all the types are moved there.
198  if (update_content_setting) {
199    UpdateContentSetting(requesting_frame, embedder, allowed);
200    if (allowed)
201      PermissionContextUmaUtil::PermissionGranted(type_);
202    else
203      PermissionContextUmaUtil::PermissionDenied(type_);
204  } else {
205    PermissionContextUmaUtil::PermissionDismissed(type_);
206  }
207
208  // Cancel this request first, then notify listeners.  TODO(pkasting): Why
209  // is this order important?
210  PendingInfobarRequests requests_to_notify;
211  PendingInfobarRequests infobars_to_remove;
212  std::vector<PendingInfobarRequests::iterator> pending_requests_to_remove;
213  for (PendingInfobarRequests::iterator i = pending_infobar_requests_.begin();
214       i != pending_infobar_requests_.end(); ++i) {
215    if (!i->IsForPair(requesting_frame, embedder))
216      continue;
217    requests_to_notify.push_back(*i);
218    if (!i->has_infobar()) {
219      // We haven't created an infobar yet, just record the pending request
220      // index and remove it later.
221      pending_requests_to_remove.push_back(i);
222      continue;
223    }
224    if (i->id().Equals(id)) {
225      // The infobar that called us is i->infobar(), and its delegate is
226      // currently in either Accept() or Cancel(). This means that
227      // RemoveInfoBar() will be called later on, and that will trigger a
228      // notification we're observing.
229      continue;
230    }
231
232    // This infobar is for the same frame/embedder pair, but in a different
233    // tab. We should remove it now that we've got an answer for it.
234    infobars_to_remove.push_back(*i);
235  }
236
237  // Remove all infobars for the same |requesting_frame| and |embedder|.
238  for (PendingInfobarRequests::iterator i = infobars_to_remove.begin();
239       i != infobars_to_remove.end(); ++i)
240    GetInfoBarService(i->id())->RemoveInfoBar(i->infobar());
241
242  // Send out the permission notifications.
243  for (PendingInfobarRequests::iterator i = requests_to_notify.begin();
244       i != requests_to_notify.end(); ++i)
245    i->RunCallback(allowed);
246
247  // Remove the pending requests in reverse order.
248  for (int i = pending_requests_to_remove.size() - 1; i >= 0; --i)
249    pending_infobar_requests_.erase(pending_requests_to_remove[i]);
250}
251
252void PermissionQueueController::Observe(
253    int type,
254    const content::NotificationSource& source,
255    const content::NotificationDetails& details) {
256  DCHECK_EQ(chrome::NOTIFICATION_TAB_CONTENTS_INFOBAR_REMOVED, type);
257  // We will receive this notification for all infobar closures, so we need to
258  // check whether this is the geolocation infobar we're tracking. Note that the
259  // InfoBarContainer (if any) may have received this notification before us and
260  // caused the infobar to be deleted, so it's not safe to dereference the
261  // contents of the infobar. The address of the infobar, however, is OK to
262  // use to find the PendingInfobarRequest to remove because
263  // pending_infobar_requests_ will not have received any new entries between
264  // the NotificationService's call to InfoBarContainer::Observe and this
265  // method.
266  infobars::InfoBar* infobar =
267      content::Details<infobars::InfoBar::RemovedDetails>(details)->first;
268  for (PendingInfobarRequests::iterator i = pending_infobar_requests_.begin();
269       i != pending_infobar_requests_.end(); ++i) {
270    if (i->infobar() == infobar) {
271      PermissionRequestID id(i->id());
272      pending_infobar_requests_.erase(i);
273      ShowQueuedInfoBarForTab(id);
274      return;
275    }
276  }
277}
278
279bool PermissionQueueController::AlreadyShowingInfoBarForTab(
280    const PermissionRequestID& id) const {
281  for (PendingInfobarRequests::const_iterator i(
282           pending_infobar_requests_.begin());
283       i != pending_infobar_requests_.end(); ++i) {
284    if (i->id().IsForSameTabAs(id) && i->has_infobar())
285      return true;
286  }
287  return false;
288}
289
290void PermissionQueueController::ShowQueuedInfoBarForTab(
291    const PermissionRequestID& id) {
292  DCHECK(!AlreadyShowingInfoBarForTab(id));
293
294  // We can get here for example during tab shutdown, when the InfoBarService is
295  // removing all existing infobars, thus calling back to Observe(). In this
296  // case the service still exists, and is supplied as the source of the
297  // notification we observed, but is no longer accessible from its WebContents.
298  // In this case we should just go ahead and cancel further infobars for this
299  // tab instead of trying to access the service.
300  //
301  // Similarly, if we're being destroyed, we should also avoid showing further
302  // infobars.
303  InfoBarService* infobar_service = GetInfoBarService(id);
304  if (!infobar_service || in_shutdown_) {
305    ClearPendingInfobarRequestsForTab(id);
306    return;
307  }
308
309  for (PendingInfobarRequests::iterator i = pending_infobar_requests_.begin();
310       i != pending_infobar_requests_.end(); ++i) {
311    if (i->id().IsForSameTabAs(id) && !i->has_infobar()) {
312      RegisterForInfoBarNotifications(infobar_service);
313      i->CreateInfoBar(
314          this, profile_->GetPrefs()->GetString(prefs::kAcceptLanguages));
315      return;
316    }
317  }
318
319  UnregisterForInfoBarNotifications(infobar_service);
320}
321
322void PermissionQueueController::ClearPendingInfobarRequestsForTab(
323    const PermissionRequestID& id) {
324  for (PendingInfobarRequests::iterator i = pending_infobar_requests_.begin();
325       i != pending_infobar_requests_.end(); ) {
326    if (i->id().IsForSameTabAs(id)) {
327      DCHECK(!i->has_infobar());
328      i = pending_infobar_requests_.erase(i);
329    } else {
330      ++i;
331    }
332  }
333}
334
335void PermissionQueueController::RegisterForInfoBarNotifications(
336    InfoBarService* infobar_service) {
337  if (!registrar_.IsRegistered(
338      this, chrome::NOTIFICATION_TAB_CONTENTS_INFOBAR_REMOVED,
339      content::Source<InfoBarService>(infobar_service))) {
340    registrar_.Add(this,
341                   chrome::NOTIFICATION_TAB_CONTENTS_INFOBAR_REMOVED,
342                   content::Source<InfoBarService>(infobar_service));
343  }
344}
345
346void PermissionQueueController::UnregisterForInfoBarNotifications(
347    InfoBarService* infobar_service) {
348  if (registrar_.IsRegistered(
349      this, chrome::NOTIFICATION_TAB_CONTENTS_INFOBAR_REMOVED,
350      content::Source<InfoBarService>(infobar_service))) {
351    registrar_.Remove(this,
352                      chrome::NOTIFICATION_TAB_CONTENTS_INFOBAR_REMOVED,
353                      content::Source<InfoBarService>(infobar_service));
354  }
355}
356
357void PermissionQueueController::UpdateContentSetting(
358    const GURL& requesting_frame,
359    const GURL& embedder,
360    bool allowed) {
361  if (requesting_frame.GetOrigin().SchemeIsFile()) {
362    // Chrome can be launched with --disable-web-security which allows
363    // geolocation requests from file:// URLs. We don't want to store these
364    // in the host content settings map.
365    return;
366  }
367
368  ContentSetting content_setting =
369      allowed ? CONTENT_SETTING_ALLOW : CONTENT_SETTING_BLOCK;
370  profile_->GetHostContentSettingsMap()->SetContentSetting(
371      ContentSettingsPattern::FromURLNoWildcard(requesting_frame.GetOrigin()),
372      ContentSettingsPattern::FromURLNoWildcard(embedder.GetOrigin()),
373      type_,
374      std::string(),
375      content_setting);
376}
377