blob: fe40cc497f88715d751cea1630569fcdd378b6fb [file] [log] [blame]
// Copyright 2020 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 "components/permissions/permission_actions_history.h"
#include <vector>
#include "base/containers/adapters.h"
#include "base/json/json_reader.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/stringprintf.h"
#include "base/time/time.h"
#include "components/content_settings/core/common/pref_names.h"
#include "components/permissions/permission_request_enums.h"
#include "components/permissions/permission_uma_util.h"
#include "components/permissions/permissions_client.h"
#include "components/permissions/pref_names.h"
#include "components/permissions/request_type.h"
#include "components/permissions/test/test_permissions_client.h"
#include "components/prefs/pref_registry_simple.h"
#include "components/prefs/scoped_user_pref_update.h"
#include "components/prefs/testing_pref_service.h"
#include "content/public/test/browser_task_environment.h"
#include "content/public/test/test_browser_context.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/abseil-cpp/absl/types/optional.h"
namespace permissions {
namespace {
struct TestEntry {
PermissionAction action;
RequestType type;
PermissionPromptDisposition prompt_disposition;
bool advance_clock;
bool is_legacy_entry;
} kTestEntries[]{
{PermissionAction::DENIED, RequestType::kNotifications,
PermissionPromptDisposition::NOT_APPLICABLE, false, true},
{PermissionAction::IGNORED, RequestType::kNotifications,
PermissionPromptDisposition::NOT_APPLICABLE, false, true},
{PermissionAction::DISMISSED, RequestType::kNotifications,
PermissionPromptDisposition::MODAL_DIALOG, true, false},
{PermissionAction::GRANTED, RequestType::kNotifications,
PermissionPromptDisposition::LOCATION_BAR_LEFT_QUIET_CHIP, false, false},
{PermissionAction::DISMISSED, RequestType::kVrSession,
PermissionPromptDisposition::ANCHORED_BUBBLE, true, false},
{PermissionAction::IGNORED, RequestType::kCameraStream,
PermissionPromptDisposition::LOCATION_BAR_LEFT_CHIP, false, false},
{PermissionAction::DISMISSED, RequestType::kGeolocation,
PermissionPromptDisposition::CUSTOM_MODAL_DIALOG, false, false},
{PermissionAction::DENIED, RequestType::kNotifications,
PermissionPromptDisposition::LOCATION_BAR_RIGHT_ANIMATED_ICON, true,
false},
{PermissionAction::GRANTED, RequestType::kNotifications,
PermissionPromptDisposition::ANCHORED_BUBBLE, false, false},
};
const char kLegacyPrefs[] = R"({
"notifications": [
{"time": "%s", "action" : 1},
{"time": "%s", "action" : 3}
]
})";
} // namespace
class PermissionActionHistoryTest : public testing::Test {
public:
PermissionActionHistoryTest() = default;
~PermissionActionHistoryTest() override = default;
PermissionActionsHistory* GetPermissionActionsHistory() {
return PermissionsClient::Get()->GetPermissionActionsHistory(
&browser_context_);
}
void SetUp() override {
testing::Test::SetUp();
RecordSetUpActions();
}
PermissionActionHistoryTest(const PermissionActionHistoryTest&) = delete;
PermissionActionHistoryTest& operator=(const PermissionActionHistoryTest&) =
delete;
std::vector<PermissionActionsHistory::Entry> GetHistory(
absl::optional<RequestType> type,
PermissionActionsHistory::EntryFilter entry_filter =
PermissionActionsHistory::EntryFilter::WANT_ALL_PROMPTS) {
if (type.has_value()) {
return GetPermissionActionsHistory()->GetHistory(
base::Time(), type.value(), entry_filter);
} else {
return GetPermissionActionsHistory()->GetHistory(base::Time(),
entry_filter);
}
}
void RecordSetUpActions() {
const int64_t time =
base::Time::Now().ToDeltaSinceWindowsEpoch().InMicroseconds();
const std::string formatted_legacy_prefs =
base::StringPrintf(kLegacyPrefs, base::NumberToString(time).c_str(),
base::NumberToString(time).c_str());
absl::optional<base::Value> legacy_pref_value =
base::JSONReader::Read(formatted_legacy_prefs);
GetPermissionActionsHistory()->GetPrefServiceForTesting()->Set(
prefs::kPermissionActions, legacy_pref_value.value());
// Record the actions needed to support test cases. This is the structure
// 3-days ago: {notification, dismissed}
// 2-days ago: {notification, granted}, {vr, dismissed}
// 1-days ago: {geolocation, ignored}, {camera, dismissed}, {notification,
// denied}
// 0-days ago: {notification, granted}
for (const auto& entry : kTestEntries) {
// Legacy entries are added directly to the pref above and not through
// Permission Actions History API
if (entry.is_legacy_entry)
continue;
GetPermissionActionsHistory()->RecordAction(entry.action, entry.type,
entry.prompt_disposition);
if (entry.advance_clock)
task_environment_.AdvanceClock(base::Days(1));
}
}
private:
content::BrowserTaskEnvironment task_environment_{
base::test::TaskEnvironment::TimeSource::MOCK_TIME};
content::TestBrowserContext browser_context_;
TestPermissionsClient permissions_client_;
};
TEST_F(PermissionActionHistoryTest, GetHistorySortedOrder) {
auto all_entries = GetHistory(absl::nullopt);
EXPECT_EQ(9u, all_entries.size());
size_t index = 0;
for (const auto& entry : kTestEntries)
EXPECT_EQ(entry.action, all_entries[index++].action);
for (const auto& request_type :
{RequestType::kVrSession, RequestType::kCameraStream,
RequestType::kGeolocation, RequestType::kNotifications}) {
auto permission_entries = GetHistory(request_type);
index = 0;
for (const auto& entry : kTestEntries) {
if (entry.type != request_type) {
continue;
}
EXPECT_EQ(entry.action, permission_entries[index++].action);
}
}
auto entries_1_day = GetPermissionActionsHistory()->GetHistory(
base::Time::Now() - base::Days(1),
PermissionActionsHistory::EntryFilter::WANT_ALL_PROMPTS);
EXPECT_TRUE(std::equal(entries_1_day.begin(), entries_1_day.end(),
std::vector<PermissionActionsHistory::Entry>(
all_entries.begin() + 5, all_entries.end())
.begin()));
}
TEST_F(PermissionActionHistoryTest, NotificationRecordAction) {
size_t general_count = GetHistory(absl::nullopt).size();
size_t notification_count = GetHistory(RequestType::kNotifications).size();
GetPermissionActionsHistory()->RecordAction(
PermissionAction::GRANTED, RequestType::kNotifications,
PermissionPromptDisposition::ANCHORED_BUBBLE);
EXPECT_EQ(general_count + 1, GetHistory(absl::nullopt).size());
EXPECT_EQ(notification_count + 1,
GetHistory(RequestType::kNotifications).size());
GetPermissionActionsHistory()->RecordAction(
PermissionAction::GRANTED, RequestType::kGeolocation,
PermissionPromptDisposition::ANCHORED_BUBBLE);
EXPECT_EQ(general_count + 2, GetHistory(absl::nullopt).size());
EXPECT_EQ(notification_count + 1,
GetHistory(RequestType::kNotifications).size());
}
TEST_F(PermissionActionHistoryTest, ClearHistory) {
struct {
base::Time begin;
base::Time end;
size_t generic_count;
size_t notifications_count;
} kTests[] = {
// Misc and baseline tests cases.
{base::Time(), base::Time::Max(), 0, 0},
{base::Time(), base::Time::Now(), 1, 1},
{base::Time(), base::Time::Now() + base::Microseconds(1), 0, 0},
// Test cases specifying only the upper bound.
{base::Time(), base::Time::Now() - base::Days(1), 4, 2},
{base::Time(), base::Time::Now() - base::Days(2), 6, 3},
{base::Time(), base::Time::Now() - base::Days(3), 9, 6},
// Test cases specifying only the lower bound.
{base::Time::Now() - base::Days(3), base::Time::Max(), 0, 0},
{base::Time::Now() - base::Days(2), base::Time::Max(), 3, 3},
{base::Time::Now() - base::Days(1), base::Time::Max(), 5, 4},
{base::Time::Now(), base::Time::Max(), 8, 5},
// Test cases with both bounds.
{base::Time::Now() - base::Days(3),
base::Time::Now() + base::Microseconds(1), 0, 0},
{base::Time::Now() - base::Days(3), base::Time::Now(), 1, 1},
{base::Time::Now() - base::Days(3), base::Time::Now() - base::Days(1), 4,
2},
{base::Time::Now() - base::Days(3), base::Time::Now() - base::Days(2), 6,
3},
{base::Time::Now() - base::Days(3), base::Time::Now() - base::Days(3), 9,
6},
{base::Time::Now() - base::Days(2),
base::Time::Now() + base::Microseconds(1), 3, 3},
{base::Time::Now() - base::Days(2), base::Time::Now(), 4, 4},
{base::Time::Now() - base::Days(2), base::Time::Now() - base::Days(1), 7,
5},
{base::Time::Now() - base::Days(2), base::Time::Now() - base::Days(2), 9,
6},
{base::Time::Now() - base::Days(1),
base::Time::Now() + base::Microseconds(1), 5, 4},
{base::Time::Now() - base::Days(1), base::Time::Now(), 6, 5},
{base::Time::Now() - base::Days(1), base::Time::Now() - base::Days(1), 9,
6},
{base::Time::Now(), base::Time::Now() + base::Microseconds(1), 8, 5},
{base::Time::Now(), base::Time::Now(), 9, 6},
};
// We need to account for much we have already advanced the time for each test
// case and so we keep track of how much we need to offset the initial test
// values.
base::TimeDelta current_offset;
for (auto& test : kTests) {
test.begin += current_offset;
test.end += current_offset;
GetPermissionActionsHistory()->ClearHistory(test.begin, test.end);
EXPECT_EQ(test.generic_count, GetHistory(absl::nullopt).size());
EXPECT_EQ(test.notifications_count,
GetHistory(RequestType::kNotifications).size());
// Clean up for next test and update offset.
base::Time last_now = base::Time::Now();
GetPermissionActionsHistory()->ClearHistory(base::Time(),
base::Time::Max());
RecordSetUpActions();
current_offset += base::Time::Now() - last_now;
}
}
TEST_F(PermissionActionHistoryTest, EntryFilterTest) {
auto loud_entries =
GetHistory(absl::nullopt,
PermissionActionsHistory::EntryFilter::WANT_LOUD_PROMPTS_ONLY);
EXPECT_EQ(4u, loud_entries.size());
auto quiet_entries = GetHistory(
absl::nullopt, PermissionActionsHistory::PermissionActionsHistory::
EntryFilter::WANT_QUIET_PROMPTS_ONLY);
EXPECT_EQ(2u, quiet_entries.size());
auto all_entries = GetHistory(
absl::nullopt, PermissionActionsHistory::EntryFilter::WANT_ALL_PROMPTS);
EXPECT_EQ(9u, all_entries.size());
auto quiet_entries_in_last_two_days =
GetPermissionActionsHistory()->GetHistory(
base::Time::Now() - base::Days(2),
PermissionActionsHistory::EntryFilter::WANT_QUIET_PROMPTS_ONLY);
EXPECT_EQ(2u, quiet_entries_in_last_two_days.size());
}
} // namespace permissions