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 DEFINE_WEB_CONTENTS_USER_DATA_KEY(PermissionBubbleManager);
55 bool PermissionBubbleManager::Enabled() {
56 if (CommandLine::ForCurrentProcess()->HasSwitch(
57 switches::kEnablePermissionsBubbles))
60 if (CommandLine::ForCurrentProcess()->HasSwitch(
61 switches::kDisablePermissionsBubbles))
67 PermissionBubbleManager::PermissionBubbleManager(
68 content::WebContents* web_contents)
69 : content::WebContentsObserver(web_contents),
70 bubble_showing_(false),
72 request_url_has_loaded_(false),
73 customization_mode_(false),
74 weak_factory_(this) {}
76 PermissionBubbleManager::~PermissionBubbleManager() {
78 view_->SetDelegate(NULL);
80 std::vector<PermissionBubbleRequest*>::iterator requests_iter;
81 for (requests_iter = requests_.begin();
82 requests_iter != requests_.end();
84 (*requests_iter)->RequestFinished();
86 for (requests_iter = queued_requests_.begin();
87 requests_iter != queued_requests_.end();
89 (*requests_iter)->RequestFinished();
93 void PermissionBubbleManager::AddRequest(PermissionBubbleRequest* request) {
94 content::RecordAction(base::UserMetricsAction("PermissionBubbleRequest"));
95 // TODO(gbillock): is there a race between an early request on a
96 // newly-navigated page and the to-be-cleaned-up requests on the previous
97 // page? We should maybe listen to DidStartNavigationToPendingEntry (and
98 // any other renderer-side nav initiations?). Double-check this for
99 // correct behavior on interstitials -- we probably want to basically queue
100 // any request for which GetVisibleURL != GetLastCommittedURL.
101 request_url_ = web_contents()->GetLastCommittedURL();
103 request->GetRequestingHostname().GetOrigin() == request_url_.GetOrigin();
105 // Don't re-add an existing request or one with a duplicate text request.
106 bool same_object = false;
107 if (ExistingRequest(request, requests_, &same_object) ||
108 ExistingRequest(request, queued_requests_, &same_object) ||
109 ExistingRequest(request, queued_frame_requests_, &same_object)) {
111 request->RequestFinished();
115 if (bubble_showing_) {
117 content::RecordAction(
118 base::UserMetricsAction("PermissionBubbleRequestQueued"));
119 queued_requests_.push_back(request);
121 content::RecordAction(
122 base::UserMetricsAction("PermissionBubbleIFrameRequestQueued"));
123 queued_frame_requests_.push_back(request);
129 requests_.push_back(request);
130 // TODO(gbillock): do we need to make default state a request property?
131 accept_states_.push_back(true);
133 content::RecordAction(
134 base::UserMetricsAction("PermissionBubbleIFrameRequestQueued"));
135 queued_frame_requests_.push_back(request);
138 if (request->HasUserGesture())
139 ScheduleShowBubble();
142 void PermissionBubbleManager::CancelRequest(PermissionBubbleRequest* request) {
143 // First look in the queued requests, where we can simply delete the request
145 std::vector<PermissionBubbleRequest*>::iterator requests_iter;
146 for (requests_iter = queued_requests_.begin();
147 requests_iter != queued_requests_.end();
149 if (*requests_iter == request) {
150 (*requests_iter)->RequestFinished();
151 queued_requests_.erase(requests_iter);
156 std::vector<bool>::iterator accepts_iter = accept_states_.begin();
157 for (requests_iter = requests_.begin(), accepts_iter = accept_states_.begin();
158 requests_iter != requests_.end();
159 requests_iter++, accepts_iter++) {
160 if (*requests_iter != request)
163 // We can simply erase the current entry in the request table if we aren't
164 // showing the dialog, or if we are showing it and it can accept the update.
165 bool can_erase = !bubble_showing_ ||
166 !view_ || view_->CanAcceptRequestUpdate();
168 (*requests_iter)->RequestFinished();
169 requests_.erase(requests_iter);
170 accept_states_.erase(accepts_iter);
171 TriggerShowBubble(); // Will redraw the bubble if it is being shown.
175 // Cancel the existing request and replace it with a dummy.
176 PermissionBubbleRequest* cancelled_request =
177 new CancelledRequest(*requests_iter);
178 (*requests_iter)->RequestFinished();
179 *requests_iter = cancelled_request;
183 NOTREACHED(); // Callers should not cancel requests that are not pending.
186 void PermissionBubbleManager::SetView(PermissionBubbleView* view) {
190 // Disengage from the existing view if there is one.
192 view_->SetDelegate(NULL);
194 bubble_showing_ = false;
201 view->SetDelegate(this);
205 void PermissionBubbleManager::DocumentOnLoadCompletedInMainFrame() {
206 request_url_has_loaded_ = true;
207 // This is scheduled because while all calls to the browser have been
208 // issued at DOMContentLoaded, they may be bouncing around in scheduled
209 // callbacks finding the UI thread still. This makes sure we allow those
210 // scheduled calls to AddRequest to complete before we show the page-load
211 // permissions bubble.
212 ScheduleShowBubble();
215 void PermissionBubbleManager::DocumentLoadedInFrame(
216 content::RenderFrameHost* render_frame_host) {
217 if (request_url_has_loaded_)
218 ScheduleShowBubble();
221 void PermissionBubbleManager::NavigationEntryCommitted(
222 const content::LoadCommittedDetails& details) {
223 // No permissions requests pending.
224 if (request_url_.is_empty())
227 // If we have navigated to a new url or reloaded the page...
228 // GetAsReferrer strips fragment and username/password, meaning
229 // the navigation is really to the same page.
230 if ((request_url_.GetAsReferrer() !=
231 web_contents()->GetLastCommittedURL().GetAsReferrer()) ||
232 details.type == content::NAVIGATION_TYPE_EXISTING_PAGE) {
233 // Kill off existing bubble and cancel any pending requests.
234 CancelPendingQueues();
239 void PermissionBubbleManager::WebContentsDestroyed() {
240 // If the web contents has been destroyed, treat the bubble as cancelled.
241 CancelPendingQueues();
244 // The WebContents is going away; be aggressively paranoid and delete
245 // ourselves lest other parts of the system attempt to add permission bubbles
246 // or use us otherwise during the destruction.
247 web_contents()->RemoveUserData(UserDataKey());
248 // That was the equivalent of "delete this". This object is now destroyed;
249 // returning from this function is the only safe thing to do.
252 void PermissionBubbleManager::ToggleAccept(int request_index, bool new_value) {
253 DCHECK(request_index < static_cast<int>(accept_states_.size()));
254 accept_states_[request_index] = new_value;
257 void PermissionBubbleManager::SetCustomizationMode() {
258 customization_mode_ = true;
260 view_->Show(requests_, accept_states_, customization_mode_);
263 void PermissionBubbleManager::Accept() {
264 std::vector<PermissionBubbleRequest*>::iterator requests_iter;
265 std::vector<bool>::iterator accepts_iter = accept_states_.begin();
266 for (requests_iter = requests_.begin(), accepts_iter = accept_states_.begin();
267 requests_iter != requests_.end();
268 requests_iter++, accepts_iter++) {
270 (*requests_iter)->PermissionGranted();
272 (*requests_iter)->PermissionDenied();
277 void PermissionBubbleManager::Deny() {
278 std::vector<PermissionBubbleRequest*>::iterator requests_iter;
279 for (requests_iter = requests_.begin();
280 requests_iter != requests_.end();
282 (*requests_iter)->PermissionDenied();
287 void PermissionBubbleManager::Closing() {
288 std::vector<PermissionBubbleRequest*>::iterator requests_iter;
289 for (requests_iter = requests_.begin();
290 requests_iter != requests_.end();
292 (*requests_iter)->Cancelled();
297 void PermissionBubbleManager::ScheduleShowBubble() {
298 content::BrowserThread::PostTask(
299 content::BrowserThread::UI,
301 base::Bind(&PermissionBubbleManager::TriggerShowBubble,
302 weak_factory_.GetWeakPtr()));
305 void PermissionBubbleManager::TriggerShowBubble() {
310 if (!request_url_has_loaded_)
312 if (requests_.empty() && queued_requests_.empty() &&
313 queued_frame_requests_.empty()) {
317 if (requests_.empty()) {
318 // Queues containing a user-gesture-generated request have priority.
319 if (HasUserGestureRequest(queued_requests_))
320 requests_.swap(queued_requests_);
321 else if (HasUserGestureRequest(queued_frame_requests_))
322 requests_.swap(queued_frame_requests_);
323 else if (queued_requests_.size())
324 requests_.swap(queued_requests_);
326 requests_.swap(queued_frame_requests_);
328 // Sets the default value for each request to be 'accept'.
329 // TODO(leng): Currently all requests default to true. If that changes:
330 // a) Add additional accept_state queues to store default values.
331 // b) Change the request API to provide the default value.
332 accept_states_.resize(requests_.size(), true);
335 // Note: this should appear above Show() for testing, since in that
336 // case we may do in-line calling of finalization.
337 bubble_showing_ = true;
338 view_->Show(requests_, accept_states_, customization_mode_);
341 void PermissionBubbleManager::FinalizeBubble() {
344 bubble_showing_ = false;
346 std::vector<PermissionBubbleRequest*>::iterator requests_iter;
347 for (requests_iter = requests_.begin();
348 requests_iter != requests_.end();
350 (*requests_iter)->RequestFinished();
353 accept_states_.clear();
354 if (queued_requests_.size() || queued_frame_requests_.size())
357 request_url_ = GURL();
360 void PermissionBubbleManager::CancelPendingQueues() {
361 std::vector<PermissionBubbleRequest*>::iterator requests_iter;
362 for (requests_iter = queued_requests_.begin();
363 requests_iter != queued_requests_.end();
365 (*requests_iter)->RequestFinished();
367 for (requests_iter = queued_frame_requests_.begin();
368 requests_iter != queued_frame_requests_.end();
370 (*requests_iter)->RequestFinished();
372 queued_requests_.clear();
373 queued_frame_requests_.clear();
376 bool PermissionBubbleManager::ExistingRequest(
377 PermissionBubbleRequest* request,
378 const std::vector<PermissionBubbleRequest*>& queue,
381 *same_object = false;
382 std::vector<PermissionBubbleRequest*>::const_iterator iter;
383 for (iter = queue.begin(); iter != queue.end(); iter++) {
384 if (*iter == request) {
388 if ((*iter)->GetMessageTextFragment() ==
389 request->GetMessageTextFragment() &&
390 (*iter)->GetRequestingHostname() == request->GetRequestingHostname()) {
397 bool PermissionBubbleManager::HasUserGestureRequest(
398 const std::vector<PermissionBubbleRequest*>& queue) {
399 std::vector<PermissionBubbleRequest*>::const_iterator iter;
400 for (iter = queue.begin(); iter != queue.end(); iter++) {
401 if ((*iter)->HasUserGesture())