Revert of Add button to add new FSP services to Files app. (patchset #8 id:140001...
[chromium-blink-merge.git] / chrome / browser / ui / website_settings / permission_bubble_manager.cc
blobb8a53e5f08765fb3e7b7617707defa9c23b538b6
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/field_trial.h"
9 #include "base/metrics/user_metrics_action.h"
10 #include "chrome/browser/ui/website_settings/permission_bubble_request.h"
11 #include "chrome/common/chrome_switches.h"
12 #include "content/public/browser/browser_thread.h"
13 #include "content/public/browser/navigation_details.h"
14 #include "content/public/browser/user_metrics.h"
16 namespace {
18 // String constants to control whether bubbles are enabled by default.
19 const char kTrialName[] = "PermissionBubbleRollout";
20 const char kEnabled[] = "Enabled";
21 const char kDisabled[] = "Disabled";
23 class CancelledRequest : public PermissionBubbleRequest {
24 public:
25 explicit CancelledRequest(PermissionBubbleRequest* cancelled)
26 : icon_(cancelled->GetIconID()),
27 message_text_(cancelled->GetMessageText()),
28 message_fragment_(cancelled->GetMessageTextFragment()),
29 user_gesture_(cancelled->HasUserGesture()),
30 hostname_(cancelled->GetRequestingHostname()) {}
31 ~CancelledRequest() override {}
33 int GetIconID() const override { return icon_; }
34 base::string16 GetMessageText() const override { return message_text_; }
35 base::string16 GetMessageTextFragment() const override {
36 return message_fragment_;
38 bool HasUserGesture() const override { return user_gesture_; }
39 GURL GetRequestingHostname() const override { return hostname_; }
41 // These are all no-ops since the placeholder is non-forwarding.
42 void PermissionGranted() override {}
43 void PermissionDenied() override {}
44 void Cancelled() override {}
46 void RequestFinished() override { delete this; }
48 private:
49 int icon_;
50 base::string16 message_text_;
51 base::string16 message_fragment_;
52 bool user_gesture_;
53 GURL hostname_;
56 } // namespace
58 DEFINE_WEB_CONTENTS_USER_DATA_KEY(PermissionBubbleManager);
60 // static
61 bool PermissionBubbleManager::Enabled() {
62 // Command line flags take precedence.
63 if (base::CommandLine::ForCurrentProcess()->HasSwitch(
64 switches::kEnablePermissionsBubbles))
65 return true;
66 if (base::CommandLine::ForCurrentProcess()->HasSwitch(
67 switches::kDisablePermissionsBubbles))
68 return false;
70 std::string group(base::FieldTrialList::FindFullName(kTrialName));
71 if (group == kEnabled)
72 return true;
73 if (group == kDisabled)
74 return false;
76 return false;
79 PermissionBubbleManager::PermissionBubbleManager(
80 content::WebContents* web_contents)
81 : content::WebContentsObserver(web_contents),
82 require_user_gesture_(false),
83 bubble_showing_(false),
84 view_(NULL),
85 request_url_has_loaded_(false),
86 weak_factory_(this) {}
88 PermissionBubbleManager::~PermissionBubbleManager() {
89 if (view_ != NULL)
90 view_->SetDelegate(NULL);
92 std::vector<PermissionBubbleRequest*>::iterator requests_iter;
93 for (requests_iter = requests_.begin();
94 requests_iter != requests_.end();
95 requests_iter++) {
96 (*requests_iter)->RequestFinished();
98 for (requests_iter = queued_requests_.begin();
99 requests_iter != queued_requests_.end();
100 requests_iter++) {
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();
114 bool is_main_frame =
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)) {
122 if (!same_object)
123 request->RequestFinished();
124 return;
127 if (bubble_showing_) {
128 if (is_main_frame) {
129 content::RecordAction(
130 base::UserMetricsAction("PermissionBubbleRequestQueued"));
131 queued_requests_.push_back(request);
132 } else {
133 content::RecordAction(
134 base::UserMetricsAction("PermissionBubbleIFrameRequestQueued"));
135 queued_frame_requests_.push_back(request);
137 return;
140 if (is_main_frame) {
141 requests_.push_back(request);
142 // TODO(gbillock): do we need to make default state a request property?
143 accept_states_.push_back(true);
144 } else {
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
156 // and go on.
157 std::vector<PermissionBubbleRequest*>::iterator requests_iter;
158 for (requests_iter = queued_requests_.begin();
159 requests_iter != queued_requests_.end();
160 requests_iter++) {
161 if (*requests_iter == request) {
162 (*requests_iter)->RequestFinished();
163 queued_requests_.erase(requests_iter);
164 return;
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)
173 continue;
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 = !bubble_showing_ ||
178 !view_ || view_->CanAcceptRequestUpdate();
179 if (can_erase) {
180 (*requests_iter)->RequestFinished();
181 requests_.erase(requests_iter);
182 accept_states_.erase(accepts_iter);
183 TriggerShowBubble(); // Will redraw the bubble if it is being shown.
184 return;
187 // Cancel the existing request and replace it with a dummy.
188 PermissionBubbleRequest* cancelled_request =
189 new CancelledRequest(*requests_iter);
190 (*requests_iter)->RequestFinished();
191 *requests_iter = cancelled_request;
192 return;
195 NOTREACHED(); // Callers should not cancel requests that are not pending.
198 void PermissionBubbleManager::SetView(PermissionBubbleView* view) {
199 if (view == view_)
200 return;
202 // Disengage from the existing view if there is one.
203 if (view_ != NULL) {
204 view_->SetDelegate(NULL);
205 view_->Hide();
206 bubble_showing_ = false;
209 view_ = view;
210 if (!view)
211 return;
213 view->SetDelegate(this);
214 TriggerShowBubble();
217 void PermissionBubbleManager::RequireUserGesture(bool required) {
218 require_user_gesture_ = required;
221 void PermissionBubbleManager::DocumentOnLoadCompletedInMainFrame() {
222 request_url_has_loaded_ = true;
223 // This is scheduled because while all calls to the browser have been
224 // issued at DOMContentLoaded, they may be bouncing around in scheduled
225 // callbacks finding the UI thread still. This makes sure we allow those
226 // scheduled calls to AddRequest to complete before we show the page-load
227 // permissions bubble.
228 ScheduleShowBubble();
231 void PermissionBubbleManager::DocumentLoadedInFrame(
232 content::RenderFrameHost* render_frame_host) {
233 if (request_url_has_loaded_)
234 ScheduleShowBubble();
237 void PermissionBubbleManager::NavigationEntryCommitted(
238 const content::LoadCommittedDetails& details) {
239 // No permissions requests pending.
240 if (request_url_.is_empty())
241 return;
243 // If we have navigated to a new url or reloaded the page...
244 // GetAsReferrer strips fragment and username/password, meaning
245 // the navigation is really to the same page.
246 if ((request_url_.GetAsReferrer() !=
247 web_contents()->GetLastCommittedURL().GetAsReferrer()) ||
248 details.type == content::NAVIGATION_TYPE_EXISTING_PAGE) {
249 // Kill off existing bubble and cancel any pending requests.
250 CancelPendingQueues();
251 FinalizeBubble();
255 void PermissionBubbleManager::WebContentsDestroyed() {
256 // If the web contents has been destroyed, treat the bubble as cancelled.
257 CancelPendingQueues();
258 FinalizeBubble();
260 // The WebContents is going away; be aggressively paranoid and delete
261 // ourselves lest other parts of the system attempt to add permission bubbles
262 // or use us otherwise during the destruction.
263 web_contents()->RemoveUserData(UserDataKey());
264 // That was the equivalent of "delete this". This object is now destroyed;
265 // returning from this function is the only safe thing to do.
268 void PermissionBubbleManager::ToggleAccept(int request_index, bool new_value) {
269 DCHECK(request_index < static_cast<int>(accept_states_.size()));
270 accept_states_[request_index] = new_value;
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++) {
279 if (*accepts_iter)
280 (*requests_iter)->PermissionGranted();
281 else
282 (*requests_iter)->PermissionDenied();
284 FinalizeBubble();
287 void PermissionBubbleManager::Deny() {
288 std::vector<PermissionBubbleRequest*>::iterator requests_iter;
289 for (requests_iter = requests_.begin();
290 requests_iter != requests_.end();
291 requests_iter++) {
292 (*requests_iter)->PermissionDenied();
294 FinalizeBubble();
297 void PermissionBubbleManager::Closing() {
298 std::vector<PermissionBubbleRequest*>::iterator requests_iter;
299 for (requests_iter = requests_.begin();
300 requests_iter != requests_.end();
301 requests_iter++) {
302 (*requests_iter)->Cancelled();
304 FinalizeBubble();
307 void PermissionBubbleManager::ScheduleShowBubble() {
308 content::BrowserThread::PostTask(
309 content::BrowserThread::UI,
310 FROM_HERE,
311 base::Bind(&PermissionBubbleManager::TriggerShowBubble,
312 weak_factory_.GetWeakPtr()));
315 void PermissionBubbleManager::TriggerShowBubble() {
316 if (!view_)
317 return;
318 if (bubble_showing_)
319 return;
320 if (!request_url_has_loaded_)
321 return;
322 if (requests_.empty() && queued_requests_.empty() &&
323 queued_frame_requests_.empty()) {
324 return;
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_);
335 else
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_);
351 void PermissionBubbleManager::FinalizeBubble() {
352 if (view_)
353 view_->Hide();
354 bubble_showing_ = false;
356 std::vector<PermissionBubbleRequest*>::iterator requests_iter;
357 for (requests_iter = requests_.begin();
358 requests_iter != requests_.end();
359 requests_iter++) {
360 (*requests_iter)->RequestFinished();
362 requests_.clear();
363 accept_states_.clear();
364 if (queued_requests_.size() || queued_frame_requests_.size())
365 TriggerShowBubble();
366 else
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();
374 requests_iter++) {
375 (*requests_iter)->RequestFinished();
377 for (requests_iter = queued_frame_requests_.begin();
378 requests_iter != queued_frame_requests_.end();
379 requests_iter++) {
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,
389 bool* same_object) {
390 CHECK(same_object);
391 *same_object = false;
392 std::vector<PermissionBubbleRequest*>::const_iterator iter;
393 for (iter = queue.begin(); iter != queue.end(); iter++) {
394 if (*iter == request) {
395 *same_object = true;
396 return true;
398 if ((*iter)->GetMessageTextFragment() ==
399 request->GetMessageTextFragment() &&
400 (*iter)->GetRequestingHostname() == request->GetRequestingHostname()) {
401 return true;
404 return false;
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())
412 return true;
414 return false;