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"
17 class CancelledRequest
: public PermissionBubbleRequest
{
19 explicit CancelledRequest(PermissionBubbleRequest
* cancelled
)
20 : icon_(cancelled
->GetIconID()),
21 message_text_(cancelled
->GetMessageText()),
22 message_fragment_(cancelled
->GetMessageTextFragment()),
23 user_gesture_(cancelled
->HasUserGesture()),
24 hostname_(cancelled
->GetRequestingHostname()) {}
25 ~CancelledRequest() override
{}
27 int GetIconID() const override
{ return icon_
; }
28 base::string16
GetMessageText() const override
{ return message_text_
; }
29 base::string16
GetMessageTextFragment() const override
{
30 return message_fragment_
;
32 bool HasUserGesture() const override
{ return user_gesture_
; }
33 GURL
GetRequestingHostname() const override
{ return hostname_
; }
35 // These are all no-ops since the placeholder is non-forwarding.
36 void PermissionGranted() override
{}
37 void PermissionDenied() override
{}
38 void Cancelled() override
{}
40 void RequestFinished() override
{ delete this; }
44 base::string16 message_text_
;
45 base::string16 message_fragment_
;
52 // PermissionBubbleManager::Observer -------------------------------------------
54 PermissionBubbleManager::Observer::~Observer() {
57 void PermissionBubbleManager::Observer::OnBubbleAdded() {
60 // PermissionBubbleManager -----------------------------------------------------
62 DEFINE_WEB_CONTENTS_USER_DATA_KEY(PermissionBubbleManager
);
65 bool PermissionBubbleManager::Enabled() {
66 #if defined(OS_ANDROID) || defined(OS_IOS)
69 if (base::CommandLine::ForCurrentProcess()->HasSwitch(
70 switches::kDisablePermissionsBubbles
))
75 PermissionBubbleManager::PermissionBubbleManager(
76 content::WebContents
* web_contents
)
77 : content::WebContentsObserver(web_contents
),
78 require_user_gesture_(false),
79 #if !defined(OS_ANDROID) // No bubbles in android tests.
80 view_factory_(base::Bind(&PermissionBubbleView::Create
)),
83 main_frame_has_fully_loaded_(false),
84 auto_response_for_test_(NONE
),
88 PermissionBubbleManager::~PermissionBubbleManager() {
90 view_
->SetDelegate(NULL
);
92 std::vector
<PermissionBubbleRequest
*>::iterator requests_iter
;
93 for (requests_iter
= requests_
.begin();
94 requests_iter
!= requests_
.end();
96 (*requests_iter
)->RequestFinished();
98 for (requests_iter
= queued_requests_
.begin();
99 requests_iter
!= queued_requests_
.end();
101 (*requests_iter
)->RequestFinished();
105 void PermissionBubbleManager::AddRequest(PermissionBubbleRequest
* request
) {
106 content::RecordAction(base::UserMetricsAction("PermissionBubbleRequest"));
107 // TODO(gbillock): is there a race between an early request on a
108 // newly-navigated page and the to-be-cleaned-up requests on the previous
109 // page? We should maybe listen to DidStartNavigationToPendingEntry (and
110 // any other renderer-side nav initiations?). Double-check this for
111 // correct behavior on interstitials -- we probably want to basically queue
112 // any request for which GetVisibleURL != GetLastCommittedURL.
113 request_url_
= web_contents()->GetLastCommittedURL();
115 request
->GetRequestingHostname().GetOrigin() == request_url_
.GetOrigin();
117 // Don't re-add an existing request or one with a duplicate text request.
118 bool same_object
= false;
119 if (ExistingRequest(request
, requests_
, &same_object
) ||
120 ExistingRequest(request
, queued_requests_
, &same_object
) ||
121 ExistingRequest(request
, queued_frame_requests_
, &same_object
)) {
123 request
->RequestFinished();
127 if (IsBubbleVisible()) {
129 content::RecordAction(
130 base::UserMetricsAction("PermissionBubbleRequestQueued"));
131 queued_requests_
.push_back(request
);
133 content::RecordAction(
134 base::UserMetricsAction("PermissionBubbleIFrameRequestQueued"));
135 queued_frame_requests_
.push_back(request
);
141 requests_
.push_back(request
);
142 // TODO(gbillock): do we need to make default state a request property?
143 accept_states_
.push_back(true);
145 content::RecordAction(
146 base::UserMetricsAction("PermissionBubbleIFrameRequestQueued"));
147 queued_frame_requests_
.push_back(request
);
150 if (!require_user_gesture_
|| request
->HasUserGesture())
151 ScheduleShowBubble();
154 void PermissionBubbleManager::CancelRequest(PermissionBubbleRequest
* request
) {
155 // First look in the queued requests, where we can simply delete the request
157 std::vector
<PermissionBubbleRequest
*>::iterator requests_iter
;
158 for (requests_iter
= queued_requests_
.begin();
159 requests_iter
!= queued_requests_
.end();
161 if (*requests_iter
== request
) {
162 (*requests_iter
)->RequestFinished();
163 queued_requests_
.erase(requests_iter
);
168 std::vector
<bool>::iterator accepts_iter
= accept_states_
.begin();
169 for (requests_iter
= requests_
.begin(), accepts_iter
= accept_states_
.begin();
170 requests_iter
!= requests_
.end();
171 requests_iter
++, accepts_iter
++) {
172 if (*requests_iter
!= request
)
175 // We can simply erase the current entry in the request table if we aren't
176 // showing the dialog, or if we are showing it and it can accept the update.
177 bool can_erase
= !IsBubbleVisible() || view_
->CanAcceptRequestUpdate();
179 (*requests_iter
)->RequestFinished();
180 requests_
.erase(requests_iter
);
181 accept_states_
.erase(accepts_iter
);
183 if (IsBubbleVisible()) {
185 // Will redraw the bubble if it is being shown.
191 // Cancel the existing request and replace it with a dummy.
192 PermissionBubbleRequest
* cancelled_request
=
193 new CancelledRequest(*requests_iter
);
194 (*requests_iter
)->RequestFinished();
195 *requests_iter
= cancelled_request
;
199 NOTREACHED(); // Callers should not cancel requests that are not pending.
202 void PermissionBubbleManager::HideBubble() {
203 // Disengage from the existing view if there is one.
207 view_
->SetDelegate(nullptr);
212 void PermissionBubbleManager::DisplayPendingRequests(Browser
* browser
) {
213 if (IsBubbleVisible())
216 view_
= view_factory_
.Run(browser
);
217 view_
->SetDelegate(this);
222 void PermissionBubbleManager::UpdateAnchorPosition() {
224 view_
->UpdateAnchorPosition();
227 bool PermissionBubbleManager::IsBubbleVisible() {
228 return view_
&& view_
->IsVisible();
231 gfx::NativeWindow
PermissionBubbleManager::GetBubbleWindow() {
233 return view_
->GetNativeWindow();
237 void PermissionBubbleManager::RequireUserGesture(bool required
) {
238 require_user_gesture_
= required
;
241 void PermissionBubbleManager::DidNavigateMainFrame(
242 const content::LoadCommittedDetails
& details
,
243 const content::FrameNavigateParams
& params
) {
244 if (details
.is_in_page
)
247 main_frame_has_fully_loaded_
= false;
250 void PermissionBubbleManager::DocumentOnLoadCompletedInMainFrame() {
251 main_frame_has_fully_loaded_
= true;
252 // This is scheduled because while all calls to the browser have been
253 // issued at DOMContentLoaded, they may be bouncing around in scheduled
254 // callbacks finding the UI thread still. This makes sure we allow those
255 // scheduled calls to AddRequest to complete before we show the page-load
256 // permissions bubble.
257 ScheduleShowBubble();
260 void PermissionBubbleManager::DocumentLoadedInFrame(
261 content::RenderFrameHost
* render_frame_host
) {
262 ScheduleShowBubble();
265 void PermissionBubbleManager::NavigationEntryCommitted(
266 const content::LoadCommittedDetails
& details
) {
267 // No permissions requests pending.
268 if (request_url_
.is_empty())
271 // If we have navigated to a new url or reloaded the page...
272 // GetAsReferrer strips fragment and username/password, meaning
273 // the navigation is really to the same page.
274 if ((request_url_
.GetAsReferrer() !=
275 web_contents()->GetLastCommittedURL().GetAsReferrer()) ||
276 (details
.type
== content::NAVIGATION_TYPE_EXISTING_PAGE
&&
277 !details
.is_in_page
)) {
278 // Kill off existing bubble and cancel any pending requests.
279 CancelPendingQueues();
284 void PermissionBubbleManager::WebContentsDestroyed() {
285 // If the web contents has been destroyed, treat the bubble as cancelled.
286 CancelPendingQueues();
289 // The WebContents is going away; be aggressively paranoid and delete
290 // ourselves lest other parts of the system attempt to add permission bubbles
291 // or use us otherwise during the destruction.
292 web_contents()->RemoveUserData(UserDataKey());
293 // That was the equivalent of "delete this". This object is now destroyed;
294 // returning from this function is the only safe thing to do.
297 void PermissionBubbleManager::ToggleAccept(int request_index
, bool new_value
) {
298 DCHECK(request_index
< static_cast<int>(accept_states_
.size()));
299 accept_states_
[request_index
] = new_value
;
302 void PermissionBubbleManager::Accept() {
303 std::vector
<PermissionBubbleRequest
*>::iterator requests_iter
;
304 std::vector
<bool>::iterator accepts_iter
= accept_states_
.begin();
305 for (requests_iter
= requests_
.begin(), accepts_iter
= accept_states_
.begin();
306 requests_iter
!= requests_
.end();
307 requests_iter
++, accepts_iter
++) {
309 (*requests_iter
)->PermissionGranted();
311 (*requests_iter
)->PermissionDenied();
316 void PermissionBubbleManager::Deny() {
317 std::vector
<PermissionBubbleRequest
*>::iterator requests_iter
;
318 for (requests_iter
= requests_
.begin();
319 requests_iter
!= requests_
.end();
321 (*requests_iter
)->PermissionDenied();
326 void PermissionBubbleManager::Closing() {
327 std::vector
<PermissionBubbleRequest
*>::iterator requests_iter
;
328 for (requests_iter
= requests_
.begin();
329 requests_iter
!= requests_
.end();
331 (*requests_iter
)->Cancelled();
336 void PermissionBubbleManager::ScheduleShowBubble() {
337 // ::ScheduleShowBubble() will be called again when the main frame will be
339 if (!main_frame_has_fully_loaded_
)
342 content::BrowserThread::PostTask(
343 content::BrowserThread::UI
,
345 base::Bind(&PermissionBubbleManager::TriggerShowBubble
,
346 weak_factory_
.GetWeakPtr()));
349 void PermissionBubbleManager::TriggerShowBubble() {
352 if (IsBubbleVisible())
354 if (!main_frame_has_fully_loaded_
)
356 if (requests_
.empty() && queued_requests_
.empty() &&
357 queued_frame_requests_
.empty()) {
361 if (requests_
.empty()) {
362 // Queues containing a user-gesture-generated request have priority.
363 if (HasUserGestureRequest(queued_requests_
))
364 requests_
.swap(queued_requests_
);
365 else if (HasUserGestureRequest(queued_frame_requests_
))
366 requests_
.swap(queued_frame_requests_
);
367 else if (queued_requests_
.size())
368 requests_
.swap(queued_requests_
);
370 requests_
.swap(queued_frame_requests_
);
372 // Sets the default value for each request to be 'accept'.
373 // TODO(leng): Currently all requests default to true. If that changes:
374 // a) Add additional accept_state queues to store default values.
375 // b) Change the request API to provide the default value.
376 accept_states_
.resize(requests_
.size(), true);
379 view_
->Show(requests_
, accept_states_
);
382 // If in testing mode, automatically respond to the bubble that was shown.
383 if (auto_response_for_test_
!= NONE
)
384 DoAutoResponseForTesting();
387 void PermissionBubbleManager::FinalizeBubble() {
391 std::vector
<PermissionBubbleRequest
*>::iterator requests_iter
;
392 for (requests_iter
= requests_
.begin();
393 requests_iter
!= requests_
.end();
395 (*requests_iter
)->RequestFinished();
398 accept_states_
.clear();
399 if (queued_requests_
.size() || queued_frame_requests_
.size())
402 request_url_
= GURL();
405 void PermissionBubbleManager::CancelPendingQueues() {
406 std::vector
<PermissionBubbleRequest
*>::iterator requests_iter
;
407 for (requests_iter
= queued_requests_
.begin();
408 requests_iter
!= queued_requests_
.end();
410 (*requests_iter
)->RequestFinished();
412 for (requests_iter
= queued_frame_requests_
.begin();
413 requests_iter
!= queued_frame_requests_
.end();
415 (*requests_iter
)->RequestFinished();
417 queued_requests_
.clear();
418 queued_frame_requests_
.clear();
421 bool PermissionBubbleManager::ExistingRequest(
422 PermissionBubbleRequest
* request
,
423 const std::vector
<PermissionBubbleRequest
*>& queue
,
426 *same_object
= false;
427 std::vector
<PermissionBubbleRequest
*>::const_iterator iter
;
428 for (iter
= queue
.begin(); iter
!= queue
.end(); iter
++) {
429 if (*iter
== request
) {
433 if ((*iter
)->GetMessageTextFragment() ==
434 request
->GetMessageTextFragment() &&
435 (*iter
)->GetRequestingHostname() == request
->GetRequestingHostname()) {
442 bool PermissionBubbleManager::HasUserGestureRequest(
443 const std::vector
<PermissionBubbleRequest
*>& queue
) {
444 std::vector
<PermissionBubbleRequest
*>::const_iterator iter
;
445 for (iter
= queue
.begin(); iter
!= queue
.end(); iter
++) {
446 if ((*iter
)->HasUserGesture())
452 void PermissionBubbleManager::AddObserver(Observer
* observer
) {
453 observer_list_
.AddObserver(observer
);
456 void PermissionBubbleManager::RemoveObserver(Observer
* observer
) {
457 observer_list_
.RemoveObserver(observer
);
460 void PermissionBubbleManager::NotifyBubbleAdded() {
461 FOR_EACH_OBSERVER(Observer
, observer_list_
, OnBubbleAdded());
464 void PermissionBubbleManager::DoAutoResponseForTesting() {
465 switch (auto_response_for_test_
) {