1 // Copyright 2014 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/ui/website_settings/permission_bubble_manager.h"
7 #include "base/command_line.h"
8 #include "base/metrics/user_metrics_action.h"
9 #include "chrome/browser/ui/website_settings/permission_bubble_request.h"
10 #include "chrome/common/chrome_switches.h"
11 #include "content/public/browser/browser_thread.h"
12 #include "content/public/browser/navigation_details.h"
13 #include "content/public/browser/user_metrics.h"
15 #if !defined(OS_ANDROID)
16 #include "chrome/browser/ui/browser_finder.h"
21 class CancelledRequest
: public PermissionBubbleRequest
{
23 explicit CancelledRequest(PermissionBubbleRequest
* cancelled
)
24 : icon_(cancelled
->GetIconId()),
25 message_text_(cancelled
->GetMessageText()),
26 message_fragment_(cancelled
->GetMessageTextFragment()),
27 user_gesture_(cancelled
->HasUserGesture()),
28 hostname_(cancelled
->GetRequestingHostname()) {}
29 ~CancelledRequest() override
{}
31 int GetIconId() const override
{ return icon_
; }
32 base::string16
GetMessageText() const override
{ return message_text_
; }
33 base::string16
GetMessageTextFragment() const override
{
34 return message_fragment_
;
36 bool HasUserGesture() const override
{ return user_gesture_
; }
37 GURL
GetRequestingHostname() const override
{ return hostname_
; }
39 // These are all no-ops since the placeholder is non-forwarding.
40 void PermissionGranted() override
{}
41 void PermissionDenied() override
{}
42 void Cancelled() override
{}
44 void RequestFinished() override
{ delete this; }
48 base::string16 message_text_
;
49 base::string16 message_fragment_
;
56 // PermissionBubbleManager::Observer -------------------------------------------
58 PermissionBubbleManager::Observer::~Observer() {
61 void PermissionBubbleManager::Observer::OnBubbleAdded() {
64 // PermissionBubbleManager -----------------------------------------------------
66 DEFINE_WEB_CONTENTS_USER_DATA_KEY(PermissionBubbleManager
);
69 bool PermissionBubbleManager::Enabled() {
70 #if defined(OS_ANDROID) || defined(OS_IOS)
73 if (base::CommandLine::ForCurrentProcess()->HasSwitch(
74 switches::kDisablePermissionsBubbles
))
79 PermissionBubbleManager::PermissionBubbleManager(
80 content::WebContents
* web_contents
)
81 : content::WebContentsObserver(web_contents
),
82 require_user_gesture_(false),
83 #if !defined(OS_ANDROID) // No bubbles in android tests.
84 view_factory_(base::Bind(&PermissionBubbleView::Create
)),
87 main_frame_has_fully_loaded_(false),
88 auto_response_for_test_(NONE
),
92 PermissionBubbleManager::~PermissionBubbleManager() {
94 view_
->SetDelegate(NULL
);
96 std::vector
<PermissionBubbleRequest
*>::iterator requests_iter
;
97 for (requests_iter
= requests_
.begin();
98 requests_iter
!= requests_
.end();
100 (*requests_iter
)->RequestFinished();
102 for (requests_iter
= queued_requests_
.begin();
103 requests_iter
!= queued_requests_
.end();
105 (*requests_iter
)->RequestFinished();
109 void PermissionBubbleManager::AddRequest(PermissionBubbleRequest
* request
) {
110 content::RecordAction(base::UserMetricsAction("PermissionBubbleRequest"));
111 // TODO(gbillock): is there a race between an early request on a
112 // newly-navigated page and the to-be-cleaned-up requests on the previous
113 // page? We should maybe listen to DidStartNavigationToPendingEntry (and
114 // any other renderer-side nav initiations?). Double-check this for
115 // correct behavior on interstitials -- we probably want to basically queue
116 // any request for which GetVisibleURL != GetLastCommittedURL.
117 request_url_
= web_contents()->GetLastCommittedURL();
119 request
->GetRequestingHostname().GetOrigin() == request_url_
.GetOrigin();
121 // Don't re-add an existing request or one with a duplicate text request.
122 bool same_object
= false;
123 if (ExistingRequest(request
, requests_
, &same_object
) ||
124 ExistingRequest(request
, queued_requests_
, &same_object
) ||
125 ExistingRequest(request
, queued_frame_requests_
, &same_object
)) {
127 request
->RequestFinished();
131 if (IsBubbleVisible()) {
133 content::RecordAction(
134 base::UserMetricsAction("PermissionBubbleRequestQueued"));
135 queued_requests_
.push_back(request
);
137 content::RecordAction(
138 base::UserMetricsAction("PermissionBubbleIFrameRequestQueued"));
139 queued_frame_requests_
.push_back(request
);
145 requests_
.push_back(request
);
146 // TODO(gbillock): do we need to make default state a request property?
147 accept_states_
.push_back(true);
149 content::RecordAction(
150 base::UserMetricsAction("PermissionBubbleIFrameRequestQueued"));
151 queued_frame_requests_
.push_back(request
);
154 if (!require_user_gesture_
|| request
->HasUserGesture())
155 ScheduleShowBubble();
158 void PermissionBubbleManager::CancelRequest(PermissionBubbleRequest
* request
) {
159 // First look in the queued requests, where we can simply delete the request
161 std::vector
<PermissionBubbleRequest
*>::iterator requests_iter
;
162 for (requests_iter
= queued_requests_
.begin();
163 requests_iter
!= queued_requests_
.end();
165 if (*requests_iter
== request
) {
166 (*requests_iter
)->RequestFinished();
167 queued_requests_
.erase(requests_iter
);
172 std::vector
<bool>::iterator accepts_iter
= accept_states_
.begin();
173 for (requests_iter
= requests_
.begin(), accepts_iter
= accept_states_
.begin();
174 requests_iter
!= requests_
.end();
175 requests_iter
++, accepts_iter
++) {
176 if (*requests_iter
!= request
)
179 // We can simply erase the current entry in the request table if we aren't
180 // showing the dialog, or if we are showing it and it can accept the update.
181 bool can_erase
= !IsBubbleVisible() || view_
->CanAcceptRequestUpdate();
183 (*requests_iter
)->RequestFinished();
184 requests_
.erase(requests_iter
);
185 accept_states_
.erase(accepts_iter
);
187 if (IsBubbleVisible()) {
189 // Will redraw the bubble if it is being shown.
195 // Cancel the existing request and replace it with a dummy.
196 PermissionBubbleRequest
* cancelled_request
=
197 new CancelledRequest(*requests_iter
);
198 (*requests_iter
)->RequestFinished();
199 *requests_iter
= cancelled_request
;
203 NOTREACHED(); // Callers should not cancel requests that are not pending.
206 void PermissionBubbleManager::HideBubble() {
207 // Disengage from the existing view if there is one.
211 view_
->SetDelegate(nullptr);
216 void PermissionBubbleManager::DisplayPendingRequests() {
217 if (IsBubbleVisible())
220 #if defined(OS_ANDROID)
224 view_
= view_factory_
.Run(chrome::FindBrowserWithWebContents(web_contents()));
225 view_
->SetDelegate(this);
231 void PermissionBubbleManager::UpdateAnchorPosition() {
233 view_
->UpdateAnchorPosition();
236 bool PermissionBubbleManager::IsBubbleVisible() {
237 return view_
&& view_
->IsVisible();
240 gfx::NativeWindow
PermissionBubbleManager::GetBubbleWindow() {
242 return view_
->GetNativeWindow();
246 void PermissionBubbleManager::RequireUserGesture(bool required
) {
247 require_user_gesture_
= required
;
250 void PermissionBubbleManager::DidNavigateMainFrame(
251 const content::LoadCommittedDetails
& details
,
252 const content::FrameNavigateParams
& params
) {
253 if (details
.is_in_page
)
256 CancelPendingQueues();
258 main_frame_has_fully_loaded_
= false;
261 void PermissionBubbleManager::DocumentOnLoadCompletedInMainFrame() {
262 main_frame_has_fully_loaded_
= true;
263 // This is scheduled because while all calls to the browser have been
264 // issued at DOMContentLoaded, they may be bouncing around in scheduled
265 // callbacks finding the UI thread still. This makes sure we allow those
266 // scheduled calls to AddRequest to complete before we show the page-load
267 // permissions bubble.
268 ScheduleShowBubble();
271 void PermissionBubbleManager::DocumentLoadedInFrame(
272 content::RenderFrameHost
* render_frame_host
) {
273 ScheduleShowBubble();
276 void PermissionBubbleManager::WebContentsDestroyed() {
277 // If the web contents has been destroyed, treat the bubble as cancelled.
278 CancelPendingQueues();
281 // The WebContents is going away; be aggressively paranoid and delete
282 // ourselves lest other parts of the system attempt to add permission bubbles
283 // or use us otherwise during the destruction.
284 web_contents()->RemoveUserData(UserDataKey());
285 // That was the equivalent of "delete this". This object is now destroyed;
286 // returning from this function is the only safe thing to do.
289 void PermissionBubbleManager::ToggleAccept(int request_index
, bool new_value
) {
290 DCHECK(request_index
< static_cast<int>(accept_states_
.size()));
291 accept_states_
[request_index
] = new_value
;
294 void PermissionBubbleManager::Accept() {
295 std::vector
<PermissionBubbleRequest
*>::iterator requests_iter
;
296 std::vector
<bool>::iterator accepts_iter
= accept_states_
.begin();
297 for (requests_iter
= requests_
.begin(), accepts_iter
= accept_states_
.begin();
298 requests_iter
!= requests_
.end();
299 requests_iter
++, accepts_iter
++) {
301 (*requests_iter
)->PermissionGranted();
303 (*requests_iter
)->PermissionDenied();
308 void PermissionBubbleManager::Deny() {
309 std::vector
<PermissionBubbleRequest
*>::iterator requests_iter
;
310 for (requests_iter
= requests_
.begin();
311 requests_iter
!= requests_
.end();
313 (*requests_iter
)->PermissionDenied();
318 void PermissionBubbleManager::Closing() {
319 std::vector
<PermissionBubbleRequest
*>::iterator requests_iter
;
320 for (requests_iter
= requests_
.begin();
321 requests_iter
!= requests_
.end();
323 (*requests_iter
)->Cancelled();
328 void PermissionBubbleManager::ScheduleShowBubble() {
329 // ::ScheduleShowBubble() will be called again when the main frame will be
331 if (!main_frame_has_fully_loaded_
)
334 content::BrowserThread::PostTask(
335 content::BrowserThread::UI
,
337 base::Bind(&PermissionBubbleManager::TriggerShowBubble
,
338 weak_factory_
.GetWeakPtr()));
341 void PermissionBubbleManager::TriggerShowBubble() {
344 if (IsBubbleVisible())
346 if (!main_frame_has_fully_loaded_
)
348 if (requests_
.empty() && queued_requests_
.empty() &&
349 queued_frame_requests_
.empty()) {
353 if (requests_
.empty()) {
354 // Queues containing a user-gesture-generated request have priority.
355 if (HasUserGestureRequest(queued_requests_
))
356 requests_
.swap(queued_requests_
);
357 else if (HasUserGestureRequest(queued_frame_requests_
))
358 requests_
.swap(queued_frame_requests_
);
359 else if (queued_requests_
.size())
360 requests_
.swap(queued_requests_
);
362 requests_
.swap(queued_frame_requests_
);
364 // Sets the default value for each request to be 'accept'.
365 // TODO(leng): Currently all requests default to true. If that changes:
366 // a) Add additional accept_state queues to store default values.
367 // b) Change the request API to provide the default value.
368 accept_states_
.resize(requests_
.size(), true);
371 view_
->Show(requests_
, accept_states_
);
374 // If in testing mode, automatically respond to the bubble that was shown.
375 if (auto_response_for_test_
!= NONE
)
376 DoAutoResponseForTesting();
379 void PermissionBubbleManager::FinalizeBubble() {
383 std::vector
<PermissionBubbleRequest
*>::iterator requests_iter
;
384 for (requests_iter
= requests_
.begin();
385 requests_iter
!= requests_
.end();
387 (*requests_iter
)->RequestFinished();
390 accept_states_
.clear();
391 if (queued_requests_
.size() || queued_frame_requests_
.size())
394 request_url_
= GURL();
397 void PermissionBubbleManager::CancelPendingQueues() {
398 std::vector
<PermissionBubbleRequest
*>::iterator requests_iter
;
399 for (requests_iter
= queued_requests_
.begin();
400 requests_iter
!= queued_requests_
.end();
402 (*requests_iter
)->RequestFinished();
404 for (requests_iter
= queued_frame_requests_
.begin();
405 requests_iter
!= queued_frame_requests_
.end();
407 (*requests_iter
)->RequestFinished();
409 queued_requests_
.clear();
410 queued_frame_requests_
.clear();
413 bool PermissionBubbleManager::ExistingRequest(
414 PermissionBubbleRequest
* request
,
415 const std::vector
<PermissionBubbleRequest
*>& queue
,
418 *same_object
= false;
419 std::vector
<PermissionBubbleRequest
*>::const_iterator iter
;
420 for (iter
= queue
.begin(); iter
!= queue
.end(); iter
++) {
421 if (*iter
== request
) {
425 if ((*iter
)->GetMessageTextFragment() ==
426 request
->GetMessageTextFragment() &&
427 (*iter
)->GetRequestingHostname() == request
->GetRequestingHostname()) {
434 bool PermissionBubbleManager::HasUserGestureRequest(
435 const std::vector
<PermissionBubbleRequest
*>& queue
) {
436 std::vector
<PermissionBubbleRequest
*>::const_iterator iter
;
437 for (iter
= queue
.begin(); iter
!= queue
.end(); iter
++) {
438 if ((*iter
)->HasUserGesture())
444 void PermissionBubbleManager::AddObserver(Observer
* observer
) {
445 observer_list_
.AddObserver(observer
);
448 void PermissionBubbleManager::RemoveObserver(Observer
* observer
) {
449 observer_list_
.RemoveObserver(observer
);
452 void PermissionBubbleManager::NotifyBubbleAdded() {
453 FOR_EACH_OBSERVER(Observer
, observer_list_
, OnBubbleAdded());
456 void PermissionBubbleManager::DoAutoResponseForTesting() {
457 switch (auto_response_for_test_
) {