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