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