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/content_settings/permission_context_uma_util.h"
11 #include "chrome/browser/geolocation/geolocation_infobar_delegate.h"
12 #include "chrome/browser/infobars/infobar_service.h"
13 #include "chrome/browser/media/midi_permission_infobar_delegate.h"
14 #include "chrome/browser/notifications/desktop_notification_infobar_delegate.h"
15 #include "chrome/browser/profiles/profile.h"
16 #include "chrome/browser/services/gcm/push_messaging_infobar_delegate.h"
17 #include "chrome/browser/tab_contents/tab_util.h"
18 #include "chrome/common/content_settings.h"
19 #include "chrome/common/pref_names.h"
20 #include "components/infobars/core/infobar.h"
21 #include "content/public/browser/browser_thread.h"
22 #include "content/public/browser/notification_details.h"
23 #include "content/public/browser/notification_source.h"
24 #include "content/public/browser/notification_types.h"
25 #include "content/public/browser/web_contents.h"
26 #include "content/public/common/url_constants.h"
28 #if defined(OS_ANDROID)
29 #include "chrome/browser/media/protected_media_identifier_infobar_delegate.h"
34 InfoBarService
* GetInfoBarService(const PermissionRequestID
& id
) {
35 content::WebContents
* web_contents
=
36 tab_util::GetWebContentsByID(id
.render_process_id(), id
.render_view_id());
37 return web_contents
? InfoBarService::FromWebContents(web_contents
) : NULL
;
43 class PermissionQueueController::PendingInfobarRequest
{
45 PendingInfobarRequest(ContentSettingsType type
,
46 const PermissionRequestID
& id
,
47 const GURL
& requesting_frame
,
49 PermissionDecidedCallback callback
);
50 ~PendingInfobarRequest();
52 bool IsForPair(const GURL
& requesting_frame
,
53 const GURL
& embedder
) const;
55 const PermissionRequestID
& id() const { return id_
; }
56 const GURL
& requesting_frame() const { return requesting_frame_
; }
57 bool has_infobar() const { return !!infobar_
; }
58 infobars::InfoBar
* infobar() { return infobar_
; }
60 void RunCallback(bool allowed
);
61 void CreateInfoBar(PermissionQueueController
* controller
,
62 const std::string
& display_languages
);
65 ContentSettingsType type_
;
66 PermissionRequestID id_
;
67 GURL requesting_frame_
;
69 PermissionDecidedCallback callback_
;
70 infobars::InfoBar
* infobar_
;
72 // Purposefully do not disable copying, as this is stored in STL containers.
75 PermissionQueueController::PendingInfobarRequest::PendingInfobarRequest(
76 ContentSettingsType type
,
77 const PermissionRequestID
& id
,
78 const GURL
& requesting_frame
,
80 PermissionDecidedCallback callback
)
83 requesting_frame_(requesting_frame
),
89 PermissionQueueController::PendingInfobarRequest::~PendingInfobarRequest() {
92 bool PermissionQueueController::PendingInfobarRequest::IsForPair(
93 const GURL
& requesting_frame
,
94 const GURL
& embedder
) const {
95 return (requesting_frame_
== requesting_frame
) && (embedder_
== embedder
);
98 void PermissionQueueController::PendingInfobarRequest::RunCallback(
100 callback_
.Run(allowed
);
103 void PermissionQueueController::PendingInfobarRequest::CreateInfoBar(
104 PermissionQueueController
* controller
,
105 const std::string
& display_languages
) {
107 case CONTENT_SETTINGS_TYPE_GEOLOCATION
:
108 infobar_
= GeolocationInfoBarDelegate::Create(
109 GetInfoBarService(id_
), controller
, id_
, requesting_frame_
,
112 #if defined(ENABLE_NOTIFICATIONS)
113 case CONTENT_SETTINGS_TYPE_NOTIFICATIONS
:
114 infobar_
= DesktopNotificationInfoBarDelegate::Create(
115 GetInfoBarService(id_
), controller
, id_
, requesting_frame_
,
118 #endif // ENABLE_NOTIFICATIONS
119 case CONTENT_SETTINGS_TYPE_MIDI_SYSEX
:
120 infobar_
= MidiPermissionInfoBarDelegate::Create(
121 GetInfoBarService(id_
), controller
, id_
, requesting_frame_
,
122 display_languages
, type_
);
124 case CONTENT_SETTINGS_TYPE_PUSH_MESSAGING
:
125 infobar_
= gcm::PushMessagingInfoBarDelegate::Create(
126 GetInfoBarService(id_
), controller
, id_
, requesting_frame_
,
127 display_languages
, type_
);
129 #if defined(OS_ANDROID)
130 case CONTENT_SETTINGS_TYPE_PROTECTED_MEDIA_IDENTIFIER
:
131 infobar_
= ProtectedMediaIdentifierInfoBarDelegate::Create(
132 GetInfoBarService(id_
), controller
, id_
, requesting_frame_
,
143 PermissionQueueController::PermissionQueueController(Profile
* profile
,
144 ContentSettingsType type
)
147 in_shutdown_(false) {
150 PermissionQueueController::~PermissionQueueController() {
151 // Cancel all outstanding requests.
153 while (!pending_infobar_requests_
.empty())
154 CancelInfoBarRequest(pending_infobar_requests_
.front().id());
157 void PermissionQueueController::CreateInfoBarRequest(
158 const PermissionRequestID
& id
,
159 const GURL
& requesting_frame
,
160 const GURL
& embedder
,
161 PermissionDecidedCallback callback
) {
162 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI
));
164 if (requesting_frame
.SchemeIs(content::kChromeUIScheme
) ||
165 embedder
.SchemeIs(content::kChromeUIScheme
))
168 pending_infobar_requests_
.push_back(PendingInfobarRequest(
169 type_
, id
, requesting_frame
, embedder
, callback
));
170 if (!AlreadyShowingInfoBarForTab(id
))
171 ShowQueuedInfoBarForTab(id
);
174 void PermissionQueueController::CancelInfoBarRequest(
175 const PermissionRequestID
& id
) {
176 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI
));
178 for (PendingInfobarRequests::iterator
i(pending_infobar_requests_
.begin());
179 i
!= pending_infobar_requests_
.end(); ++i
) {
180 if (i
->id().Equals(id
)) {
181 if (i
->has_infobar())
182 GetInfoBarService(id
)->RemoveInfoBar(i
->infobar());
184 pending_infobar_requests_
.erase(i
);
190 void PermissionQueueController::OnPermissionSet(
191 const PermissionRequestID
& id
,
192 const GURL
& requesting_frame
,
193 const GURL
& embedder
,
194 bool update_content_setting
,
196 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI
));
198 // TODO(miguelg): move the permission persistence to
199 // PermissionContextBase once all the types are moved there.
200 if (update_content_setting
) {
201 UpdateContentSetting(requesting_frame
, embedder
, allowed
);
203 PermissionContextUmaUtil::PermissionGranted(type_
);
205 PermissionContextUmaUtil::PermissionDenied(type_
);
207 PermissionContextUmaUtil::PermissionDismissed(type_
);
210 // Cancel this request first, then notify listeners. TODO(pkasting): Why
211 // is this order important?
212 PendingInfobarRequests requests_to_notify
;
213 PendingInfobarRequests infobars_to_remove
;
214 std::vector
<PendingInfobarRequests::iterator
> pending_requests_to_remove
;
215 for (PendingInfobarRequests::iterator i
= pending_infobar_requests_
.begin();
216 i
!= pending_infobar_requests_
.end(); ++i
) {
217 if (!i
->IsForPair(requesting_frame
, embedder
))
219 requests_to_notify
.push_back(*i
);
220 if (!i
->has_infobar()) {
221 // We haven't created an infobar yet, just record the pending request
222 // index and remove it later.
223 pending_requests_to_remove
.push_back(i
);
226 if (i
->id().Equals(id
)) {
227 // The infobar that called us is i->infobar(), and its delegate is
228 // currently in either Accept() or Cancel(). This means that
229 // RemoveInfoBar() will be called later on, and that will trigger a
230 // notification we're observing.
234 // This infobar is for the same frame/embedder pair, but in a different
235 // tab. We should remove it now that we've got an answer for it.
236 infobars_to_remove
.push_back(*i
);
239 // Remove all infobars for the same |requesting_frame| and |embedder|.
240 for (PendingInfobarRequests::iterator i
= infobars_to_remove
.begin();
241 i
!= infobars_to_remove
.end(); ++i
)
242 GetInfoBarService(i
->id())->RemoveInfoBar(i
->infobar());
244 // Send out the permission notifications.
245 for (PendingInfobarRequests::iterator i
= requests_to_notify
.begin();
246 i
!= requests_to_notify
.end(); ++i
)
247 i
->RunCallback(allowed
);
249 // Remove the pending requests in reverse order.
250 for (int i
= pending_requests_to_remove
.size() - 1; i
>= 0; --i
)
251 pending_infobar_requests_
.erase(pending_requests_to_remove
[i
]);
254 void PermissionQueueController::Observe(
256 const content::NotificationSource
& source
,
257 const content::NotificationDetails
& details
) {
258 DCHECK_EQ(chrome::NOTIFICATION_TAB_CONTENTS_INFOBAR_REMOVED
, type
);
259 // We will receive this notification for all infobar closures, so we need to
260 // check whether this is the geolocation infobar we're tracking. Note that the
261 // InfoBarContainer (if any) may have received this notification before us and
262 // caused the infobar to be deleted, so it's not safe to dereference the
263 // contents of the infobar. The address of the infobar, however, is OK to
264 // use to find the PendingInfobarRequest to remove because
265 // pending_infobar_requests_ will not have received any new entries between
266 // the NotificationService's call to InfoBarContainer::Observe and this
268 infobars::InfoBar
* infobar
=
269 content::Details
<infobars::InfoBar::RemovedDetails
>(details
)->first
;
270 for (PendingInfobarRequests::iterator i
= pending_infobar_requests_
.begin();
271 i
!= pending_infobar_requests_
.end(); ++i
) {
272 if (i
->infobar() == infobar
) {
273 PermissionRequestID
id(i
->id());
274 pending_infobar_requests_
.erase(i
);
275 ShowQueuedInfoBarForTab(id
);
281 bool PermissionQueueController::AlreadyShowingInfoBarForTab(
282 const PermissionRequestID
& id
) const {
283 for (PendingInfobarRequests::const_iterator
i(
284 pending_infobar_requests_
.begin());
285 i
!= pending_infobar_requests_
.end(); ++i
) {
286 if (i
->id().IsForSameTabAs(id
) && i
->has_infobar())
292 void PermissionQueueController::ShowQueuedInfoBarForTab(
293 const PermissionRequestID
& id
) {
294 DCHECK(!AlreadyShowingInfoBarForTab(id
));
296 // We can get here for example during tab shutdown, when the InfoBarService is
297 // removing all existing infobars, thus calling back to Observe(). In this
298 // case the service still exists, and is supplied as the source of the
299 // notification we observed, but is no longer accessible from its WebContents.
300 // In this case we should just go ahead and cancel further infobars for this
301 // tab instead of trying to access the service.
303 // Similarly, if we're being destroyed, we should also avoid showing further
305 InfoBarService
* infobar_service
= GetInfoBarService(id
);
306 if (!infobar_service
|| in_shutdown_
) {
307 ClearPendingInfobarRequestsForTab(id
);
311 for (PendingInfobarRequests::iterator i
= pending_infobar_requests_
.begin();
312 i
!= pending_infobar_requests_
.end(); ++i
) {
313 if (i
->id().IsForSameTabAs(id
) && !i
->has_infobar()) {
314 RegisterForInfoBarNotifications(infobar_service
);
316 this, profile_
->GetPrefs()->GetString(prefs::kAcceptLanguages
));
321 UnregisterForInfoBarNotifications(infobar_service
);
324 void PermissionQueueController::ClearPendingInfobarRequestsForTab(
325 const PermissionRequestID
& id
) {
326 for (PendingInfobarRequests::iterator i
= pending_infobar_requests_
.begin();
327 i
!= pending_infobar_requests_
.end(); ) {
328 if (i
->id().IsForSameTabAs(id
)) {
329 DCHECK(!i
->has_infobar());
330 i
= pending_infobar_requests_
.erase(i
);
337 void PermissionQueueController::RegisterForInfoBarNotifications(
338 InfoBarService
* infobar_service
) {
339 if (!registrar_
.IsRegistered(
340 this, chrome::NOTIFICATION_TAB_CONTENTS_INFOBAR_REMOVED
,
341 content::Source
<InfoBarService
>(infobar_service
))) {
343 chrome::NOTIFICATION_TAB_CONTENTS_INFOBAR_REMOVED
,
344 content::Source
<InfoBarService
>(infobar_service
));
348 void PermissionQueueController::UnregisterForInfoBarNotifications(
349 InfoBarService
* infobar_service
) {
350 if (registrar_
.IsRegistered(
351 this, chrome::NOTIFICATION_TAB_CONTENTS_INFOBAR_REMOVED
,
352 content::Source
<InfoBarService
>(infobar_service
))) {
353 registrar_
.Remove(this,
354 chrome::NOTIFICATION_TAB_CONTENTS_INFOBAR_REMOVED
,
355 content::Source
<InfoBarService
>(infobar_service
));
359 void PermissionQueueController::UpdateContentSetting(
360 const GURL
& requesting_frame
,
361 const GURL
& embedder
,
363 if (requesting_frame
.GetOrigin().SchemeIsFile()) {
364 // Chrome can be launched with --disable-web-security which allows
365 // geolocation requests from file:// URLs. We don't want to store these
366 // in the host content settings map.
370 ContentSetting content_setting
=
371 allowed
? CONTENT_SETTING_ALLOW
: CONTENT_SETTING_BLOCK
;
372 profile_
->GetHostContentSettingsMap()->SetContentSetting(
373 ContentSettingsPattern::FromURLNoWildcard(requesting_frame
.GetOrigin()),
374 ContentSettingsPattern::FromURLNoWildcard(embedder
.GetOrigin()),