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.
4
5#include "chrome/browser/ui/website_settings/permission_bubble_manager.h"
6
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"
14
15namespace {
16
17class CancelledRequest : public PermissionBubbleRequest {
18 public:
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() {}
26
27  virtual int GetIconID() const OVERRIDE {
28    return icon_;
29  }
30  virtual base::string16 GetMessageText() const OVERRIDE {
31    return message_text_;
32  }
33  virtual base::string16 GetMessageTextFragment() const OVERRIDE {
34    return message_fragment_;
35  }
36  virtual bool HasUserGesture() const OVERRIDE {
37    return user_gesture_;
38  }
39  virtual GURL GetRequestingHostname() const OVERRIDE {
40    return hostname_;
41  }
42
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 {}
47
48  virtual void RequestFinished() OVERRIDE {
49    delete this;
50  }
51
52 private:
53  int icon_;
54  base::string16 message_text_;
55  base::string16 message_fragment_;
56  bool user_gesture_;
57  GURL hostname_;
58};
59
60}  // namespace
61
62DEFINE_WEB_CONTENTS_USER_DATA_KEY(PermissionBubbleManager);
63
64// static
65bool PermissionBubbleManager::Enabled() {
66  if (CommandLine::ForCurrentProcess()->HasSwitch(
67          switches::kEnablePermissionsBubbles))
68    return true;
69
70  if (CommandLine::ForCurrentProcess()->HasSwitch(
71          switches::kDisablePermissionsBubbles))
72    return false;
73
74  return false;
75}
76
77PermissionBubbleManager::PermissionBubbleManager(
78    content::WebContents* web_contents)
79  : content::WebContentsObserver(web_contents),
80    bubble_showing_(false),
81    view_(NULL),
82    request_url_has_loaded_(false),
83    customization_mode_(false),
84    weak_factory_(this) {}
85
86PermissionBubbleManager::~PermissionBubbleManager() {
87  if (view_ != NULL)
88    view_->SetDelegate(NULL);
89
90  std::vector<PermissionBubbleRequest*>::iterator requests_iter;
91  for (requests_iter = requests_.begin();
92       requests_iter != requests_.end();
93       requests_iter++) {
94    (*requests_iter)->RequestFinished();
95  }
96  for (requests_iter = queued_requests_.begin();
97       requests_iter != queued_requests_.end();
98       requests_iter++) {
99    (*requests_iter)->RequestFinished();
100  }
101}
102
103void 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();
112  bool is_main_frame =
113      request->GetRequestingHostname().GetOrigin() == request_url_.GetOrigin();
114
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)) {
120    if (!same_object)
121      request->RequestFinished();
122    return;
123  }
124
125  if (bubble_showing_) {
126    if (is_main_frame) {
127      content::RecordAction(
128          base::UserMetricsAction("PermissionBubbleRequestQueued"));
129      queued_requests_.push_back(request);
130    } else {
131      content::RecordAction(
132          base::UserMetricsAction("PermissionBubbleIFrameRequestQueued"));
133      queued_frame_requests_.push_back(request);
134    }
135    return;
136  }
137
138  if (is_main_frame) {
139    requests_.push_back(request);
140    // TODO(gbillock): do we need to make default state a request property?
141    accept_states_.push_back(true);
142  } else {
143    content::RecordAction(
144        base::UserMetricsAction("PermissionBubbleIFrameRequestQueued"));
145    queued_frame_requests_.push_back(request);
146  }
147
148  if (request->HasUserGesture())
149    ScheduleShowBubble();
150}
151
152void PermissionBubbleManager::CancelRequest(PermissionBubbleRequest* request) {
153  // First look in the queued requests, where we can simply delete the request
154  // and go on.
155  std::vector<PermissionBubbleRequest*>::iterator requests_iter;
156  for (requests_iter = queued_requests_.begin();
157       requests_iter != queued_requests_.end();
158       requests_iter++) {
159    if (*requests_iter == request) {
160      (*requests_iter)->RequestFinished();
161      queued_requests_.erase(requests_iter);
162      return;
163    }
164  }
165
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)
171      continue;
172
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();
177    if (can_erase) {
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.
182      return;
183    }
184
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;
190    return;
191  }
192
193  NOTREACHED();  // Callers should not cancel requests that are not pending.
194}
195
196void PermissionBubbleManager::SetView(PermissionBubbleView* view) {
197  if (view == view_)
198    return;
199
200  // Disengage from the existing view if there is one.
201  if (view_ != NULL) {
202    view_->SetDelegate(NULL);
203    view_->Hide();
204    bubble_showing_ = false;
205  }
206
207  view_ = view;
208  if (!view)
209    return;
210
211  view->SetDelegate(this);
212  TriggerShowBubble();
213}
214
215void 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();
223}
224
225void PermissionBubbleManager::DocumentLoadedInFrame(
226    content::RenderFrameHost* render_frame_host) {
227  if (request_url_has_loaded_)
228    ScheduleShowBubble();
229}
230
231void PermissionBubbleManager::NavigationEntryCommitted(
232    const content::LoadCommittedDetails& details) {
233  // No permissions requests pending.
234  if (request_url_.is_empty())
235    return;
236
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();
245    FinalizeBubble();
246  }
247}
248
249void PermissionBubbleManager::WebContentsDestroyed() {
250  // If the web contents has been destroyed, treat the bubble as cancelled.
251  CancelPendingQueues();
252  FinalizeBubble();
253
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.
260}
261
262void 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;
265}
266
267void PermissionBubbleManager::SetCustomizationMode() {
268  customization_mode_ = true;
269  if (view_)
270    view_->Show(requests_, accept_states_, customization_mode_);
271}
272
273void 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();
283  }
284  FinalizeBubble();
285}
286
287void 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();
293  }
294  FinalizeBubble();
295}
296
297void 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();
303  }
304  FinalizeBubble();
305}
306
307void PermissionBubbleManager::ScheduleShowBubble() {
308  content::BrowserThread::PostTask(
309      content::BrowserThread::UI,
310      FROM_HERE,
311      base::Bind(&PermissionBubbleManager::TriggerShowBubble,
312                 weak_factory_.GetWeakPtr()));
313}
314
315void 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;
325  }
326
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_);
337
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);
343  }
344
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_);
349}
350
351void PermissionBubbleManager::FinalizeBubble() {
352  if (view_)
353    view_->Hide();
354  bubble_showing_ = false;
355
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();
361  }
362  requests_.clear();
363  accept_states_.clear();
364  if (queued_requests_.size() || queued_frame_requests_.size())
365    TriggerShowBubble();
366  else
367    request_url_ = GURL();
368}
369
370void 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();
376  }
377  for (requests_iter = queued_frame_requests_.begin();
378       requests_iter != queued_frame_requests_.end();
379       requests_iter++) {
380    (*requests_iter)->RequestFinished();
381  }
382  queued_requests_.clear();
383  queued_frame_requests_.clear();
384}
385
386bool 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;
397    }
398    if ((*iter)->GetMessageTextFragment() ==
399            request->GetMessageTextFragment() &&
400        (*iter)->GetRequestingHostname() == request->GetRequestingHostname()) {
401      return true;
402    }
403  }
404  return false;
405}
406
407bool 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;
413  }
414  return false;
415}
416
417