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.
5 #include "chrome/browser/content_settings/permission_queue_controller.h"
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"
24 #if defined(OS_ANDROID)
25 #include "chrome/browser/media/protected_media_identifier_infobar_delegate.h"
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;
39 class PermissionQueueController::PendingInfoBarRequest {
41 PendingInfoBarRequest(ContentSettingsType type,
42 const PermissionRequestID& id,
43 const GURL& requesting_frame,
45 PermissionDecidedCallback callback);
46 ~PendingInfoBarRequest();
48 bool IsForPair(const GURL& requesting_frame,
49 const GURL& embedder) const;
51 const PermissionRequestID& id() const { return id_; }
52 const GURL& requesting_frame() const { return requesting_frame_; }
53 bool has_infobar() const { return !!infobar_; }
54 InfoBarDelegate* infobar() { return infobar_; }
56 void RunCallback(bool allowed);
57 void CreateInfoBar(PermissionQueueController* controller,
58 const std::string& display_languages);
61 ContentSettingsType type_;
62 PermissionRequestID id_;
63 GURL requesting_frame_;
65 PermissionDecidedCallback callback_;
66 InfoBarDelegate* infobar_;
68 // Purposefully do not disable copying, as this is stored in STL containers.
71 PermissionQueueController::PendingInfoBarRequest::PendingInfoBarRequest(
72 ContentSettingsType type,
73 const PermissionRequestID& id,
74 const GURL& requesting_frame,
76 PermissionDecidedCallback callback)
79 requesting_frame_(requesting_frame),
85 PermissionQueueController::PendingInfoBarRequest::~PendingInfoBarRequest() {
88 bool PermissionQueueController::PendingInfoBarRequest::IsForPair(
89 const GURL& requesting_frame,
90 const GURL& embedder) const {
91 return (requesting_frame_ == requesting_frame) && (embedder_ == embedder);
94 void PermissionQueueController::PendingInfoBarRequest::RunCallback(
96 callback_.Run(allowed);
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
106 case CONTENT_SETTINGS_TYPE_GEOLOCATION:
107 infobar_ = GeolocationInfoBarDelegate::Create(
108 GetInfoBarService(id_), controller, id_, requesting_frame_,
111 case CONTENT_SETTINGS_TYPE_MIDI_SYSEX:
112 infobar_ = MIDIPermissionInfoBarDelegate::Create(
113 GetInfoBarService(id_), controller, id_, requesting_frame_,
116 #if defined(OS_ANDROID)
117 case CONTENT_SETTINGS_TYPE_PROTECTED_MEDIA_IDENTIFIER:
118 infobar_ = ProtectedMediaIdentifierInfoBarDelegate::Create(
119 GetInfoBarService(id_), controller, id_, requesting_frame_,
130 PermissionQueueController::PermissionQueueController(Profile* profile,
131 ContentSettingsType type)
134 in_shutdown_(false) {
137 PermissionQueueController::~PermissionQueueController() {
138 // Cancel all outstanding requests.
140 while (!pending_infobar_requests_.empty())
141 CancelInfoBarRequest(pending_infobar_requests_.front().id());
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));
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));
157 pending_infobar_requests_.push_back(PendingInfoBarRequest(
158 type_, id, requesting_frame, embedder, callback));
159 if (!AlreadyShowingInfoBarForTab(id))
160 ShowQueuedInfoBarForTab(id);
163 void PermissionQueueController::CancelInfoBarRequest(
164 const PermissionRequestID& id) {
165 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
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());
173 pending_infobar_requests_.erase(i);
179 void PermissionQueueController::OnPermissionSet(
180 const PermissionRequestID& id,
181 const GURL& requesting_frame,
182 const GURL& embedder,
183 bool update_content_setting,
185 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
187 if (update_content_setting)
188 UpdateContentSetting(requesting_frame, embedder, allowed);
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 it's currently in
200 // either Accept() or Cancel(). This means that RemoveInfoBar() will be
201 // called later on, and that will trigger a notification we're
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);
210 // We haven't created an infobar yet, just remove the pending request.
211 i = pending_infobar_requests_.erase(i);
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());
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);
229 void PermissionQueueController::Observe(
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
243 InfoBarDelegate* infobar =
244 content::Details<InfoBarRemovedDetails>(details)->first;
245 for (PendingInfoBarRequests::iterator i = pending_infobar_requests_.begin();
246 i != pending_infobar_requests_.end(); ++i) {
247 if (i->infobar() == infobar) {
248 PermissionRequestID id(i->id());
249 pending_infobar_requests_.erase(i);
250 ShowQueuedInfoBarForTab(id);
256 bool PermissionQueueController::AlreadyShowingInfoBarForTab(
257 const PermissionRequestID& id) const {
258 for (PendingInfoBarRequests::const_iterator i(
259 pending_infobar_requests_.begin());
260 i != pending_infobar_requests_.end(); ++i) {
261 if (i->id().IsForSameTabAs(id) && i->has_infobar())
267 void PermissionQueueController::ShowQueuedInfoBarForTab(
268 const PermissionRequestID& id) {
269 DCHECK(!AlreadyShowingInfoBarForTab(id));
271 // We can get here for example during tab shutdown, when the InfoBarService is
272 // removing all existing infobars, thus calling back to Observe(). In this
273 // case the service still exists, and is supplied as the source of the
274 // notification we observed, but is no longer accessible from its WebContents.
275 // In this case we should just go ahead and cancel further infobars for this
276 // tab instead of trying to access the service.
278 // Similarly, if we're being destroyed, we should also avoid showing further
280 InfoBarService* infobar_service = GetInfoBarService(id);
281 if (!infobar_service || in_shutdown_) {
282 ClearPendingInfoBarRequestsForTab(id);
286 for (PendingInfoBarRequests::iterator i = pending_infobar_requests_.begin();
287 i != pending_infobar_requests_.end(); ++i) {
288 if (i->id().IsForSameTabAs(id) && !i->has_infobar()) {
289 RegisterForInfoBarNotifications(infobar_service);
291 this, profile_->GetPrefs()->GetString(prefs::kAcceptLanguages));
296 UnregisterForInfoBarNotifications(infobar_service);
299 void PermissionQueueController::ClearPendingInfoBarRequestsForTab(
300 const PermissionRequestID& id) {
301 for (PendingInfoBarRequests::iterator i = pending_infobar_requests_.begin();
302 i != pending_infobar_requests_.end(); ) {
303 if (i->id().IsForSameTabAs(id)) {
304 DCHECK(!i->has_infobar());
305 i = pending_infobar_requests_.erase(i);
312 void PermissionQueueController::RegisterForInfoBarNotifications(
313 InfoBarService* infobar_service) {
314 if (!registrar_.IsRegistered(
315 this, chrome::NOTIFICATION_TAB_CONTENTS_INFOBAR_REMOVED,
316 content::Source<InfoBarService>(infobar_service))) {
318 chrome::NOTIFICATION_TAB_CONTENTS_INFOBAR_REMOVED,
319 content::Source<InfoBarService>(infobar_service));
323 void PermissionQueueController::UnregisterForInfoBarNotifications(
324 InfoBarService* infobar_service) {
325 if (registrar_.IsRegistered(
326 this, chrome::NOTIFICATION_TAB_CONTENTS_INFOBAR_REMOVED,
327 content::Source<InfoBarService>(infobar_service))) {
328 registrar_.Remove(this,
329 chrome::NOTIFICATION_TAB_CONTENTS_INFOBAR_REMOVED,
330 content::Source<InfoBarService>(infobar_service));
334 void PermissionQueueController::UpdateContentSetting(
335 const GURL& requesting_frame,
336 const GURL& embedder,
338 if (requesting_frame.GetOrigin().SchemeIsFile()) {
339 // Chrome can be launched with --disable-web-security which allows
340 // geolocation requests from file:// URLs. We don't want to store these
341 // in the host content settings map.
345 ContentSetting content_setting =
346 allowed ? CONTENT_SETTING_ALLOW : CONTENT_SETTING_BLOCK;
347 profile_->GetHostContentSettingsMap()->SetContentSetting(
348 ContentSettingsPattern::FromURLNoWildcard(requesting_frame.GetOrigin()),
349 ContentSettingsPattern::FromURLNoWildcard(embedder.GetOrigin()),