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 32namespace { 33 34InfoBarService* 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 43class 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 75PermissionQueueController::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 89PermissionQueueController::PendingInfobarRequest::~PendingInfobarRequest() { 90} 91 92bool PermissionQueueController::PendingInfobarRequest::IsForPair( 93 const GURL& requesting_frame, 94 const GURL& embedder) const { 95 return (requesting_frame_ == requesting_frame) && (embedder_ == embedder); 96} 97 98void PermissionQueueController::PendingInfobarRequest::RunCallback( 99 bool allowed) { 100 callback_.Run(allowed); 101} 102 103void 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 143PermissionQueueController::PermissionQueueController(Profile* profile, 144 ContentSettingsType type) 145 : profile_(profile), 146 type_(type), 147 in_shutdown_(false) { 148} 149 150PermissionQueueController::~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 157void 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 174void 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 190void 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 254void 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 281bool 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 292void 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 324void 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 337void 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 348void 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 359void 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