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_service.h"
12 #include "chrome/browser/media/midi_permission_infobar_delegate.h"
13 #include "chrome/browser/profiles/profile.h"
14 #include "chrome/browser/tab_contents/tab_util.h"
15 #include "chrome/common/content_settings.h"
16 #include "chrome/common/pref_names.h"
17 #include "components/infobars/core/infobar.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 const std::string
& accept_button_label
,
46 PermissionDecidedCallback callback
);
47 ~PendingInfobarRequest();
49 bool IsForPair(const GURL
& requesting_frame
,
50 const GURL
& embedder
) const;
52 const PermissionRequestID
& id() const { return id_
; }
53 const GURL
& requesting_frame() const { return requesting_frame_
; }
54 bool has_infobar() const { return !!infobar_
; }
55 infobars::InfoBar
* infobar() { return infobar_
; }
57 void RunCallback(bool allowed
);
58 void CreateInfoBar(PermissionQueueController
* controller
,
59 const std::string
& display_languages
);
62 ContentSettingsType type_
;
63 PermissionRequestID id_
;
64 GURL requesting_frame_
;
66 std::string accept_button_label_
;
67 PermissionDecidedCallback callback_
;
68 infobars::InfoBar
* infobar_
;
70 // Purposefully do not disable copying, as this is stored in STL containers.
73 PermissionQueueController::PendingInfobarRequest::PendingInfobarRequest(
74 ContentSettingsType type
,
75 const PermissionRequestID
& id
,
76 const GURL
& requesting_frame
,
78 const std::string
& accept_button_label
,
79 PermissionDecidedCallback callback
)
82 requesting_frame_(requesting_frame
),
84 accept_button_label_(accept_button_label
),
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
) {
106 // TODO(toyoshim): Remove following ContentType dependent code.
107 // Also these InfoBarDelegate can share much more code each other.
108 // http://crbug.com/266743
110 case CONTENT_SETTINGS_TYPE_GEOLOCATION
:
111 infobar_
= GeolocationInfoBarDelegate::Create(
112 GetInfoBarService(id_
), controller
, id_
, requesting_frame_
,
113 display_languages
, accept_button_label_
);
115 case CONTENT_SETTINGS_TYPE_MIDI_SYSEX
:
116 infobar_
= MidiPermissionInfoBarDelegate::Create(
117 GetInfoBarService(id_
), controller
, id_
, requesting_frame_
,
120 #if defined(OS_ANDROID)
121 case CONTENT_SETTINGS_TYPE_PROTECTED_MEDIA_IDENTIFIER
:
122 infobar_
= ProtectedMediaIdentifierInfoBarDelegate::Create(
123 GetInfoBarService(id_
), controller
, id_
, requesting_frame_
,
134 PermissionQueueController::PermissionQueueController(Profile
* profile
,
135 ContentSettingsType type
)
138 in_shutdown_(false) {
141 PermissionQueueController::~PermissionQueueController() {
142 // Cancel all outstanding requests.
144 while (!pending_infobar_requests_
.empty())
145 CancelInfoBarRequest(pending_infobar_requests_
.front().id());
148 void PermissionQueueController::CreateInfoBarRequest(
149 const PermissionRequestID
& id
,
150 const GURL
& requesting_frame
,
151 const GURL
& embedder
,
152 const std::string
& accept_button_label
,
153 PermissionDecidedCallback callback
) {
154 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI
));
156 // We shouldn't get duplicate requests.
157 for (PendingInfobarRequests::const_iterator
i(
158 pending_infobar_requests_
.begin());
159 i
!= pending_infobar_requests_
.end(); ++i
)
160 DCHECK(!i
->id().Equals(id
));
162 pending_infobar_requests_
.push_back(PendingInfobarRequest(
163 type_
, id
, requesting_frame
, embedder
,
164 accept_button_label
, callback
));
165 if (!AlreadyShowingInfoBarForTab(id
))
166 ShowQueuedInfoBarForTab(id
);
169 void PermissionQueueController::CancelInfoBarRequest(
170 const PermissionRequestID
& id
) {
171 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI
));
173 for (PendingInfobarRequests::iterator
i(pending_infobar_requests_
.begin());
174 i
!= pending_infobar_requests_
.end(); ++i
) {
175 if (i
->id().Equals(id
)) {
176 if (i
->has_infobar())
177 GetInfoBarService(id
)->RemoveInfoBar(i
->infobar());
179 pending_infobar_requests_
.erase(i
);
185 void PermissionQueueController::OnPermissionSet(
186 const PermissionRequestID
& id
,
187 const GURL
& requesting_frame
,
188 const GURL
& embedder
,
189 bool update_content_setting
,
191 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI
));
193 if (update_content_setting
)
194 UpdateContentSetting(requesting_frame
, embedder
, allowed
);
196 // Cancel this request first, then notify listeners. TODO(pkasting): Why
197 // is this order important?
198 PendingInfobarRequests requests_to_notify
;
199 PendingInfobarRequests infobars_to_remove
;
200 for (PendingInfobarRequests::iterator i
= pending_infobar_requests_
.begin();
201 i
!= pending_infobar_requests_
.end(); ) {
202 if (i
->IsForPair(requesting_frame
, embedder
)) {
203 requests_to_notify
.push_back(*i
);
204 if (i
->id().Equals(id
)) {
205 // The infobar that called us is i->infobar(), and its delegate is
206 // currently in either Accept() or Cancel(). This means that
207 // RemoveInfoBar() will be called later on, and that will trigger a
208 // notification we're observing.
210 } else if (i
->has_infobar()) {
211 // This infobar is for the same frame/embedder pair, but in a different
212 // tab. We should remove it now that we've got an answer for it.
213 infobars_to_remove
.push_back(*i
);
216 // We haven't created an infobar yet, just remove the pending request.
217 i
= pending_infobar_requests_
.erase(i
);
224 // Remove all infobars for the same |requesting_frame| and |embedder|.
225 for (PendingInfobarRequests::iterator i
= infobars_to_remove
.begin();
226 i
!= infobars_to_remove
.end(); ++i
)
227 GetInfoBarService(i
->id())->RemoveInfoBar(i
->infobar());
229 // Send out the permission notifications.
230 for (PendingInfobarRequests::iterator i
= requests_to_notify
.begin();
231 i
!= requests_to_notify
.end(); ++i
)
232 i
->RunCallback(allowed
);
235 void PermissionQueueController::Observe(
237 const content::NotificationSource
& source
,
238 const content::NotificationDetails
& details
) {
239 DCHECK_EQ(chrome::NOTIFICATION_TAB_CONTENTS_INFOBAR_REMOVED
, type
);
240 // We will receive this notification for all infobar closures, so we need to
241 // check whether this is the geolocation infobar we're tracking. Note that the
242 // InfoBarContainer (if any) may have received this notification before us and
243 // caused the infobar to be deleted, so it's not safe to dereference the
244 // contents of the infobar. The address of the infobar, however, is OK to
245 // use to find the PendingInfobarRequest to remove because
246 // pending_infobar_requests_ will not have received any new entries between
247 // the NotificationService's call to InfoBarContainer::Observe and this
249 infobars::InfoBar
* infobar
=
250 content::Details
<infobars::InfoBar::RemovedDetails
>(details
)->first
;
251 for (PendingInfobarRequests::iterator i
= pending_infobar_requests_
.begin();
252 i
!= pending_infobar_requests_
.end(); ++i
) {
253 if (i
->infobar() == infobar
) {
254 PermissionRequestID
id(i
->id());
255 pending_infobar_requests_
.erase(i
);
256 ShowQueuedInfoBarForTab(id
);
262 bool PermissionQueueController::AlreadyShowingInfoBarForTab(
263 const PermissionRequestID
& id
) const {
264 for (PendingInfobarRequests::const_iterator
i(
265 pending_infobar_requests_
.begin());
266 i
!= pending_infobar_requests_
.end(); ++i
) {
267 if (i
->id().IsForSameTabAs(id
) && i
->has_infobar())
273 void PermissionQueueController::ShowQueuedInfoBarForTab(
274 const PermissionRequestID
& id
) {
275 DCHECK(!AlreadyShowingInfoBarForTab(id
));
277 // We can get here for example during tab shutdown, when the InfoBarService is
278 // removing all existing infobars, thus calling back to Observe(). In this
279 // case the service still exists, and is supplied as the source of the
280 // notification we observed, but is no longer accessible from its WebContents.
281 // In this case we should just go ahead and cancel further infobars for this
282 // tab instead of trying to access the service.
284 // Similarly, if we're being destroyed, we should also avoid showing further
286 InfoBarService
* infobar_service
= GetInfoBarService(id
);
287 if (!infobar_service
|| in_shutdown_
) {
288 ClearPendingInfobarRequestsForTab(id
);
292 for (PendingInfobarRequests::iterator i
= pending_infobar_requests_
.begin();
293 i
!= pending_infobar_requests_
.end(); ++i
) {
294 if (i
->id().IsForSameTabAs(id
) && !i
->has_infobar()) {
295 RegisterForInfoBarNotifications(infobar_service
);
297 this, profile_
->GetPrefs()->GetString(prefs::kAcceptLanguages
));
302 UnregisterForInfoBarNotifications(infobar_service
);
305 void PermissionQueueController::ClearPendingInfobarRequestsForTab(
306 const PermissionRequestID
& id
) {
307 for (PendingInfobarRequests::iterator i
= pending_infobar_requests_
.begin();
308 i
!= pending_infobar_requests_
.end(); ) {
309 if (i
->id().IsForSameTabAs(id
)) {
310 DCHECK(!i
->has_infobar());
311 i
= pending_infobar_requests_
.erase(i
);
318 void PermissionQueueController::RegisterForInfoBarNotifications(
319 InfoBarService
* infobar_service
) {
320 if (!registrar_
.IsRegistered(
321 this, chrome::NOTIFICATION_TAB_CONTENTS_INFOBAR_REMOVED
,
322 content::Source
<InfoBarService
>(infobar_service
))) {
324 chrome::NOTIFICATION_TAB_CONTENTS_INFOBAR_REMOVED
,
325 content::Source
<InfoBarService
>(infobar_service
));
329 void PermissionQueueController::UnregisterForInfoBarNotifications(
330 InfoBarService
* infobar_service
) {
331 if (registrar_
.IsRegistered(
332 this, chrome::NOTIFICATION_TAB_CONTENTS_INFOBAR_REMOVED
,
333 content::Source
<InfoBarService
>(infobar_service
))) {
334 registrar_
.Remove(this,
335 chrome::NOTIFICATION_TAB_CONTENTS_INFOBAR_REMOVED
,
336 content::Source
<InfoBarService
>(infobar_service
));
340 void PermissionQueueController::UpdateContentSetting(
341 const GURL
& requesting_frame
,
342 const GURL
& embedder
,
344 if (requesting_frame
.GetOrigin().SchemeIsFile()) {
345 // Chrome can be launched with --disable-web-security which allows
346 // geolocation requests from file:// URLs. We don't want to store these
347 // in the host content settings map.
351 ContentSetting content_setting
=
352 allowed
? CONTENT_SETTING_ALLOW
: CONTENT_SETTING_BLOCK
;
353 profile_
->GetHostContentSettingsMap()->SetContentSetting(
354 ContentSettingsPattern::FromURLNoWildcard(requesting_frame
.GetOrigin()),
355 ContentSettingsPattern::FromURLNoWildcard(embedder
.GetOrigin()),