| // Copyright 2013 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "chrome/browser/content_settings/permission_queue_controller.h" |
| |
| #include "base/prefs/pref_service.h" |
| #include "chrome/browser/chrome_notification_types.h" |
| #include "chrome/browser/content_settings/host_content_settings_map.h" |
| #include "chrome/browser/geolocation/geolocation_infobar_delegate.h" |
| #include "chrome/browser/infobars/infobar_service.h" |
| #include "chrome/browser/media/midi_permission_infobar_delegate.h" |
| #include "chrome/browser/profiles/profile.h" |
| #include "chrome/browser/tab_contents/tab_util.h" |
| #include "chrome/common/content_settings.h" |
| #include "chrome/common/pref_names.h" |
| #include "components/infobars/core/infobar.h" |
| #include "content/public/browser/browser_thread.h" |
| #include "content/public/browser/notification_details.h" |
| #include "content/public/browser/notification_source.h" |
| #include "content/public/browser/notification_types.h" |
| #include "content/public/browser/web_contents.h" |
| |
| #if defined(OS_ANDROID) |
| #include "chrome/browser/media/protected_media_identifier_infobar_delegate.h" |
| #endif |
| |
| namespace { |
| |
| InfoBarService* GetInfoBarService(const PermissionRequestID& id) { |
| content::WebContents* web_contents = |
| tab_util::GetWebContentsByID(id.render_process_id(), id.render_view_id()); |
| return web_contents ? InfoBarService::FromWebContents(web_contents) : NULL; |
| } |
| |
| } |
| |
| |
| class PermissionQueueController::PendingInfobarRequest { |
| public: |
| PendingInfobarRequest(ContentSettingsType type, |
| const PermissionRequestID& id, |
| const GURL& requesting_frame, |
| const GURL& embedder, |
| const std::string& accept_button_label, |
| PermissionDecidedCallback callback); |
| ~PendingInfobarRequest(); |
| |
| bool IsForPair(const GURL& requesting_frame, |
| const GURL& embedder) const; |
| |
| const PermissionRequestID& id() const { return id_; } |
| const GURL& requesting_frame() const { return requesting_frame_; } |
| bool has_infobar() const { return !!infobar_; } |
| infobars::InfoBar* infobar() { return infobar_; } |
| |
| void RunCallback(bool allowed); |
| void CreateInfoBar(PermissionQueueController* controller, |
| const std::string& display_languages); |
| |
| private: |
| ContentSettingsType type_; |
| PermissionRequestID id_; |
| GURL requesting_frame_; |
| GURL embedder_; |
| std::string accept_button_label_; |
| PermissionDecidedCallback callback_; |
| infobars::InfoBar* infobar_; |
| |
| // Purposefully do not disable copying, as this is stored in STL containers. |
| }; |
| |
| PermissionQueueController::PendingInfobarRequest::PendingInfobarRequest( |
| ContentSettingsType type, |
| const PermissionRequestID& id, |
| const GURL& requesting_frame, |
| const GURL& embedder, |
| const std::string& accept_button_label, |
| PermissionDecidedCallback callback) |
| : type_(type), |
| id_(id), |
| requesting_frame_(requesting_frame), |
| embedder_(embedder), |
| accept_button_label_(accept_button_label), |
| callback_(callback), |
| infobar_(NULL) { |
| } |
| |
| PermissionQueueController::PendingInfobarRequest::~PendingInfobarRequest() { |
| } |
| |
| bool PermissionQueueController::PendingInfobarRequest::IsForPair( |
| const GURL& requesting_frame, |
| const GURL& embedder) const { |
| return (requesting_frame_ == requesting_frame) && (embedder_ == embedder); |
| } |
| |
| void PermissionQueueController::PendingInfobarRequest::RunCallback( |
| bool allowed) { |
| callback_.Run(allowed); |
| } |
| |
| void PermissionQueueController::PendingInfobarRequest::CreateInfoBar( |
| PermissionQueueController* controller, |
| const std::string& display_languages) { |
| // TODO(toyoshim): Remove following ContentType dependent code. |
| // Also these InfoBarDelegate can share much more code each other. |
| // http://crbug.com/266743 |
| switch (type_) { |
| case CONTENT_SETTINGS_TYPE_GEOLOCATION: |
| infobar_ = GeolocationInfoBarDelegate::Create( |
| GetInfoBarService(id_), controller, id_, requesting_frame_, |
| display_languages, accept_button_label_); |
| break; |
| case CONTENT_SETTINGS_TYPE_MIDI_SYSEX: |
| infobar_ = MidiPermissionInfoBarDelegate::Create( |
| GetInfoBarService(id_), controller, id_, requesting_frame_, |
| display_languages); |
| break; |
| #if defined(OS_ANDROID) |
| case CONTENT_SETTINGS_TYPE_PROTECTED_MEDIA_IDENTIFIER: |
| infobar_ = ProtectedMediaIdentifierInfoBarDelegate::Create( |
| GetInfoBarService(id_), controller, id_, requesting_frame_, |
| display_languages); |
| break; |
| #endif |
| default: |
| NOTREACHED(); |
| break; |
| } |
| } |
| |
| |
| PermissionQueueController::PermissionQueueController(Profile* profile, |
| ContentSettingsType type) |
| : profile_(profile), |
| type_(type), |
| in_shutdown_(false) { |
| } |
| |
| PermissionQueueController::~PermissionQueueController() { |
| // Cancel all outstanding requests. |
| in_shutdown_ = true; |
| while (!pending_infobar_requests_.empty()) |
| CancelInfoBarRequest(pending_infobar_requests_.front().id()); |
| } |
| |
| void PermissionQueueController::CreateInfoBarRequest( |
| const PermissionRequestID& id, |
| const GURL& requesting_frame, |
| const GURL& embedder, |
| const std::string& accept_button_label, |
| PermissionDecidedCallback callback) { |
| DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI)); |
| |
| pending_infobar_requests_.push_back(PendingInfobarRequest( |
| type_, id, requesting_frame, embedder, |
| accept_button_label, callback)); |
| if (!AlreadyShowingInfoBarForTab(id)) |
| ShowQueuedInfoBarForTab(id); |
| } |
| |
| void PermissionQueueController::CancelInfoBarRequest( |
| const PermissionRequestID& id) { |
| DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI)); |
| |
| for (PendingInfobarRequests::iterator i(pending_infobar_requests_.begin()); |
| i != pending_infobar_requests_.end(); ++i) { |
| if (i->id().Equals(id)) { |
| if (i->has_infobar()) |
| GetInfoBarService(id)->RemoveInfoBar(i->infobar()); |
| else |
| pending_infobar_requests_.erase(i); |
| return; |
| } |
| } |
| } |
| |
| void PermissionQueueController::OnPermissionSet( |
| const PermissionRequestID& id, |
| const GURL& requesting_frame, |
| const GURL& embedder, |
| bool update_content_setting, |
| bool allowed) { |
| DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI)); |
| if (update_content_setting) |
| UpdateContentSetting(requesting_frame, embedder, allowed); |
| |
| // Cancel this request first, then notify listeners. TODO(pkasting): Why |
| // is this order important? |
| PendingInfobarRequests requests_to_notify; |
| PendingInfobarRequests infobars_to_remove; |
| std::vector<PendingInfobarRequests::iterator> pending_requests_to_remove; |
| for (PendingInfobarRequests::iterator i = pending_infobar_requests_.begin(); |
| i != pending_infobar_requests_.end(); ++i) { |
| if (!i->IsForPair(requesting_frame, embedder)) |
| continue; |
| requests_to_notify.push_back(*i); |
| if (!i->has_infobar()) { |
| // We haven't created an infobar yet, just record the pending request |
| // index and remove it later. |
| pending_requests_to_remove.push_back(i); |
| continue; |
| } |
| if (i->id().Equals(id)) { |
| // The infobar that called us is i->infobar(), and its delegate is |
| // currently in either Accept() or Cancel(). This means that |
| // RemoveInfoBar() will be called later on, and that will trigger a |
| // notification we're observing. |
| continue; |
| } |
| |
| // This infobar is for the same frame/embedder pair, but in a different |
| // tab. We should remove it now that we've got an answer for it. |
| infobars_to_remove.push_back(*i); |
| } |
| |
| // Remove all infobars for the same |requesting_frame| and |embedder|. |
| for (PendingInfobarRequests::iterator i = infobars_to_remove.begin(); |
| i != infobars_to_remove.end(); ++i) |
| GetInfoBarService(i->id())->RemoveInfoBar(i->infobar()); |
| |
| // Send out the permission notifications. |
| for (PendingInfobarRequests::iterator i = requests_to_notify.begin(); |
| i != requests_to_notify.end(); ++i) |
| i->RunCallback(allowed); |
| |
| // Remove the pending requests in reverse order. |
| for (int i = pending_requests_to_remove.size() - 1; i >= 0; --i) |
| pending_infobar_requests_.erase(pending_requests_to_remove[i]); |
| } |
| |
| void PermissionQueueController::Observe( |
| int type, |
| const content::NotificationSource& source, |
| const content::NotificationDetails& details) { |
| DCHECK_EQ(chrome::NOTIFICATION_TAB_CONTENTS_INFOBAR_REMOVED, type); |
| // We will receive this notification for all infobar closures, so we need to |
| // check whether this is the geolocation infobar we're tracking. Note that the |
| // InfoBarContainer (if any) may have received this notification before us and |
| // caused the infobar to be deleted, so it's not safe to dereference the |
| // contents of the infobar. The address of the infobar, however, is OK to |
| // use to find the PendingInfobarRequest to remove because |
| // pending_infobar_requests_ will not have received any new entries between |
| // the NotificationService's call to InfoBarContainer::Observe and this |
| // method. |
| infobars::InfoBar* infobar = |
| content::Details<infobars::InfoBar::RemovedDetails>(details)->first; |
| for (PendingInfobarRequests::iterator i = pending_infobar_requests_.begin(); |
| i != pending_infobar_requests_.end(); ++i) { |
| if (i->infobar() == infobar) { |
| PermissionRequestID id(i->id()); |
| pending_infobar_requests_.erase(i); |
| ShowQueuedInfoBarForTab(id); |
| return; |
| } |
| } |
| } |
| |
| bool PermissionQueueController::AlreadyShowingInfoBarForTab( |
| const PermissionRequestID& id) const { |
| for (PendingInfobarRequests::const_iterator i( |
| pending_infobar_requests_.begin()); |
| i != pending_infobar_requests_.end(); ++i) { |
| if (i->id().IsForSameTabAs(id) && i->has_infobar()) |
| return true; |
| } |
| return false; |
| } |
| |
| void PermissionQueueController::ShowQueuedInfoBarForTab( |
| const PermissionRequestID& id) { |
| DCHECK(!AlreadyShowingInfoBarForTab(id)); |
| |
| // We can get here for example during tab shutdown, when the InfoBarService is |
| // removing all existing infobars, thus calling back to Observe(). In this |
| // case the service still exists, and is supplied as the source of the |
| // notification we observed, but is no longer accessible from its WebContents. |
| // In this case we should just go ahead and cancel further infobars for this |
| // tab instead of trying to access the service. |
| // |
| // Similarly, if we're being destroyed, we should also avoid showing further |
| // infobars. |
| InfoBarService* infobar_service = GetInfoBarService(id); |
| if (!infobar_service || in_shutdown_) { |
| ClearPendingInfobarRequestsForTab(id); |
| return; |
| } |
| |
| for (PendingInfobarRequests::iterator i = pending_infobar_requests_.begin(); |
| i != pending_infobar_requests_.end(); ++i) { |
| if (i->id().IsForSameTabAs(id) && !i->has_infobar()) { |
| RegisterForInfoBarNotifications(infobar_service); |
| i->CreateInfoBar( |
| this, profile_->GetPrefs()->GetString(prefs::kAcceptLanguages)); |
| return; |
| } |
| } |
| |
| UnregisterForInfoBarNotifications(infobar_service); |
| } |
| |
| void PermissionQueueController::ClearPendingInfobarRequestsForTab( |
| const PermissionRequestID& id) { |
| for (PendingInfobarRequests::iterator i = pending_infobar_requests_.begin(); |
| i != pending_infobar_requests_.end(); ) { |
| if (i->id().IsForSameTabAs(id)) { |
| DCHECK(!i->has_infobar()); |
| i = pending_infobar_requests_.erase(i); |
| } else { |
| ++i; |
| } |
| } |
| } |
| |
| void PermissionQueueController::RegisterForInfoBarNotifications( |
| InfoBarService* infobar_service) { |
| if (!registrar_.IsRegistered( |
| this, chrome::NOTIFICATION_TAB_CONTENTS_INFOBAR_REMOVED, |
| content::Source<InfoBarService>(infobar_service))) { |
| registrar_.Add(this, |
| chrome::NOTIFICATION_TAB_CONTENTS_INFOBAR_REMOVED, |
| content::Source<InfoBarService>(infobar_service)); |
| } |
| } |
| |
| void PermissionQueueController::UnregisterForInfoBarNotifications( |
| InfoBarService* infobar_service) { |
| if (registrar_.IsRegistered( |
| this, chrome::NOTIFICATION_TAB_CONTENTS_INFOBAR_REMOVED, |
| content::Source<InfoBarService>(infobar_service))) { |
| registrar_.Remove(this, |
| chrome::NOTIFICATION_TAB_CONTENTS_INFOBAR_REMOVED, |
| content::Source<InfoBarService>(infobar_service)); |
| } |
| } |
| |
| void PermissionQueueController::UpdateContentSetting( |
| const GURL& requesting_frame, |
| const GURL& embedder, |
| bool allowed) { |
| if (requesting_frame.GetOrigin().SchemeIsFile()) { |
| // Chrome can be launched with --disable-web-security which allows |
| // geolocation requests from file:// URLs. We don't want to store these |
| // in the host content settings map. |
| return; |
| } |
| |
| ContentSetting content_setting = |
| allowed ? CONTENT_SETTING_ALLOW : CONTENT_SETTING_BLOCK; |
| profile_->GetHostContentSettingsMap()->SetContentSetting( |
| ContentSettingsPattern::FromURLNoWildcard(requesting_frame.GetOrigin()), |
| ContentSettingsPattern::FromURLNoWildcard(embedder.GetOrigin()), |
| type_, |
| std::string(), |
| content_setting); |
| } |