- add sources.
[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/geolocation/geolocation_infobar_delegate.h"
11 #include "chrome/browser/infobars/infobar.h"
12 #include "chrome/browser/infobars/infobar_service.h"
13 #include "chrome/browser/media/midi_permission_infobar_delegate.h"
14 #include "chrome/browser/profiles/profile.h"
15 #include "chrome/browser/tab_contents/tab_util.h"
16 #include "chrome/common/content_settings.h"
17 #include "chrome/common/pref_names.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
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                         PermissionDecidedCallback callback);
46   ~PendingInfoBarRequest();
47
48   bool IsForPair(const GURL& requesting_frame,
49                  const GURL& embedder) const;
50
51   const PermissionRequestID& id() const { return id_; }
52   const GURL& requesting_frame() const { return requesting_frame_; }
53   bool has_infobar() const { return !!infobar_; }
54   InfoBarDelegate* infobar() { return infobar_; }
55
56   void RunCallback(bool allowed);
57   void CreateInfoBar(PermissionQueueController* controller,
58                      const std::string& display_languages);
59
60  private:
61   ContentSettingsType type_;
62   PermissionRequestID id_;
63   GURL requesting_frame_;
64   GURL embedder_;
65   PermissionDecidedCallback callback_;
66   InfoBarDelegate* infobar_;
67
68   // Purposefully do not disable copying, as this is stored in STL containers.
69 };
70
71 PermissionQueueController::PendingInfoBarRequest::PendingInfoBarRequest(
72     ContentSettingsType type,
73     const PermissionRequestID& id,
74     const GURL& requesting_frame,
75     const GURL& embedder,
76     PermissionDecidedCallback callback)
77     : type_(type),
78       id_(id),
79       requesting_frame_(requesting_frame),
80       embedder_(embedder),
81       callback_(callback),
82       infobar_(NULL) {
83 }
84
85 PermissionQueueController::PendingInfoBarRequest::~PendingInfoBarRequest() {
86 }
87
88 bool PermissionQueueController::PendingInfoBarRequest::IsForPair(
89     const GURL& requesting_frame,
90     const GURL& embedder) const {
91   return (requesting_frame_ == requesting_frame) && (embedder_ == embedder);
92 }
93
94 void PermissionQueueController::PendingInfoBarRequest::RunCallback(
95     bool allowed) {
96   callback_.Run(allowed);
97 }
98
99 void PermissionQueueController::PendingInfoBarRequest::CreateInfoBar(
100     PermissionQueueController* controller,
101     const std::string& display_languages) {
102   // TODO(toyoshim): Remove following ContentType dependent code.
103   // Also these InfoBarDelegate can share much more code each other.
104   // http://crbug.com/266743
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     case CONTENT_SETTINGS_TYPE_MIDI_SYSEX:
112       infobar_ = MIDIPermissionInfoBarDelegate::Create(
113           GetInfoBarService(id_), controller, id_, requesting_frame_,
114           display_languages);
115       break;
116 #if defined(OS_ANDROID)
117     case CONTENT_SETTINGS_TYPE_PROTECTED_MEDIA_IDENTIFIER:
118       infobar_ = ProtectedMediaIdentifierInfoBarDelegate::Create(
119           GetInfoBarService(id_), controller, id_, requesting_frame_,
120           display_languages);
121       break;
122 #endif
123     default:
124       NOTREACHED();
125       break;
126   }
127 }
128
129
130 PermissionQueueController::PermissionQueueController(Profile* profile,
131                                                      ContentSettingsType type)
132     : profile_(profile),
133       type_(type),
134       in_shutdown_(false) {
135 }
136
137 PermissionQueueController::~PermissionQueueController() {
138   // Cancel all outstanding requests.
139   in_shutdown_ = true;
140   while (!pending_infobar_requests_.empty())
141     CancelInfoBarRequest(pending_infobar_requests_.front().id());
142 }
143
144 void PermissionQueueController::CreateInfoBarRequest(
145     const PermissionRequestID& id,
146     const GURL& requesting_frame,
147     const GURL& embedder,
148     PermissionDecidedCallback callback) {
149   DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
150
151   // We shouldn't get duplicate requests.
152   for (PendingInfoBarRequests::const_iterator i(
153            pending_infobar_requests_.begin());
154        i != pending_infobar_requests_.end(); ++i)
155     DCHECK(!i->id().Equals(id));
156
157   pending_infobar_requests_.push_back(PendingInfoBarRequest(
158       type_, id, requesting_frame, embedder, callback));
159   if (!AlreadyShowingInfoBarForTab(id))
160     ShowQueuedInfoBarForTab(id);
161 }
162
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
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
187   if (update_content_setting)
188     UpdateContentSetting(requesting_frame, embedder, allowed);
189
190   // Cancel this request first, then notify listeners.  TODO(pkasting): Why
191   // is this order important?
192   PendingInfoBarRequests requests_to_notify;
193   PendingInfoBarRequests infobars_to_remove;
194   for (PendingInfoBarRequests::iterator i = pending_infobar_requests_.begin();
195        i != pending_infobar_requests_.end(); ) {
196     if (i->IsForPair(requesting_frame, embedder)) {
197       requests_to_notify.push_back(*i);
198       if (i->id().Equals(id)) {
199         // The infobar that called us is i->infobar(), and it's currently in
200         // either Accept() or Cancel(). This means that RemoveInfoBar() will be
201         // called later on, and that will trigger a notification we're
202         // observing.
203         ++i;
204       } else if (i->has_infobar()) {
205         // This infobar is for the same frame/embedder pair, but in a different
206         // tab. We should remove it now that we've got an answer for it.
207         infobars_to_remove.push_back(*i);
208         ++i;
209       } else {
210         // We haven't created an infobar yet, just remove the pending request.
211         i = pending_infobar_requests_.erase(i);
212       }
213     } else {
214       ++i;
215     }
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
229 void PermissionQueueController::Observe(
230     int type,
231     const content::NotificationSource& source,
232     const content::NotificationDetails& details) {
233   DCHECK_EQ(chrome::NOTIFICATION_TAB_CONTENTS_INFOBAR_REMOVED, type);
234   // We will receive this notification for all infobar closures, so we need to
235   // check whether this is the geolocation infobar we're tracking. Note that the
236   // InfoBarContainer (if any) may have received this notification before us and
237   // caused the infobar to be deleted, so it's not safe to dereference the
238   // contents of the infobar. The address of the infobar, however, is OK to
239   // use to find the PendingInfoBarRequest to remove because
240   // pending_infobar_requests_ will not have received any new entries between
241   // the NotificationService's call to InfoBarContainer::Observe and this
242   // method.
243   InfoBarDelegate* infobar =
244       content::Details<InfoBarRemovedDetails>(details)->first;
245   for (PendingInfoBarRequests::iterator i = pending_infobar_requests_.begin();
246        i != pending_infobar_requests_.end(); ++i) {
247     if (i->infobar() == infobar) {
248       PermissionRequestID id(i->id());
249       pending_infobar_requests_.erase(i);
250       ShowQueuedInfoBarForTab(id);
251       return;
252     }
253   }
254 }
255
256 bool PermissionQueueController::AlreadyShowingInfoBarForTab(
257     const PermissionRequestID& id) const {
258   for (PendingInfoBarRequests::const_iterator i(
259            pending_infobar_requests_.begin());
260        i != pending_infobar_requests_.end(); ++i) {
261     if (i->id().IsForSameTabAs(id) && i->has_infobar())
262       return true;
263   }
264   return false;
265 }
266
267 void PermissionQueueController::ShowQueuedInfoBarForTab(
268     const PermissionRequestID& id) {
269   DCHECK(!AlreadyShowingInfoBarForTab(id));
270
271   // We can get here for example during tab shutdown, when the InfoBarService is
272   // removing all existing infobars, thus calling back to Observe(). In this
273   // case the service still exists, and is supplied as the source of the
274   // notification we observed, but is no longer accessible from its WebContents.
275   // In this case we should just go ahead and cancel further infobars for this
276   // tab instead of trying to access the service.
277   //
278   // Similarly, if we're being destroyed, we should also avoid showing further
279   // infobars.
280   InfoBarService* infobar_service = GetInfoBarService(id);
281   if (!infobar_service || in_shutdown_) {
282     ClearPendingInfoBarRequestsForTab(id);
283     return;
284   }
285
286   for (PendingInfoBarRequests::iterator i = pending_infobar_requests_.begin();
287        i != pending_infobar_requests_.end(); ++i) {
288     if (i->id().IsForSameTabAs(id) && !i->has_infobar()) {
289       RegisterForInfoBarNotifications(infobar_service);
290       i->CreateInfoBar(
291           this, profile_->GetPrefs()->GetString(prefs::kAcceptLanguages));
292       return;
293     }
294   }
295
296   UnregisterForInfoBarNotifications(infobar_service);
297 }
298
299 void PermissionQueueController::ClearPendingInfoBarRequestsForTab(
300     const PermissionRequestID& id) {
301   for (PendingInfoBarRequests::iterator i = pending_infobar_requests_.begin();
302        i != pending_infobar_requests_.end(); ) {
303     if (i->id().IsForSameTabAs(id)) {
304       DCHECK(!i->has_infobar());
305       i = pending_infobar_requests_.erase(i);
306     } else {
307       ++i;
308     }
309   }
310 }
311
312 void PermissionQueueController::RegisterForInfoBarNotifications(
313     InfoBarService* infobar_service) {
314   if (!registrar_.IsRegistered(
315       this, chrome::NOTIFICATION_TAB_CONTENTS_INFOBAR_REMOVED,
316       content::Source<InfoBarService>(infobar_service))) {
317     registrar_.Add(this,
318                    chrome::NOTIFICATION_TAB_CONTENTS_INFOBAR_REMOVED,
319                    content::Source<InfoBarService>(infobar_service));
320   }
321 }
322
323 void PermissionQueueController::UnregisterForInfoBarNotifications(
324     InfoBarService* infobar_service) {
325   if (registrar_.IsRegistered(
326       this, chrome::NOTIFICATION_TAB_CONTENTS_INFOBAR_REMOVED,
327       content::Source<InfoBarService>(infobar_service))) {
328     registrar_.Remove(this,
329                       chrome::NOTIFICATION_TAB_CONTENTS_INFOBAR_REMOVED,
330                       content::Source<InfoBarService>(infobar_service));
331   }
332 }
333
334 void PermissionQueueController::UpdateContentSetting(
335     const GURL& requesting_frame,
336     const GURL& embedder,
337     bool allowed) {
338   if (requesting_frame.GetOrigin().SchemeIsFile()) {
339     // Chrome can be launched with --disable-web-security which allows
340     // geolocation requests from file:// URLs. We don't want to store these
341     // in the host content settings map.
342     return;
343   }
344
345   ContentSetting content_setting =
346       allowed ? CONTENT_SETTING_ALLOW : CONTENT_SETTING_BLOCK;
347   profile_->GetHostContentSettingsMap()->SetContentSetting(
348       ContentSettingsPattern::FromURLNoWildcard(requesting_frame.GetOrigin()),
349       ContentSettingsPattern::FromURLNoWildcard(embedder.GetOrigin()),
350       type_,
351       std::string(),
352       content_setting);
353 }