blob: 670a864ae63054b78cdea6fc1506fbcb26a35060 [file] [log] [blame]
// Copyright 2014 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/ui/website_settings/permission_bubble_manager.h"
#include "base/command_line.h"
#include "chrome/browser/ui/website_settings/permission_bubble_request.h"
#include "chrome/common/chrome_switches.h"
#include "content/public/browser/browser_thread.h"
DEFINE_WEB_CONTENTS_USER_DATA_KEY(PermissionBubbleManager);
// static
bool PermissionBubbleManager::Enabled() {
return CommandLine::ForCurrentProcess()->HasSwitch(
switches::kEnablePermissionsBubbles);
}
PermissionBubbleManager::PermissionBubbleManager(
content::WebContents* web_contents)
: content::WebContentsObserver(web_contents),
bubble_showing_(false),
view_(NULL),
request_url_has_loaded_(false),
customization_mode_(false),
weak_factory_(this) {}
PermissionBubbleManager::~PermissionBubbleManager() {
if (view_ != NULL)
view_->SetDelegate(NULL);
std::vector<PermissionBubbleRequest*>::iterator requests_iter;
for (requests_iter = requests_.begin();
requests_iter != requests_.end();
requests_iter++) {
(*requests_iter)->RequestFinished();
}
for (requests_iter = queued_requests_.begin();
requests_iter != queued_requests_.end();
requests_iter++) {
(*requests_iter)->RequestFinished();
}
}
void PermissionBubbleManager::AddRequest(PermissionBubbleRequest* request) {
// TODO(gbillock): is there a race between an early request on a
// newly-navigated page and the to-be-cleaned-up requests on the previous
// page? We should maybe listen to DidStartNavigationToPendingEntry (and
// any other renderer-side nav initiations?). Double-check this for
// correct behavior on interstitials -- we probably want to basically queue
// any request for which GetVisibleURL != GetLastCommittedURL.
request_url_ = web_contents()->GetLastCommittedURL();
// Don't re-add an existing request or one with a duplicate text request.
std::vector<PermissionBubbleRequest*>::iterator requests_iter;
for (requests_iter = requests_.begin();
requests_iter != requests_.end();
requests_iter++) {
if (*requests_iter == request)
return;
// TODO(gbillock): worry about the requesting host name as well.
if ((*requests_iter)->GetMessageTextFragment() ==
request->GetMessageTextFragment()) {
request->RequestFinished();
return;
}
}
for (requests_iter = queued_requests_.begin();
requests_iter != queued_requests_.end();
requests_iter++) {
if (*requests_iter == request)
return;
if ((*requests_iter)->GetMessageTextFragment() ==
request->GetMessageTextFragment()) {
request->RequestFinished();
return;
}
}
if (bubble_showing_) {
queued_requests_.push_back(request);
return;
}
requests_.push_back(request);
// TODO(gbillock): do we need to make default state a request property?
accept_states_.push_back(true);
if (request->HasUserGesture())
ShowBubble();
}
void PermissionBubbleManager::CancelRequest(PermissionBubbleRequest* request) {
// TODO(gbillock): implement
NOTREACHED();
}
void PermissionBubbleManager::SetView(PermissionBubbleView* view) {
if (view == view_)
return;
if (view_ != NULL) {
view_->SetDelegate(NULL);
view_->Hide();
bubble_showing_ = false;
}
view_ = view;
if (view_)
view_->SetDelegate(this);
else
return;
ShowBubble();
}
void PermissionBubbleManager::DocumentOnLoadCompletedInMainFrame(
int32 page_id) {
request_url_has_loaded_ = true;
// This is scheduled because while all calls to the browser have been
// issued at DOMContentLoaded, they may be bouncing around in scheduled
// callbacks finding the UI thread still. This makes sure we allow those
// scheduled calls to AddRequest to complete before we show the page-load
// permissions bubble.
// TODO(gbillock): make this bind safe with a weak ptr.
content::BrowserThread::PostTask(
content::BrowserThread::UI, FROM_HERE,
base::Bind(&PermissionBubbleManager::ShowBubble,
weak_factory_.GetWeakPtr()));
}
void PermissionBubbleManager::NavigationEntryCommitted(
const content::LoadCommittedDetails& details) {
if (!request_url_.is_empty() &&
request_url_ != web_contents()->GetLastCommittedURL()) {
// Kill off existing bubble and cancel any pending requests.
CancelPendingQueue();
FinalizeBubble();
}
}
void PermissionBubbleManager::WebContentsDestroyed(
content::WebContents* web_contents) {
// If the web contents has been destroyed, do not attempt to notify
// the requests of any changes - simply close the bubble.
CancelPendingQueue();
FinalizeBubble();
// The WebContents is going away; be aggressively paranoid and delete
// ourselves lest other parts of the system attempt to add permission bubbles
// or use us otherwise during the destruction.
web_contents->RemoveUserData(UserDataKey());
// That was the equivalent of "delete this". This object is now destroyed;
// returning from this function is the only safe thing to do.
}
void PermissionBubbleManager::ToggleAccept(int request_index, bool new_value) {
DCHECK(request_index < static_cast<int>(accept_states_.size()));
accept_states_[request_index] = new_value;
}
void PermissionBubbleManager::SetCustomizationMode() {
customization_mode_ = true;
if (view_)
view_->Show(requests_, accept_states_, customization_mode_);
}
void PermissionBubbleManager::Accept() {
std::vector<PermissionBubbleRequest*>::iterator requests_iter;
std::vector<bool>::iterator accepts_iter = accept_states_.begin();
for (requests_iter = requests_.begin(), accepts_iter = accept_states_.begin();
requests_iter != requests_.end();
requests_iter++, accepts_iter++) {
if (*accepts_iter)
(*requests_iter)->PermissionGranted();
else
(*requests_iter)->PermissionDenied();
}
FinalizeBubble();
}
void PermissionBubbleManager::Deny() {
std::vector<PermissionBubbleRequest*>::iterator requests_iter;
for (requests_iter = requests_.begin();
requests_iter != requests_.end();
requests_iter++) {
(*requests_iter)->PermissionDenied();
}
FinalizeBubble();
}
void PermissionBubbleManager::Closing() {
std::vector<PermissionBubbleRequest*>::iterator requests_iter;
for (requests_iter = requests_.begin();
requests_iter != requests_.end();
requests_iter++) {
(*requests_iter)->Cancelled();
}
FinalizeBubble();
}
void PermissionBubbleManager::ShowBubble() {
if (view_ && !bubble_showing_ && request_url_has_loaded_ &&
requests_.size()) {
// Note: this should appear above Show() for testing, since in that
// case we may do in-line calling of finalization.
bubble_showing_ = true;
view_->Show(requests_, accept_states_, customization_mode_);
}
}
void PermissionBubbleManager::FinalizeBubble() {
if (view_)
view_->Hide();
bubble_showing_ = false;
std::vector<PermissionBubbleRequest*>::iterator requests_iter;
for (requests_iter = requests_.begin();
requests_iter != requests_.end();
requests_iter++) {
(*requests_iter)->RequestFinished();
}
requests_.clear();
accept_states_.clear();
if (queued_requests_.size()) {
requests_ = queued_requests_;
accept_states_.resize(requests_.size(), true);
queued_requests_.clear();
ShowBubble();
} else {
request_url_ = GURL();
}
}
void PermissionBubbleManager::CancelPendingQueue() {
std::vector<PermissionBubbleRequest*>::iterator requests_iter;
for (requests_iter = queued_requests_.begin();
requests_iter != queued_requests_.end();
requests_iter++) {
(*requests_iter)->RequestFinished();
}
}