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.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/tab_contents/tab_util.h"
     16 #include "chrome/common/content_settings.h"
     17 #include "chrome/common/pref_names.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                         PermissionDecidedCallback callback);
     46   ~PendingInfoBarRequest();
     47 
     48   bool IsForPair(const GURL& requesting_frame,
     49                  const GURL& embedder) const;
     50 
     51   const PermissionRequestID& id() const { return id_; }
     52   const GURL& requesting_frame() const { return requesting_frame_; }
     53   bool has_infobar() const { return !!infobar_; }
     54   InfoBar* infobar() { return infobar_; }
     55 
     56   void RunCallback(bool allowed);
     57   void CreateInfoBar(PermissionQueueController* controller,
     58                      const std::string& display_languages);
     59 
     60  private:
     61   ContentSettingsType type_;
     62   PermissionRequestID id_;
     63   GURL requesting_frame_;
     64   GURL embedder_;
     65   PermissionDecidedCallback callback_;
     66   InfoBar* infobar_;
     67 
     68   // Purposefully do not disable copying, as this is stored in STL containers.
     69 };
     70 
     71 PermissionQueueController::PendingInfoBarRequest::PendingInfoBarRequest(
     72     ContentSettingsType type,
     73     const PermissionRequestID& id,
     74     const GURL& requesting_frame,
     75     const GURL& embedder,
     76     PermissionDecidedCallback callback)
     77     : type_(type),
     78       id_(id),
     79       requesting_frame_(requesting_frame),
     80       embedder_(embedder),
     81       callback_(callback),
     82       infobar_(NULL) {
     83 }
     84 
     85 PermissionQueueController::PendingInfoBarRequest::~PendingInfoBarRequest() {
     86 }
     87 
     88 bool PermissionQueueController::PendingInfoBarRequest::IsForPair(
     89     const GURL& requesting_frame,
     90     const GURL& embedder) const {
     91   return (requesting_frame_ == requesting_frame) && (embedder_ == embedder);
     92 }
     93 
     94 void PermissionQueueController::PendingInfoBarRequest::RunCallback(
     95     bool allowed) {
     96   callback_.Run(allowed);
     97 }
     98 
     99 void PermissionQueueController::PendingInfoBarRequest::CreateInfoBar(
    100     PermissionQueueController* controller,
    101     const std::string& display_languages) {
    102   // TODO(toyoshim): Remove following ContentType dependent code.
    103   // Also these InfoBarDelegate can share much more code each other.
    104   // http://crbug.com/266743
    105   switch (type_) {
    106     case CONTENT_SETTINGS_TYPE_GEOLOCATION:
    107       infobar_ = GeolocationInfoBarDelegate::Create(
    108           GetInfoBarService(id_), controller, id_, requesting_frame_,
    109           display_languages);
    110       break;
    111     case CONTENT_SETTINGS_TYPE_MIDI_SYSEX:
    112       infobar_ = MIDIPermissionInfoBarDelegate::Create(
    113           GetInfoBarService(id_), controller, id_, requesting_frame_,
    114           display_languages);
    115       break;
    116 #if defined(OS_ANDROID)
    117     case CONTENT_SETTINGS_TYPE_PROTECTED_MEDIA_IDENTIFIER:
    118       infobar_ = ProtectedMediaIdentifierInfoBarDelegate::Create(
    119           GetInfoBarService(id_), controller, id_, requesting_frame_,
    120           display_languages);
    121       break;
    122 #endif
    123     default:
    124       NOTREACHED();
    125       break;
    126   }
    127 }
    128 
    129 
    130 PermissionQueueController::PermissionQueueController(Profile* profile,
    131                                                      ContentSettingsType type)
    132     : profile_(profile),
    133       type_(type),
    134       in_shutdown_(false) {
    135 }
    136 
    137 PermissionQueueController::~PermissionQueueController() {
    138   // Cancel all outstanding requests.
    139   in_shutdown_ = true;
    140   while (!pending_infobar_requests_.empty())
    141     CancelInfoBarRequest(pending_infobar_requests_.front().id());
    142 }
    143 
    144 void PermissionQueueController::CreateInfoBarRequest(
    145     const PermissionRequestID& id,
    146     const GURL& requesting_frame,
    147     const GURL& embedder,
    148     PermissionDecidedCallback callback) {
    149   DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
    150 
    151   // We shouldn't get duplicate requests.
    152   for (PendingInfoBarRequests::const_iterator i(
    153            pending_infobar_requests_.begin());
    154        i != pending_infobar_requests_.end(); ++i)
    155     DCHECK(!i->id().Equals(id));
    156 
    157   pending_infobar_requests_.push_back(PendingInfoBarRequest(
    158       type_, id, requesting_frame, embedder, 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 
    187   if (update_content_setting)
    188     UpdateContentSetting(requesting_frame, embedder, allowed);
    189 
    190   // Cancel this request first, then notify listeners.  TODO(pkasting): Why
    191   // is this order important?
    192   PendingInfoBarRequests requests_to_notify;
    193   PendingInfoBarRequests infobars_to_remove;
    194   for (PendingInfoBarRequests::iterator i = pending_infobar_requests_.begin();
    195        i != pending_infobar_requests_.end(); ) {
    196     if (i->IsForPair(requesting_frame, embedder)) {
    197       requests_to_notify.push_back(*i);
    198       if (i->id().Equals(id)) {
    199         // The infobar that called us is i->infobar(), and its delegate is
    200         // currently in either Accept() or Cancel(). This means that
    201         // RemoveInfoBar() will be called later on, and that will trigger a
    202         // notification we're observing.
    203         ++i;
    204       } else if (i->has_infobar()) {
    205         // This infobar is for the same frame/embedder pair, but in a different
    206         // tab. We should remove it now that we've got an answer for it.
    207         infobars_to_remove.push_back(*i);
    208         ++i;
    209       } else {
    210         // We haven't created an infobar yet, just remove the pending request.
    211         i = pending_infobar_requests_.erase(i);
    212       }
    213     } else {
    214       ++i;
    215     }
    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 
    229 void PermissionQueueController::Observe(
    230     int type,
    231     const content::NotificationSource& source,
    232     const content::NotificationDetails& details) {
    233   DCHECK_EQ(chrome::NOTIFICATION_TAB_CONTENTS_INFOBAR_REMOVED, type);
    234   // We will receive this notification for all infobar closures, so we need to
    235   // check whether this is the geolocation infobar we're tracking. Note that the
    236   // InfoBarContainer (if any) may have received this notification before us and
    237   // caused the infobar to be deleted, so it's not safe to dereference the
    238   // contents of the infobar. The address of the infobar, however, is OK to
    239   // use to find the PendingInfoBarRequest to remove because
    240   // pending_infobar_requests_ will not have received any new entries between
    241   // the NotificationService's call to InfoBarContainer::Observe and this
    242   // method.
    243   InfoBar* infobar = content::Details<InfoBar::RemovedDetails>(details)->first;
    244   for (PendingInfoBarRequests::iterator i = pending_infobar_requests_.begin();
    245        i != pending_infobar_requests_.end(); ++i) {
    246     if (i->infobar() == infobar) {
    247       PermissionRequestID id(i->id());
    248       pending_infobar_requests_.erase(i);
    249       ShowQueuedInfoBarForTab(id);
    250       return;
    251     }
    252   }
    253 }
    254 
    255 bool PermissionQueueController::AlreadyShowingInfoBarForTab(
    256     const PermissionRequestID& id) const {
    257   for (PendingInfoBarRequests::const_iterator i(
    258            pending_infobar_requests_.begin());
    259        i != pending_infobar_requests_.end(); ++i) {
    260     if (i->id().IsForSameTabAs(id) && i->has_infobar())
    261       return true;
    262   }
    263   return false;
    264 }
    265 
    266 void PermissionQueueController::ShowQueuedInfoBarForTab(
    267     const PermissionRequestID& id) {
    268   DCHECK(!AlreadyShowingInfoBarForTab(id));
    269 
    270   // We can get here for example during tab shutdown, when the InfoBarService is
    271   // removing all existing infobars, thus calling back to Observe(). In this
    272   // case the service still exists, and is supplied as the source of the
    273   // notification we observed, but is no longer accessible from its WebContents.
    274   // In this case we should just go ahead and cancel further infobars for this
    275   // tab instead of trying to access the service.
    276   //
    277   // Similarly, if we're being destroyed, we should also avoid showing further
    278   // infobars.
    279   InfoBarService* infobar_service = GetInfoBarService(id);
    280   if (!infobar_service || in_shutdown_) {
    281     ClearPendingInfoBarRequestsForTab(id);
    282     return;
    283   }
    284 
    285   for (PendingInfoBarRequests::iterator i = pending_infobar_requests_.begin();
    286        i != pending_infobar_requests_.end(); ++i) {
    287     if (i->id().IsForSameTabAs(id) && !i->has_infobar()) {
    288       RegisterForInfoBarNotifications(infobar_service);
    289       i->CreateInfoBar(
    290           this, profile_->GetPrefs()->GetString(prefs::kAcceptLanguages));
    291       return;
    292     }
    293   }
    294 
    295   UnregisterForInfoBarNotifications(infobar_service);
    296 }
    297 
    298 void PermissionQueueController::ClearPendingInfoBarRequestsForTab(
    299     const PermissionRequestID& id) {
    300   for (PendingInfoBarRequests::iterator i = pending_infobar_requests_.begin();
    301        i != pending_infobar_requests_.end(); ) {
    302     if (i->id().IsForSameTabAs(id)) {
    303       DCHECK(!i->has_infobar());
    304       i = pending_infobar_requests_.erase(i);
    305     } else {
    306       ++i;
    307     }
    308   }
    309 }
    310 
    311 void PermissionQueueController::RegisterForInfoBarNotifications(
    312     InfoBarService* infobar_service) {
    313   if (!registrar_.IsRegistered(
    314       this, chrome::NOTIFICATION_TAB_CONTENTS_INFOBAR_REMOVED,
    315       content::Source<InfoBarService>(infobar_service))) {
    316     registrar_.Add(this,
    317                    chrome::NOTIFICATION_TAB_CONTENTS_INFOBAR_REMOVED,
    318                    content::Source<InfoBarService>(infobar_service));
    319   }
    320 }
    321 
    322 void PermissionQueueController::UnregisterForInfoBarNotifications(
    323     InfoBarService* infobar_service) {
    324   if (registrar_.IsRegistered(
    325       this, chrome::NOTIFICATION_TAB_CONTENTS_INFOBAR_REMOVED,
    326       content::Source<InfoBarService>(infobar_service))) {
    327     registrar_.Remove(this,
    328                       chrome::NOTIFICATION_TAB_CONTENTS_INFOBAR_REMOVED,
    329                       content::Source<InfoBarService>(infobar_service));
    330   }
    331 }
    332 
    333 void PermissionQueueController::UpdateContentSetting(
    334     const GURL& requesting_frame,
    335     const GURL& embedder,
    336     bool allowed) {
    337   if (requesting_frame.GetOrigin().SchemeIsFile()) {
    338     // Chrome can be launched with --disable-web-security which allows
    339     // geolocation requests from file:// URLs. We don't want to store these
    340     // in the host content settings map.
    341     return;
    342   }
    343 
    344   ContentSetting content_setting =
    345       allowed ? CONTENT_SETTING_ALLOW : CONTENT_SETTING_BLOCK;
    346   profile_->GetHostContentSettingsMap()->SetContentSetting(
    347       ContentSettingsPattern::FromURLNoWildcard(requesting_frame.GetOrigin()),
    348       ContentSettingsPattern::FromURLNoWildcard(embedder.GetOrigin()),
    349       type_,
    350       std::string(),
    351       content_setting);
    352 }
    353