• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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