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