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.
4
5 #include "chrome/browser/content_settings/permission_queue_controller.h"
6
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"
23
24 #if defined(OS_ANDROID)
25 #include "chrome/browser/media/protected_media_identifier_infobar_delegate.h"
26 #endif
27
28 namespace {
29
GetInfoBarService(const PermissionRequestID & id)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;
34 }
35
36 }
37
38
39 class PermissionQueueController::PendingInfobarRequest {
40 public:
41 PendingInfobarRequest(ContentSettingsType type,
42 const PermissionRequestID& id,
43 const GURL& requesting_frame,
44 const GURL& embedder,
45 const std::string& accept_button_label,
46 PermissionDecidedCallback callback);
47 ~PendingInfobarRequest();
48
49 bool IsForPair(const GURL& requesting_frame,
50 const GURL& embedder) const;
51
id() const52 const PermissionRequestID& id() const { return id_; }
requesting_frame() const53 const GURL& requesting_frame() const { return requesting_frame_; }
has_infobar() const54 bool has_infobar() const { return !!infobar_; }
infobar()55 infobars::InfoBar* infobar() { return infobar_; }
56
57 void RunCallback(bool allowed);
58 void CreateInfoBar(PermissionQueueController* controller,
59 const std::string& display_languages);
60
61 private:
62 ContentSettingsType type_;
63 PermissionRequestID id_;
64 GURL requesting_frame_;
65 GURL embedder_;
66 std::string accept_button_label_;
67 PermissionDecidedCallback callback_;
68 infobars::InfoBar* infobar_;
69
70 // Purposefully do not disable copying, as this is stored in STL containers.
71 };
72
PendingInfobarRequest(ContentSettingsType type,const PermissionRequestID & id,const GURL & requesting_frame,const GURL & embedder,const std::string & accept_button_label,PermissionDecidedCallback callback)73 PermissionQueueController::PendingInfobarRequest::PendingInfobarRequest(
74 ContentSettingsType type,
75 const PermissionRequestID& id,
76 const GURL& requesting_frame,
77 const GURL& embedder,
78 const std::string& accept_button_label,
79 PermissionDecidedCallback callback)
80 : type_(type),
81 id_(id),
82 requesting_frame_(requesting_frame),
83 embedder_(embedder),
84 accept_button_label_(accept_button_label),
85 callback_(callback),
86 infobar_(NULL) {
87 }
88
~PendingInfobarRequest()89 PermissionQueueController::PendingInfobarRequest::~PendingInfobarRequest() {
90 }
91
IsForPair(const GURL & requesting_frame,const GURL & embedder) const92 bool PermissionQueueController::PendingInfobarRequest::IsForPair(
93 const GURL& requesting_frame,
94 const GURL& embedder) const {
95 return (requesting_frame_ == requesting_frame) && (embedder_ == embedder);
96 }
97
RunCallback(bool allowed)98 void PermissionQueueController::PendingInfobarRequest::RunCallback(
99 bool allowed) {
100 callback_.Run(allowed);
101 }
102
CreateInfoBar(PermissionQueueController * controller,const std::string & display_languages)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
109 switch (type_) {
110 case CONTENT_SETTINGS_TYPE_GEOLOCATION:
111 infobar_ = GeolocationInfoBarDelegate::Create(
112 GetInfoBarService(id_), controller, id_, requesting_frame_,
113 display_languages, accept_button_label_);
114 break;
115 case CONTENT_SETTINGS_TYPE_MIDI_SYSEX:
116 infobar_ = MidiPermissionInfoBarDelegate::Create(
117 GetInfoBarService(id_), controller, id_, requesting_frame_,
118 display_languages);
119 break;
120 #if defined(OS_ANDROID)
121 case CONTENT_SETTINGS_TYPE_PROTECTED_MEDIA_IDENTIFIER:
122 infobar_ = ProtectedMediaIdentifierInfoBarDelegate::Create(
123 GetInfoBarService(id_), controller, id_, requesting_frame_,
124 display_languages);
125 break;
126 #endif
127 default:
128 NOTREACHED();
129 break;
130 }
131 }
132
133
PermissionQueueController(Profile * profile,ContentSettingsType type)134 PermissionQueueController::PermissionQueueController(Profile* profile,
135 ContentSettingsType type)
136 : profile_(profile),
137 type_(type),
138 in_shutdown_(false) {
139 }
140
~PermissionQueueController()141 PermissionQueueController::~PermissionQueueController() {
142 // Cancel all outstanding requests.
143 in_shutdown_ = true;
144 while (!pending_infobar_requests_.empty())
145 CancelInfoBarRequest(pending_infobar_requests_.front().id());
146 }
147
CreateInfoBarRequest(const PermissionRequestID & id,const GURL & requesting_frame,const GURL & embedder,const std::string & accept_button_label,PermissionDecidedCallback callback)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));
155
156 pending_infobar_requests_.push_back(PendingInfobarRequest(
157 type_, id, requesting_frame, embedder,
158 accept_button_label, callback));
159 if (!AlreadyShowingInfoBarForTab(id))
160 ShowQueuedInfoBarForTab(id);
161 }
162
CancelInfoBarRequest(const PermissionRequestID & id)163 void PermissionQueueController::CancelInfoBarRequest(
164 const PermissionRequestID& id) {
165 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
166
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());
172 else
173 pending_infobar_requests_.erase(i);
174 return;
175 }
176 }
177 }
178
OnPermissionSet(const PermissionRequestID & id,const GURL & requesting_frame,const GURL & embedder,bool update_content_setting,bool allowed)179 void PermissionQueueController::OnPermissionSet(
180 const PermissionRequestID& id,
181 const GURL& requesting_frame,
182 const GURL& embedder,
183 bool update_content_setting,
184 bool allowed) {
185 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
186 if (update_content_setting)
187 UpdateContentSetting(requesting_frame, embedder, allowed);
188
189 // Cancel this request first, then notify listeners. TODO(pkasting): Why
190 // is this order important?
191 PendingInfobarRequests requests_to_notify;
192 PendingInfobarRequests infobars_to_remove;
193 std::vector<PendingInfobarRequests::iterator> pending_requests_to_remove;
194 for (PendingInfobarRequests::iterator i = pending_infobar_requests_.begin();
195 i != pending_infobar_requests_.end(); ++i) {
196 if (!i->IsForPair(requesting_frame, embedder))
197 continue;
198 requests_to_notify.push_back(*i);
199 if (!i->has_infobar()) {
200 // We haven't created an infobar yet, just record the pending request
201 // index and remove it later.
202 pending_requests_to_remove.push_back(i);
203 continue;
204 }
205 if (i->id().Equals(id)) {
206 // The infobar that called us is i->infobar(), and its delegate is
207 // currently in either Accept() or Cancel(). This means that
208 // RemoveInfoBar() will be called later on, and that will trigger a
209 // notification we're observing.
210 continue;
211 }
212
213 // This infobar is for the same frame/embedder pair, but in a different
214 // tab. We should remove it now that we've got an answer for it.
215 infobars_to_remove.push_back(*i);
216 }
217
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());
222
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);
227
228 // Remove the pending requests in reverse order.
229 for (int i = pending_requests_to_remove.size() - 1; i >= 0; --i)
230 pending_infobar_requests_.erase(pending_requests_to_remove[i]);
231 }
232
Observe(int type,const content::NotificationSource & source,const content::NotificationDetails & details)233 void PermissionQueueController::Observe(
234 int type,
235 const content::NotificationSource& source,
236 const content::NotificationDetails& details) {
237 DCHECK_EQ(chrome::NOTIFICATION_TAB_CONTENTS_INFOBAR_REMOVED, type);
238 // We will receive this notification for all infobar closures, so we need to
239 // check whether this is the geolocation infobar we're tracking. Note that the
240 // InfoBarContainer (if any) may have received this notification before us and
241 // caused the infobar to be deleted, so it's not safe to dereference the
242 // contents of the infobar. The address of the infobar, however, is OK to
243 // use to find the PendingInfobarRequest to remove because
244 // pending_infobar_requests_ will not have received any new entries between
245 // the NotificationService's call to InfoBarContainer::Observe and this
246 // method.
247 infobars::InfoBar* infobar =
248 content::Details<infobars::InfoBar::RemovedDetails>(details)->first;
249 for (PendingInfobarRequests::iterator i = pending_infobar_requests_.begin();
250 i != pending_infobar_requests_.end(); ++i) {
251 if (i->infobar() == infobar) {
252 PermissionRequestID id(i->id());
253 pending_infobar_requests_.erase(i);
254 ShowQueuedInfoBarForTab(id);
255 return;
256 }
257 }
258 }
259
AlreadyShowingInfoBarForTab(const PermissionRequestID & id) const260 bool PermissionQueueController::AlreadyShowingInfoBarForTab(
261 const PermissionRequestID& id) const {
262 for (PendingInfobarRequests::const_iterator i(
263 pending_infobar_requests_.begin());
264 i != pending_infobar_requests_.end(); ++i) {
265 if (i->id().IsForSameTabAs(id) && i->has_infobar())
266 return true;
267 }
268 return false;
269 }
270
ShowQueuedInfoBarForTab(const PermissionRequestID & id)271 void PermissionQueueController::ShowQueuedInfoBarForTab(
272 const PermissionRequestID& id) {
273 DCHECK(!AlreadyShowingInfoBarForTab(id));
274
275 // We can get here for example during tab shutdown, when the InfoBarService is
276 // removing all existing infobars, thus calling back to Observe(). In this
277 // case the service still exists, and is supplied as the source of the
278 // notification we observed, but is no longer accessible from its WebContents.
279 // In this case we should just go ahead and cancel further infobars for this
280 // tab instead of trying to access the service.
281 //
282 // Similarly, if we're being destroyed, we should also avoid showing further
283 // infobars.
284 InfoBarService* infobar_service = GetInfoBarService(id);
285 if (!infobar_service || in_shutdown_) {
286 ClearPendingInfobarRequestsForTab(id);
287 return;
288 }
289
290 for (PendingInfobarRequests::iterator i = pending_infobar_requests_.begin();
291 i != pending_infobar_requests_.end(); ++i) {
292 if (i->id().IsForSameTabAs(id) && !i->has_infobar()) {
293 RegisterForInfoBarNotifications(infobar_service);
294 i->CreateInfoBar(
295 this, profile_->GetPrefs()->GetString(prefs::kAcceptLanguages));
296 return;
297 }
298 }
299
300 UnregisterForInfoBarNotifications(infobar_service);
301 }
302
ClearPendingInfobarRequestsForTab(const PermissionRequestID & id)303 void PermissionQueueController::ClearPendingInfobarRequestsForTab(
304 const PermissionRequestID& id) {
305 for (PendingInfobarRequests::iterator i = pending_infobar_requests_.begin();
306 i != pending_infobar_requests_.end(); ) {
307 if (i->id().IsForSameTabAs(id)) {
308 DCHECK(!i->has_infobar());
309 i = pending_infobar_requests_.erase(i);
310 } else {
311 ++i;
312 }
313 }
314 }
315
RegisterForInfoBarNotifications(InfoBarService * infobar_service)316 void PermissionQueueController::RegisterForInfoBarNotifications(
317 InfoBarService* infobar_service) {
318 if (!registrar_.IsRegistered(
319 this, chrome::NOTIFICATION_TAB_CONTENTS_INFOBAR_REMOVED,
320 content::Source<InfoBarService>(infobar_service))) {
321 registrar_.Add(this,
322 chrome::NOTIFICATION_TAB_CONTENTS_INFOBAR_REMOVED,
323 content::Source<InfoBarService>(infobar_service));
324 }
325 }
326
UnregisterForInfoBarNotifications(InfoBarService * infobar_service)327 void PermissionQueueController::UnregisterForInfoBarNotifications(
328 InfoBarService* infobar_service) {
329 if (registrar_.IsRegistered(
330 this, chrome::NOTIFICATION_TAB_CONTENTS_INFOBAR_REMOVED,
331 content::Source<InfoBarService>(infobar_service))) {
332 registrar_.Remove(this,
333 chrome::NOTIFICATION_TAB_CONTENTS_INFOBAR_REMOVED,
334 content::Source<InfoBarService>(infobar_service));
335 }
336 }
337
UpdateContentSetting(const GURL & requesting_frame,const GURL & embedder,bool allowed)338 void PermissionQueueController::UpdateContentSetting(
339 const GURL& requesting_frame,
340 const GURL& embedder,
341 bool allowed) {
342 if (requesting_frame.GetOrigin().SchemeIsFile()) {
343 // Chrome can be launched with --disable-web-security which allows
344 // geolocation requests from file:// URLs. We don't want to store these
345 // in the host content settings map.
346 return;
347 }
348
349 ContentSetting content_setting =
350 allowed ? CONTENT_SETTING_ALLOW : CONTENT_SETTING_BLOCK;
351 profile_->GetHostContentSettingsMap()->SetContentSetting(
352 ContentSettingsPattern::FromURLNoWildcard(requesting_frame.GetOrigin()),
353 ContentSettingsPattern::FromURLNoWildcard(embedder.GetOrigin()),
354 type_,
355 std::string(),
356 content_setting);
357 }
358