Commit 0a992857 authored by Timothy Loh's avatar Timothy Loh Committed by Commit Bot

Scope PermissionPrompt lifetime to UI visibility

This patch make the lifetime of PermissionPrompt objects more sensible
and match what we occasionally think it actually is. Currently these
objects exist when a tab is active, or on Android all the time. This
patch changes it so that, as the name might suggest, it corresponds to
when a prompt is actually visible. We retain the distinction between
desktop/Android where desktop hides the UI (now deletes the object) on
tab switching, while Android retains the UI (keeps the object alive)
as the InfoBar system manages hiding the prompt.

Since we don't explicitly manage lifetimes of the actual UI surface on
Android (infobars in particular, as it's clearer for modals when the UI
is actually destroyed) from the PermissionPrompt, we change the pointer
to a WeakPtr for safety. When the PermissionPrompt is destroyed due to
navigation, if an infobar happens to still persist due to a bug it
won't be able to be resolve different permission requests.

Bug: 606138, 737102
Change-Id: I26a8843b9b446e91282a9b0ce67c4b442bf440e9
Reviewed-on: https://chromium-review.googlesource.com/567940
Commit-Queue: Timothy Loh <timloh@chromium.org>
Reviewed-by: default avatarRaymes Khoury <raymes@chromium.org>
Cr-Commit-Position: refs/heads/master@{#487029}
parent c53fb608
...@@ -21,7 +21,7 @@ GroupedPermissionInfoBarDelegate::~GroupedPermissionInfoBarDelegate() {} ...@@ -21,7 +21,7 @@ GroupedPermissionInfoBarDelegate::~GroupedPermissionInfoBarDelegate() {}
// static // static
infobars::InfoBar* GroupedPermissionInfoBarDelegate::Create( infobars::InfoBar* GroupedPermissionInfoBarDelegate::Create(
PermissionPromptAndroid* permission_prompt, const base::WeakPtr<PermissionPromptAndroid>& permission_prompt,
InfoBarService* infobar_service, InfoBarService* infobar_service,
const GURL& requesting_origin) { const GURL& requesting_origin) {
return infobar_service->AddInfoBar(base::MakeUnique<GroupedPermissionInfoBar>( return infobar_service->AddInfoBar(base::MakeUnique<GroupedPermissionInfoBar>(
...@@ -34,6 +34,8 @@ size_t GroupedPermissionInfoBarDelegate::PermissionCount() const { ...@@ -34,6 +34,8 @@ size_t GroupedPermissionInfoBarDelegate::PermissionCount() const {
} }
bool GroupedPermissionInfoBarDelegate::ShouldShowPersistenceToggle() const { bool GroupedPermissionInfoBarDelegate::ShouldShowPersistenceToggle() const {
if (!permission_prompt_)
return false;
return permission_prompt_->ShouldShowPersistenceToggle(); return permission_prompt_->ShouldShowPersistenceToggle();
} }
...@@ -78,7 +80,7 @@ base::string16 GroupedPermissionInfoBarDelegate::GetLinkText() const { ...@@ -78,7 +80,7 @@ base::string16 GroupedPermissionInfoBarDelegate::GetLinkText() const {
} }
GroupedPermissionInfoBarDelegate::GroupedPermissionInfoBarDelegate( GroupedPermissionInfoBarDelegate::GroupedPermissionInfoBarDelegate(
PermissionPromptAndroid* permission_prompt, const base::WeakPtr<PermissionPromptAndroid>& permission_prompt,
const GURL& requesting_origin) const GURL& requesting_origin)
: requesting_origin_(requesting_origin), : requesting_origin_(requesting_origin),
persist_(true), persist_(true),
......
...@@ -24,9 +24,10 @@ class GroupedPermissionInfoBarDelegate : public ConfirmInfoBarDelegate { ...@@ -24,9 +24,10 @@ class GroupedPermissionInfoBarDelegate : public ConfirmInfoBarDelegate {
// Public so we can have std::unique_ptr<GroupedPermissionInfoBarDelegate>. // Public so we can have std::unique_ptr<GroupedPermissionInfoBarDelegate>.
~GroupedPermissionInfoBarDelegate() override; ~GroupedPermissionInfoBarDelegate() override;
static infobars::InfoBar* Create(PermissionPromptAndroid* permission_prompt, static infobars::InfoBar* Create(
InfoBarService* infobar_service, const base::WeakPtr<PermissionPromptAndroid>& permission_prompt,
const GURL& requesting_origin); InfoBarService* infobar_service,
const GURL& requesting_origin);
bool persist() const { return persist_; } bool persist() const { return persist_; }
void set_persist(bool persist) { persist_ = persist; } void set_persist(bool persist) { persist_ = persist; }
...@@ -52,8 +53,9 @@ class GroupedPermissionInfoBarDelegate : public ConfirmInfoBarDelegate { ...@@ -52,8 +53,9 @@ class GroupedPermissionInfoBarDelegate : public ConfirmInfoBarDelegate {
bool GetAcceptState(size_t position); bool GetAcceptState(size_t position);
private: private:
GroupedPermissionInfoBarDelegate(PermissionPromptAndroid* permission_prompt, GroupedPermissionInfoBarDelegate(
const GURL& requesting_origin); const base::WeakPtr<PermissionPromptAndroid>& permission_prompt,
const GURL& requesting_origin);
// ConfirmInfoBarDelegate: // ConfirmInfoBarDelegate:
InfoBarIdentifier GetIdentifier() const override; InfoBarIdentifier GetIdentifier() const override;
...@@ -68,7 +70,7 @@ class GroupedPermissionInfoBarDelegate : public ConfirmInfoBarDelegate { ...@@ -68,7 +70,7 @@ class GroupedPermissionInfoBarDelegate : public ConfirmInfoBarDelegate {
const GURL requesting_origin_; const GURL requesting_origin_;
// Whether the accept/deny decision is persisted. // Whether the accept/deny decision is persisted.
bool persist_; bool persist_;
PermissionPromptAndroid* permission_prompt_; base::WeakPtr<PermissionPromptAndroid> permission_prompt_;
DISALLOW_COPY_AND_ASSIGN(GroupedPermissionInfoBarDelegate); DISALLOW_COPY_AND_ASSIGN(GroupedPermissionInfoBarDelegate);
}; };
......
...@@ -100,9 +100,9 @@ class PermissionDialogDelegate : public content::WebContentsObserver { ...@@ -100,9 +100,9 @@ class PermissionDialogDelegate : public content::WebContentsObserver {
// delete the PermissionQueueController. // delete the PermissionQueueController.
std::unique_ptr<PermissionInfoBarDelegate> infobar_delegate_; std::unique_ptr<PermissionInfoBarDelegate> infobar_delegate_;
// The PermissionPromptAndroid is alive until the tab navigates or is closed. // The PermissionPromptAndroid is deleted when either the dialog is resolved
// We close the prompt on DidFinishNavigation and WebContentsDestroyed, so it // or the tab is navigated/closed. We close the prompt on DidFinishNavigation
// should always be safe to use this pointer. // and WebContentsDestroyed, so it should always be safe to use this pointer.
PermissionPromptAndroid* permission_prompt_; PermissionPromptAndroid* permission_prompt_;
DISALLOW_COPY_AND_ASSIGN(PermissionDialogDelegate); DISALLOW_COPY_AND_ASSIGN(PermissionDialogDelegate);
......
...@@ -19,7 +19,10 @@ ...@@ -19,7 +19,10 @@
PermissionPromptAndroid::PermissionPromptAndroid( PermissionPromptAndroid::PermissionPromptAndroid(
content::WebContents* web_contents) content::WebContents* web_contents)
: web_contents_(web_contents), delegate_(nullptr), persist_(true) { : web_contents_(web_contents),
delegate_(nullptr),
persist_(true),
weak_factory_(this) {
DCHECK(web_contents); DCHECK(web_contents);
} }
...@@ -46,7 +49,8 @@ void PermissionPromptAndroid::Show() { ...@@ -46,7 +49,8 @@ void PermissionPromptAndroid::Show() {
return; return;
GroupedPermissionInfoBarDelegate::Create( GroupedPermissionInfoBarDelegate::Create(
this, infobar_service, delegate_->Requests()[0]->GetOrigin()); weak_factory_.GetWeakPtr(), infobar_service,
delegate_->Requests()[0]->GetOrigin());
} }
bool PermissionPromptAndroid::CanAcceptRequestUpdate() { bool PermissionPromptAndroid::CanAcceptRequestUpdate() {
......
...@@ -7,6 +7,7 @@ ...@@ -7,6 +7,7 @@
#include <vector> #include <vector>
#include "base/memory/weak_ptr.h"
#include "base/strings/string16.h" #include "base/strings/string16.h"
#include "chrome/browser/ui/permission_bubble/permission_prompt.h" #include "chrome/browser/ui/permission_bubble/permission_prompt.h"
#include "components/content_settings/core/common/content_settings_types.h" #include "components/content_settings/core/common/content_settings_types.h"
...@@ -56,6 +57,8 @@ class PermissionPromptAndroid : public PermissionPrompt { ...@@ -56,6 +57,8 @@ class PermissionPromptAndroid : public PermissionPrompt {
bool persist_; bool persist_;
base::WeakPtrFactory<PermissionPromptAndroid> weak_factory_;
DISALLOW_COPY_AND_ASSIGN(PermissionPromptAndroid); DISALLOW_COPY_AND_ASSIGN(PermissionPromptAndroid);
}; };
......
...@@ -112,12 +112,12 @@ PermissionRequestManager::PermissionRequestManager( ...@@ -112,12 +112,12 @@ PermissionRequestManager::PermissionRequestManager(
view_factory_(base::Bind(&PermissionPrompt::Create)), view_factory_(base::Bind(&PermissionPrompt::Create)),
view_(nullptr), view_(nullptr),
main_frame_has_fully_loaded_(false), main_frame_has_fully_loaded_(false),
tab_can_show_prompts_(false),
persist_(true), persist_(true),
auto_response_for_test_(NONE), auto_response_for_test_(NONE),
weak_factory_(this) { weak_factory_(this) {
#if defined(OS_ANDROID) #if defined(OS_ANDROID)
view_ = view_factory_.Run(web_contents); tab_can_show_prompts_ = true;
view_->SetDelegate(this);
#endif #endif
} }
...@@ -195,16 +195,18 @@ void PermissionRequestManager::CancelRequest(PermissionRequest* request) { ...@@ -195,16 +195,18 @@ void PermissionRequestManager::CancelRequest(PermissionRequest* request) {
// We can simply erase the current entry in the request table if we aren't // We can simply erase the current entry in the request table if we aren't
// showing the dialog, or if we are showing it and it can accept the update. // showing the dialog, or if we are showing it and it can accept the update.
bool can_erase = !view_ || view_->CanAcceptRequestUpdate(); bool can_erase = view_->CanAcceptRequestUpdate();
if (can_erase) { if (can_erase) {
DeleteBubble();
RequestFinishedIncludingDuplicates(*requests_iter); RequestFinishedIncludingDuplicates(*requests_iter);
requests_.erase(requests_iter); requests_.erase(requests_iter);
if (view_) { if (requests_.empty())
view_->Hide();
// Will redraw the bubble if it is being shown.
DequeueRequestsAndShowBubble(); DequeueRequestsAndShowBubble();
} else
ShowBubble();
return; return;
} }
...@@ -234,22 +236,14 @@ void PermissionRequestManager::CancelRequest(PermissionRequest* request) { ...@@ -234,22 +236,14 @@ void PermissionRequestManager::CancelRequest(PermissionRequest* request) {
} }
void PermissionRequestManager::HideBubble() { void PermissionRequestManager::HideBubble() {
// Disengage from the existing view if there is one and it doesn't manage tab_can_show_prompts_ = false;
// its own visibility.
if (!view_ || view_->HidesAutomatically())
return;
view_->SetDelegate(nullptr); if (view_)
view_->Hide(); DeleteBubble();
view_.reset();
} }
void PermissionRequestManager::DisplayPendingRequests() { void PermissionRequestManager::DisplayPendingRequests() {
if (IsBubbleVisible()) tab_can_show_prompts_ = true;
return;
view_ = view_factory_.Run(web_contents());
view_->SetDelegate(this);
if (!main_frame_has_fully_loaded_) if (!main_frame_has_fully_loaded_)
return; return;
...@@ -294,8 +288,7 @@ void PermissionRequestManager::DidFinishNavigation( ...@@ -294,8 +288,7 @@ void PermissionRequestManager::DidFinishNavigation(
return; return;
} }
CancelPendingQueues(); CleanUpRequests();
FinalizeBubble();
main_frame_has_fully_loaded_ = false; main_frame_has_fully_loaded_ = false;
} }
...@@ -316,8 +309,7 @@ void PermissionRequestManager::DocumentLoadedInFrame( ...@@ -316,8 +309,7 @@ void PermissionRequestManager::DocumentLoadedInFrame(
void PermissionRequestManager::WebContentsDestroyed() { void PermissionRequestManager::WebContentsDestroyed() {
// If the web contents has been destroyed, treat the bubble as cancelled. // If the web contents has been destroyed, treat the bubble as cancelled.
CancelPendingQueues(); CleanUpRequests();
FinalizeBubble();
// The WebContents is going away; be aggressively paranoid and delete // The WebContents is going away; be aggressively paranoid and delete
// ourselves lest other parts of the system attempt to add permission bubbles // ourselves lest other parts of the system attempt to add permission bubbles
...@@ -359,6 +351,11 @@ void PermissionRequestManager::Deny() { ...@@ -359,6 +351,11 @@ void PermissionRequestManager::Deny() {
} }
void PermissionRequestManager::Closing() { void PermissionRequestManager::Closing() {
#if defined(OS_MACOSX)
// Mac calls this whenever you press Esc.
if (!view_)
return;
#endif
std::vector<PermissionRequest*>::iterator requests_iter; std::vector<PermissionRequest*>::iterator requests_iter;
for (requests_iter = requests_.begin(); for (requests_iter = requests_.begin();
requests_iter != requests_.end(); requests_iter != requests_.end();
...@@ -381,15 +378,14 @@ void PermissionRequestManager::ScheduleShowBubble() { ...@@ -381,15 +378,14 @@ void PermissionRequestManager::ScheduleShowBubble() {
} }
void PermissionRequestManager::DequeueRequestsAndShowBubble() { void PermissionRequestManager::DequeueRequestsAndShowBubble() {
if (!view_) if (view_)
return;
if (!requests_.empty())
return; return;
if (!main_frame_has_fully_loaded_) if (!main_frame_has_fully_loaded_ || !tab_can_show_prompts_)
return; return;
if (queued_requests_.empty()) if (queued_requests_.empty())
return; return;
DCHECK(requests_.empty());
requests_.push_back(queued_requests_.front()); requests_.push_back(queued_requests_.front());
queued_requests_.pop_front(); queued_requests_.pop_front();
...@@ -403,10 +399,13 @@ void PermissionRequestManager::DequeueRequestsAndShowBubble() { ...@@ -403,10 +399,13 @@ void PermissionRequestManager::DequeueRequestsAndShowBubble() {
} }
void PermissionRequestManager::ShowBubble() { void PermissionRequestManager::ShowBubble() {
DCHECK(view_); DCHECK(!view_);
DCHECK(!requests_.empty()); DCHECK(!requests_.empty());
DCHECK(main_frame_has_fully_loaded_); DCHECK(main_frame_has_fully_loaded_);
DCHECK(tab_can_show_prompts_);
view_ = view_factory_.Run(web_contents());
view_->SetDelegate(this);
view_->Show(); view_->Show();
PermissionUmaUtil::PermissionPromptShown(requests_); PermissionUmaUtil::PermissionPromptShown(requests_);
NotifyBubbleAdded(); NotifyBubbleAdded();
...@@ -416,9 +415,19 @@ void PermissionRequestManager::ShowBubble() { ...@@ -416,9 +415,19 @@ void PermissionRequestManager::ShowBubble() {
DoAutoResponseForTesting(); DoAutoResponseForTesting();
} }
void PermissionRequestManager::FinalizeBubble() { void PermissionRequestManager::DeleteBubble() {
if (view_ && !view_->HidesAutomatically()) DCHECK(view_);
if (!view_->HidesAutomatically())
view_->Hide(); view_->Hide();
view_->SetDelegate(nullptr);
view_.reset();
}
void PermissionRequestManager::FinalizeBubble() {
DCHECK(view_);
DCHECK(!requests_.empty());
DeleteBubble();
std::vector<PermissionRequest*>::iterator requests_iter; std::vector<PermissionRequest*>::iterator requests_iter;
for (requests_iter = requests_.begin(); for (requests_iter = requests_.begin();
...@@ -431,7 +440,7 @@ void PermissionRequestManager::FinalizeBubble() { ...@@ -431,7 +440,7 @@ void PermissionRequestManager::FinalizeBubble() {
DequeueRequestsAndShowBubble(); DequeueRequestsAndShowBubble();
} }
void PermissionRequestManager::CancelPendingQueues() { void PermissionRequestManager::CleanUpRequests() {
std::deque<PermissionRequest*>::iterator requests_iter; std::deque<PermissionRequest*>::iterator requests_iter;
for (requests_iter = queued_requests_.begin(); for (requests_iter = queued_requests_.begin();
requests_iter != queued_requests_.end(); requests_iter != queued_requests_.end();
...@@ -439,6 +448,9 @@ void PermissionRequestManager::CancelPendingQueues() { ...@@ -439,6 +448,9 @@ void PermissionRequestManager::CancelPendingQueues() {
RequestFinishedIncludingDuplicates(*requests_iter); RequestFinishedIncludingDuplicates(*requests_iter);
} }
queued_requests_.clear(); queued_requests_.clear();
if (view_)
FinalizeBubble();
} }
PermissionRequest* PermissionRequestManager::GetExistingRequest( PermissionRequest* PermissionRequestManager::GetExistingRequest(
......
...@@ -77,6 +77,7 @@ class PermissionRequestManager ...@@ -77,6 +77,7 @@ class PermissionRequestManager
// Temporarily hides the bubble, and destroys the prompt UI surface. Any // Temporarily hides the bubble, and destroys the prompt UI surface. Any
// existing requests will be reshown when DisplayPendingRequests is called // existing requests will be reshown when DisplayPendingRequests is called
// (e.g. when switching tabs away and back to a page with a prompt). // (e.g. when switching tabs away and back to a page with a prompt).
// TODO(timloh): Rename this to something more fitting (e.g. TabSwitchedAway).
void HideBubble(); void HideBubble();
// Will show a permission bubble if there is a pending permission request on // Will show a permission bubble if there is a pending permission request on
...@@ -152,13 +153,17 @@ class PermissionRequestManager ...@@ -152,13 +153,17 @@ class PermissionRequestManager
// bubble after switching tabs away and back. // bubble after switching tabs away and back.
void ShowBubble(); void ShowBubble();
// Finalize the pending permissions request. // Delete the view object
void DeleteBubble();
// Delete the view object, finalize requests, asynchronously show a queued
// request if present.
void FinalizeBubble(); void FinalizeBubble();
// Cancel any pending requests. This is called if the WebContents // Cancel all pending or active requests and destroy the PermissionPrompt if
// on which permissions calls are pending is destroyed or navigated away // one exists. This is called if the WebContents is destroyed or navigates its
// from the requesting page. // main frame.
void CancelPendingQueues(); void CleanUpRequests();
// Searches |requests_|, |queued_requests_| and |queued_frame_requests_| - but // Searches |requests_|, |queued_requests_| and |queued_frame_requests_| - but
// *not* |duplicate_requests_| - for a request matching |request|, and returns // *not* |duplicate_requests_| - for a request matching |request|, and returns
...@@ -182,8 +187,13 @@ class PermissionRequestManager ...@@ -182,8 +187,13 @@ class PermissionRequestManager
// Factory to be used to create views when needed. // Factory to be used to create views when needed.
PermissionPrompt::Factory view_factory_; PermissionPrompt::Factory view_factory_;
// The UI surface to be used to display the permissions requests. // The UI surface for an active permission prompt if we're displaying one.
std::unique_ptr<PermissionPrompt> view_; std::unique_ptr<PermissionPrompt> view_;
// We only show prompts when both of these are true. On Desktop, we hide any
// active prompt on tab switching, while on Android we let the infobar system
// handle it.
bool main_frame_has_fully_loaded_;
bool tab_can_show_prompts_;
std::vector<PermissionRequest*> requests_; std::vector<PermissionRequest*> requests_;
std::deque<PermissionRequest*> queued_requests_; std::deque<PermissionRequest*> queued_requests_;
...@@ -192,8 +202,6 @@ class PermissionRequestManager ...@@ -192,8 +202,6 @@ class PermissionRequestManager
std::unordered_multimap<PermissionRequest*, PermissionRequest*> std::unordered_multimap<PermissionRequest*, PermissionRequest*>
duplicate_requests_; duplicate_requests_;
bool main_frame_has_fully_loaded_;
// Whether the response to each request should be persisted. // Whether the response to each request should be persisted.
bool persist_; bool persist_;
......
...@@ -9,6 +9,7 @@ ...@@ -9,6 +9,7 @@
#include "base/run_loop.h" #include "base/run_loop.h"
#include "base/strings/utf_string_conversions.h" #include "base/strings/utf_string_conversions.h"
#include "base/test/histogram_tester.h" #include "base/test/histogram_tester.h"
#include "build/build_config.h"
#include "chrome/browser/permissions/mock_permission_request.h" #include "chrome/browser/permissions/mock_permission_request.h"
#include "chrome/browser/permissions/permission_request.h" #include "chrome/browser/permissions/permission_request.h"
#include "chrome/browser/permissions/permission_request_manager.h" #include "chrome/browser/permissions/permission_request_manager.h"
...@@ -199,12 +200,14 @@ TEST_F(PermissionRequestManagerTest, NoRequests) { ...@@ -199,12 +200,14 @@ TEST_F(PermissionRequestManagerTest, NoRequests) {
EXPECT_FALSE(prompt_factory_->is_visible()); EXPECT_FALSE(prompt_factory_->is_visible());
} }
TEST_F(PermissionRequestManagerTest, NoView) { #if !defined(OS_ANDROID)
TEST_F(PermissionRequestManagerTest, PermissionRequestWhileTabSwitchedAway) {
manager_->AddRequest(&request1_); manager_->AddRequest(&request1_);
// Don't display the pending requests. // Don't mark the tab as active.
WaitForBubbleToBeShown(); WaitForBubbleToBeShown();
EXPECT_FALSE(prompt_factory_->is_visible()); EXPECT_FALSE(prompt_factory_->is_visible());
} }
#endif
TEST_F(PermissionRequestManagerTest, TwoRequestsDoNotCoalesce) { TEST_F(PermissionRequestManagerTest, TwoRequestsDoNotCoalesce) {
manager_->DisplayPendingRequests(); manager_->DisplayPendingRequests();
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment