Use weak pointer to store duplicate requests
Bug: 1423304
Change-Id: I7ab170f085c3d05c582f7065b88c1ad2510cc633
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/4365724
Commit-Queue: Thomas Nguyen <tungnh@chromium.org>
Reviewed-by: Andy Paicu <andypaicu@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1124133}
diff --git a/components/permissions/permission_request.cc b/components/permissions/permission_request.cc
index 7f33022..10f2d44 100644
--- a/components/permissions/permission_request.cc
+++ b/components/permissions/permission_request.cc
@@ -36,6 +36,10 @@
requesting_origin() == other_request->requesting_origin();
}
+base::WeakPtr<PermissionRequest> PermissionRequest::GetWeakPtr() {
+ return weak_factory_.GetWeakPtr();
+}
+
#if BUILDFLAG(IS_ANDROID)
std::u16string PermissionRequest::GetDialogMessageText() const {
int message_id = 0;
diff --git a/components/permissions/permission_request.h b/components/permissions/permission_request.h
index d0c57a5..0328b53 100644
--- a/components/permissions/permission_request.h
+++ b/components/permissions/permission_request.h
@@ -8,6 +8,7 @@
#include <string>
#include "base/functional/callback.h"
+#include "base/memory/weak_ptr.h"
#include "build/build_config.h"
#include "components/content_settings/core/common/content_settings.h"
#include "components/content_settings/core/common/content_settings_types.h"
@@ -83,6 +84,9 @@
virtual std::u16string GetDialogMessageText() const;
#endif
+ // Returns a weak pointer to this instance.
+ base::WeakPtr<PermissionRequest> GetWeakPtr();
+
#if !BUILDFLAG(IS_ANDROID)
// Returns whether displaying a confirmation chip for the request is
// supported.
@@ -163,6 +167,8 @@
// Called when the request is no longer in use so it can be deleted by the
// caller.
base::OnceClosure delete_callback_;
+
+ base::WeakPtrFactory<PermissionRequest> weak_factory_{this};
};
} // namespace permissions
diff --git a/components/permissions/permission_request_manager.cc b/components/permissions/permission_request_manager.cc
index c151daa..bbb71cd 100644
--- a/components/permissions/permission_request_manager.cc
+++ b/components/permissions/permission_request_manager.cc
@@ -233,18 +233,25 @@
}
// Don't re-add an existing request or one with a duplicate text request.
- PermissionRequest* existing_request = GetExistingRequest(request);
- if (existing_request) {
+ if (auto* existing_request = GetExistingRequest(request)) {
+ if (request == existing_request) {
+ return;
+ }
+
// |request| is a duplicate. Add it to |duplicate_requests_| unless it's the
// same object as |existing_request| or an existing duplicate.
- if (request == existing_request)
+ auto iter = FindDuplicateRequestList(existing_request);
+ if (iter == duplicate_requests_.end()) {
+ duplicate_requests_.push_back({request->GetWeakPtr()});
return;
- auto range = duplicate_requests_.equal_range(existing_request);
- for (auto it = range.first; it != range.second; ++it) {
- if (request == it->second)
- return;
}
- duplicate_requests_.insert(std::make_pair(existing_request, request));
+
+ for (const auto& weak_request : (*iter)) {
+ if (weak_request && request == weak_request.get()) {
+ return;
+ }
+ }
+ iter->push_back(request->GetWeakPtr());
return;
}
@@ -1050,12 +1057,60 @@
PermissionRequest* PermissionRequestManager::GetExistingRequest(
PermissionRequest* request) const {
for (PermissionRequest* existing_request : requests_) {
- if (request->IsDuplicateOf(existing_request))
+ if (request->IsDuplicateOf(existing_request)) {
return existing_request;
+ }
}
return pending_permission_requests_.FindDuplicate(request);
}
+PermissionRequestManager::WeakPermissionRequestList::iterator
+PermissionRequestManager::FindDuplicateRequestList(PermissionRequest* request) {
+ for (auto request_list = duplicate_requests_.begin();
+ request_list != duplicate_requests_.end(); ++request_list) {
+ for (auto iter = request_list->begin(); iter != request_list->end();) {
+ // Remove any requests that have been destroyed.
+ const auto& weak_request = (*iter);
+ if (!weak_request) {
+ iter = request_list->erase(iter);
+ continue;
+ }
+
+ // The first valid request in the list will indicate whether all other
+ // members are duplicate or not.
+ if (weak_request->IsDuplicateOf(request)) {
+ return request_list;
+ }
+
+ break;
+ }
+ }
+
+ return duplicate_requests_.end();
+}
+
+PermissionRequestManager::WeakPermissionRequestList::iterator
+PermissionRequestManager::VisitDuplicateRequests(
+ DuplicateRequestVisitor visitor,
+ PermissionRequest* request) {
+ auto request_list = FindDuplicateRequestList(request);
+ if (request_list == duplicate_requests_.end()) {
+ return request_list;
+ }
+
+ for (auto iter = request_list->begin(); iter != request_list->end();) {
+ if (auto& weak_request = (*iter)) {
+ visitor.Run(weak_request);
+ ++iter;
+ } else {
+ // Remove any requests that have been destroyed.
+ iter = request_list->erase(iter);
+ }
+ }
+
+ return request_list;
+}
+
void PermissionRequestManager::PermissionGrantedIncludingDuplicates(
PermissionRequest* request,
bool is_one_time) {
@@ -1063,9 +1118,14 @@
pending_permission_requests_.Count(request))
<< "Only requests in [pending_permission_]requests_ can have duplicates";
request->PermissionGranted(is_one_time);
- auto range = duplicate_requests_.equal_range(request);
- for (auto it = range.first; it != range.second; ++it)
- it->second->PermissionGranted(is_one_time);
+ VisitDuplicateRequests(
+ base::BindRepeating(
+ [](bool is_one_time,
+ const base::WeakPtr<PermissionRequest>& weak_request) {
+ weak_request->PermissionGranted(is_one_time);
+ },
+ is_one_time),
+ request);
}
void PermissionRequestManager::PermissionDeniedIncludingDuplicates(
@@ -1074,9 +1134,12 @@
pending_permission_requests_.Count(request))
<< "Only requests in [pending_permission_]requests_ can have duplicates";
request->PermissionDenied();
- auto range = duplicate_requests_.equal_range(request);
- for (auto it = range.first; it != range.second; ++it)
- it->second->PermissionDenied();
+ VisitDuplicateRequests(
+ base::BindRepeating(
+ [](const base::WeakPtr<PermissionRequest>& weak_request) {
+ weak_request->PermissionDenied();
+ }),
+ request);
}
void PermissionRequestManager::CancelledIncludingDuplicates(
@@ -1086,9 +1149,14 @@
pending_permission_requests_.Count(request))
<< "Only requests in [pending_permission_]requests_ can have duplicates";
request->Cancelled(is_final_decision);
- auto range = duplicate_requests_.equal_range(request);
- for (auto it = range.first; it != range.second; ++it)
- it->second->Cancelled(is_final_decision);
+ VisitDuplicateRequests(
+ base::BindRepeating(
+ [](bool is_final,
+ const base::WeakPtr<PermissionRequest>& weak_request) {
+ weak_request->Cancelled(is_final);
+ },
+ is_final_decision),
+ request);
}
void PermissionRequestManager::RequestFinishedIncludingDuplicates(
@@ -1096,13 +1164,21 @@
DCHECK_EQ(1ul, base::ranges::count(requests_, request) +
pending_permission_requests_.Count(request))
<< "Only requests in [pending_permission_]requests_ can have duplicates";
+ auto duplicate_list = VisitDuplicateRequests(
+ base::BindRepeating(
+ [](const base::WeakPtr<PermissionRequest>& weak_request) {
+ weak_request->RequestFinished();
+ }),
+ request);
+
+ // Note: beyond this point, |request| has probably been deleted, any
+ // dereference of |request| must be done prior this point.
request->RequestFinished();
- // Beyond this point, |request| has probably been deleted.
- auto range = duplicate_requests_.equal_range(request);
- for (auto it = range.first; it != range.second; ++it)
- it->second->RequestFinished();
+
// Additionally, we can now remove the duplicates.
- duplicate_requests_.erase(request);
+ if (duplicate_list != duplicate_requests_.end()) {
+ duplicate_requests_.erase(duplicate_list);
+ }
}
void PermissionRequestManager::AddObserver(Observer* observer) {
diff --git a/components/permissions/permission_request_manager.h b/components/permissions/permission_request_manager.h
index 226b91cc..f60017c 100644
--- a/components/permissions/permission_request_manager.h
+++ b/components/permissions/permission_request_manager.h
@@ -254,6 +254,9 @@
private:
friend class test::PermissionRequestManagerTestApi;
friend class content::WebContentsUserData<PermissionRequestManager>;
+ FRIEND_TEST_ALL_PREFIXES(PermissionRequestManagerTest, WeakDuplicateRequests);
+ FRIEND_TEST_ALL_PREFIXES(PermissionRequestManagerTest,
+ WeakDuplicateRequestsAccept);
explicit PermissionRequestManager(content::WebContents* web_contents);
@@ -328,6 +331,25 @@
// may or may not be the same object as |request|.
PermissionRequest* GetExistingRequest(PermissionRequest* request) const;
+ // Returns an iterator into |duplicate_requests_|, points the matching list,
+ // or duplicate_requests_.end() if no match. The matching list contains all
+ // the weak requests which are duplicate of the given |request| (see
+ // |IsDuplicateOf|)
+ using WeakPermissionRequestList =
+ std::list<std::list<base::WeakPtr<PermissionRequest>>>;
+ WeakPermissionRequestList::iterator FindDuplicateRequestList(
+ PermissionRequest* request);
+
+ // Trigger |visitor| for each live weak request which matches the given
+ // request (see |IsDuplicateOf|) in the |duplicate_requests_|. Returns an
+ // iterator into |duplicate_requests_|, points the matching list, or
+ // duplicate_requests_.end() if no match.
+ using DuplicateRequestVisitor =
+ base::RepeatingCallback<void(const base::WeakPtr<PermissionRequest>&)>;
+ WeakPermissionRequestList::iterator VisitDuplicateRequests(
+ DuplicateRequestVisitor visitor,
+ PermissionRequest* request);
+
// Calls PermissionGranted on a request and all its duplicates.
void PermissionGrantedIncludingDuplicates(PermissionRequest* request,
bool is_one_time);
@@ -393,10 +415,8 @@
PermissionRequestQueue pending_permission_requests_;
- // Maps from the first request of a kind to subsequent requests that were
- // duped against it.
- std::unordered_multimap<PermissionRequest*, PermissionRequest*>
- duplicate_requests_;
+ // Stores the weak pointers of duplicated requests in a 2D list.
+ WeakPermissionRequestList duplicate_requests_;
// Maps each PermissionRequest currently in |requests_| or
// |pending_permission_requests_| to which RenderFrameHost it originated from.
diff --git a/components/permissions/permission_request_manager_unittest.cc b/components/permissions/permission_request_manager_unittest.cc
index 32649a1..b23be4f9 100644
--- a/components/permissions/permission_request_manager_unittest.cc
+++ b/components/permissions/permission_request_manager_unittest.cc
@@ -11,6 +11,7 @@
#include "base/memory/raw_ptr.h"
#include "base/run_loop.h"
#include "base/task/sequenced_task_runner.h"
+#include "base/test/bind.h"
#include "base/test/metrics/histogram_tester.h"
#include "base/test/scoped_feature_list.h"
#include "build/build_config.h"
@@ -515,6 +516,90 @@
EXPECT_FALSE(prompt_factory_->is_visible());
}
+TEST_P(PermissionRequestManagerTest, WeakDuplicateRequests) {
+ manager_->AddRequest(web_contents()->GetPrimaryMainFrame(), &request1_);
+ WaitForBubbleToBeShown();
+ auto dupe_request_1 = request1_.CreateDuplicateRequest();
+ auto dupe_request_2 = request1_.CreateDuplicateRequest();
+ auto dupe_request_3 = request1_.CreateDuplicateRequest();
+ auto dupe_request_4 = request1_.CreateDuplicateRequest();
+ manager_->AddRequest(web_contents()->GetPrimaryMainFrame(),
+ dupe_request_1.get());
+ manager_->AddRequest(web_contents()->GetPrimaryMainFrame(),
+ dupe_request_2.get());
+ manager_->AddRequest(web_contents()->GetPrimaryMainFrame(),
+ dupe_request_3.get());
+ manager_->AddRequest(web_contents()->GetPrimaryMainFrame(),
+ dupe_request_4.get());
+ dupe_request_1.reset();
+ dupe_request_3.reset();
+ EXPECT_EQ(4ul, manager_->duplicate_requests_.front().size());
+ EXPECT_EQ(1ul, manager_->duplicate_requests_.size());
+ auto request_list = manager_->FindDuplicateRequestList(&request1_);
+ EXPECT_NE(request_list, manager_->duplicate_requests_.end());
+ EXPECT_EQ(3ul, manager_->duplicate_requests_.front().size());
+ dupe_request_4.reset();
+ manager_->VisitDuplicateRequests(
+ base::BindRepeating(
+ [](const base::WeakPtr<PermissionRequest>& weak_request) {}),
+ &request1_);
+ EXPECT_EQ(1ul, manager_->duplicate_requests_.front().size());
+ EXPECT_EQ(1ul, manager_->duplicate_requests_.size());
+ Accept();
+ EXPECT_EQ(0ul, manager_->duplicate_requests_.size());
+}
+
+class QuicklyDeletedRequest : public PermissionRequest {
+ public:
+ QuicklyDeletedRequest(const GURL& requesting_origin,
+ RequestType request_type,
+ PermissionRequestGestureType gesture_type)
+ : PermissionRequest(requesting_origin,
+ request_type,
+ gesture_type == PermissionRequestGestureType::GESTURE,
+ base::BindLambdaForTesting(
+ [](ContentSetting result,
+ bool is_one_time,
+ bool is_final_decision) { NOTREACHED(); }),
+ base::NullCallback()) {}
+
+ static std::unique_ptr<QuicklyDeletedRequest> CreateRequest(
+ MockPermissionRequest* request) {
+ return std::make_unique<QuicklyDeletedRequest>(request->requesting_origin(),
+ request->request_type(),
+ request->GetGestureType());
+ }
+};
+
+TEST_P(PermissionRequestManagerTest, WeakDuplicateRequestsAccept) {
+ manager_->AddRequest(web_contents()->GetPrimaryMainFrame(), &request1_);
+ WaitForBubbleToBeShown();
+ manager_->AddRequest(web_contents()->GetPrimaryMainFrame(), &request2_);
+ auto dupe_request_1 = QuicklyDeletedRequest::CreateRequest(&request1_);
+ auto dupe_request_2 = request1_.CreateDuplicateRequest();
+ manager_->AddRequest(web_contents()->GetPrimaryMainFrame(),
+ dupe_request_1.get());
+ manager_->AddRequest(web_contents()->GetPrimaryMainFrame(),
+ dupe_request_2.get());
+ auto dupe_request_3 = QuicklyDeletedRequest::CreateRequest(&request2_);
+ auto dupe_request_4 = request2_.CreateDuplicateRequest();
+ manager_->AddRequest(web_contents()->GetPrimaryMainFrame(),
+ dupe_request_3.get());
+ manager_->AddRequest(web_contents()->GetPrimaryMainFrame(),
+ dupe_request_4.get());
+ dupe_request_1.reset();
+ dupe_request_3.reset();
+ EXPECT_EQ(2ul, manager_->duplicate_requests_.size());
+ EXPECT_EQ(2ul, manager_->duplicate_requests_.front().size());
+ EXPECT_EQ(2ul, manager_->duplicate_requests_.back().size());
+ WaitForBubbleToBeShown();
+ Accept();
+ EXPECT_EQ(1ul, manager_->duplicate_requests_.size());
+ WaitForBubbleToBeShown();
+ Accept();
+ EXPECT_EQ(0ul, manager_->duplicate_requests_.size());
+}
+
TEST_P(PermissionRequestManagerTest, DuplicateRequest) {
manager_->AddRequest(web_contents()->GetPrimaryMainFrame(), &request1_);
WaitForBubbleToBeShown();