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