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