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