Add more checks to investigate SupervisedUserPrefStore crash at startup.
[chromium-blink-merge.git] / chrome / browser / content_settings / permission_queue_controller.cc
blob9747ce78d9a4833ec78bc44c893464560a5a8a3f
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/permission_context_uma_util.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/notifications/desktop_notification_infobar_delegate.h"
14 #include "chrome/browser/profiles/profile.h"
15 #include "chrome/browser/tab_contents/tab_util.h"
16 #include "chrome/common/pref_names.h"
17 #include "components/content_settings/core/browser/host_content_settings_map.h"
18 #include "components/content_settings/core/common/content_settings.h"
19 #include "components/infobars/core/infobar.h"
20 #include "content/public/browser/browser_thread.h"
21 #include "content/public/browser/notification_details.h"
22 #include "content/public/browser/notification_source.h"
23 #include "content/public/browser/notification_types.h"
24 #include "content/public/browser/web_contents.h"
25 #include "content/public/common/url_constants.h"
27 #if defined(OS_ANDROID) || defined(OS_CHROMEOS)
28 #include "chrome/browser/media/protected_media_identifier_infobar_delegate.h"
29 #endif
31 namespace {
33 InfoBarService* GetInfoBarService(const PermissionRequestID& id) {
34 content::WebContents* web_contents =
35 tab_util::GetWebContentsByID(id.render_process_id(), id.render_view_id());
36 return web_contents ? InfoBarService::FromWebContents(web_contents) : NULL;
42 class PermissionQueueController::PendingInfobarRequest {
43 public:
44 PendingInfobarRequest(ContentSettingsType type,
45 const PermissionRequestID& id,
46 const GURL& requesting_frame,
47 const GURL& embedder,
48 PermissionDecidedCallback callback);
49 ~PendingInfobarRequest();
51 bool IsForPair(const GURL& requesting_frame,
52 const GURL& embedder) const;
54 const PermissionRequestID& id() const { return id_; }
55 const GURL& requesting_frame() const { return requesting_frame_; }
56 bool has_infobar() const { return !!infobar_; }
57 infobars::InfoBar* infobar() { return infobar_; }
59 void RunCallback(bool allowed);
60 void CreateInfoBar(PermissionQueueController* controller,
61 const std::string& display_languages);
63 private:
64 ContentSettingsType type_;
65 PermissionRequestID id_;
66 GURL requesting_frame_;
67 GURL embedder_;
68 PermissionDecidedCallback callback_;
69 infobars::InfoBar* infobar_;
71 // Purposefully do not disable copying, as this is stored in STL containers.
74 PermissionQueueController::PendingInfobarRequest::PendingInfobarRequest(
75 ContentSettingsType type,
76 const PermissionRequestID& id,
77 const GURL& requesting_frame,
78 const GURL& embedder,
79 PermissionDecidedCallback callback)
80 : type_(type),
81 id_(id),
82 requesting_frame_(requesting_frame),
83 embedder_(embedder),
84 callback_(callback),
85 infobar_(NULL) {
88 PermissionQueueController::PendingInfobarRequest::~PendingInfobarRequest() {
91 bool PermissionQueueController::PendingInfobarRequest::IsForPair(
92 const GURL& requesting_frame,
93 const GURL& embedder) const {
94 return (requesting_frame_ == requesting_frame) && (embedder_ == embedder);
97 void PermissionQueueController::PendingInfobarRequest::RunCallback(
98 bool allowed) {
99 callback_.Run(allowed);
102 void PermissionQueueController::PendingInfobarRequest::CreateInfoBar(
103 PermissionQueueController* controller,
104 const std::string& display_languages) {
105 switch (type_) {
106 case CONTENT_SETTINGS_TYPE_GEOLOCATION:
107 infobar_ = GeolocationInfoBarDelegate::Create(
108 GetInfoBarService(id_), controller, id_, requesting_frame_,
109 display_languages);
110 break;
111 #if defined(ENABLE_NOTIFICATIONS)
112 case CONTENT_SETTINGS_TYPE_NOTIFICATIONS:
113 infobar_ = DesktopNotificationInfoBarDelegate::Create(
114 GetInfoBarService(id_), controller, id_, requesting_frame_,
115 display_languages);
116 break;
117 #endif // ENABLE_NOTIFICATIONS
118 case CONTENT_SETTINGS_TYPE_MIDI_SYSEX:
119 infobar_ = MidiPermissionInfoBarDelegate::Create(
120 GetInfoBarService(id_), controller, id_, requesting_frame_,
121 display_languages, type_);
122 break;
123 #if defined(OS_ANDROID) || defined(OS_CHROMEOS)
124 case CONTENT_SETTINGS_TYPE_PROTECTED_MEDIA_IDENTIFIER:
125 infobar_ = ProtectedMediaIdentifierInfoBarDelegate::Create(
126 GetInfoBarService(id_), controller, id_, requesting_frame_,
127 display_languages);
128 break;
129 #endif
130 default:
131 NOTREACHED();
132 break;
137 PermissionQueueController::PermissionQueueController(Profile* profile,
138 ContentSettingsType type)
139 : profile_(profile),
140 type_(type),
141 in_shutdown_(false) {
144 PermissionQueueController::~PermissionQueueController() {
145 // Cancel all outstanding requests.
146 in_shutdown_ = true;
147 while (!pending_infobar_requests_.empty())
148 CancelInfoBarRequest(pending_infobar_requests_.front().id());
151 void PermissionQueueController::CreateInfoBarRequest(
152 const PermissionRequestID& id,
153 const GURL& requesting_frame,
154 const GURL& embedder,
155 PermissionDecidedCallback callback) {
156 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
158 if (requesting_frame.SchemeIs(content::kChromeUIScheme) ||
159 embedder.SchemeIs(content::kChromeUIScheme))
160 return;
162 pending_infobar_requests_.push_back(PendingInfobarRequest(
163 type_, id, requesting_frame, embedder, callback));
164 if (!AlreadyShowingInfoBarForTab(id))
165 ShowQueuedInfoBarForTab(id);
168 void PermissionQueueController::CancelInfoBarRequest(
169 const PermissionRequestID& id) {
170 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
172 for (PendingInfobarRequests::iterator i(pending_infobar_requests_.begin());
173 i != pending_infobar_requests_.end(); ++i) {
174 if (i->id().Equals(id)) {
175 if (i->has_infobar())
176 GetInfoBarService(id)->RemoveInfoBar(i->infobar());
177 else
178 pending_infobar_requests_.erase(i);
179 return;
184 void PermissionQueueController::OnPermissionSet(
185 const PermissionRequestID& id,
186 const GURL& requesting_frame,
187 const GURL& embedder,
188 bool update_content_setting,
189 bool allowed) {
190 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
192 // TODO(miguelg): move the permission persistence to
193 // PermissionContextBase once all the types are moved there.
194 if (update_content_setting) {
195 UpdateContentSetting(requesting_frame, embedder, allowed);
196 if (allowed)
197 PermissionContextUmaUtil::PermissionGranted(type_, requesting_frame);
198 else
199 PermissionContextUmaUtil::PermissionDenied(type_, requesting_frame);
200 } else {
201 PermissionContextUmaUtil::PermissionDismissed(type_, requesting_frame);
204 // Cancel this request first, then notify listeners. TODO(pkasting): Why
205 // is this order important?
206 PendingInfobarRequests requests_to_notify;
207 PendingInfobarRequests infobars_to_remove;
208 std::vector<PendingInfobarRequests::iterator> pending_requests_to_remove;
209 for (PendingInfobarRequests::iterator i = pending_infobar_requests_.begin();
210 i != pending_infobar_requests_.end(); ++i) {
211 if (!i->IsForPair(requesting_frame, embedder))
212 continue;
213 requests_to_notify.push_back(*i);
214 if (!i->has_infobar()) {
215 // We haven't created an infobar yet, just record the pending request
216 // index and remove it later.
217 pending_requests_to_remove.push_back(i);
218 continue;
220 if (i->id().Equals(id)) {
221 // The infobar that called us is i->infobar(), and its delegate is
222 // currently in either Accept() or Cancel(). This means that
223 // RemoveInfoBar() will be called later on, and that will trigger a
224 // notification we're observing.
225 continue;
228 // This infobar is for the same frame/embedder pair, but in a different
229 // tab. We should remove it now that we've got an answer for it.
230 infobars_to_remove.push_back(*i);
233 // Remove all infobars for the same |requesting_frame| and |embedder|.
234 for (PendingInfobarRequests::iterator i = infobars_to_remove.begin();
235 i != infobars_to_remove.end(); ++i)
236 GetInfoBarService(i->id())->RemoveInfoBar(i->infobar());
238 // Send out the permission notifications.
239 for (PendingInfobarRequests::iterator i = requests_to_notify.begin();
240 i != requests_to_notify.end(); ++i)
241 i->RunCallback(allowed);
243 // Remove the pending requests in reverse order.
244 for (int i = pending_requests_to_remove.size() - 1; i >= 0; --i)
245 pending_infobar_requests_.erase(pending_requests_to_remove[i]);
248 void PermissionQueueController::Observe(
249 int type,
250 const content::NotificationSource& source,
251 const content::NotificationDetails& details) {
252 DCHECK_EQ(chrome::NOTIFICATION_TAB_CONTENTS_INFOBAR_REMOVED, type);
253 // We will receive this notification for all infobar closures, so we need to
254 // check whether this is the geolocation infobar we're tracking. Note that the
255 // InfoBarContainer (if any) may have received this notification before us and
256 // caused the infobar to be deleted, so it's not safe to dereference the
257 // contents of the infobar. The address of the infobar, however, is OK to
258 // use to find the PendingInfobarRequest to remove because
259 // pending_infobar_requests_ will not have received any new entries between
260 // the NotificationService's call to InfoBarContainer::Observe and this
261 // method.
262 infobars::InfoBar* infobar =
263 content::Details<infobars::InfoBar::RemovedDetails>(details)->first;
264 for (PendingInfobarRequests::iterator i = pending_infobar_requests_.begin();
265 i != pending_infobar_requests_.end(); ++i) {
266 if (i->infobar() == infobar) {
267 PermissionRequestID id(i->id());
268 pending_infobar_requests_.erase(i);
269 ShowQueuedInfoBarForTab(id);
270 return;
275 bool PermissionQueueController::AlreadyShowingInfoBarForTab(
276 const PermissionRequestID& id) const {
277 for (PendingInfobarRequests::const_iterator i(
278 pending_infobar_requests_.begin());
279 i != pending_infobar_requests_.end(); ++i) {
280 if (i->id().IsForSameTabAs(id) && i->has_infobar())
281 return true;
283 return false;
286 void PermissionQueueController::ShowQueuedInfoBarForTab(
287 const PermissionRequestID& id) {
288 DCHECK(!AlreadyShowingInfoBarForTab(id));
290 // We can get here for example during tab shutdown, when the InfoBarService is
291 // removing all existing infobars, thus calling back to Observe(). In this
292 // case the service still exists, and is supplied as the source of the
293 // notification we observed, but is no longer accessible from its WebContents.
294 // In this case we should just go ahead and cancel further infobars for this
295 // tab instead of trying to access the service.
297 // Similarly, if we're being destroyed, we should also avoid showing further
298 // infobars.
299 InfoBarService* infobar_service = GetInfoBarService(id);
300 if (!infobar_service || in_shutdown_) {
301 ClearPendingInfobarRequestsForTab(id);
302 return;
305 for (PendingInfobarRequests::iterator i = pending_infobar_requests_.begin();
306 i != pending_infobar_requests_.end(); ++i) {
307 if (i->id().IsForSameTabAs(id) && !i->has_infobar()) {
308 RegisterForInfoBarNotifications(infobar_service);
309 i->CreateInfoBar(
310 this, profile_->GetPrefs()->GetString(prefs::kAcceptLanguages));
311 return;
315 UnregisterForInfoBarNotifications(infobar_service);
318 void PermissionQueueController::ClearPendingInfobarRequestsForTab(
319 const PermissionRequestID& id) {
320 for (PendingInfobarRequests::iterator i = pending_infobar_requests_.begin();
321 i != pending_infobar_requests_.end(); ) {
322 if (i->id().IsForSameTabAs(id)) {
323 DCHECK(!i->has_infobar());
324 i = pending_infobar_requests_.erase(i);
325 } else {
326 ++i;
331 void PermissionQueueController::RegisterForInfoBarNotifications(
332 InfoBarService* infobar_service) {
333 if (!registrar_.IsRegistered(
334 this, chrome::NOTIFICATION_TAB_CONTENTS_INFOBAR_REMOVED,
335 content::Source<InfoBarService>(infobar_service))) {
336 registrar_.Add(this,
337 chrome::NOTIFICATION_TAB_CONTENTS_INFOBAR_REMOVED,
338 content::Source<InfoBarService>(infobar_service));
342 void PermissionQueueController::UnregisterForInfoBarNotifications(
343 InfoBarService* infobar_service) {
344 if (registrar_.IsRegistered(
345 this, chrome::NOTIFICATION_TAB_CONTENTS_INFOBAR_REMOVED,
346 content::Source<InfoBarService>(infobar_service))) {
347 registrar_.Remove(this,
348 chrome::NOTIFICATION_TAB_CONTENTS_INFOBAR_REMOVED,
349 content::Source<InfoBarService>(infobar_service));
353 void PermissionQueueController::UpdateContentSetting(
354 const GURL& requesting_frame,
355 const GURL& embedder,
356 bool allowed) {
357 if (requesting_frame.GetOrigin().SchemeIsFile()) {
358 // Chrome can be launched with --disable-web-security which allows
359 // geolocation requests from file:// URLs. We don't want to store these
360 // in the host content settings map.
361 return;
364 ContentSetting content_setting =
365 allowed ? CONTENT_SETTING_ALLOW : CONTENT_SETTING_BLOCK;
367 ContentSettingsPattern embedder_pattern =
368 (type_ == CONTENT_SETTINGS_TYPE_NOTIFICATIONS) ?
369 ContentSettingsPattern::Wildcard() :
370 ContentSettingsPattern::FromURLNoWildcard(embedder.GetOrigin());
372 profile_->GetHostContentSettingsMap()->SetContentSetting(
373 ContentSettingsPattern::FromURLNoWildcard(requesting_frame.GetOrigin()),
374 embedder_pattern,
375 type_,
376 std::string(),
377 content_setting);