// Copyright 2022 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "components/permissions/android/permissions_reprompt_controller_android.h"
#include <memory>
#include "base/functional/bind.h"
#include "base/memory/raw_ptr.h"
#include "base/run_loop.h"
#include "base/task/sequenced_task_runner.h"
#include "base/test/mock_callback.h"
#include "components/permissions/test/test_permissions_client.h"
#include "content/public/test/test_renderer_host.h"
namespace permissions {
class PermissionsRepromptControllerAndroidTest
: public content::RenderViewHostTestHarness {
public:
PermissionsRepromptControllerAndroidTest() = default;
~PermissionsRepromptControllerAndroidTest() override = default;
void SetUp() override {
content::RenderViewHostTestHarness::SetUp();
PermissionsRepromptControllerAndroid::CreateForWebContents(web_contents());
controller_ =
PermissionsRepromptControllerAndroid::FromWebContents(web_contents());
client_ = std::make_unique<RepromptTestPermissionsClient>(controller_);
}
void RepromtMissingLocation(base::OnceCallback<void(bool)> callback) {
controller_->RepromptPermissionRequestInternal(
{ContentSettingsType::GEOLOCATION}, {ContentSettingsType::GEOLOCATION},
ContentSettingsType::GEOLOCATION, std::move(callback));
}
void RepromtMissingCameraMediaStream(
base::OnceCallback<void(bool)> callback) {
controller_->RepromptPermissionRequestInternal(
{ContentSettingsType::MEDIASTREAM_CAMERA},
{ContentSettingsType::MEDIASTREAM_CAMERA},
ContentSettingsType::MEDIASTREAM_CAMERA, std::move(callback));
}
void RepromtMissingCameraAR(base::OnceCallback<void(bool)> callback) {
controller_->RepromptPermissionRequestInternal(
{ContentSettingsType::MEDIASTREAM_CAMERA},
{ContentSettingsType::MEDIASTREAM_CAMERA}, ContentSettingsType::AR,
std::move(callback));
}
size_t GetPendingCallbackCount() const {
size_t callback_count = 0;
for (const auto& it : controller_->pending_callbacks_) {
callback_count += it.second.second.size();
}
return callback_count;
}
size_t GetRepromptCount() const { return client_->reprompt_count(); }
void WaitForNextReprompting() { client_->WaitForNextReprompting(); }
private:
class RepromptTestPermissionsClient : public TestPermissionsClient {
public:
explicit RepromptTestPermissionsClient(
PermissionsRepromptControllerAndroid* controller)
: controller_(controller) {}
~RepromptTestPermissionsClient() override = default;
RepromptTestPermissionsClient(const RepromptTestPermissionsClient&) =
delete;
RepromptTestPermissionsClient& operator=(
const RepromptTestPermissionsClient&) = delete;
// Getters
size_t reprompt_count() const { return reprompt_count_; }
void WaitForNextReprompting() {
base::RunLoop run_loop;
run_loop_ = &run_loop;
run_loop_->Run();
run_loop_ = nullptr;
}
private:
void RepromptForAndroidPermissions(
content::WebContents* web_contents,
const std::vector<ContentSettingsType>& content_settings_types,
const std::vector<ContentSettingsType>& filtered_content_settings_types,
const std::vector<std::string>& required_permissions,
const std::vector<std::string>& optional_permissions,
PermissionsUpdatedCallback callback) override {
++reprompt_count_;
base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
FROM_HERE,
base::BindOnce(
&RepromptTestPermissionsClient::OnRepromptPermissionDone,
base::Unretained(this), std::move(callback)));
}
void OnRepromptPermissionDone(PermissionsUpdatedCallback callback) {
if (run_loop_)
run_loop_->Quit();
std::move(callback).Run(false);
}
size_t reprompt_count_ = 0;
raw_ptr<PermissionsRepromptControllerAndroid> controller_ = nullptr;
raw_ptr<base::RunLoop> run_loop_ = nullptr;
};
raw_ptr<PermissionsRepromptControllerAndroid> controller_;
std::unique_ptr<RepromptTestPermissionsClient> client_;
};
// Duplicated requests from same contexts. Callback from same context will be
// ignored and no new prompt will be shown
TEST_F(PermissionsRepromptControllerAndroidTest, DuplicatedRequestSameContext) {
base::MockOnceCallback<void(bool)> mock_callback1;
base::MockOnceCallback<void(bool)> mock_callback2;
RepromtMissingLocation(mock_callback1.Get());
EXPECT_EQ(1u, GetPendingCallbackCount());
RepromtMissingLocation(mock_callback2.Get());
EXPECT_EQ(1u, GetPendingCallbackCount());
EXPECT_CALL(mock_callback1, Run(false));
EXPECT_CALL(mock_callback2, Run).Times(0);
WaitForNextReprompting();
EXPECT_EQ(1u, GetRepromptCount());
EXPECT_EQ(0u, GetPendingCallbackCount());
}
// Duplicated requests from different contexts. All callbacks are expected to be
// called but only 1 prompt should be shown.
TEST_F(PermissionsRepromptControllerAndroidTest,
DuplicatedRequestsDifferentContexts) {
base::MockOnceCallback<void(bool)> mock_callback1;
base::MockOnceCallback<void(bool)> mock_callback2;
RepromtMissingCameraMediaStream(mock_callback1.Get());
EXPECT_EQ(1u, GetPendingCallbackCount());
RepromtMissingCameraAR(mock_callback2.Get());
EXPECT_EQ(2u, GetPendingCallbackCount());
EXPECT_CALL(mock_callback1, Run(false));
EXPECT_CALL(mock_callback2, Run(false));
WaitForNextReprompting();
EXPECT_EQ(1u, GetRepromptCount());
EXPECT_EQ(0u, GetPendingCallbackCount());
}
// Different requests, all callbacks are expected to be called, and new prompt
// should be shown
TEST_F(PermissionsRepromptControllerAndroidTest, DifferentRequests) {
base::MockOnceCallback<void(bool)> mock_callback1;
base::MockOnceCallback<void(bool)> mock_callback2;
RepromtMissingLocation(mock_callback1.Get());
EXPECT_EQ(1u, GetPendingCallbackCount());
RepromtMissingCameraAR(mock_callback2.Get());
EXPECT_EQ(2u, GetPendingCallbackCount());
EXPECT_CALL(mock_callback1, Run(false));
WaitForNextReprompting();
EXPECT_CALL(mock_callback2, Run(false));
WaitForNextReprompting();
EXPECT_EQ(2u, GetRepromptCount());
EXPECT_EQ(0u, GetPendingCallbackCount());
}
// Mixed requests including both duplicated and different request.
TEST_F(PermissionsRepromptControllerAndroidTest, MixedRequests) {
base::MockOnceCallback<void(bool)> mock_callback1;
base::MockOnceCallback<void(bool)> mock_callback2;
base::MockOnceCallback<void(bool)> mock_callback3;
base::MockOnceCallback<void(bool)> mock_callback4;
RepromtMissingLocation(mock_callback1.Get());
EXPECT_EQ(1u, GetPendingCallbackCount());
RepromtMissingCameraMediaStream(mock_callback2.Get());
EXPECT_EQ(2u, GetPendingCallbackCount());
RepromtMissingLocation(mock_callback3.Get());
EXPECT_EQ(2u, GetPendingCallbackCount());
RepromtMissingCameraAR(mock_callback4.Get());
EXPECT_EQ(3u, GetPendingCallbackCount());
EXPECT_CALL(mock_callback1, Run(false));
EXPECT_CALL(mock_callback2, Run(false));
WaitForNextReprompting();
EXPECT_CALL(mock_callback4, Run(false));
EXPECT_CALL(mock_callback3, Run).Times(0);
WaitForNextReprompting();
EXPECT_EQ(2u, GetRepromptCount());
EXPECT_EQ(0u, GetPendingCallbackCount());
}
// Reprompt missing permission again after finished the last ones.
TEST_F(PermissionsRepromptControllerAndroidTest, OnRepromptPermissionDone) {
base::MockOnceCallback<void(bool)> mock_callback1;
base::MockOnceCallback<void(bool)> mock_callback2;
base::MockOnceCallback<void(bool)> mock_callback3;
base::MockOnceCallback<void(bool)> mock_callback4;
RepromtMissingLocation(mock_callback1.Get());
EXPECT_EQ(1u, GetPendingCallbackCount());
RepromtMissingCameraMediaStream(mock_callback2.Get());
EXPECT_EQ(2u, GetPendingCallbackCount());
EXPECT_CALL(mock_callback1, Run(false));
WaitForNextReprompting();
EXPECT_CALL(mock_callback2, Run(false));
WaitForNextReprompting();
EXPECT_EQ(0u, GetPendingCallbackCount());
RepromtMissingLocation(mock_callback3.Get());
EXPECT_EQ(1u, GetPendingCallbackCount());
EXPECT_CALL(mock_callback3, Run(false));
WaitForNextReprompting();
EXPECT_EQ(0u, GetPendingCallbackCount());
RepromtMissingCameraAR(mock_callback4.Get());
EXPECT_EQ(1u, GetPendingCallbackCount());
EXPECT_CALL(mock_callback4, Run(false));
WaitForNextReprompting();
EXPECT_EQ(4u, GetRepromptCount());
EXPECT_EQ(0u, GetPendingCallbackCount());
}
} // namespace permissions