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 virtual ~CancelledRequest() {}
27 virtual int GetIconID() const OVERRIDE
{
30 virtual base::string16
GetMessageText() const OVERRIDE
{
33 virtual base::string16
GetMessageTextFragment() const OVERRIDE
{
34 return message_fragment_
;
36 virtual bool HasUserGesture() const OVERRIDE
{
39 virtual GURL
GetRequestingHostname() const OVERRIDE
{
43 // These are all no-ops since the placeholder is non-forwarding.
44 virtual void PermissionGranted() OVERRIDE
{}
45 virtual void PermissionDenied() OVERRIDE
{}
46 virtual void Cancelled() OVERRIDE
{}
48 virtual void RequestFinished() OVERRIDE
{
54 base::string16 message_text_
;
55 base::string16 message_fragment_
;
62 DEFINE_WEB_CONTENTS_USER_DATA_KEY(PermissionBubbleManager
);
65 bool PermissionBubbleManager::Enabled() {
66 if (CommandLine::ForCurrentProcess()->HasSwitch(
67 switches::kEnablePermissionsBubbles
))
70 if (CommandLine::ForCurrentProcess()->HasSwitch(
71 switches::kDisablePermissionsBubbles
))
77 PermissionBubbleManager::PermissionBubbleManager(
78 content::WebContents
* web_contents
)
79 : content::WebContentsObserver(web_contents
),
80 bubble_showing_(false),
82 request_url_has_loaded_(false),
83 customization_mode_(false),
84 weak_factory_(this) {}
86 PermissionBubbleManager::~PermissionBubbleManager() {
88 view_
->SetDelegate(NULL
);
90 std::vector
<PermissionBubbleRequest
*>::iterator requests_iter
;
91 for (requests_iter
= requests_
.begin();
92 requests_iter
!= requests_
.end();
94 (*requests_iter
)->RequestFinished();
96 for (requests_iter
= queued_requests_
.begin();
97 requests_iter
!= queued_requests_
.end();
99 (*requests_iter
)->RequestFinished();
103 void PermissionBubbleManager::AddRequest(PermissionBubbleRequest
* request
) {
104 content::RecordAction(base::UserMetricsAction("PermissionBubbleRequest"));
105 // TODO(gbillock): is there a race between an early request on a
106 // newly-navigated page and the to-be-cleaned-up requests on the previous
107 // page? We should maybe listen to DidStartNavigationToPendingEntry (and
108 // any other renderer-side nav initiations?). Double-check this for
109 // correct behavior on interstitials -- we probably want to basically queue
110 // any request for which GetVisibleURL != GetLastCommittedURL.
111 request_url_
= web_contents()->GetLastCommittedURL();
113 request
->GetRequestingHostname().GetOrigin() == request_url_
.GetOrigin();
115 // Don't re-add an existing request or one with a duplicate text request.
116 bool same_object
= false;
117 if (ExistingRequest(request
, requests_
, &same_object
) ||
118 ExistingRequest(request
, queued_requests_
, &same_object
) ||
119 ExistingRequest(request
, queued_frame_requests_
, &same_object
)) {
121 request
->RequestFinished();
125 if (bubble_showing_
) {
127 content::RecordAction(
128 base::UserMetricsAction("PermissionBubbleRequestQueued"));
129 queued_requests_
.push_back(request
);
131 content::RecordAction(
132 base::UserMetricsAction("PermissionBubbleIFrameRequestQueued"));
133 queued_frame_requests_
.push_back(request
);
139 requests_
.push_back(request
);
140 // TODO(gbillock): do we need to make default state a request property?
141 accept_states_
.push_back(true);
143 content::RecordAction(
144 base::UserMetricsAction("PermissionBubbleIFrameRequestQueued"));
145 queued_frame_requests_
.push_back(request
);
148 if (request
->HasUserGesture())
149 ScheduleShowBubble();
152 void PermissionBubbleManager::CancelRequest(PermissionBubbleRequest
* request
) {
153 // First look in the queued requests, where we can simply delete the request
155 std::vector
<PermissionBubbleRequest
*>::iterator requests_iter
;
156 for (requests_iter
= queued_requests_
.begin();
157 requests_iter
!= queued_requests_
.end();
159 if (*requests_iter
== request
) {
160 (*requests_iter
)->RequestFinished();
161 queued_requests_
.erase(requests_iter
);
166 std::vector
<bool>::iterator accepts_iter
= accept_states_
.begin();
167 for (requests_iter
= requests_
.begin(), accepts_iter
= accept_states_
.begin();
168 requests_iter
!= requests_
.end();
169 requests_iter
++, accepts_iter
++) {
170 if (*requests_iter
!= request
)
173 // We can simply erase the current entry in the request table if we aren't
174 // showing the dialog, or if we are showing it and it can accept the update.
175 bool can_erase
= !bubble_showing_
||
176 !view_
|| view_
->CanAcceptRequestUpdate();
178 (*requests_iter
)->RequestFinished();
179 requests_
.erase(requests_iter
);
180 accept_states_
.erase(accepts_iter
);
181 TriggerShowBubble(); // Will redraw the bubble if it is being shown.
185 // Cancel the existing request and replace it with a dummy.
186 PermissionBubbleRequest
* cancelled_request
=
187 new CancelledRequest(*requests_iter
);
188 (*requests_iter
)->RequestFinished();
189 *requests_iter
= cancelled_request
;
193 NOTREACHED(); // Callers should not cancel requests that are not pending.
196 void PermissionBubbleManager::SetView(PermissionBubbleView
* view
) {
200 // Disengage from the existing view if there is one.
202 view_
->SetDelegate(NULL
);
204 bubble_showing_
= false;
211 view
->SetDelegate(this);
215 void PermissionBubbleManager::DocumentOnLoadCompletedInMainFrame() {
216 request_url_has_loaded_
= true;
217 // This is scheduled because while all calls to the browser have been
218 // issued at DOMContentLoaded, they may be bouncing around in scheduled
219 // callbacks finding the UI thread still. This makes sure we allow those
220 // scheduled calls to AddRequest to complete before we show the page-load
221 // permissions bubble.
222 ScheduleShowBubble();
225 void PermissionBubbleManager::DocumentLoadedInFrame(
226 content::RenderFrameHost
* render_frame_host
) {
227 if (request_url_has_loaded_
)
228 ScheduleShowBubble();
231 void PermissionBubbleManager::NavigationEntryCommitted(
232 const content::LoadCommittedDetails
& details
) {
233 // No permissions requests pending.
234 if (request_url_
.is_empty())
237 // If we have navigated to a new url or reloaded the page...
238 // GetAsReferrer strips fragment and username/password, meaning
239 // the navigation is really to the same page.
240 if ((request_url_
.GetAsReferrer() !=
241 web_contents()->GetLastCommittedURL().GetAsReferrer()) ||
242 details
.type
== content::NAVIGATION_TYPE_EXISTING_PAGE
) {
243 // Kill off existing bubble and cancel any pending requests.
244 CancelPendingQueues();
249 void PermissionBubbleManager::WebContentsDestroyed() {
250 // If the web contents has been destroyed, treat the bubble as cancelled.
251 CancelPendingQueues();
254 // The WebContents is going away; be aggressively paranoid and delete
255 // ourselves lest other parts of the system attempt to add permission bubbles
256 // or use us otherwise during the destruction.
257 web_contents()->RemoveUserData(UserDataKey());
258 // That was the equivalent of "delete this". This object is now destroyed;
259 // returning from this function is the only safe thing to do.
262 void PermissionBubbleManager::ToggleAccept(int request_index
, bool new_value
) {
263 DCHECK(request_index
< static_cast<int>(accept_states_
.size()));
264 accept_states_
[request_index
] = new_value
;
267 void PermissionBubbleManager::SetCustomizationMode() {
268 customization_mode_
= true;
270 view_
->Show(requests_
, accept_states_
, customization_mode_
);
273 void PermissionBubbleManager::Accept() {
274 std::vector
<PermissionBubbleRequest
*>::iterator requests_iter
;
275 std::vector
<bool>::iterator accepts_iter
= accept_states_
.begin();
276 for (requests_iter
= requests_
.begin(), accepts_iter
= accept_states_
.begin();
277 requests_iter
!= requests_
.end();
278 requests_iter
++, accepts_iter
++) {
280 (*requests_iter
)->PermissionGranted();
282 (*requests_iter
)->PermissionDenied();
287 void PermissionBubbleManager::Deny() {
288 std::vector
<PermissionBubbleRequest
*>::iterator requests_iter
;
289 for (requests_iter
= requests_
.begin();
290 requests_iter
!= requests_
.end();
292 (*requests_iter
)->PermissionDenied();
297 void PermissionBubbleManager::Closing() {
298 std::vector
<PermissionBubbleRequest
*>::iterator requests_iter
;
299 for (requests_iter
= requests_
.begin();
300 requests_iter
!= requests_
.end();
302 (*requests_iter
)->Cancelled();
307 void PermissionBubbleManager::ScheduleShowBubble() {
308 content::BrowserThread::PostTask(
309 content::BrowserThread::UI
,
311 base::Bind(&PermissionBubbleManager::TriggerShowBubble
,
312 weak_factory_
.GetWeakPtr()));
315 void PermissionBubbleManager::TriggerShowBubble() {
320 if (!request_url_has_loaded_
)
322 if (requests_
.empty() && queued_requests_
.empty() &&
323 queued_frame_requests_
.empty()) {
327 if (requests_
.empty()) {
328 // Queues containing a user-gesture-generated request have priority.
329 if (HasUserGestureRequest(queued_requests_
))
330 requests_
.swap(queued_requests_
);
331 else if (HasUserGestureRequest(queued_frame_requests_
))
332 requests_
.swap(queued_frame_requests_
);
333 else if (queued_requests_
.size())
334 requests_
.swap(queued_requests_
);
336 requests_
.swap(queued_frame_requests_
);
338 // Sets the default value for each request to be 'accept'.
339 // TODO(leng): Currently all requests default to true. If that changes:
340 // a) Add additional accept_state queues to store default values.
341 // b) Change the request API to provide the default value.
342 accept_states_
.resize(requests_
.size(), true);
345 // Note: this should appear above Show() for testing, since in that
346 // case we may do in-line calling of finalization.
347 bubble_showing_
= true;
348 view_
->Show(requests_
, accept_states_
, customization_mode_
);
351 void PermissionBubbleManager::FinalizeBubble() {
354 bubble_showing_
= false;
356 std::vector
<PermissionBubbleRequest
*>::iterator requests_iter
;
357 for (requests_iter
= requests_
.begin();
358 requests_iter
!= requests_
.end();
360 (*requests_iter
)->RequestFinished();
363 accept_states_
.clear();
364 if (queued_requests_
.size() || queued_frame_requests_
.size())
367 request_url_
= GURL();
370 void PermissionBubbleManager::CancelPendingQueues() {
371 std::vector
<PermissionBubbleRequest
*>::iterator requests_iter
;
372 for (requests_iter
= queued_requests_
.begin();
373 requests_iter
!= queued_requests_
.end();
375 (*requests_iter
)->RequestFinished();
377 for (requests_iter
= queued_frame_requests_
.begin();
378 requests_iter
!= queued_frame_requests_
.end();
380 (*requests_iter
)->RequestFinished();
382 queued_requests_
.clear();
383 queued_frame_requests_
.clear();
386 bool PermissionBubbleManager::ExistingRequest(
387 PermissionBubbleRequest
* request
,
388 const std::vector
<PermissionBubbleRequest
*>& queue
,
391 *same_object
= false;
392 std::vector
<PermissionBubbleRequest
*>::const_iterator iter
;
393 for (iter
= queue
.begin(); iter
!= queue
.end(); iter
++) {
394 if (*iter
== request
) {
398 if ((*iter
)->GetMessageTextFragment() ==
399 request
->GetMessageTextFragment() &&
400 (*iter
)->GetRequestingHostname() == request
->GetRequestingHostname()) {
407 bool PermissionBubbleManager::HasUserGestureRequest(
408 const std::vector
<PermissionBubbleRequest
*>& queue
) {
409 std::vector
<PermissionBubbleRequest
*>::const_iterator iter
;
410 for (iter
= queue
.begin(); iter
!= queue
.end(); iter
++) {
411 if ((*iter
)->HasUserGesture())