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