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