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/permissions/permission_queue_controller.h"
7 #include "base/prefs/pref_service.h"
8 #include "chrome/browser/chrome_notification_types.h"
9 #include "chrome/browser/geolocation/geolocation_infobar_delegate.h"
10 #include "chrome/browser/infobars/infobar_service.h"
11 #include "chrome/browser/media/midi_permission_infobar_delegate.h"
12 #include "chrome/browser/notifications/notification_permission_infobar_delegate.h"
13 #include "chrome/browser/permissions/permission_context_uma_util.h"
14 #include "chrome/browser/profiles/profile.h"
15 #include "chrome/browser/storage/durable_storage_permission_infobar_delegate.h"
16 #include "chrome/browser/tab_contents/tab_util.h"
17 #include "chrome/common/pref_names.h"
18 #include "components/content_settings/core/browser/host_content_settings_map.h"
19 #include "components/content_settings/core/common/content_settings.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) || defined(OS_CHROMEOS)
29 #include "chrome/browser/media/protected_media_identifier_infobar_delegate.h"
34 InfoBarService
* GetInfoBarService(const PermissionRequestID
& id
) {
35 content::WebContents
* web_contents
= tab_util::GetWebContentsByFrameID(
36 id
.render_process_id(), id
.render_frame_id());
37 return web_contents
? InfoBarService::FromWebContents(web_contents
) : NULL
;
40 bool ArePermissionRequestsForSameTab(
41 const PermissionRequestID
& request
,
42 const PermissionRequestID
& other_request
) {
43 content::WebContents
* web_contents
= tab_util::GetWebContentsByFrameID(
44 request
.render_process_id(), request
.render_frame_id());
45 content::WebContents
* other_web_contents
= tab_util::GetWebContentsByFrameID(
46 other_request
.render_process_id(), other_request
.render_frame_id());
48 return web_contents
== other_web_contents
;
51 } // anonymous namespace
53 class PermissionQueueController::PendingInfobarRequest
{
55 PendingInfobarRequest(ContentSettingsType type
,
56 const PermissionRequestID
& id
,
57 const GURL
& requesting_frame
,
59 const PermissionDecidedCallback
& callback
);
60 ~PendingInfobarRequest();
62 bool IsForPair(const GURL
& requesting_frame
,
63 const GURL
& embedder
) const;
65 const PermissionRequestID
& id() const { return id_
; }
66 const GURL
& requesting_frame() const { return requesting_frame_
; }
67 bool has_infobar() const { return !!infobar_
; }
68 infobars::InfoBar
* infobar() { return infobar_
; }
70 void RunCallback(ContentSetting content_setting
);
71 void CreateInfoBar(PermissionQueueController
* controller
,
72 const std::string
& display_languages
);
75 ContentSettingsType type_
;
76 PermissionRequestID id_
;
77 GURL requesting_frame_
;
79 PermissionDecidedCallback callback_
;
80 infobars::InfoBar
* infobar_
;
82 // Purposefully do not disable copying, as this is stored in STL containers.
85 PermissionQueueController::PendingInfobarRequest::PendingInfobarRequest(
86 ContentSettingsType type
,
87 const PermissionRequestID
& id
,
88 const GURL
& requesting_frame
,
90 const PermissionDecidedCallback
& callback
)
93 requesting_frame_(requesting_frame
),
99 PermissionQueueController::PendingInfobarRequest::~PendingInfobarRequest() {
102 bool PermissionQueueController::PendingInfobarRequest::IsForPair(
103 const GURL
& requesting_frame
,
104 const GURL
& embedder
) const {
105 return (requesting_frame_
== requesting_frame
) && (embedder_
== embedder
);
108 void PermissionQueueController::PendingInfobarRequest::RunCallback(
109 ContentSetting content_setting
) {
110 callback_
.Run(content_setting
);
113 void PermissionQueueController::PendingInfobarRequest::CreateInfoBar(
114 PermissionQueueController
* controller
,
115 const std::string
& display_languages
) {
117 case CONTENT_SETTINGS_TYPE_GEOLOCATION
:
118 infobar_
= GeolocationInfoBarDelegate::Create(
119 GetInfoBarService(id_
), controller
, id_
, requesting_frame_
,
122 #if defined(ENABLE_NOTIFICATIONS)
123 case CONTENT_SETTINGS_TYPE_NOTIFICATIONS
:
124 infobar_
= NotificationPermissionInfobarDelegate::Create(
125 GetInfoBarService(id_
), controller
, id_
, requesting_frame_
,
128 #endif // ENABLE_NOTIFICATIONS
129 case CONTENT_SETTINGS_TYPE_MIDI_SYSEX
:
130 infobar_
= MidiPermissionInfoBarDelegate::Create(
131 GetInfoBarService(id_
), controller
, id_
, requesting_frame_
,
132 display_languages
, type_
);
134 #if defined(OS_ANDROID) || defined(OS_CHROMEOS)
135 case CONTENT_SETTINGS_TYPE_PROTECTED_MEDIA_IDENTIFIER
:
136 infobar_
= ProtectedMediaIdentifierInfoBarDelegate::Create(
137 GetInfoBarService(id_
), controller
, id_
, requesting_frame_
,
141 case CONTENT_SETTINGS_TYPE_DURABLE_STORAGE
:
142 infobar_
= DurableStoragePermissionInfoBarDelegate::Create(
143 GetInfoBarService(id_
), controller
, id_
, requesting_frame_
,
144 display_languages
, type_
);
153 PermissionQueueController::PermissionQueueController(Profile
* profile
,
154 ContentSettingsType type
)
157 in_shutdown_(false) {
160 PermissionQueueController::~PermissionQueueController() {
161 // Cancel all outstanding requests.
163 while (!pending_infobar_requests_
.empty())
164 CancelInfoBarRequest(pending_infobar_requests_
.front().id());
167 void PermissionQueueController::CreateInfoBarRequest(
168 const PermissionRequestID
& id
,
169 const GURL
& requesting_frame
,
170 const GURL
& embedder
,
171 const PermissionDecidedCallback
& callback
) {
172 DCHECK_CURRENTLY_ON(content::BrowserThread::UI
);
174 if (requesting_frame
.SchemeIs(content::kChromeUIScheme
) ||
175 embedder
.SchemeIs(content::kChromeUIScheme
))
178 pending_infobar_requests_
.push_back(PendingInfobarRequest(
179 type_
, id
, requesting_frame
, embedder
, callback
));
180 if (!AlreadyShowingInfoBarForTab(id
))
181 ShowQueuedInfoBarForTab(id
);
184 void PermissionQueueController::CancelInfoBarRequest(
185 const PermissionRequestID
& id
) {
186 DCHECK_CURRENTLY_ON(content::BrowserThread::UI
);
188 for (PendingInfobarRequests::iterator
i(pending_infobar_requests_
.begin());
189 i
!= pending_infobar_requests_
.end(); ++i
) {
193 InfoBarService
* infobar_service
= GetInfoBarService(id
);
194 if (infobar_service
&& i
->has_infobar())
195 infobar_service
->RemoveInfoBar(i
->infobar());
197 pending_infobar_requests_
.erase(i
);
202 void PermissionQueueController::OnPermissionSet(
203 const PermissionRequestID
& id
,
204 const GURL
& requesting_frame
,
205 const GURL
& embedder
,
206 bool update_content_setting
,
208 DCHECK_CURRENTLY_ON(content::BrowserThread::UI
);
210 // TODO(miguelg): move the permission persistence to
211 // PermissionContextBase once all the types are moved there.
212 if (update_content_setting
) {
213 UpdateContentSetting(requesting_frame
, embedder
, allowed
);
215 PermissionContextUmaUtil::PermissionGranted(type_
, requesting_frame
);
217 PermissionContextUmaUtil::PermissionDenied(type_
, requesting_frame
);
219 PermissionContextUmaUtil::PermissionDismissed(type_
, requesting_frame
);
222 // Cancel this request first, then notify listeners. TODO(pkasting): Why
223 // is this order important?
224 PendingInfobarRequests requests_to_notify
;
225 PendingInfobarRequests infobars_to_remove
;
226 std::vector
<PendingInfobarRequests::iterator
> pending_requests_to_remove
;
227 for (PendingInfobarRequests::iterator i
= pending_infobar_requests_
.begin();
228 i
!= pending_infobar_requests_
.end(); ++i
) {
229 if (!i
->IsForPair(requesting_frame
, embedder
))
231 requests_to_notify
.push_back(*i
);
232 if (!i
->has_infobar()) {
233 // We haven't created an infobar yet, just record the pending request
234 // index and remove it later.
235 pending_requests_to_remove
.push_back(i
);
239 // The infobar that called us is i->infobar(), and its delegate is
240 // currently in either Accept() or Cancel(). This means that
241 // RemoveInfoBar() will be called later on, and that will trigger a
242 // notification we're observing.
246 // This infobar is for the same frame/embedder pair, but in a different
247 // tab. We should remove it now that we've got an answer for it.
248 infobars_to_remove
.push_back(*i
);
251 // Remove all infobars for the same |requesting_frame| and |embedder|.
252 for (PendingInfobarRequests::iterator i
= infobars_to_remove
.begin();
253 i
!= infobars_to_remove
.end(); ++i
)
254 GetInfoBarService(i
->id())->RemoveInfoBar(i
->infobar());
256 // PermissionContextBase needs to know about the new ContentSetting value,
257 // CONTENT_SETTING_DEFAULT being the value for nothing happened. The callers
258 // of ::OnPermissionSet passes { true, true } for allow, { true, false } for
259 // block and { false, * } for dismissed. The tuple being
260 // { update_content_setting, allowed }.
261 ContentSetting content_setting
= CONTENT_SETTING_DEFAULT
;
262 if (update_content_setting
) {
263 content_setting
= allowed
? CONTENT_SETTING_ALLOW
: CONTENT_SETTING_BLOCK
;
266 // Send out the permission notifications.
267 for (PendingInfobarRequests::iterator i
= requests_to_notify
.begin();
268 i
!= requests_to_notify
.end(); ++i
)
269 i
->RunCallback(content_setting
);
271 // Remove the pending requests in reverse order.
272 for (int i
= pending_requests_to_remove
.size() - 1; i
>= 0; --i
)
273 pending_infobar_requests_
.erase(pending_requests_to_remove
[i
]);
276 void PermissionQueueController::Observe(
278 const content::NotificationSource
& source
,
279 const content::NotificationDetails
& details
) {
280 DCHECK_EQ(chrome::NOTIFICATION_TAB_CONTENTS_INFOBAR_REMOVED
, type
);
281 // We will receive this notification for all infobar closures, so we need to
282 // check whether this is the geolocation infobar we're tracking. Note that the
283 // InfoBarContainer (if any) may have received this notification before us and
284 // caused the infobar to be deleted, so it's not safe to dereference the
285 // contents of the infobar. The address of the infobar, however, is OK to
286 // use to find the PendingInfobarRequest to remove because
287 // pending_infobar_requests_ will not have received any new entries between
288 // the NotificationService's call to InfoBarContainer::Observe and this
290 infobars::InfoBar
* infobar
=
291 content::Details
<infobars::InfoBar::RemovedDetails
>(details
)->first
;
292 for (PendingInfobarRequests::iterator i
= pending_infobar_requests_
.begin();
293 i
!= pending_infobar_requests_
.end(); ++i
) {
294 if (i
->infobar() == infobar
) {
295 PermissionRequestID
id(i
->id());
296 pending_infobar_requests_
.erase(i
);
297 ShowQueuedInfoBarForTab(id
);
303 bool PermissionQueueController::AlreadyShowingInfoBarForTab(
304 const PermissionRequestID
& id
) const {
305 for (PendingInfobarRequests::const_iterator
i(
306 pending_infobar_requests_
.begin());
307 i
!= pending_infobar_requests_
.end(); ++i
) {
308 if (ArePermissionRequestsForSameTab(i
->id(), id
) && i
->has_infobar())
314 void PermissionQueueController::ShowQueuedInfoBarForTab(
315 const PermissionRequestID
& id
) {
316 DCHECK(!AlreadyShowingInfoBarForTab(id
));
318 // We can get here for example during tab shutdown, when the InfoBarService is
319 // removing all existing infobars, thus calling back to Observe(). In this
320 // case the service still exists, and is supplied as the source of the
321 // notification we observed, but is no longer accessible from its WebContents.
322 // In this case we should just go ahead and cancel further infobars for this
323 // tab instead of trying to access the service.
325 // Similarly, if we're being destroyed, we should also avoid showing further
327 InfoBarService
* infobar_service
= GetInfoBarService(id
);
328 if (!infobar_service
|| in_shutdown_
) {
329 ClearPendingInfobarRequestsForTab(id
);
333 for (PendingInfobarRequests::iterator i
= pending_infobar_requests_
.begin();
334 i
!= pending_infobar_requests_
.end(); ++i
) {
335 if (ArePermissionRequestsForSameTab(i
->id(), id
) && !i
->has_infobar()) {
336 RegisterForInfoBarNotifications(infobar_service
);
338 this, profile_
->GetPrefs()->GetString(prefs::kAcceptLanguages
));
343 UnregisterForInfoBarNotifications(infobar_service
);
346 void PermissionQueueController::ClearPendingInfobarRequestsForTab(
347 const PermissionRequestID
& id
) {
348 for (PendingInfobarRequests::iterator i
= pending_infobar_requests_
.begin();
349 i
!= pending_infobar_requests_
.end(); ) {
350 if (ArePermissionRequestsForSameTab(i
->id(), id
)) {
351 DCHECK(!i
->has_infobar());
352 i
= pending_infobar_requests_
.erase(i
);
359 void PermissionQueueController::RegisterForInfoBarNotifications(
360 InfoBarService
* infobar_service
) {
361 if (!registrar_
.IsRegistered(
362 this, chrome::NOTIFICATION_TAB_CONTENTS_INFOBAR_REMOVED
,
363 content::Source
<InfoBarService
>(infobar_service
))) {
365 chrome::NOTIFICATION_TAB_CONTENTS_INFOBAR_REMOVED
,
366 content::Source
<InfoBarService
>(infobar_service
));
370 void PermissionQueueController::UnregisterForInfoBarNotifications(
371 InfoBarService
* infobar_service
) {
372 if (registrar_
.IsRegistered(
373 this, chrome::NOTIFICATION_TAB_CONTENTS_INFOBAR_REMOVED
,
374 content::Source
<InfoBarService
>(infobar_service
))) {
375 registrar_
.Remove(this,
376 chrome::NOTIFICATION_TAB_CONTENTS_INFOBAR_REMOVED
,
377 content::Source
<InfoBarService
>(infobar_service
));
381 void PermissionQueueController::UpdateContentSetting(
382 const GURL
& requesting_frame
,
383 const GURL
& embedder
,
385 if (requesting_frame
.GetOrigin().SchemeIsFile()) {
386 // Chrome can be launched with --disable-web-security which allows
387 // geolocation requests from file:// URLs. We don't want to store these
388 // in the host content settings map.
392 ContentSetting content_setting
=
393 allowed
? CONTENT_SETTING_ALLOW
: CONTENT_SETTING_BLOCK
;
395 ContentSettingsPattern embedder_pattern
=
396 (type_
== CONTENT_SETTINGS_TYPE_NOTIFICATIONS
) ?
397 ContentSettingsPattern::Wildcard() :
398 ContentSettingsPattern::FromURLNoWildcard(embedder
.GetOrigin());
400 profile_
->GetHostContentSettingsMap()->SetContentSetting(
401 ContentSettingsPattern::FromURLNoWildcard(requesting_frame
.GetOrigin()),