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