summaryrefslogtreecommitdiff
path: root/chromium/components/blocklist
diff options
context:
space:
mode:
Diffstat (limited to 'chromium/components/blocklist')
-rw-r--r--chromium/components/blocklist/OWNERS3
-rw-r--r--chromium/components/blocklist/README.md58
-rw-r--r--chromium/components/blocklist/opt_out_blocklist/BUILD.gn34
-rw-r--r--chromium/components/blocklist/opt_out_blocklist/opt_out_blocklist.cc221
-rw-r--r--chromium/components/blocklist/opt_out_blocklist/opt_out_blocklist.h183
-rw-r--r--chromium/components/blocklist/opt_out_blocklist/opt_out_blocklist_data.cc173
-rw-r--r--chromium/components/blocklist/opt_out_blocklist/opt_out_blocklist_data.h176
-rw-r--r--chromium/components/blocklist/opt_out_blocklist/opt_out_blocklist_delegate.h41
-rw-r--r--chromium/components/blocklist/opt_out_blocklist/opt_out_blocklist_item.cc74
-rw-r--r--chromium/components/blocklist/opt_out_blocklist/opt_out_blocklist_item.h99
-rw-r--r--chromium/components/blocklist/opt_out_blocklist/opt_out_blocklist_item_unittest.cc80
-rw-r--r--chromium/components/blocklist/opt_out_blocklist/opt_out_blocklist_unittest.cc1194
-rw-r--r--chromium/components/blocklist/opt_out_blocklist/opt_out_store.h49
-rw-r--r--chromium/components/blocklist/opt_out_blocklist/sql/BUILD.gn32
-rw-r--r--chromium/components/blocklist/opt_out_blocklist/sql/DEPS3
-rw-r--r--chromium/components/blocklist/opt_out_blocklist/sql/opt_out_store_sql.cc410
-rw-r--r--chromium/components/blocklist/opt_out_blocklist/sql/opt_out_store_sql.h68
-rw-r--r--chromium/components/blocklist/opt_out_blocklist/sql/opt_out_store_sql_unittest.cc310
18 files changed, 3208 insertions, 0 deletions
diff --git a/chromium/components/blocklist/OWNERS b/chromium/components/blocklist/OWNERS
new file mode 100644
index 00000000000..75baba8b67f
--- /dev/null
+++ b/chromium/components/blocklist/OWNERS
@@ -0,0 +1,3 @@
+file://components/data_reduction_proxy/OWNERS
+
+# COMPONENT: Blink>Previews \ No newline at end of file
diff --git a/chromium/components/blocklist/README.md b/chromium/components/blocklist/README.md
new file mode 100644
index 00000000000..5d354e7b985
--- /dev/null
+++ b/chromium/components/blocklist/README.md
@@ -0,0 +1,58 @@
+# Blocklist component #
+
+The goal of the blocklist component is to provide various blocklists that allow
+different policies for features to consume. Currently, the only implemented
+blocklist is the opt out blocklist.
+
+## Opt out blocklist ##
+The opt out blocklist makes decisions based on user history actions. Each user
+action is evaluated based on action type, time of the evaluation, host name of
+the action (can be any string representation), and previous action history.
+
+### Expected feature behavior ###
+When a feature action is allowed, the feature may perform said action. After
+performing the action, the user interaction should be determined to be an opt
+out (the user did not like the action) or a non-opt out (the user was not
+opposed to the action). The action, type, host name, and whether it was an opt
+out should be reported back to the blocklist to build user action history.
+
+For example, a feature may wish to show an InfoBar (or different types of
+InfoBars) displaying information about the page a user is on. After querying the
+opt out blocklist for action eligibility, an InfoBar may be allowed to be shown.
+If it is shown, the user may interact with it in a number of ways. If the user
+dismisses the InfoBar, that could be considered an opt out; if the user does
+not dismiss the InfoBar that could be considered a non-opt out. All of the
+information related to that action should be reported to the blocklist.
+
+### Supported evaluation policies ###
+In general, policies follow a specific form: the most recent _n_ actions are
+evaluated, and if _t_ or more of them are opt outs the action will not be
+allowed for a specified duration, _d_. For each policy, the feature specifies
+whether the policy is enabled, and, if it is, the feature specifies _n_
+(history), _t_ (threshold), and _d_ (duration) for each policy.
+
+* Session policy: This policy only applies across all types and host names, but
+is limited to actions that happened within the current session. The beginning of
+a session is defined as the creation of the blocklist object or when the
+blocklist is cleared (see below for details on clearing the blocklist).
+
+* Persistent policy: This policy applies across all sessions, types and host
+names.
+
+* Host policy: This policy applies across all session and types, but keeps a
+separate history for each host names. This rule allows specific host names to be
+prevented from having an action performed for the specific user. When this
+policy is enabled, the feature specifies a number of hosts that are stored in
+memory (to limit memory footprint, query time, etc.)
+
+* Type policy: This policy applies across all session and host names, but keeps
+a separate history for each type. This rule allows specific types to be
+prevented from having an action performed for the specific user. The feature
+specifies a set of enabled types and versions for each type. This allows
+removing past versions of types to be removed from the backing store.
+
+### Clearing the blocklist ###
+Because many actions should be cleared when user clears history, the opt out
+blocklist allows clearing history in certain time ranges. All entries are
+cleared for the specified time range, and the data in memory is repopulated
+from the backing store.
diff --git a/chromium/components/blocklist/opt_out_blocklist/BUILD.gn b/chromium/components/blocklist/opt_out_blocklist/BUILD.gn
new file mode 100644
index 00000000000..9b8f3d38182
--- /dev/null
+++ b/chromium/components/blocklist/opt_out_blocklist/BUILD.gn
@@ -0,0 +1,34 @@
+# Copyright 2018 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.
+
+static_library("opt_out_blocklist") {
+ sources = [
+ "opt_out_blocklist.cc",
+ "opt_out_blocklist.h",
+ "opt_out_blocklist_data.cc",
+ "opt_out_blocklist_data.h",
+ "opt_out_blocklist_delegate.h",
+ "opt_out_blocklist_item.cc",
+ "opt_out_blocklist_item.h",
+ "opt_out_store.h",
+ ]
+
+ deps = [ "//base" ]
+}
+
+source_set("unit_tests") {
+ testonly = true
+ sources = [
+ "opt_out_blocklist_item_unittest.cc",
+ "opt_out_blocklist_unittest.cc",
+ ]
+
+ deps = [
+ ":opt_out_blocklist",
+ "//base",
+ "//base/test:test_support",
+ "//testing/gmock",
+ "//testing/gtest",
+ ]
+}
diff --git a/chromium/components/blocklist/opt_out_blocklist/opt_out_blocklist.cc b/chromium/components/blocklist/opt_out_blocklist/opt_out_blocklist.cc
new file mode 100644
index 00000000000..8a451845af9
--- /dev/null
+++ b/chromium/components/blocklist/opt_out_blocklist/opt_out_blocklist.cc
@@ -0,0 +1,221 @@
+// Copyright 2018 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/blocklist/opt_out_blocklist/opt_out_blocklist.h"
+
+#include "base/bind.h"
+#include "base/memory/ptr_util.h"
+#include "base/metrics/histogram.h"
+#include "base/optional.h"
+#include "base/strings/stringprintf.h"
+#include "base/time/clock.h"
+#include "components/blocklist/opt_out_blocklist/opt_out_blocklist_delegate.h"
+#include "components/blocklist/opt_out_blocklist/opt_out_blocklist_item.h"
+#include "components/blocklist/opt_out_blocklist/opt_out_store.h"
+
+namespace blocklist {
+
+OptOutBlocklist::OptOutBlocklist(std::unique_ptr<OptOutStore> opt_out_store,
+ base::Clock* clock,
+ OptOutBlocklistDelegate* blocklist_delegate)
+ : loaded_(false),
+ opt_out_store_(std::move(opt_out_store)),
+ clock_(clock),
+ blocklist_delegate_(blocklist_delegate) {
+ DCHECK(clock_);
+ DCHECK(blocklist_delegate_);
+}
+
+OptOutBlocklist::~OptOutBlocklist() = default;
+
+void OptOutBlocklist::Init() {
+ DCHECK(!loaded_);
+ DCHECK(!blocklist_data_);
+ base::TimeDelta duration;
+ size_t history = 0;
+ int threshold = 0;
+
+ std::unique_ptr<BlocklistData::Policy> session_policy;
+ if (ShouldUseSessionPolicy(&duration, &history, &threshold)) {
+ session_policy =
+ std::make_unique<BlocklistData::Policy>(duration, history, threshold);
+ }
+
+ std::unique_ptr<BlocklistData::Policy> persistent_policy;
+ if (ShouldUsePersistentPolicy(&duration, &history, &threshold)) {
+ persistent_policy =
+ std::make_unique<BlocklistData::Policy>(duration, history, threshold);
+ }
+
+ size_t max_hosts = 0;
+ std::unique_ptr<BlocklistData::Policy> host_policy;
+ if (ShouldUseHostPolicy(&duration, &history, &threshold, &max_hosts)) {
+ host_policy =
+ std::make_unique<BlocklistData::Policy>(duration, history, threshold);
+ }
+
+ std::unique_ptr<BlocklistData::Policy> type_policy;
+ if (ShouldUseTypePolicy(&duration, &history, &threshold)) {
+ type_policy =
+ std::make_unique<BlocklistData::Policy>(duration, history, threshold);
+ }
+
+ auto blocklist_data = std::make_unique<BlocklistData>(
+ std::move(session_policy), std::move(persistent_policy),
+ std::move(host_policy), std::move(type_policy), max_hosts,
+ GetAllowedTypes());
+
+ if (opt_out_store_) {
+ opt_out_store_->LoadBlockList(
+ std::move(blocklist_data),
+ base::BindOnce(&OptOutBlocklist::LoadBlockListDone,
+ weak_factory_.GetWeakPtr()));
+ } else {
+ LoadBlockListDone(std::move(blocklist_data));
+ }
+}
+
+base::Time OptOutBlocklist::AddEntry(const std::string& host_name,
+ bool opt_out,
+ int type) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+ base::Time now = clock_->Now();
+
+ // If the |blocklist_data| has been loaded from |opt_out_store_|, synchronous
+ // operations will be accurate. Otherwise, queue the task to run
+ // asynchronously.
+ if (loaded_) {
+ AddEntrySync(host_name, opt_out, type, now);
+ } else {
+ QueuePendingTask(base::BindOnce(&OptOutBlocklist::AddEntrySync,
+ base::Unretained(this), host_name, opt_out,
+ type, now));
+ }
+
+ return now;
+}
+
+void OptOutBlocklist::AddEntrySync(const std::string& host_name,
+ bool opt_out,
+ int type,
+ base::Time time) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ DCHECK(loaded_);
+
+ bool host_was_blocklisted =
+ blocklist_data_->IsHostBlocklisted(host_name, time);
+ bool user_was_blocklisted = blocklist_data_->IsUserOptedOutInGeneral(time);
+ blocklist_data_->AddEntry(host_name, opt_out, type, time, false);
+
+ if (!host_was_blocklisted &&
+ blocklist_data_->IsHostBlocklisted(host_name, time)) {
+ // Notify |blocklist_delegate_| about a new blocklisted host.
+ blocklist_delegate_->OnNewBlocklistedHost(host_name, time);
+ }
+
+ if (user_was_blocklisted != blocklist_data_->IsUserOptedOutInGeneral(time)) {
+ // Notify |blocklist_delegate_| about a new blocklisted host.
+ blocklist_delegate_->OnUserBlocklistedStatusChange(
+ blocklist_data_->IsUserOptedOutInGeneral(time));
+ }
+
+ if (!opt_out_store_)
+ return;
+ opt_out_store_->AddEntry(opt_out, host_name, type, time);
+}
+
+BlocklistReason OptOutBlocklist::IsLoadedAndAllowed(
+ const std::string& host_name,
+ int type,
+ bool ignore_long_term_block_list_rules,
+ std::vector<BlocklistReason>* passed_reasons) const {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+ if (!loaded_)
+ return BlocklistReason::kBlocklistNotLoaded;
+ passed_reasons->push_back(BlocklistReason::kBlocklistNotLoaded);
+
+ return blocklist_data_->IsAllowed(host_name, type,
+ ignore_long_term_block_list_rules,
+ clock_->Now(), passed_reasons);
+}
+
+void OptOutBlocklist::ClearBlockList(base::Time begin_time,
+ base::Time end_time) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ DCHECK_LE(begin_time, end_time);
+ // If the |blocklist_data| has been loaded from |opt_out_store_|,
+ // synchronous operations will be accurate. Otherwise, queue the task to run
+ // asynchronously.
+ if (loaded_) {
+ ClearBlockListSync(begin_time, end_time);
+ } else {
+ QueuePendingTask(base::BindOnce(&OptOutBlocklist::ClearBlockListSync,
+ base::Unretained(this), begin_time,
+ end_time));
+ }
+}
+
+void OptOutBlocklist::ClearBlockListSync(base::Time begin_time,
+ base::Time end_time) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ DCHECK(loaded_);
+ DCHECK_LE(begin_time, end_time);
+
+ // Clear the in-memory rules entirely.
+ blocklist_data_->ClearData();
+ loaded_ = false;
+
+ // Notify |blocklist_delegate_| that the blocklist is cleared.
+ blocklist_delegate_->OnBlocklistCleared(clock_->Now());
+
+ // Delete relevant entries and reload the blocklist into memory.
+ if (opt_out_store_) {
+ opt_out_store_->ClearBlockList(begin_time, end_time);
+ opt_out_store_->LoadBlockList(
+ std::move(blocklist_data_),
+ base::BindOnce(&OptOutBlocklist::LoadBlockListDone,
+ weak_factory_.GetWeakPtr()));
+ } else {
+ LoadBlockListDone(std::move(blocklist_data_));
+ }
+}
+
+void OptOutBlocklist::QueuePendingTask(base::OnceClosure callback) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ DCHECK(!loaded_);
+ DCHECK(!callback.is_null());
+ pending_callbacks_.push(std::move(callback));
+}
+
+void OptOutBlocklist::LoadBlockListDone(
+ std::unique_ptr<BlocklistData> blocklist_data) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ DCHECK(blocklist_data);
+ DCHECK(!loaded_);
+ DCHECK(!blocklist_data_);
+ loaded_ = true;
+ blocklist_data_ = std::move(blocklist_data);
+
+ // Notify |blocklist_delegate_| on current user blocklisted status.
+ blocklist_delegate_->OnUserBlocklistedStatusChange(
+ blocklist_data_->IsUserOptedOutInGeneral(clock_->Now()));
+
+ // Notify the |blocklist_delegate_| on historical blocklisted hosts.
+ for (const auto& entry : blocklist_data_->block_list_item_host_map()) {
+ if (blocklist_data_->IsHostBlocklisted(entry.first, clock_->Now())) {
+ blocklist_delegate_->OnNewBlocklistedHost(
+ entry.first, entry.second.most_recent_opt_out_time().value());
+ }
+ }
+
+ // Run all pending tasks. |loaded_| may change if ClearBlockList is queued.
+ while (pending_callbacks_.size() > 0 && loaded_) {
+ std::move(pending_callbacks_.front()).Run();
+ pending_callbacks_.pop();
+ }
+}
+
+} // namespace blocklist
diff --git a/chromium/components/blocklist/opt_out_blocklist/opt_out_blocklist.h b/chromium/components/blocklist/opt_out_blocklist/opt_out_blocklist.h
new file mode 100644
index 00000000000..ece381483fe
--- /dev/null
+++ b/chromium/components/blocklist/opt_out_blocklist/opt_out_blocklist.h
@@ -0,0 +1,183 @@
+// Copyright 2018 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.
+
+#ifndef COMPONENTS_BLOCKLIST_OPT_OUT_BLOCKLIST_OPT_OUT_BLOCKLIST_H_
+#define COMPONENTS_BLOCKLIST_OPT_OUT_BLOCKLIST_OPT_OUT_BLOCKLIST_H_
+
+#include <stdint.h>
+
+#include <map>
+#include <memory>
+#include <string>
+#include <vector>
+
+#include "base/callback.h"
+#include "base/containers/queue.h"
+#include "base/macros.h"
+#include "base/memory/weak_ptr.h"
+#include "base/optional.h"
+#include "base/sequence_checker.h"
+#include "base/time/time.h"
+#include "components/blocklist/opt_out_blocklist/opt_out_blocklist_data.h"
+
+namespace base {
+class Clock;
+}
+
+namespace blocklist {
+
+class BlocklistData;
+class OptOutBlocklistDelegate;
+class OptOutStore;
+
+class OptOutBlocklist {
+ public:
+ // |opt_out_store| is the backing store to retrieve and store blocklist
+ // information, and can be null. When |opt_out_store| is null, the in-memory
+ // data will be immediately loaded to empty. If |opt_out_store| is non-null,
+ // it will be used to load the in-memory map asynchronously.
+ // |blocklist_delegate| is a single object listening for blocklist events, and
+ // it is guaranteed to outlive the life time of |this|.
+ OptOutBlocklist(std::unique_ptr<OptOutStore> opt_out_store,
+ base::Clock* clock,
+ OptOutBlocklistDelegate* blocklist_delegate);
+ virtual ~OptOutBlocklist();
+
+ // Creates the BlocklistData that backs the blocklist.
+ void Init();
+
+ // Asynchronously deletes all entries in the in-memory blocklist. Informs
+ // the backing store to delete entries between |begin_time| and |end_time|,
+ // and reloads entries into memory from the backing store. If the embedder
+ // passed in a null store, resets all history in the in-memory blocklist.
+ void ClearBlockList(base::Time begin_time, base::Time end_time);
+
+ // Asynchronously adds a new navigation to to the in-memory blocklist and
+ // backing store. |opt_out| is whether the user opted out of the action. If
+ // the in memory map has reached the max number of hosts allowed, and
+ // |host_name| is a new host, a host will be evicted based on recency of the
+ // hosts most recent opt out. It returns the time used for recording the
+ // moment when the navigation is added for logging.
+ base::Time AddEntry(const std::string& host_name, bool opt_out, int type);
+
+ // Synchronously determines if the action should be allowed for |host_name|
+ // and |type|. Returns the reason the blocklist disallowed the action, or
+ // kAllowed if the action is allowed. Record checked reasons in
+ // |passed_reasons|. |ignore_long_term_block_list_rules| will cause session,
+ // type, and host rules, but the session rule will still be queried.
+ BlocklistReason IsLoadedAndAllowed(
+ const std::string& host_name,
+ int type,
+ bool ignore_long_term_block_list_rules,
+ std::vector<BlocklistReason>* passed_reasons) const;
+
+ protected:
+ // Whether the session rule should be enabled. |duration| specifies how long a
+ // user remains blocklisted. |history| specifies how many entries should be
+ // evaluated; |threshold| specifies how many opt outs would cause
+ // blocklisting. I.e., the most recent |history| are looked at and if
+ // |threshold| (or more) of them are opt outs, the user is considered
+ // blocklisted unless the most recent opt out was longer than |duration| ago.
+ // This rule only considers entries within this session (it does not use the
+ // data that was persisted in previous sessions). When the blocklist is
+ // cleared, this rule is reset as if it were a new session. Queried in Init().
+ virtual bool ShouldUseSessionPolicy(base::TimeDelta* duration,
+ size_t* history,
+ int* threshold) const = 0;
+
+ // Whether the persistent rule should be enabled. |duration| specifies how
+ // long a user remains blocklisted. |history| specifies how many entries
+ // should be evaluated; |threshold| specifies how many opt outs would cause
+ // blocklisting. I.e., the most recent |history| are looked at and if
+ // |threshold| (or more) of them are opt outs, the user is considered
+ // blocklisted unless the most recent opt out was longer than |duration| ago.
+ // Queried in Init().
+ virtual bool ShouldUsePersistentPolicy(base::TimeDelta* duration,
+ size_t* history,
+ int* threshold) const = 0;
+
+ // Whether the host rule should be enabled. |duration| specifies how long a
+ // host remains blocklisted. |history| specifies how many entries should be
+ // evaluated per host; |threshold| specifies how many opt outs would cause
+ // blocklisting. I.e., the most recent |history| entries per host are looked
+ // at and if |threshold| (or more) of them are opt outs, the host is
+ // considered blocklisted unless the most recent opt out was longer than
+ // |duration| ago. |max_hosts| will limit the number of hosts stored in this
+ // class when non-zero.
+ // Queried in Init().
+ virtual bool ShouldUseHostPolicy(base::TimeDelta* duration,
+ size_t* history,
+ int* threshold,
+ size_t* max_hosts) const = 0;
+
+ // Whether the type rule should be enabled. |duration| specifies how long a
+ // type remains blocklisted. |history| specifies how many entries should be
+ // evaluated per type; |threshold| specifies how many opt outs would cause
+ // blocklisting.
+ // I.e., the most recent |history| entries per type are looked at and if
+ // |threshold| (or more) of them are opt outs, the type is considered
+ // blocklisted unless the most recent opt out was longer than |duration| ago.
+ // Queried in Init().
+ virtual bool ShouldUseTypePolicy(base::TimeDelta* duration,
+ size_t* history,
+ int* threshold) const = 0;
+
+ // The allowed types and what version they are. Should be empty unless the
+ // caller will not be using the blocklist in the session. It is used to remove
+ // stale entries from the database and to DCHECK that other methods are not
+ // using disallowed types. Queried in Init().
+ virtual BlocklistData::AllowedTypesAndVersions GetAllowedTypes() const = 0;
+
+ private:
+ // Synchronous version of AddEntry method. |time| is the time
+ // stamp of when the navigation was determined to be an opt-out or non-opt
+ // out.
+ void AddEntrySync(const std::string& host_name,
+ bool opt_out,
+ int type,
+ base::Time time);
+
+ // Synchronous version of ClearBlockList method.
+ void ClearBlockListSync(base::Time begin_time, base::Time end_time);
+
+ // Callback passed to the backing store when loading block list information.
+ // Takes ownership of |blocklist_data|.
+ void LoadBlockListDone(std::unique_ptr<BlocklistData> blocklist_data);
+
+ // Called while waiting for the blocklist to be loaded from the backing
+ // store.
+ // Enqueues a task to run when when loading blocklist information has
+ // completed. Maintains the order that tasks were called in.
+ void QueuePendingTask(base::OnceClosure callback);
+
+ // An in-memory representation of the various rules of the blocklist. This is
+ // null while reading from the backing store.
+ std::unique_ptr<BlocklistData> blocklist_data_;
+
+ // Whether the blocklist is done being loaded from the backing store.
+ bool loaded_;
+
+ // The backing store of the blocklist information.
+ std::unique_ptr<OptOutStore> opt_out_store_;
+
+ // Callbacks to be run after loading information from the backing store has
+ // completed.
+ base::queue<base::OnceClosure> pending_callbacks_;
+
+ base::Clock* clock_;
+
+ // The delegate listening to this blocklist. |blocklist_delegate_| lifetime is
+ // guaranteed to outlive |this|.
+ OptOutBlocklistDelegate* blocklist_delegate_;
+
+ SEQUENCE_CHECKER(sequence_checker_);
+
+ base::WeakPtrFactory<OptOutBlocklist> weak_factory_{this};
+
+ DISALLOW_COPY_AND_ASSIGN(OptOutBlocklist);
+};
+
+} // namespace blocklist
+
+#endif // COMPONENTS_BLOCKLIST_OPT_OUT_BLOCKLIST_OPT_OUT_BLOCKLIST_H_
diff --git a/chromium/components/blocklist/opt_out_blocklist/opt_out_blocklist_data.cc b/chromium/components/blocklist/opt_out_blocklist/opt_out_blocklist_data.cc
new file mode 100644
index 00000000000..711e409fd42
--- /dev/null
+++ b/chromium/components/blocklist/opt_out_blocklist/opt_out_blocklist_data.cc
@@ -0,0 +1,173 @@
+// Copyright 2018 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/blocklist/opt_out_blocklist/opt_out_blocklist_data.h"
+
+#include "base/memory/ptr_util.h"
+
+namespace blocklist {
+
+BlocklistData::BlocklistData(std::unique_ptr<Policy> session_policy,
+ std::unique_ptr<Policy> persistent_policy,
+ std::unique_ptr<Policy> host_policy,
+ std::unique_ptr<Policy> type_policy,
+ size_t max_hosts,
+ AllowedTypesAndVersions allowed_types)
+ : session_policy_(std::move(session_policy)),
+ persistent_policy_(std::move(persistent_policy)),
+ host_policy_(std::move(host_policy)),
+ max_hosts_(max_hosts),
+ type_policy_(std::move(type_policy)),
+ allowed_types_(std::move(allowed_types)) {
+ DCHECK_GE(100u, max_hosts);
+}
+BlocklistData::~BlocklistData() = default;
+
+void BlocklistData::ClearData() {
+ session_block_list_item_.reset();
+ persistent_block_list_item_.reset();
+ block_list_item_host_map_.clear();
+ block_list_item_type_map_.clear();
+}
+
+void BlocklistData::AddEntry(const std::string& host_name,
+ bool opt_out,
+ int type,
+ base::Time time,
+ bool is_from_persistent_storage) {
+ // Add to the session based rule if it is enabled.
+ if (session_policy_ && !is_from_persistent_storage) {
+ if (!session_block_list_item_) {
+ session_block_list_item_ = std::make_unique<OptOutBlocklistItem>(
+ session_policy_->history, session_policy_->threshold,
+ session_policy_->duration);
+ }
+ session_block_list_item_->AddEntry(opt_out, time);
+ }
+
+ // Add to the persistent rule if it is enabled.
+ if (persistent_policy_) {
+ if (!persistent_block_list_item_) {
+ persistent_block_list_item_ = std::make_unique<OptOutBlocklistItem>(
+ persistent_policy_->history, persistent_policy_->threshold,
+ persistent_policy_->duration);
+ }
+ persistent_block_list_item_->AddEntry(opt_out, time);
+ }
+
+ // Add to the host rule if it is enabled. Remove hosts if there are more than
+ // |max_hosts_| in the map.
+ if (host_policy_) {
+ auto item = block_list_item_host_map_.find(host_name);
+ if (item == block_list_item_host_map_.end()) {
+ auto value = block_list_item_host_map_.emplace(
+ std::piecewise_construct, std::forward_as_tuple(host_name),
+ std::forward_as_tuple(host_policy_->history, host_policy_->threshold,
+ host_policy_->duration));
+ DCHECK(value.second);
+ item = value.first;
+ }
+ item->second.AddEntry(opt_out, time);
+ if (max_hosts_ > 0 && block_list_item_host_map_.size() > max_hosts_)
+ EvictOldestHost();
+ }
+
+ if (type_policy_) {
+ auto item = block_list_item_type_map_.find(type);
+ if (item == block_list_item_type_map_.end()) {
+ auto value = block_list_item_type_map_.emplace(
+ std::piecewise_construct, std::forward_as_tuple(type),
+ std::forward_as_tuple(type_policy_->history, type_policy_->threshold,
+ type_policy_->duration));
+ DCHECK(value.second);
+ item = value.first;
+ }
+ item->second.AddEntry(opt_out, time);
+ }
+}
+
+BlocklistReason BlocklistData::IsAllowed(
+ const std::string& host_name,
+ int type,
+ bool ignore_long_term_block_list_rules,
+ base::Time time,
+ std::vector<BlocklistReason>* passed_reasons) const {
+ // Check the session rule.
+ if (session_policy_) {
+ if (session_block_list_item_ &&
+ session_block_list_item_->IsBlockListed(time)) {
+ return BlocklistReason::kUserOptedOutInSession;
+ }
+ passed_reasons->push_back(BlocklistReason::kUserOptedOutInSession);
+ }
+
+ // Check whether the persistent rules should be checked this time.
+ if (ignore_long_term_block_list_rules)
+ return BlocklistReason::kAllowed;
+
+ // Check the persistent rule.
+ if (persistent_policy_) {
+ if (IsUserOptedOutInGeneral(time)) {
+ return BlocklistReason::kUserOptedOutInGeneral;
+ }
+ passed_reasons->push_back(BlocklistReason::kUserOptedOutInGeneral);
+ }
+
+ // Check the host rule.
+ if (host_policy_) {
+ if (IsHostBlocklisted(host_name, time))
+ return BlocklistReason::kUserOptedOutOfHost;
+ passed_reasons->push_back(BlocklistReason::kUserOptedOutOfHost);
+ }
+
+ // Only allowed types should be recorded.
+ DCHECK(allowed_types_.find(type) != allowed_types_.end());
+
+ // Check the type rule.
+ if (type_policy_) {
+ auto item = block_list_item_type_map_.find(type);
+ if (item != block_list_item_type_map_.end() &&
+ item->second.IsBlockListed(time)) {
+ return BlocklistReason::kUserOptedOutOfType;
+ }
+ passed_reasons->push_back(BlocklistReason::kUserOptedOutOfType);
+ }
+
+ return BlocklistReason::kAllowed;
+}
+
+void BlocklistData::EvictOldestHost() {
+ DCHECK_LT(max_hosts_, block_list_item_host_map_.size());
+ base::Optional<base::Time> oldest_opt_out;
+ std::string key_to_delete;
+ for (auto& item : block_list_item_host_map_) {
+ base::Optional<base::Time> most_recent_opt_out =
+ item.second.most_recent_opt_out_time();
+ if (!most_recent_opt_out) {
+ // If there is no opt out time, this is a good choice to evict.
+ key_to_delete = item.first;
+ break;
+ }
+ if (!oldest_opt_out ||
+ most_recent_opt_out.value() < oldest_opt_out.value()) {
+ oldest_opt_out = most_recent_opt_out.value();
+ key_to_delete = item.first;
+ }
+ }
+ block_list_item_host_map_.erase(key_to_delete);
+}
+
+bool BlocklistData::IsHostBlocklisted(const std::string& host_name,
+ base::Time time) const {
+ auto item = block_list_item_host_map_.find(host_name);
+ return item != block_list_item_host_map_.end() &&
+ item->second.IsBlockListed(time);
+}
+
+bool BlocklistData::IsUserOptedOutInGeneral(base::Time time) const {
+ return persistent_block_list_item_ &&
+ persistent_block_list_item_->IsBlockListed(time);
+}
+
+} // namespace blocklist
diff --git a/chromium/components/blocklist/opt_out_blocklist/opt_out_blocklist_data.h b/chromium/components/blocklist/opt_out_blocklist/opt_out_blocklist_data.h
new file mode 100644
index 00000000000..659d1e26c74
--- /dev/null
+++ b/chromium/components/blocklist/opt_out_blocklist/opt_out_blocklist_data.h
@@ -0,0 +1,176 @@
+// Copyright 2018 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.
+
+#ifndef COMPONENTS_BLOCKLIST_OPT_OUT_BLOCKLIST_OPT_OUT_BLOCKLIST_DATA_H_
+#define COMPONENTS_BLOCKLIST_OPT_OUT_BLOCKLIST_OPT_OUT_BLOCKLIST_DATA_H_
+
+#include <stdint.h>
+
+#include <map>
+#include <memory>
+#include <set>
+#include <string>
+
+#include "base/macros.h"
+#include "base/optional.h"
+#include "base/time/time.h"
+#include "components/blocklist/opt_out_blocklist/opt_out_blocklist_item.h"
+
+namespace blocklist {
+
+// The various reasons the Blocklist may tell that the user is blocklisted.
+// This should remain synchronized with enums.xml
+enum class BlocklistReason {
+ // The blocklist may not be loaded very early in the session or when the user
+ // has cleared the blocklist history (usually by clearing their browsing
+ // history).
+ kBlocklistNotLoaded = 0,
+ kUserOptedOutInSession = 1,
+ kUserOptedOutInGeneral = 2,
+ kUserOptedOutOfHost = 3,
+ kUserOptedOutOfType = 4,
+ kAllowed = 5,
+ kMaxValue = kAllowed,
+
+};
+
+// This class describes all of the data used to determine whether an action is
+// allowed based on four possible rules: Session: if the user has opted out
+// of j of the last k entries this session, the action will be blocklisted for a
+// set duration. Persistent: if the user has opted out of j of the last k
+// entries, the action will be blocklisted for a set duration. Host: if the user
+// has opted out of threshold of the last history entries for a specific host,
+// the action will be blocklisted for a set duration. Type: if the user has
+// opted out of j of the last k entries for a specific type, the action will be
+// blocklisted for a set duration. This is the in-memory version of the block
+// list policy. This object is moved from the embedder thread to a background
+// thread, It is not safe to access concurrently on two threads.
+class BlocklistData {
+ public:
+ // A struct describing the general blocklisting pattern used by all of the
+ // blocklisting rules.
+ // The most recent |history| entries are looked at and if |threshold| (or
+ // more) of them are opt outs, new actions are considered blocklisted unless
+ // the most recent opt out was longer than |duration| ago.
+ struct Policy {
+ Policy(base::TimeDelta duration, size_t history, int threshold)
+ : duration(duration), history(history), threshold(threshold) {}
+
+ ~Policy() = default;
+
+ // Specifies how long the blocklisting rule lasts after the most recent opt
+ // out.
+ const base::TimeDelta duration;
+ // Amount of entries evaluated for the rule.
+ const size_t history;
+ // The number of opt outs that will trigger blocklisting for the rule.
+ const int threshold;
+ };
+
+ // A map of types that are allowed to be used in the blocklist as well as the
+ // version that those types are in. Versioning allows removals from persistent
+ // memory at session start.
+ using AllowedTypesAndVersions = std::map<int, int>;
+
+ // |session_policy| if non-null, is the policy that is not persisted across
+ // sessions and is not specific to host or type. |persistent_policy| if
+ // non-null, is the policy that is persisted across sessions and is not
+ // specific to host or type. |host_policy| if non-null, is the policy that is
+ // persisted across sessions applies at the per-host level. |host_policy| if
+ // non-null, is the policy that is persisted across sessions and applies at
+ // the per-type level. |max_hosts| is the maximum number of hosts stored in
+ // memory. |allowed_types| contains the action types that are allowed in the
+ // session and their corresponding versions. Conversioning is used to clear
+ // stale data from the persistent storage.
+ BlocklistData(std::unique_ptr<Policy> session_policy,
+ std::unique_ptr<Policy> persistent_policy,
+ std::unique_ptr<Policy> host_policy,
+ std::unique_ptr<Policy> type_policy,
+ size_t max_hosts,
+ AllowedTypesAndVersions allowed_types);
+ ~BlocklistData();
+
+ // Adds a new entry for all rules to use when evaluating blocklisting state.
+ // |is_from_persistent_storage| is used to delineate between data added from
+ // this session, and previous sessions.
+ void AddEntry(const std::string& host_name,
+ bool opt_out,
+ int type,
+ base::Time time,
+ bool is_from_persistent_storage);
+
+ // Whether the user is opted out when considering all enabled rules. if
+ // |ignore_long_term_block_list_rules| is true, this will only check the
+ // session rule. For every reason that is checked, but does not trigger
+ // blocklisting, a new reason will be appended to the end |passed_reasons|.
+ // |time| is the time that decision should be evaluated at (usually now).
+ BlocklistReason IsAllowed(const std::string& host_name,
+ int type,
+ bool ignore_long_term_block_list_rules,
+ base::Time time,
+ std::vector<BlocklistReason>* passed_reasons) const;
+
+ // This clears all data in all rules.
+ void ClearData();
+
+ // The allowed types and what version they are. If it is non-empty, it is used
+ // to remove stale entries from the database and to DCHECK that other methods
+ // are not using disallowed types.
+ const AllowedTypesAndVersions& allowed_types() const {
+ return allowed_types_;
+ }
+
+ // Whether the specific |host_name| is blocklisted based only on the host
+ // rule.
+ bool IsHostBlocklisted(const std::string& host_name, base::Time time) const;
+
+ // Whether the user is opted out based solely on the persistent blocklist
+ // rule.
+ bool IsUserOptedOutInGeneral(base::Time time) const;
+
+ // Exposed for logging purposes only.
+ const std::map<std::string, OptOutBlocklistItem>& block_list_item_host_map()
+ const {
+ return block_list_item_host_map_;
+ }
+
+ private:
+ // Removes the oldest (or safest) host item from |block_list_item_host_map_|.
+ // Oldest is defined by most recent opt out time, and safest is defined as an
+ // item with no opt outs.
+ void EvictOldestHost();
+
+ // The session rule policy. If non-null the session rule is enforced.
+ std::unique_ptr<Policy> session_policy_;
+ // The session rule history.
+ std::unique_ptr<OptOutBlocklistItem> session_block_list_item_;
+
+ // The persistent rule policy. If non-null the persistent rule is enforced.
+ std::unique_ptr<Policy> persistent_policy_;
+ // The persistent rule history.
+ std::unique_ptr<OptOutBlocklistItem> persistent_block_list_item_;
+
+ // The host rule policy. If non-null the host rule is enforced.
+ std::unique_ptr<Policy> host_policy_;
+ // The maximum number of hosts allowed in the host blocklist.
+ size_t max_hosts_;
+ // The host rule history. Each host is stored as a separate blocklist history.
+ std::map<std::string, OptOutBlocklistItem> block_list_item_host_map_;
+
+ // The type rule policy. If non-null the type rule is enforced.
+ std::unique_ptr<Policy> type_policy_;
+ // The type rule history. Each type is stored as a separate blocklist history.
+ std::map<int, OptOutBlocklistItem> block_list_item_type_map_;
+
+ // The allowed types and what version they are. If it is non-empty, it is used
+ // to remove stale entries from the database and to DCHECK that other methods
+ // are not using disallowed types.
+ AllowedTypesAndVersions allowed_types_;
+
+ DISALLOW_COPY_AND_ASSIGN(BlocklistData);
+};
+
+} // namespace blocklist
+
+#endif // COMPONENTS_BLOCKLIST_OPT_OUT_BLOCKLIST_OPT_OUT_BLOCKLIST_DATA_H_
diff --git a/chromium/components/blocklist/opt_out_blocklist/opt_out_blocklist_delegate.h b/chromium/components/blocklist/opt_out_blocklist/opt_out_blocklist_delegate.h
new file mode 100644
index 00000000000..14b767aa3a3
--- /dev/null
+++ b/chromium/components/blocklist/opt_out_blocklist/opt_out_blocklist_delegate.h
@@ -0,0 +1,41 @@
+// Copyright 2017 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.
+
+#ifndef COMPONENTS_BLOCKLIST_OPT_OUT_BLOCKLIST_OPT_OUT_BLOCKLIST_DELEGATE_H_
+#define COMPONENTS_BLOCKLIST_OPT_OUT_BLOCKLIST_OPT_OUT_BLOCKLIST_DELEGATE_H_
+
+#include <string>
+
+#include "base/macros.h"
+#include "base/time/time.h"
+
+namespace blocklist {
+
+// An interface for a delegate to the opt out blocklist. This interface is for
+// responding to events occurring in the opt out blocklist (e.g. New blocklisted
+// host and user is blocklisted).
+class OptOutBlocklistDelegate {
+ public:
+ OptOutBlocklistDelegate() = default;
+ virtual ~OptOutBlocklistDelegate() = default;
+
+ // Notifies |this| that |host| has been blocklisted at |time|. This method is
+ // guaranteed to be called when a previously allowlisted host is now
+ // blocklisted.
+ virtual void OnNewBlocklistedHost(const std::string& host, base::Time time) {}
+
+ // Notifies |this| that the user blocklisted has changed, and it is
+ // guaranteed to be called when the user blocklisted status is changed.
+ //
+ // TODO(crbug/1099030): Update the comment and interface to support providing
+ // a signal that the blocklist is loaded and available.
+ virtual void OnUserBlocklistedStatusChange(bool blocklisted) {}
+
+ // Notifies |this| that the blocklist is cleared at |time|.
+ virtual void OnBlocklistCleared(base::Time time) {}
+};
+
+} // namespace blocklist
+
+#endif // COMPONENTS_BLOCKLIST_OPT_OUT_BLOCKLIST_OPT_OUT_BLOCKLIST_DELEGATE_H_
diff --git a/chromium/components/blocklist/opt_out_blocklist/opt_out_blocklist_item.cc b/chromium/components/blocklist/opt_out_blocklist/opt_out_blocklist_item.cc
new file mode 100644
index 00000000000..e8645277147
--- /dev/null
+++ b/chromium/components/blocklist/opt_out_blocklist/opt_out_blocklist_item.cc
@@ -0,0 +1,74 @@
+// Copyright 2016 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/blocklist/opt_out_blocklist/opt_out_blocklist_item.h"
+
+#include <algorithm>
+#include <tuple>
+
+#include "components/blocklist/opt_out_blocklist/opt_out_store.h"
+
+namespace blocklist {
+
+OptOutBlocklistItem::OptOutRecord::OptOutRecord(base::Time entry_time,
+ bool opt_out)
+ : entry_time_(entry_time), opt_out_(opt_out) {}
+
+OptOutBlocklistItem::OptOutRecord::~OptOutRecord() = default;
+
+OptOutBlocklistItem::OptOutRecord::OptOutRecord(OptOutRecord&&) noexcept =
+ default;
+OptOutBlocklistItem::OptOutRecord& OptOutBlocklistItem::OptOutRecord::operator=(
+ OptOutRecord&&) noexcept = default;
+
+bool OptOutBlocklistItem::OptOutRecord::operator<(
+ const OptOutRecord& other) const {
+ // Fresher entries are lower priority to evict, as are non-opt-outs.
+ return std::tie(entry_time_, opt_out_) >
+ std::tie(other.entry_time_, other.opt_out_);
+}
+
+OptOutBlocklistItem::OptOutBlocklistItem(size_t stored_history_length,
+ int opt_out_block_list_threshold,
+ base::TimeDelta block_list_duration)
+ : max_stored_history_length_(stored_history_length),
+ opt_out_block_list_threshold_(opt_out_block_list_threshold),
+ max_block_list_duration_(block_list_duration),
+ total_opt_out_(0) {}
+
+OptOutBlocklistItem::~OptOutBlocklistItem() = default;
+
+void OptOutBlocklistItem::AddEntry(bool opt_out, base::Time entry_time) {
+ DCHECK_LE(opt_out_records_.size(), max_stored_history_length_);
+
+ opt_out_records_.emplace(entry_time, opt_out);
+
+ if (opt_out && (!most_recent_opt_out_time_ ||
+ entry_time > most_recent_opt_out_time_.value())) {
+ most_recent_opt_out_time_ = entry_time;
+ }
+ total_opt_out_ += opt_out ? 1 : 0;
+
+ // Remove the oldest entry if the size exceeds the max history size.
+ if (opt_out_records_.size() > max_stored_history_length_) {
+ DCHECK_EQ(opt_out_records_.size(), max_stored_history_length_ + 1);
+ DCHECK_LE(opt_out_records_.top().entry_time(), entry_time);
+ total_opt_out_ -= opt_out_records_.top().opt_out() ? 1 : 0;
+ opt_out_records_.pop();
+ }
+ DCHECK_LE(opt_out_records_.size(), max_stored_history_length_);
+}
+
+bool OptOutBlocklistItem::IsBlockListed(base::Time now) const {
+ DCHECK_LE(opt_out_records_.size(), max_stored_history_length_);
+ return most_recent_opt_out_time_ &&
+ now - most_recent_opt_out_time_.value() < max_block_list_duration_ &&
+ total_opt_out_ >= opt_out_block_list_threshold_;
+}
+
+size_t OptOutBlocklistItem::OptOutRecordsSizeForTesting() const {
+ return opt_out_records_.size();
+}
+
+} // namespace blocklist
diff --git a/chromium/components/blocklist/opt_out_blocklist/opt_out_blocklist_item.h b/chromium/components/blocklist/opt_out_blocklist/opt_out_blocklist_item.h
new file mode 100644
index 00000000000..1ad6c01c1e5
--- /dev/null
+++ b/chromium/components/blocklist/opt_out_blocklist/opt_out_blocklist_item.h
@@ -0,0 +1,99 @@
+// Copyright 2016 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.
+
+#ifndef COMPONENTS_BLOCKLIST_OPT_OUT_BLOCKLIST_OPT_OUT_BLOCKLIST_ITEM_H_
+#define COMPONENTS_BLOCKLIST_OPT_OUT_BLOCKLIST_OPT_OUT_BLOCKLIST_ITEM_H_
+
+#include <stdint.h>
+
+#include <map>
+#include <memory>
+#include <queue>
+#include <string>
+
+#include "base/callback.h"
+#include "base/macros.h"
+#include "base/optional.h"
+#include "base/time/time.h"
+
+namespace blocklist {
+
+// Stores the recent block list history for a single host. Stores
+// |stored_history_length| of the most recent actions. To determine action
+// eligibility fewer than |opt_out_block_list_threshold| out of the past
+// |stored_history_length| navigations must be opt outs. |block_list_duration|
+// is the amount of time that elapses until the host is no longer on the block
+// list.
+class OptOutBlocklistItem {
+ public:
+ OptOutBlocklistItem(size_t stored_history_length,
+ int opt_out_block_list_threshold,
+ base::TimeDelta block_list_duration);
+
+ ~OptOutBlocklistItem();
+
+ // Adds a new navigation at the specified |entry_time|.
+ void AddEntry(bool opt_out, base::Time entry_time);
+
+ // Whether the action corresponding to |this| should be disallowed.
+ bool IsBlockListed(base::Time now) const;
+
+ base::Optional<base::Time> most_recent_opt_out_time() const {
+ return most_recent_opt_out_time_;
+ }
+
+ size_t OptOutRecordsSizeForTesting() const;
+
+ private:
+ // An action to |this| is represented by time and whether the action was an
+ // opt out.
+ class OptOutRecord {
+ public:
+ OptOutRecord(base::Time entry_time, bool opt_out);
+ ~OptOutRecord();
+ OptOutRecord(OptOutRecord&&) noexcept;
+ OptOutRecord& operator=(OptOutRecord&&) noexcept;
+
+ // Used to determine eviction priority.
+ bool operator<(const OptOutRecord& other) const;
+
+ // The time that the opt out state was determined.
+ base::Time entry_time() const { return entry_time_; }
+
+ // Whether the user opted out of the action.
+ bool opt_out() const { return opt_out_; }
+
+ private:
+ // The time that the opt out state was determined.
+ base::Time entry_time_;
+ // Whether the user opted out of the action.
+ bool opt_out_;
+
+ DISALLOW_COPY_AND_ASSIGN(OptOutRecord);
+ };
+
+ // The number of entries to store to determine action eligibility.
+ const size_t max_stored_history_length_;
+ // The number opt outs in recent history that will trigger blocklisting.
+ const int opt_out_block_list_threshold_;
+ // The amount of time to block list a domain after the most recent opt out.
+ const base::TimeDelta max_block_list_duration_;
+
+ // The |max_stored_history_length_| most recent action. Is maintained as a
+ // priority queue that has high priority for items that should be evicted
+ // (i.e., they are old).
+ std::priority_queue<OptOutRecord> opt_out_records_;
+
+ // Time of the most recent opt out.
+ base::Optional<base::Time> most_recent_opt_out_time_;
+
+ // The total number of opt outs currently in |opt_out_records_|.
+ int total_opt_out_;
+
+ DISALLOW_COPY_AND_ASSIGN(OptOutBlocklistItem);
+};
+
+} // namespace blocklist
+
+#endif // COMPONENTS_BLOCKLIST_OPT_OUT_BLOCKLIST_OPT_OUT_BLOCKLIST_ITEM_H_
diff --git a/chromium/components/blocklist/opt_out_blocklist/opt_out_blocklist_item_unittest.cc b/chromium/components/blocklist/opt_out_blocklist/opt_out_blocklist_item_unittest.cc
new file mode 100644
index 00000000000..c54bc22dfea
--- /dev/null
+++ b/chromium/components/blocklist/opt_out_blocklist/opt_out_blocklist_item_unittest.cc
@@ -0,0 +1,80 @@
+// Copyright 2016 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/blocklist/opt_out_blocklist/opt_out_blocklist_item.h"
+
+#include <memory>
+
+#include "base/optional.h"
+#include "base/time/time.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace {
+
+using OptOutBlocklistItemTest = testing::Test;
+
+} // namespace
+
+namespace blocklist {
+
+TEST_F(OptOutBlocklistItemTest, BlockListState) {
+ const int history = 4;
+ const int threshold = 2;
+ const base::TimeDelta max_blocklist_duration =
+ base::TimeDelta::FromSeconds(30);
+ const base::Time now = base::Time::UnixEpoch();
+ const base::TimeDelta delay_between_entries = base::TimeDelta::FromSeconds(1);
+ const base::Time later =
+ now + max_blocklist_duration + (delay_between_entries * 3);
+
+ OptOutBlocklistItem block_list_item(history, threshold,
+ max_blocklist_duration);
+
+ // Empty block list item should report that the host is allowed.
+ EXPECT_FALSE(block_list_item.IsBlockListed(now));
+ EXPECT_FALSE(block_list_item.IsBlockListed(later));
+
+ EXPECT_FALSE(block_list_item.most_recent_opt_out_time());
+ block_list_item.AddEntry(false, now);
+ EXPECT_FALSE(block_list_item.most_recent_opt_out_time());
+
+ block_list_item.AddEntry(true, now);
+ EXPECT_TRUE(block_list_item.most_recent_opt_out_time());
+ EXPECT_EQ(now, block_list_item.most_recent_opt_out_time().value());
+ // Block list item of size less that |threshold| should report that the host
+ // is allowed.
+ EXPECT_FALSE(block_list_item.IsBlockListed(now));
+ EXPECT_FALSE(block_list_item.IsBlockListed(later));
+
+ block_list_item.AddEntry(true, now + delay_between_entries);
+ // Block list item with |threshold| fresh entries should report the host as
+ // disallowed.
+ EXPECT_TRUE(block_list_item.IsBlockListed(now));
+ // Block list item with only entries from longer than |duration| ago should
+ // report the host is allowed.
+ EXPECT_FALSE(block_list_item.IsBlockListed(later));
+ block_list_item.AddEntry(true, later - (delay_between_entries * 2));
+ // Block list item with a fresh opt out and total number of opt outs larger
+ // than |threshold| should report the host is disallowed.
+ EXPECT_TRUE(block_list_item.IsBlockListed(later));
+
+ // The block list item should maintain entries based on time, so adding
+ // |history| entries should not push out newer entries.
+ block_list_item.AddEntry(true, later - delay_between_entries * 2);
+ block_list_item.AddEntry(false, later - delay_between_entries * 3);
+ block_list_item.AddEntry(false, later - delay_between_entries * 3);
+ block_list_item.AddEntry(false, later - delay_between_entries * 3);
+ block_list_item.AddEntry(false, later - delay_between_entries * 3);
+ EXPECT_TRUE(block_list_item.IsBlockListed(later));
+
+ // The block list item should maintain entries based on time, so adding
+ // |history| newer entries should push out older entries.
+ block_list_item.AddEntry(false, later - delay_between_entries * 1);
+ block_list_item.AddEntry(false, later - delay_between_entries * 1);
+ block_list_item.AddEntry(false, later - delay_between_entries * 1);
+ block_list_item.AddEntry(false, later - delay_between_entries * 1);
+ EXPECT_FALSE(block_list_item.IsBlockListed(later));
+}
+
+} // namespace blocklist
diff --git a/chromium/components/blocklist/opt_out_blocklist/opt_out_blocklist_unittest.cc b/chromium/components/blocklist/opt_out_blocklist/opt_out_blocklist_unittest.cc
new file mode 100644
index 00000000000..a5d5cee9cb8
--- /dev/null
+++ b/chromium/components/blocklist/opt_out_blocklist/opt_out_blocklist_unittest.cc
@@ -0,0 +1,1194 @@
+// Copyright 2018 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/blocklist/opt_out_blocklist/opt_out_blocklist.h"
+
+#include <algorithm>
+#include <map>
+#include <memory>
+#include <string>
+#include <unordered_map>
+#include <utility>
+#include <vector>
+
+#include "base/bind.h"
+#include "base/bind_helpers.h"
+#include "base/memory/ptr_util.h"
+#include "base/run_loop.h"
+#include "base/stl_util.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/test/metrics/histogram_tester.h"
+#include "base/test/simple_test_clock.h"
+#include "base/test/task_environment.h"
+#include "base/threading/thread_task_runner_handle.h"
+#include "base/time/time.h"
+#include "components/blocklist/opt_out_blocklist/opt_out_blocklist_delegate.h"
+#include "components/blocklist/opt_out_blocklist/opt_out_blocklist_item.h"
+#include "components/blocklist/opt_out_blocklist/opt_out_store.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace blocklist {
+
+namespace {
+
+const char kTestHost1[] = "testhost1.com";
+const char kTestHost2[] = "testhost2.com";
+
+// Mock class to test that OptOutBlocklist notifies the delegate with correct
+// events (e.g. New host blocklisted, user blocklisted, and blocklist cleared).
+class TestOptOutBlocklistDelegate : public OptOutBlocklistDelegate {
+ public:
+ TestOptOutBlocklistDelegate() : blocklist_cleared_time_(base::Time::Now()) {}
+
+ // OptOutBlocklistDelegate:
+ void OnNewBlocklistedHost(const std::string& host, base::Time time) override {
+ blocklisted_hosts_[host] = time;
+ }
+ void OnUserBlocklistedStatusChange(bool blocklisted) override {
+ user_blocklisted_ = blocklisted;
+ }
+ void OnBlocklistCleared(base::Time time) override {
+ blocklist_cleared_ = true;
+ blocklist_cleared_time_ = time;
+ }
+
+ // Gets the set of blocklisted hosts recorded.
+ const std::unordered_map<std::string, base::Time>& blocklisted_hosts() const {
+ return blocklisted_hosts_;
+ }
+
+ // Gets the state of user blocklisted status.
+ bool user_blocklisted() const { return user_blocklisted_; }
+
+ // Gets the state of blocklisted cleared status of |this| for testing.
+ bool blocklist_cleared() const { return blocklist_cleared_; }
+
+ // Gets the event time of blocklist is as cleared.
+ base::Time blocklist_cleared_time() const { return blocklist_cleared_time_; }
+
+ private:
+ // The user blocklisted status of |this| blocklist_delegate.
+ bool user_blocklisted_ = false;
+
+ // Check if the blocklist is notified as cleared on |this| blocklist_delegate.
+ bool blocklist_cleared_ = false;
+
+ // The time when blocklist is cleared.
+ base::Time blocklist_cleared_time_;
+
+ // |this| blocklist_delegate's collection of blocklisted hosts.
+ std::unordered_map<std::string, base::Time> blocklisted_hosts_;
+};
+
+class TestOptOutStore : public OptOutStore {
+ public:
+ TestOptOutStore() = default;
+ ~TestOptOutStore() override = default;
+
+ int clear_blocklist_count() { return clear_blocklist_count_; }
+
+ void SetBlocklistData(std::unique_ptr<BlocklistData> data) {
+ data_ = std::move(data);
+ }
+
+ private:
+ // OptOutStore implementation:
+ void AddEntry(bool opt_out,
+ const std::string& host_name,
+ int type,
+ base::Time now) override {}
+
+ void LoadBlockList(std::unique_ptr<BlocklistData> blocklist_data,
+ LoadBlockListCallback callback) override {
+ base::ThreadTaskRunnerHandle::Get()->PostTask(
+ FROM_HERE,
+ base::BindOnce(std::move(callback),
+ data_ ? std::move(data_) : std::move(blocklist_data)));
+ }
+
+ void ClearBlockList(base::Time begin_time, base::Time end_time) override {
+ ++clear_blocklist_count_;
+ }
+
+ int clear_blocklist_count_ = 0;
+
+ std::unique_ptr<BlocklistData> data_;
+};
+
+class TestOptOutBlocklist : public OptOutBlocklist {
+ public:
+ TestOptOutBlocklist(std::unique_ptr<OptOutStore> opt_out_store,
+ base::Clock* clock,
+ OptOutBlocklistDelegate* blocklist_delegate)
+ : OptOutBlocklist(std::move(opt_out_store), clock, blocklist_delegate) {}
+ ~TestOptOutBlocklist() override = default;
+
+ void SetSessionRule(std::unique_ptr<BlocklistData::Policy> policy) {
+ session_policy_ = std::move(policy);
+ }
+
+ void SetPersistentRule(std::unique_ptr<BlocklistData::Policy> policy) {
+ persistent_policy_ = std::move(policy);
+ }
+
+ void SetHostRule(std::unique_ptr<BlocklistData::Policy> policy,
+ size_t max_hosts) {
+ host_policy_ = std::move(policy);
+ max_hosts_ = max_hosts;
+ }
+
+ void SetTypeRule(std::unique_ptr<BlocklistData::Policy> policy) {
+ type_policy_ = std::move(policy);
+ }
+
+ void SetAllowedTypes(BlocklistData::AllowedTypesAndVersions allowed_types) {
+ allowed_types_ = std::move(allowed_types);
+ }
+
+ private:
+ bool ShouldUseSessionPolicy(base::TimeDelta* duration,
+ size_t* history,
+ int* threshold) const override {
+ if (!session_policy_)
+ return false;
+ *duration = session_policy_->duration;
+ *history = session_policy_->history;
+ *threshold = session_policy_->threshold;
+
+ return true;
+ }
+
+ bool ShouldUsePersistentPolicy(base::TimeDelta* duration,
+ size_t* history,
+ int* threshold) const override {
+ if (!persistent_policy_)
+ return false;
+ *duration = persistent_policy_->duration;
+ *history = persistent_policy_->history;
+ *threshold = persistent_policy_->threshold;
+
+ return true;
+ }
+
+ bool ShouldUseHostPolicy(base::TimeDelta* duration,
+ size_t* history,
+ int* threshold,
+ size_t* max_hosts) const override {
+ if (!host_policy_)
+ return false;
+ *duration = host_policy_->duration;
+ *history = host_policy_->history;
+ *threshold = host_policy_->threshold;
+ *max_hosts = max_hosts_;
+
+ return true;
+ }
+
+ bool ShouldUseTypePolicy(base::TimeDelta* duration,
+ size_t* history,
+ int* threshold) const override {
+ if (!type_policy_)
+ return false;
+ *duration = type_policy_->duration;
+ *history = type_policy_->history;
+ *threshold = type_policy_->threshold;
+
+ return true;
+ }
+
+ BlocklistData::AllowedTypesAndVersions GetAllowedTypes() const override {
+ return allowed_types_;
+ }
+
+ std::unique_ptr<BlocklistData::Policy> session_policy_;
+ std::unique_ptr<BlocklistData::Policy> persistent_policy_;
+ std::unique_ptr<BlocklistData::Policy> host_policy_;
+ std::unique_ptr<BlocklistData::Policy> type_policy_;
+
+ size_t max_hosts_ = 0;
+
+ BlocklistData::AllowedTypesAndVersions allowed_types_;
+};
+
+class OptOutBlocklistTest : public testing::Test {
+ public:
+ OptOutBlocklistTest() = default;
+ ~OptOutBlocklistTest() override = default;
+
+ void StartTest(bool null_opt_out_store) {
+ std::unique_ptr<TestOptOutStore> opt_out_store =
+ null_opt_out_store ? nullptr : std::make_unique<TestOptOutStore>();
+ opt_out_store_ = opt_out_store.get();
+
+ block_list_ = std::make_unique<TestOptOutBlocklist>(
+ std::move(opt_out_store), &test_clock_, &blocklist_delegate_);
+ if (session_policy_) {
+ block_list_->SetSessionRule(std::move(session_policy_));
+ }
+ if (persistent_policy_) {
+ block_list_->SetPersistentRule(std::move(persistent_policy_));
+ }
+ if (host_policy_) {
+ block_list_->SetHostRule(std::move(host_policy_), max_hosts_);
+ }
+ if (type_policy_) {
+ block_list_->SetTypeRule(std::move(type_policy_));
+ }
+
+ block_list_->SetAllowedTypes(std::move(allowed_types_));
+ block_list_->Init();
+
+ start_ = test_clock_.Now();
+
+ passed_reasons_ = {};
+ }
+
+ void SetSessionRule(std::unique_ptr<BlocklistData::Policy> policy) {
+ session_policy_ = std::move(policy);
+ }
+
+ void SetPersistentRule(std::unique_ptr<BlocklistData::Policy> policy) {
+ persistent_policy_ = std::move(policy);
+ }
+
+ void SetHostRule(std::unique_ptr<BlocklistData::Policy> policy,
+ size_t max_hosts) {
+ host_policy_ = std::move(policy);
+ max_hosts_ = max_hosts;
+ }
+
+ void SetTypeRule(std::unique_ptr<BlocklistData::Policy> policy) {
+ type_policy_ = std::move(policy);
+ }
+
+ void SetAllowedTypes(BlocklistData::AllowedTypesAndVersions allowed_types) {
+ allowed_types_ = std::move(allowed_types);
+ }
+
+ protected:
+ base::test::SingleThreadTaskEnvironment task_environment_;
+
+ // Observer to |block_list_|.
+ TestOptOutBlocklistDelegate blocklist_delegate_;
+
+ base::SimpleTestClock test_clock_;
+ TestOptOutStore* opt_out_store_;
+ base::Time start_;
+
+ std::unique_ptr<TestOptOutBlocklist> block_list_;
+ std::vector<BlocklistReason> passed_reasons_;
+
+ private:
+ std::unique_ptr<BlocklistData::Policy> session_policy_;
+ std::unique_ptr<BlocklistData::Policy> persistent_policy_;
+ std::unique_ptr<BlocklistData::Policy> host_policy_;
+ std::unique_ptr<BlocklistData::Policy> type_policy_;
+
+ size_t max_hosts_ = 0;
+
+ BlocklistData::AllowedTypesAndVersions allowed_types_;
+
+ DISALLOW_COPY_AND_ASSIGN(OptOutBlocklistTest);
+};
+
+TEST_F(OptOutBlocklistTest, HostBlockListNoStore) {
+ // Tests the block list behavior when a null OptOutStore is passed in.
+ auto host_policy = std::make_unique<BlocklistData::Policy>(
+ base::TimeDelta::FromDays(365), 4u, 2);
+ SetHostRule(std::move(host_policy), 5);
+ BlocklistData::AllowedTypesAndVersions allowed_types;
+ allowed_types.insert({1, 0});
+ SetAllowedTypes(std::move(allowed_types));
+
+ StartTest(true /* null_opt_out */);
+
+ test_clock_.Advance(base::TimeDelta::FromSeconds(1));
+
+ EXPECT_EQ(
+ BlocklistReason::kAllowed,
+ block_list_->IsLoadedAndAllowed(kTestHost1, 1, false, &passed_reasons_));
+ EXPECT_EQ(
+ BlocklistReason::kAllowed,
+ block_list_->IsLoadedAndAllowed(kTestHost2, 1, false, &passed_reasons_));
+
+ block_list_->AddEntry(kTestHost1, true, 1);
+ test_clock_.Advance(base::TimeDelta::FromSeconds(1));
+ block_list_->AddEntry(kTestHost1, true, 1);
+ test_clock_.Advance(base::TimeDelta::FromSeconds(1));
+
+ EXPECT_EQ(
+ BlocklistReason::kUserOptedOutOfHost,
+ block_list_->IsLoadedAndAllowed(kTestHost1, 1, false, &passed_reasons_));
+ EXPECT_EQ(
+ BlocklistReason::kAllowed,
+ block_list_->IsLoadedAndAllowed(kTestHost1, 1, true, &passed_reasons_));
+ EXPECT_EQ(
+ BlocklistReason::kAllowed,
+ block_list_->IsLoadedAndAllowed(kTestHost2, 1, false, &passed_reasons_));
+
+ block_list_->AddEntry(kTestHost2, true, 1);
+ test_clock_.Advance(base::TimeDelta::FromSeconds(1));
+ block_list_->AddEntry(kTestHost2, true, 1);
+ test_clock_.Advance(base::TimeDelta::FromSeconds(1));
+
+ EXPECT_EQ(
+ BlocklistReason::kUserOptedOutOfHost,
+ block_list_->IsLoadedAndAllowed(kTestHost1, 1, false, &passed_reasons_));
+ EXPECT_EQ(
+ BlocklistReason::kAllowed,
+ block_list_->IsLoadedAndAllowed(kTestHost1, 1, true, &passed_reasons_));
+ EXPECT_EQ(
+ BlocklistReason::kUserOptedOutOfHost,
+ block_list_->IsLoadedAndAllowed(kTestHost2, 1, false, &passed_reasons_));
+
+ block_list_->AddEntry(kTestHost2, false, 1);
+ test_clock_.Advance(base::TimeDelta::FromSeconds(1));
+ block_list_->AddEntry(kTestHost2, false, 1);
+ test_clock_.Advance(base::TimeDelta::FromSeconds(1));
+ block_list_->AddEntry(kTestHost2, false, 1);
+ test_clock_.Advance(base::TimeDelta::FromSeconds(1));
+
+ EXPECT_EQ(
+ BlocklistReason::kUserOptedOutOfHost,
+ block_list_->IsLoadedAndAllowed(kTestHost1, 1, false, &passed_reasons_));
+ EXPECT_EQ(
+ BlocklistReason::kAllowed,
+ block_list_->IsLoadedAndAllowed(kTestHost1, 1, true, &passed_reasons_));
+ EXPECT_EQ(
+ BlocklistReason::kAllowed,
+ block_list_->IsLoadedAndAllowed(kTestHost2, 1, false, &passed_reasons_));
+
+ block_list_->ClearBlockList(start_, test_clock_.Now());
+
+ EXPECT_EQ(
+ BlocklistReason::kAllowed,
+ block_list_->IsLoadedAndAllowed(kTestHost1, 1, false, &passed_reasons_));
+ EXPECT_EQ(
+ BlocklistReason::kAllowed,
+ block_list_->IsLoadedAndAllowed(kTestHost2, 1, false, &passed_reasons_));
+}
+
+TEST_F(OptOutBlocklistTest, TypeBlockListWithStore) {
+ // Tests the block list behavior when a non-null OptOutStore is passed in.
+
+ auto type_policy = std::make_unique<BlocklistData::Policy>(
+ base::TimeDelta::FromDays(365), 4u, 2);
+ SetTypeRule(std::move(type_policy));
+ BlocklistData::AllowedTypesAndVersions allowed_types;
+ allowed_types.insert({1, 0});
+ allowed_types.insert({2, 0});
+ SetAllowedTypes(std::move(allowed_types));
+
+ StartTest(false /* null_opt_out */);
+
+ test_clock_.Advance(base::TimeDelta::FromSeconds(1));
+
+ EXPECT_EQ(
+ BlocklistReason::kBlocklistNotLoaded,
+ block_list_->IsLoadedAndAllowed(kTestHost1, 1, false, &passed_reasons_));
+ EXPECT_EQ(
+ BlocklistReason::kBlocklistNotLoaded,
+ block_list_->IsLoadedAndAllowed(kTestHost1, 1, false, &passed_reasons_));
+ EXPECT_EQ(
+ BlocklistReason::kBlocklistNotLoaded,
+ block_list_->IsLoadedAndAllowed(kTestHost1, 2, false, &passed_reasons_));
+
+ base::RunLoop().RunUntilIdle();
+
+ EXPECT_EQ(
+ BlocklistReason::kAllowed,
+ block_list_->IsLoadedAndAllowed(kTestHost1, 1, false, &passed_reasons_));
+ EXPECT_EQ(
+ BlocklistReason::kAllowed,
+ block_list_->IsLoadedAndAllowed(kTestHost1, 1, false, &passed_reasons_));
+ EXPECT_EQ(
+ BlocklistReason::kAllowed,
+ block_list_->IsLoadedAndAllowed(kTestHost1, 2, false, &passed_reasons_));
+
+ block_list_->AddEntry(kTestHost1, true, 1);
+ test_clock_.Advance(base::TimeDelta::FromSeconds(1));
+ block_list_->AddEntry(kTestHost1, true, 1);
+ test_clock_.Advance(base::TimeDelta::FromSeconds(1));
+
+ EXPECT_EQ(
+ BlocklistReason::kUserOptedOutOfType,
+ block_list_->IsLoadedAndAllowed(kTestHost1, 1, false, &passed_reasons_));
+ EXPECT_EQ(
+ BlocklistReason::kUserOptedOutOfType,
+ block_list_->IsLoadedAndAllowed(kTestHost1, 1, false, &passed_reasons_));
+ EXPECT_EQ(
+ BlocklistReason::kAllowed,
+ block_list_->IsLoadedAndAllowed(kTestHost1, 2, true, &passed_reasons_));
+ EXPECT_EQ(
+ BlocklistReason::kAllowed,
+ block_list_->IsLoadedAndAllowed(kTestHost1, 2, false, &passed_reasons_));
+
+ block_list_->AddEntry(kTestHost1, true, 2);
+ test_clock_.Advance(base::TimeDelta::FromSeconds(1));
+ block_list_->AddEntry(kTestHost1, true, 2);
+ test_clock_.Advance(base::TimeDelta::FromSeconds(1));
+
+ EXPECT_EQ(
+ BlocklistReason::kUserOptedOutOfType,
+ block_list_->IsLoadedAndAllowed(kTestHost1, 1, false, &passed_reasons_));
+ EXPECT_EQ(
+ BlocklistReason::kUserOptedOutOfType,
+ block_list_->IsLoadedAndAllowed(kTestHost1, 1, false, &passed_reasons_));
+ EXPECT_EQ(
+ BlocklistReason::kUserOptedOutOfType,
+ block_list_->IsLoadedAndAllowed(kTestHost1, 2, false, &passed_reasons_));
+
+ block_list_->AddEntry(kTestHost1, false, 2);
+ test_clock_.Advance(base::TimeDelta::FromSeconds(1));
+ block_list_->AddEntry(kTestHost1, false, 2);
+ test_clock_.Advance(base::TimeDelta::FromSeconds(1));
+ block_list_->AddEntry(kTestHost1, false, 2);
+ test_clock_.Advance(base::TimeDelta::FromSeconds(1));
+
+ EXPECT_EQ(
+ BlocklistReason::kUserOptedOutOfType,
+ block_list_->IsLoadedAndAllowed(kTestHost1, 1, false, &passed_reasons_));
+ EXPECT_EQ(
+ BlocklistReason::kUserOptedOutOfType,
+ block_list_->IsLoadedAndAllowed(kTestHost1, 1, false, &passed_reasons_));
+ EXPECT_EQ(
+ BlocklistReason::kAllowed,
+ block_list_->IsLoadedAndAllowed(kTestHost1, 2, false, &passed_reasons_));
+
+ EXPECT_EQ(0, opt_out_store_->clear_blocklist_count());
+ block_list_->ClearBlockList(start_, base::Time::Now());
+ EXPECT_EQ(1, opt_out_store_->clear_blocklist_count());
+
+ EXPECT_EQ(
+ BlocklistReason::kBlocklistNotLoaded,
+ block_list_->IsLoadedAndAllowed(kTestHost1, 1, false, &passed_reasons_));
+ EXPECT_EQ(
+ BlocklistReason::kBlocklistNotLoaded,
+ block_list_->IsLoadedAndAllowed(kTestHost1, 1, false, &passed_reasons_));
+ EXPECT_EQ(
+ BlocklistReason::kBlocklistNotLoaded,
+ block_list_->IsLoadedAndAllowed(kTestHost1, 2, false, &passed_reasons_));
+
+ base::RunLoop().RunUntilIdle();
+ EXPECT_EQ(1, opt_out_store_->clear_blocklist_count());
+
+ EXPECT_EQ(
+ BlocklistReason::kAllowed,
+ block_list_->IsLoadedAndAllowed(kTestHost1, 1, false, &passed_reasons_));
+ EXPECT_EQ(
+ BlocklistReason::kAllowed,
+ block_list_->IsLoadedAndAllowed(kTestHost1, 1, false, &passed_reasons_));
+ EXPECT_EQ(
+ BlocklistReason::kAllowed,
+ block_list_->IsLoadedAndAllowed(kTestHost1, 2, false, &passed_reasons_));
+}
+
+TEST_F(OptOutBlocklistTest, TypeBlockListNoStore) {
+ // Tests the block list behavior when a null OptOutStore is passed in.
+ auto type_policy = std::make_unique<BlocklistData::Policy>(
+ base::TimeDelta::FromDays(365), 4u, 2);
+ SetTypeRule(std::move(type_policy));
+ BlocklistData::AllowedTypesAndVersions allowed_types;
+ allowed_types.insert({1, 0});
+ allowed_types.insert({2, 0});
+ SetAllowedTypes(std::move(allowed_types));
+
+ StartTest(true /* null_opt_out */);
+
+ test_clock_.Advance(base::TimeDelta::FromSeconds(1));
+
+ EXPECT_EQ(
+ BlocklistReason::kAllowed,
+ block_list_->IsLoadedAndAllowed(kTestHost1, 1, false, &passed_reasons_));
+ EXPECT_EQ(
+ BlocklistReason::kAllowed,
+ block_list_->IsLoadedAndAllowed(kTestHost1, 2, false, &passed_reasons_));
+
+ block_list_->AddEntry(kTestHost1, true, 1);
+ test_clock_.Advance(base::TimeDelta::FromSeconds(1));
+ block_list_->AddEntry(kTestHost1, true, 1);
+ test_clock_.Advance(base::TimeDelta::FromSeconds(1));
+
+ EXPECT_EQ(
+ BlocklistReason::kUserOptedOutOfType,
+ block_list_->IsLoadedAndAllowed(kTestHost1, 1, false, &passed_reasons_));
+ EXPECT_EQ(
+ BlocklistReason::kAllowed,
+ block_list_->IsLoadedAndAllowed(kTestHost1, 1, true, &passed_reasons_));
+ EXPECT_EQ(
+ BlocklistReason::kAllowed,
+ block_list_->IsLoadedAndAllowed(kTestHost1, 2, false, &passed_reasons_));
+
+ block_list_->AddEntry(kTestHost1, true, 2);
+ test_clock_.Advance(base::TimeDelta::FromSeconds(1));
+ block_list_->AddEntry(kTestHost1, true, 2);
+ test_clock_.Advance(base::TimeDelta::FromSeconds(1));
+
+ EXPECT_EQ(
+ BlocklistReason::kUserOptedOutOfType,
+ block_list_->IsLoadedAndAllowed(kTestHost1, 1, false, &passed_reasons_));
+ EXPECT_EQ(
+ BlocklistReason::kAllowed,
+ block_list_->IsLoadedAndAllowed(kTestHost1, 1, true, &passed_reasons_));
+ EXPECT_EQ(
+ BlocklistReason::kUserOptedOutOfType,
+ block_list_->IsLoadedAndAllowed(kTestHost1, 2, false, &passed_reasons_));
+
+ block_list_->AddEntry(kTestHost1, false, 2);
+ test_clock_.Advance(base::TimeDelta::FromSeconds(1));
+ block_list_->AddEntry(kTestHost1, false, 2);
+ test_clock_.Advance(base::TimeDelta::FromSeconds(1));
+ block_list_->AddEntry(kTestHost1, false, 2);
+ test_clock_.Advance(base::TimeDelta::FromSeconds(1));
+
+ EXPECT_EQ(
+ BlocklistReason::kUserOptedOutOfType,
+ block_list_->IsLoadedAndAllowed(kTestHost1, 1, false, &passed_reasons_));
+ EXPECT_EQ(
+ BlocklistReason::kAllowed,
+ block_list_->IsLoadedAndAllowed(kTestHost1, 1, true, &passed_reasons_));
+ EXPECT_EQ(
+ BlocklistReason::kAllowed,
+ block_list_->IsLoadedAndAllowed(kTestHost1, 2, false, &passed_reasons_));
+
+ block_list_->ClearBlockList(start_, test_clock_.Now());
+
+ EXPECT_EQ(
+ BlocklistReason::kAllowed,
+ block_list_->IsLoadedAndAllowed(kTestHost1, 1, false, &passed_reasons_));
+ EXPECT_EQ(
+ BlocklistReason::kAllowed,
+ block_list_->IsLoadedAndAllowed(kTestHost1, 2, false, &passed_reasons_));
+}
+
+TEST_F(OptOutBlocklistTest, HostIndifferentBlocklist) {
+ // Tests the block list behavior when a null OptOutStore is passed in.
+ const std::string hosts[] = {
+ "url_0.com",
+ "url_1.com",
+ "url_2.com",
+ "url_3.com",
+ };
+
+ int host_indifferent_threshold = 4;
+
+ auto persistent_policy = std::make_unique<BlocklistData::Policy>(
+ base::TimeDelta::FromDays(365), 4u, host_indifferent_threshold);
+ SetPersistentRule(std::move(persistent_policy));
+ BlocklistData::AllowedTypesAndVersions allowed_types;
+ allowed_types.insert({1, 0});
+ SetAllowedTypes(std::move(allowed_types));
+
+ StartTest(true /* null_opt_out */);
+ test_clock_.Advance(base::TimeDelta::FromSeconds(1));
+
+ EXPECT_EQ(
+ BlocklistReason::kAllowed,
+ block_list_->IsLoadedAndAllowed(hosts[0], 1, false, &passed_reasons_));
+ EXPECT_EQ(
+ BlocklistReason::kAllowed,
+ block_list_->IsLoadedAndAllowed(hosts[1], 1, false, &passed_reasons_));
+ EXPECT_EQ(
+ BlocklistReason::kAllowed,
+ block_list_->IsLoadedAndAllowed(hosts[2], 1, false, &passed_reasons_));
+ EXPECT_EQ(
+ BlocklistReason::kAllowed,
+ block_list_->IsLoadedAndAllowed(hosts[3], 1, false, &passed_reasons_));
+
+ for (int i = 0; i < host_indifferent_threshold; i++) {
+ block_list_->AddEntry(hosts[i], true, 1);
+ EXPECT_EQ(
+ i != 3 ? BlocklistReason::kAllowed
+ : BlocklistReason::kUserOptedOutInGeneral,
+ block_list_->IsLoadedAndAllowed(hosts[0], 1, false, &passed_reasons_));
+ test_clock_.Advance(base::TimeDelta::FromSeconds(1));
+ }
+
+ EXPECT_EQ(
+ BlocklistReason::kUserOptedOutInGeneral,
+ block_list_->IsLoadedAndAllowed(hosts[0], 1, false, &passed_reasons_));
+ EXPECT_EQ(
+ BlocklistReason::kUserOptedOutInGeneral,
+ block_list_->IsLoadedAndAllowed(hosts[1], 1, false, &passed_reasons_));
+ EXPECT_EQ(
+ BlocklistReason::kUserOptedOutInGeneral,
+ block_list_->IsLoadedAndAllowed(hosts[2], 1, false, &passed_reasons_));
+ EXPECT_EQ(
+ BlocklistReason::kUserOptedOutInGeneral,
+ block_list_->IsLoadedAndAllowed(hosts[3], 1, false, &passed_reasons_));
+ EXPECT_EQ(
+ BlocklistReason::kAllowed,
+ block_list_->IsLoadedAndAllowed(hosts[3], 1, true, &passed_reasons_));
+
+ block_list_->AddEntry(hosts[3], false, 1);
+ test_clock_.Advance(base::TimeDelta::FromSeconds(1));
+
+ // New non-opt-out entry will cause these to be allowed now.
+ EXPECT_EQ(
+ BlocklistReason::kAllowed,
+ block_list_->IsLoadedAndAllowed(hosts[0], 1, false, &passed_reasons_));
+ EXPECT_EQ(
+ BlocklistReason::kAllowed,
+ block_list_->IsLoadedAndAllowed(hosts[1], 1, false, &passed_reasons_));
+ EXPECT_EQ(
+ BlocklistReason::kAllowed,
+ block_list_->IsLoadedAndAllowed(hosts[2], 1, false, &passed_reasons_));
+ EXPECT_EQ(
+ BlocklistReason::kAllowed,
+ block_list_->IsLoadedAndAllowed(hosts[3], 1, false, &passed_reasons_));
+}
+
+TEST_F(OptOutBlocklistTest, QueueBehavior) {
+ // Tests the block list asynchronous queue behavior. Methods called while
+ // loading the opt-out store are queued and should run in the order they were
+ // queued.
+
+ std::vector<bool> test_opt_out{true, false};
+
+ for (auto opt_out : test_opt_out) {
+ auto host_policy = std::make_unique<BlocklistData::Policy>(
+ base::TimeDelta::FromDays(365), 4u, 2);
+ SetHostRule(std::move(host_policy), 5);
+ BlocklistData::AllowedTypesAndVersions allowed_types;
+ allowed_types.insert({1, 0});
+ SetAllowedTypes(std::move(allowed_types));
+
+ StartTest(false /* null_opt_out */);
+
+ EXPECT_EQ(BlocklistReason::kBlocklistNotLoaded,
+ block_list_->IsLoadedAndAllowed(kTestHost1, 1, false,
+ &passed_reasons_));
+ block_list_->AddEntry(kTestHost1, opt_out, 1);
+ test_clock_.Advance(base::TimeDelta::FromSeconds(1));
+ block_list_->AddEntry(kTestHost1, opt_out, 1);
+ test_clock_.Advance(base::TimeDelta::FromSeconds(1));
+ EXPECT_EQ(BlocklistReason::kBlocklistNotLoaded,
+ block_list_->IsLoadedAndAllowed(kTestHost1, 1, false,
+ &passed_reasons_));
+ base::RunLoop().RunUntilIdle();
+ EXPECT_EQ(opt_out ? BlocklistReason::kUserOptedOutOfHost
+ : BlocklistReason::kAllowed,
+ block_list_->IsLoadedAndAllowed(kTestHost1, 1, false,
+ &passed_reasons_));
+ block_list_->AddEntry(kTestHost1, opt_out, 1);
+ test_clock_.Advance(base::TimeDelta::FromSeconds(1));
+ block_list_->AddEntry(kTestHost1, opt_out, 1);
+ test_clock_.Advance(base::TimeDelta::FromSeconds(1));
+ EXPECT_EQ(0, opt_out_store_->clear_blocklist_count());
+ block_list_->ClearBlockList(
+ start_, test_clock_.Now() + base::TimeDelta::FromSeconds(1));
+ EXPECT_EQ(1, opt_out_store_->clear_blocklist_count());
+ block_list_->AddEntry(kTestHost2, opt_out, 1);
+ test_clock_.Advance(base::TimeDelta::FromSeconds(1));
+ block_list_->AddEntry(kTestHost2, opt_out, 1);
+ test_clock_.Advance(base::TimeDelta::FromSeconds(1));
+ base::RunLoop().RunUntilIdle();
+ EXPECT_EQ(1, opt_out_store_->clear_blocklist_count());
+
+ EXPECT_EQ(BlocklistReason::kAllowed,
+ block_list_->IsLoadedAndAllowed(kTestHost1, 1, false,
+ &passed_reasons_));
+ EXPECT_EQ(opt_out ? BlocklistReason::kUserOptedOutOfHost
+ : BlocklistReason::kAllowed,
+ block_list_->IsLoadedAndAllowed(kTestHost2, 1, false,
+ &passed_reasons_));
+ }
+}
+
+TEST_F(OptOutBlocklistTest, MaxHosts) {
+ // Test that the block list only stores n hosts, and it stores the correct n
+ // hosts.
+ const std::string test_host_3("host3.com");
+ const std::string test_host_4("host4.com");
+ const std::string test_host_5("host5.com");
+
+ auto host_policy = std::make_unique<BlocklistData::Policy>(
+ base::TimeDelta::FromDays(365), 1u, 1);
+ SetHostRule(std::move(host_policy), 2);
+ BlocklistData::AllowedTypesAndVersions allowed_types;
+ allowed_types.insert({1, 0});
+ SetAllowedTypes(std::move(allowed_types));
+
+ StartTest(true /* null_opt_out */);
+
+ block_list_->AddEntry(kTestHost1, true, 1);
+ test_clock_.Advance(base::TimeDelta::FromSeconds(1));
+ block_list_->AddEntry(kTestHost2, false, 1);
+ test_clock_.Advance(base::TimeDelta::FromSeconds(1));
+ block_list_->AddEntry(test_host_3, false, 1);
+ // kTestHost1 should stay in the map, since it has an opt out time.
+ EXPECT_EQ(
+ BlocklistReason::kUserOptedOutOfHost,
+ block_list_->IsLoadedAndAllowed(kTestHost1, 1, false, &passed_reasons_));
+ EXPECT_EQ(
+ BlocklistReason::kAllowed,
+ block_list_->IsLoadedAndAllowed(kTestHost2, 1, false, &passed_reasons_));
+ EXPECT_EQ(
+ BlocklistReason::kAllowed,
+ block_list_->IsLoadedAndAllowed(test_host_3, 1, false, &passed_reasons_));
+
+ test_clock_.Advance(base::TimeDelta::FromSeconds(1));
+ block_list_->AddEntry(test_host_4, true, 1);
+ test_clock_.Advance(base::TimeDelta::FromSeconds(1));
+ block_list_->AddEntry(test_host_5, true, 1);
+ // test_host_4 and test_host_5 should remain in the map, but host should be
+ // evicted.
+ EXPECT_EQ(
+ BlocklistReason::kAllowed,
+ block_list_->IsLoadedAndAllowed(kTestHost1, 1, false, &passed_reasons_));
+ EXPECT_EQ(
+ BlocklistReason::kUserOptedOutOfHost,
+ block_list_->IsLoadedAndAllowed(test_host_4, 1, false, &passed_reasons_));
+ EXPECT_EQ(
+ BlocklistReason::kUserOptedOutOfHost,
+ block_list_->IsLoadedAndAllowed(test_host_5, 1, false, &passed_reasons_));
+}
+
+TEST_F(OptOutBlocklistTest, SingleOptOut) {
+ // Test that when a user opts out of an action, actions won't be allowed until
+ // |single_opt_out_duration| has elapsed.
+ int single_opt_out_duration = 5;
+ const std::string test_host_3("host3.com");
+
+ auto session_policy = std::make_unique<BlocklistData::Policy>(
+ base::TimeDelta::FromSeconds(single_opt_out_duration), 1u, 1);
+ SetSessionRule(std::move(session_policy));
+ BlocklistData::AllowedTypesAndVersions allowed_types;
+ allowed_types.insert({1, 0});
+ SetAllowedTypes(std::move(allowed_types));
+
+ StartTest(true /* null_opt_out */);
+
+ block_list_->AddEntry(kTestHost1, false, 1);
+ EXPECT_EQ(
+ BlocklistReason::kAllowed,
+ block_list_->IsLoadedAndAllowed(kTestHost1, 1, false, &passed_reasons_));
+ EXPECT_EQ(
+ BlocklistReason::kAllowed,
+ block_list_->IsLoadedAndAllowed(test_host_3, 1, false, &passed_reasons_));
+
+ test_clock_.Advance(
+ base::TimeDelta::FromSeconds(single_opt_out_duration + 1));
+
+ block_list_->AddEntry(kTestHost2, true, 1);
+ EXPECT_EQ(
+ BlocklistReason::kUserOptedOutInSession,
+ block_list_->IsLoadedAndAllowed(kTestHost2, 1, false, &passed_reasons_));
+ EXPECT_EQ(
+ BlocklistReason::kUserOptedOutInSession,
+ block_list_->IsLoadedAndAllowed(test_host_3, 1, false, &passed_reasons_));
+
+ test_clock_.Advance(
+ base::TimeDelta::FromSeconds(single_opt_out_duration - 1));
+
+ EXPECT_EQ(
+ BlocklistReason::kUserOptedOutInSession,
+ block_list_->IsLoadedAndAllowed(kTestHost2, 1, false, &passed_reasons_));
+ EXPECT_EQ(
+ BlocklistReason::kUserOptedOutInSession,
+ block_list_->IsLoadedAndAllowed(test_host_3, 1, false, &passed_reasons_));
+
+ test_clock_.Advance(
+ base::TimeDelta::FromSeconds(single_opt_out_duration + 1));
+
+ EXPECT_EQ(
+ BlocklistReason::kAllowed,
+ block_list_->IsLoadedAndAllowed(kTestHost2, 1, false, &passed_reasons_));
+ EXPECT_EQ(
+ BlocklistReason::kAllowed,
+ block_list_->IsLoadedAndAllowed(test_host_3, 1, false, &passed_reasons_));
+}
+
+TEST_F(OptOutBlocklistTest, ClearingBlockListClearsRecentNavigation) {
+ // Tests that clearing the block list for a long amount of time (relative to
+ // "single_opt_out_duration_in_seconds") resets the blocklist's recent opt out
+ // rule.
+
+ auto session_policy = std::make_unique<BlocklistData::Policy>(
+ base::TimeDelta::FromSeconds(5), 1u, 1);
+ SetSessionRule(std::move(session_policy));
+ BlocklistData::AllowedTypesAndVersions allowed_types;
+ allowed_types.insert({1, 0});
+ SetAllowedTypes(std::move(allowed_types));
+
+ StartTest(false /* null_opt_out */);
+
+ block_list_->AddEntry(kTestHost1, true /* opt_out */, 1);
+ test_clock_.Advance(base::TimeDelta::FromSeconds(1));
+ block_list_->ClearBlockList(start_, test_clock_.Now());
+ base::RunLoop().RunUntilIdle();
+
+ EXPECT_EQ(
+ BlocklistReason::kAllowed,
+ block_list_->IsLoadedAndAllowed(kTestHost1, 1, false, &passed_reasons_));
+}
+
+TEST_F(OptOutBlocklistTest, ObserverIsNotifiedOnHostBlocklisted) {
+ // Tests the block list behavior when a null OptOutStore is passed in.
+
+ auto host_policy = std::make_unique<BlocklistData::Policy>(
+ base::TimeDelta::FromDays(365), 4u, 2);
+ SetHostRule(std::move(host_policy), 5);
+ BlocklistData::AllowedTypesAndVersions allowed_types;
+ allowed_types.insert({1, 0});
+ SetAllowedTypes(std::move(allowed_types));
+
+ StartTest(true /* null_opt_out */);
+
+ EXPECT_EQ(
+ BlocklistReason::kAllowed,
+ block_list_->IsLoadedAndAllowed(kTestHost1, 1, false, &passed_reasons_));
+
+ // Observer is not notified as blocklisted when the threshold does not met.
+ test_clock_.Advance(base::TimeDelta::FromSeconds(1));
+ block_list_->AddEntry(kTestHost1, true, 1);
+ base::RunLoop().RunUntilIdle();
+ EXPECT_THAT(blocklist_delegate_.blocklisted_hosts(), ::testing::SizeIs(0));
+
+ // Observer is notified as blocklisted when the threshold is met.
+ test_clock_.Advance(base::TimeDelta::FromSeconds(1));
+ block_list_->AddEntry(kTestHost1, true, 1);
+ base::RunLoop().RunUntilIdle();
+ const base::Time blocklisted_time = test_clock_.Now();
+ EXPECT_THAT(blocklist_delegate_.blocklisted_hosts(), ::testing::SizeIs(1));
+ EXPECT_EQ(blocklisted_time,
+ blocklist_delegate_.blocklisted_hosts().find(kTestHost1)->second);
+
+ // Observer is not notified when the host is already blocklisted.
+ test_clock_.Advance(base::TimeDelta::FromSeconds(1));
+ block_list_->AddEntry(kTestHost1, true, 1);
+ base::RunLoop().RunUntilIdle();
+ EXPECT_THAT(blocklist_delegate_.blocklisted_hosts(), ::testing::SizeIs(1));
+ EXPECT_EQ(blocklisted_time,
+ blocklist_delegate_.blocklisted_hosts().find(kTestHost1)->second);
+
+ // Observer is notified when blocklist is cleared.
+ EXPECT_FALSE(blocklist_delegate_.blocklist_cleared());
+
+ test_clock_.Advance(base::TimeDelta::FromSeconds(1));
+ block_list_->ClearBlockList(start_, test_clock_.Now());
+ base::RunLoop().RunUntilIdle();
+
+ EXPECT_TRUE(blocklist_delegate_.blocklist_cleared());
+ EXPECT_EQ(test_clock_.Now(), blocklist_delegate_.blocklist_cleared_time());
+}
+
+TEST_F(OptOutBlocklistTest, ObserverIsNotifiedOnUserBlocklisted) {
+ // Tests the block list behavior when a null OptOutStore is passed in.
+ const std::string hosts[] = {
+ "url_0.com",
+ "url_1.com",
+ "url_2.com",
+ "url_3.com",
+ };
+
+ int host_indifferent_threshold = 4;
+
+ auto persistent_policy = std::make_unique<BlocklistData::Policy>(
+ base::TimeDelta::FromDays(30), 4u, host_indifferent_threshold);
+ SetPersistentRule(std::move(persistent_policy));
+ BlocklistData::AllowedTypesAndVersions allowed_types;
+ allowed_types.insert({1, 0});
+ SetAllowedTypes(std::move(allowed_types));
+
+ StartTest(true /* null_opt_out */);
+
+ // Initially no host is blocklisted, and user is not blocklisted.
+ EXPECT_THAT(blocklist_delegate_.blocklisted_hosts(), ::testing::SizeIs(0));
+ EXPECT_FALSE(blocklist_delegate_.user_blocklisted());
+
+ for (int i = 0; i < host_indifferent_threshold; ++i) {
+ test_clock_.Advance(base::TimeDelta::FromSeconds(1));
+ block_list_->AddEntry(hosts[i], true, 1);
+ base::RunLoop().RunUntilIdle();
+
+ EXPECT_THAT(blocklist_delegate_.blocklisted_hosts(), ::testing::SizeIs(0));
+ // Observer is notified when number of recently opt out meets
+ // |host_indifferent_threshold|.
+ EXPECT_EQ(i >= host_indifferent_threshold - 1,
+ blocklist_delegate_.user_blocklisted());
+ }
+
+ // Observer is notified when the user is no longer blocklisted.
+ test_clock_.Advance(base::TimeDelta::FromSeconds(1));
+ block_list_->AddEntry(hosts[3], false, 1);
+ base::RunLoop().RunUntilIdle();
+
+ EXPECT_FALSE(blocklist_delegate_.user_blocklisted());
+}
+
+TEST_F(OptOutBlocklistTest, ObserverIsNotifiedWhenLoadBlocklistDone) {
+ int host_indifferent_threshold = 4;
+ size_t host_indifferent_history = 4u;
+ auto persistent_policy = std::make_unique<BlocklistData::Policy>(
+ base::TimeDelta::FromDays(30), host_indifferent_history,
+ host_indifferent_threshold);
+ SetPersistentRule(std::move(persistent_policy));
+ BlocklistData::AllowedTypesAndVersions allowed_types;
+ allowed_types.insert({1, 0});
+ SetAllowedTypes(std::move(allowed_types));
+
+ StartTest(false /* null_opt_out */);
+
+ allowed_types.clear();
+ allowed_types[0] = 0;
+ std::unique_ptr<BlocklistData> data = std::make_unique<BlocklistData>(
+ nullptr,
+ std::make_unique<BlocklistData::Policy>(base::TimeDelta::FromSeconds(365),
+ host_indifferent_history,
+ host_indifferent_threshold),
+ nullptr, nullptr, 0, std::move(allowed_types));
+ base::SimpleTestClock test_clock;
+
+ for (int i = 0; i < host_indifferent_threshold; ++i) {
+ test_clock.Advance(base::TimeDelta::FromSeconds(1));
+ data->AddEntry(kTestHost1, true, 0, test_clock.Now(), true);
+ }
+
+ std::unique_ptr<TestOptOutStore> opt_out_store =
+ std::make_unique<TestOptOutStore>();
+ opt_out_store->SetBlocklistData(std::move(data));
+
+ EXPECT_FALSE(blocklist_delegate_.user_blocklisted());
+ allowed_types.clear();
+ allowed_types[1] = 0;
+ auto block_list = std::make_unique<TestOptOutBlocklist>(
+ std::move(opt_out_store), &test_clock, &blocklist_delegate_);
+ block_list->SetAllowedTypes(std::move(allowed_types));
+
+ persistent_policy = std::make_unique<BlocklistData::Policy>(
+ base::TimeDelta::FromDays(30), host_indifferent_history,
+ host_indifferent_threshold);
+ block_list->SetPersistentRule(std::move(persistent_policy));
+
+ block_list->Init();
+ base::RunLoop().RunUntilIdle();
+ EXPECT_TRUE(blocklist_delegate_.user_blocklisted());
+}
+
+TEST_F(OptOutBlocklistTest, ObserverIsNotifiedOfHistoricalBlocklistedHosts) {
+ // Tests the block list behavior when a non-null OptOutStore is passed in.
+ int host_indifferent_threshold = 2;
+ size_t host_indifferent_history = 4u;
+ auto host_policy = std::make_unique<BlocklistData::Policy>(
+ base::TimeDelta::FromDays(365), host_indifferent_history,
+ host_indifferent_threshold);
+ SetHostRule(std::move(host_policy), 5);
+ BlocklistData::AllowedTypesAndVersions allowed_types;
+ allowed_types.insert({1, 0});
+ SetAllowedTypes(std::move(allowed_types));
+
+ StartTest(false /* null_opt_out */);
+
+ base::SimpleTestClock test_clock;
+
+ allowed_types.clear();
+ allowed_types[static_cast<int>(1)] = 0;
+ std::unique_ptr<BlocklistData> data = std::make_unique<BlocklistData>(
+ nullptr, nullptr,
+ std::make_unique<BlocklistData::Policy>(base::TimeDelta::FromDays(365),
+ host_indifferent_history,
+ host_indifferent_threshold),
+ nullptr, 2, std::move(allowed_types));
+
+ test_clock.Advance(base::TimeDelta::FromSeconds(1));
+ data->AddEntry(kTestHost1, true, static_cast<int>(1), test_clock.Now(), true);
+ test_clock.Advance(base::TimeDelta::FromSeconds(1));
+ data->AddEntry(kTestHost1, true, static_cast<int>(1), test_clock.Now(), true);
+ base::Time blocklisted_time = test_clock.Now();
+
+ base::RunLoop().RunUntilIdle();
+ std::vector<BlocklistReason> reasons;
+ EXPECT_NE(BlocklistReason::kAllowed,
+ data->IsAllowed(kTestHost1, static_cast<int>(1), false,
+ test_clock.Now(), &reasons));
+
+ // Host |url_b| is not blocklisted.
+ test_clock.Advance(base::TimeDelta::FromSeconds(1));
+ data->AddEntry(kTestHost2, true, static_cast<int>(1), test_clock.Now(), true);
+
+ std::unique_ptr<TestOptOutStore> opt_out_store =
+ std::make_unique<TestOptOutStore>();
+ opt_out_store->SetBlocklistData(std::move(data));
+
+ allowed_types.clear();
+ allowed_types[static_cast<int>(1)] = 0;
+ auto block_list = std::make_unique<TestOptOutBlocklist>(
+ std::move(opt_out_store), &test_clock, &blocklist_delegate_);
+ block_list->SetAllowedTypes(std::move(allowed_types));
+
+ host_policy = std::make_unique<BlocklistData::Policy>(
+ base::TimeDelta::FromDays(30), host_indifferent_history,
+ host_indifferent_threshold);
+ block_list->SetPersistentRule(std::move(host_policy));
+
+ block_list->Init();
+
+ base::RunLoop().RunUntilIdle();
+
+ ASSERT_THAT(blocklist_delegate_.blocklisted_hosts(), ::testing::SizeIs(1));
+ EXPECT_EQ(blocklisted_time,
+ blocklist_delegate_.blocklisted_hosts().find(kTestHost1)->second);
+}
+
+TEST_F(OptOutBlocklistTest, PassedReasonsWhenBlocklistDataNotLoaded) {
+ // Test that IsLoadedAndAllow, push checked BlocklistReasons to the
+ // |passed_reasons| vector.
+
+ BlocklistData::AllowedTypesAndVersions allowed_types;
+ allowed_types.insert({1, 0});
+ SetAllowedTypes(std::move(allowed_types));
+ StartTest(false /* null_opt_out */);
+
+ EXPECT_EQ(
+ BlocklistReason::kBlocklistNotLoaded,
+ block_list_->IsLoadedAndAllowed(kTestHost1, 1, false, &passed_reasons_));
+
+ EXPECT_EQ(0UL, passed_reasons_.size());
+}
+
+TEST_F(OptOutBlocklistTest, PassedReasonsWhenUserRecentlyOptedOut) {
+ // Test that IsLoadedAndAllow, push checked BlocklistReasons to the
+ // |passed_reasons| vector.
+
+ auto session_policy = std::make_unique<BlocklistData::Policy>(
+ base::TimeDelta::FromSeconds(5), 1u, 1);
+ SetSessionRule(std::move(session_policy));
+ BlocklistData::AllowedTypesAndVersions allowed_types;
+ allowed_types.insert({1, 0});
+ SetAllowedTypes(std::move(allowed_types));
+
+ StartTest(true /* null_opt_out */);
+
+ block_list_->AddEntry(kTestHost1, true, 1);
+ EXPECT_EQ(
+ BlocklistReason::kUserOptedOutInSession,
+ block_list_->IsLoadedAndAllowed(kTestHost1, 1, false, &passed_reasons_));
+ EXPECT_EQ(1UL, passed_reasons_.size());
+ EXPECT_EQ(BlocklistReason::kBlocklistNotLoaded, passed_reasons_[0]);
+}
+
+TEST_F(OptOutBlocklistTest, PassedReasonsWhenUserBlocklisted) {
+ // Test that IsLoadedAndAllow, push checked BlocklistReasons to the
+ // |passed_reasons| vector.
+ const std::string hosts[] = {
+ "http://www.url_0.com",
+ "http://www.url_1.com",
+ "http://www.url_2.com",
+ "http://www.url_3.com",
+ };
+
+ auto session_policy = std::make_unique<BlocklistData::Policy>(
+ base::TimeDelta::FromSeconds(1), 1u, 1);
+ SetSessionRule(std::move(session_policy));
+ auto persistent_policy = std::make_unique<BlocklistData::Policy>(
+ base::TimeDelta::FromDays(365), 4u, 4);
+ SetPersistentRule(std::move(persistent_policy));
+ BlocklistData::AllowedTypesAndVersions allowed_types;
+ allowed_types.insert({1, 0});
+ SetAllowedTypes(std::move(allowed_types));
+
+ StartTest(true /* null_opt_out */);
+ test_clock_.Advance(base::TimeDelta::FromSeconds(1));
+
+ for (auto host : hosts) {
+ block_list_->AddEntry(host, true, 1);
+ }
+
+ test_clock_.Advance(base::TimeDelta::FromSeconds(2));
+
+ EXPECT_EQ(
+ BlocklistReason::kUserOptedOutInGeneral,
+ block_list_->IsLoadedAndAllowed(hosts[0], 1, false, &passed_reasons_));
+
+ BlocklistReason expected_reasons[] = {
+ BlocklistReason::kBlocklistNotLoaded,
+ BlocklistReason::kUserOptedOutInSession,
+ };
+ EXPECT_EQ(base::size(expected_reasons), passed_reasons_.size());
+ for (size_t i = 0; i < passed_reasons_.size(); i++) {
+ EXPECT_EQ(expected_reasons[i], passed_reasons_[i]);
+ }
+}
+
+TEST_F(OptOutBlocklistTest, PassedReasonsWhenHostBlocklisted) {
+ // Test that IsLoadedAndAllow, push checked BlocklistReasons to the
+ // |passed_reasons| vector.
+
+ auto session_policy = std::make_unique<BlocklistData::Policy>(
+ base::TimeDelta::FromDays(5), 3u, 3);
+ SetSessionRule(std::move(session_policy));
+ auto persistent_policy = std::make_unique<BlocklistData::Policy>(
+ base::TimeDelta::FromDays(365), 4u, 4);
+ SetPersistentRule(std::move(persistent_policy));
+ auto host_policy = std::make_unique<BlocklistData::Policy>(
+ base::TimeDelta::FromDays(30), 4u, 2);
+ SetHostRule(std::move(host_policy), 2);
+ BlocklistData::AllowedTypesAndVersions allowed_types;
+ allowed_types.insert({1, 0});
+ SetAllowedTypes(std::move(allowed_types));
+
+ StartTest(true /* null_opt_out */);
+
+ block_list_->AddEntry(kTestHost1, true, 1);
+ block_list_->AddEntry(kTestHost1, true, 1);
+
+ EXPECT_EQ(
+ BlocklistReason::kUserOptedOutOfHost,
+ block_list_->IsLoadedAndAllowed(kTestHost1, 1, false, &passed_reasons_));
+
+ BlocklistReason expected_reasons[] = {
+ BlocklistReason::kBlocklistNotLoaded,
+ BlocklistReason::kUserOptedOutInSession,
+ BlocklistReason::kUserOptedOutInGeneral,
+ };
+ EXPECT_EQ(base::size(expected_reasons), passed_reasons_.size());
+ for (size_t i = 0; i < passed_reasons_.size(); i++) {
+ EXPECT_EQ(expected_reasons[i], passed_reasons_[i]);
+ }
+}
+
+TEST_F(OptOutBlocklistTest, PassedReasonsWhenAllowed) {
+ // Test that IsLoadedAndAllow, push checked BlocklistReasons to the
+ // |passed_reasons| vector.
+
+ auto session_policy = std::make_unique<BlocklistData::Policy>(
+ base::TimeDelta::FromSeconds(1), 1u, 1);
+ SetSessionRule(std::move(session_policy));
+ auto persistent_policy = std::make_unique<BlocklistData::Policy>(
+ base::TimeDelta::FromDays(365), 4u, 4);
+ SetPersistentRule(std::move(persistent_policy));
+ auto host_policy = std::make_unique<BlocklistData::Policy>(
+ base::TimeDelta::FromDays(30), 4u, 4);
+ SetHostRule(std::move(host_policy), 1);
+ auto type_policy = std::make_unique<BlocklistData::Policy>(
+ base::TimeDelta::FromDays(30), 4u, 4);
+ SetTypeRule(std::move(type_policy));
+ BlocklistData::AllowedTypesAndVersions allowed_types;
+ allowed_types.insert({1, 0});
+ SetAllowedTypes(std::move(allowed_types));
+
+ StartTest(true /* null_opt_out */);
+
+ EXPECT_EQ(
+ BlocklistReason::kAllowed,
+ block_list_->IsLoadedAndAllowed(kTestHost1, 1, false, &passed_reasons_));
+
+ BlocklistReason expected_reasons[] = {
+ BlocklistReason::kBlocklistNotLoaded,
+ BlocklistReason::kUserOptedOutInSession,
+ BlocklistReason::kUserOptedOutInGeneral,
+ BlocklistReason::kUserOptedOutOfHost,
+ BlocklistReason::kUserOptedOutOfType,
+ };
+ EXPECT_EQ(base::size(expected_reasons), passed_reasons_.size());
+ for (size_t i = 0; i < passed_reasons_.size(); i++) {
+ EXPECT_EQ(expected_reasons[i], passed_reasons_[i]);
+ }
+}
+
+} // namespace
+
+} // namespace blocklist
diff --git a/chromium/components/blocklist/opt_out_blocklist/opt_out_store.h b/chromium/components/blocklist/opt_out_blocklist/opt_out_store.h
new file mode 100644
index 00000000000..4bfbe4c44b9
--- /dev/null
+++ b/chromium/components/blocklist/opt_out_blocklist/opt_out_store.h
@@ -0,0 +1,49 @@
+// Copyright 2016 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.
+
+#ifndef COMPONENTS_BLOCKLIST_OPT_OUT_BLOCKLIST_OPT_OUT_STORE_H_
+#define COMPONENTS_BLOCKLIST_OPT_OUT_BLOCKLIST_OPT_OUT_STORE_H_
+
+#include <stdint.h>
+
+#include <memory>
+#include <string>
+
+#include "base/callback.h"
+#include "base/time/time.h"
+#include "components/blocklist/opt_out_blocklist/opt_out_blocklist_data.h"
+
+namespace blocklist {
+
+typedef base::OnceCallback<void(std::unique_ptr<BlocklistData>)>
+ LoadBlockListCallback;
+
+// OptOutStore keeps opt out information for the blocklist.
+// Ability to create multiple instances of the store as well as behavior of
+// asynchronous operations when the object is being destroyed, before such
+// operation finishes will depend on implementation. It is possible to issue
+// multiple asynchronous operations in parallel and maintain ordering.
+class OptOutStore {
+ public:
+ virtual ~OptOutStore() {}
+
+ // Adds a new navigation to the store. |opt_out| is whether the user opted out
+ // of the action.
+ virtual void AddEntry(bool opt_out,
+ const std::string& host_name,
+ int type,
+ base::Time now) = 0;
+
+ // Asynchronously loads a map of host names to OptOutBlocklistItem for that
+ // host from the store. And runs |callback| once loading is finished.
+ virtual void LoadBlockList(std::unique_ptr<BlocklistData> blocklist_data,
+ LoadBlockListCallback callback) = 0;
+
+ // Deletes all history in the store between |begin_time| and |end_time|.
+ virtual void ClearBlockList(base::Time begin_time, base::Time end_time) = 0;
+};
+
+} // namespace blocklist
+
+#endif // COMPONENTS_BLOCKLIST_OPT_OUT_BLOCKLIST_OPT_OUT_STORE_H_
diff --git a/chromium/components/blocklist/opt_out_blocklist/sql/BUILD.gn b/chromium/components/blocklist/opt_out_blocklist/sql/BUILD.gn
new file mode 100644
index 00000000000..cb270b6888f
--- /dev/null
+++ b/chromium/components/blocklist/opt_out_blocklist/sql/BUILD.gn
@@ -0,0 +1,32 @@
+# Copyright 2018 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.
+
+static_library("opt_out_blocklist_sql") {
+ sources = [
+ "opt_out_store_sql.cc",
+ "opt_out_store_sql.h",
+ ]
+
+ deps = [
+ "//base",
+ "//components/blocklist/opt_out_blocklist:opt_out_blocklist",
+ "//sql",
+ ]
+}
+
+source_set("unit_tests") {
+ testonly = true
+ sources = [ "opt_out_store_sql_unittest.cc" ]
+
+ deps = [
+ ":opt_out_blocklist_sql",
+ "//base",
+ "//base/test:test_support",
+ "//components/blocklist/opt_out_blocklist:opt_out_blocklist",
+ "//sql",
+ "//sql:test_support",
+ "//testing/gmock",
+ "//testing/gtest",
+ ]
+}
diff --git a/chromium/components/blocklist/opt_out_blocklist/sql/DEPS b/chromium/components/blocklist/opt_out_blocklist/sql/DEPS
new file mode 100644
index 00000000000..6fff87d325a
--- /dev/null
+++ b/chromium/components/blocklist/opt_out_blocklist/sql/DEPS
@@ -0,0 +1,3 @@
+include_rules = [
+ "+sql",
+]
diff --git a/chromium/components/blocklist/opt_out_blocklist/sql/opt_out_store_sql.cc b/chromium/components/blocklist/opt_out_blocklist/sql/opt_out_store_sql.cc
new file mode 100644
index 00000000000..f13d2014a9e
--- /dev/null
+++ b/chromium/components/blocklist/opt_out_blocklist/sql/opt_out_store_sql.cc
@@ -0,0 +1,410 @@
+// Copyright 2016 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/blocklist/opt_out_blocklist/sql/opt_out_store_sql.h"
+
+#include <map>
+#include <string>
+#include <utility>
+
+#include "base/bind.h"
+#include "base/bind_helpers.h"
+#include "base/command_line.h"
+#include "base/files/file_util.h"
+#include "base/location.h"
+#include "base/logging.h"
+#include "base/metrics/histogram_functions.h"
+#include "base/metrics/histogram_macros.h"
+#include "base/sequenced_task_runner.h"
+#include "base/single_thread_task_runner.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/threading/thread_task_runner_handle.h"
+#include "components/blocklist/opt_out_blocklist/opt_out_blocklist_data.h"
+#include "sql/database.h"
+#include "sql/recovery.h"
+#include "sql/statement.h"
+#include "sql/transaction.h"
+
+namespace blocklist {
+
+namespace {
+
+// Command line switch to change the entry per host DB size.
+const char kMaxRowsPerHost[] = "max-opt-out-rows-per-host";
+
+// Command line switch to change the DB size.
+const char kMaxRows[] = "max-opt-out-rows";
+
+// Returns the maximum number of table rows allowed per host for the sql
+// opt out store. This is enforced during insertion of new navigation entries.
+int MaxRowsPerHostInOptOutDB() {
+ std::string max_rows =
+ base::CommandLine::ForCurrentProcess()->GetSwitchValueASCII(
+ kMaxRowsPerHost);
+ int value;
+ return base::StringToInt(max_rows, &value) ? value : 32;
+}
+
+// Returns the maximum number of table rows allowed for the blocklist opt out
+// store. This is enforced during load time; thus the database can grow
+// larger than this temporarily.
+int MaxRowsInOptOutDB() {
+ std::string max_rows =
+ base::CommandLine::ForCurrentProcess()->GetSwitchValueASCII(kMaxRows);
+ int value;
+ return base::StringToInt(max_rows, &value) ? value : 3200;
+}
+
+// Table names use a macro instead of a const, so they can be used inline in
+// other SQL statements below.
+
+// The Opt Out table holds entries for hosts that should not use a specified
+// type. Historically, this was named previews_v1.
+#define OPT_OUT_TABLE_NAME "previews_v1"
+
+// The Enabled types table hold the list of enabled types
+// treatments with a version for that enabled treatment. If the version
+// changes or the type becomes disabled, then any entries in the Opt Out
+// table for that treatment type should be cleared. Historically, this was named
+// enabled_previews_v1.
+#define ENABLED_TYPES_TABLE_NAME "enabled_previews_v1"
+
+void CreateSchema(sql::Database* db) {
+ static const char kSqlCreateTable[] =
+ "CREATE TABLE IF NOT EXISTS " OPT_OUT_TABLE_NAME
+ " (host_name VARCHAR NOT NULL,"
+ " time INTEGER NOT NULL,"
+ " opt_out INTEGER NOT NULL,"
+ " type INTEGER NOT NULL,"
+ " PRIMARY KEY(host_name, time DESC, opt_out, type))";
+ if (!db->Execute(kSqlCreateTable))
+ return;
+
+ static const char kSqlCreateEnabledTypeVersionTable[] =
+ "CREATE TABLE IF NOT EXISTS " ENABLED_TYPES_TABLE_NAME
+ " (type INTEGER NOT NULL,"
+ " version INTEGER NOT NULL,"
+ " PRIMARY KEY(type))";
+ if (!db->Execute(kSqlCreateEnabledTypeVersionTable))
+ return;
+}
+
+void DatabaseErrorCallback(sql::Database* db,
+ const base::FilePath& db_path,
+ int extended_error,
+ sql::Statement* stmt) {
+ if (sql::Recovery::ShouldRecover(extended_error)) {
+ // Prevent reentrant calls.
+ db->reset_error_callback();
+
+ // After this call, the |db| handle is poisoned so that future calls will
+ // return errors until the handle is re-opened.
+ sql::Recovery::RecoverDatabase(db, db_path);
+
+ // The DLOG(WARNING) below is intended to draw immediate attention to errors
+ // in newly-written code. Database corruption is generally a result of OS
+ // or hardware issues, not coding errors at the client level, so displaying
+ // the error would probably lead to confusion. The ignored call signals the
+ // test-expectation framework that the error was handled.
+ ignore_result(sql::Database::IsExpectedSqliteError(extended_error));
+ return;
+ }
+}
+
+void InitDatabase(sql::Database* db, base::FilePath path) {
+ // The entry size should be between 11 and 10 + x bytes, where x is the the
+ // length of the host name string in bytes.
+ // The total number of entries per host is bounded at 32, and the total number
+ // of hosts is currently unbounded (but typically expected to be under 100).
+ // Assuming average of 100 bytes per entry, and 100 hosts, the total size will
+ // be 4096 * 78. 250 allows room for extreme cases such as many host names
+ // or very long host names.
+ // The average case should be much smaller as users rarely visit hosts that
+ // are not in their top 20 hosts. It should be closer to 32 * 100 * 20 for
+ // most users, which is about 4096 * 15.
+ // The total size of the database will be capped at 3200 entries.
+ db->set_page_size(4096);
+ db->set_cache_size(250);
+ // TODO(crbug.com/1092101): Migrate to OptOutBlocklist and update any backend
+ // code that may depend on this tag.
+ db->set_histogram_tag("OptOutBlacklist");
+ db->set_exclusive_locking();
+
+ db->set_error_callback(base::BindRepeating(&DatabaseErrorCallback, db, path));
+
+ base::File::Error err;
+ if (!base::CreateDirectoryAndGetError(path.DirName(), &err)) {
+ return;
+ }
+ if (!db->Open(path)) {
+ return;
+ }
+
+ CreateSchema(db);
+}
+
+// Adds a new OptOut entry to the data base.
+void AddEntryToDataBase(sql::Database* db,
+ bool opt_out,
+ const std::string& host_name,
+ int type,
+ base::Time now) {
+ // Adds the new entry.
+ static const char kSqlInsert[] = "INSERT INTO " OPT_OUT_TABLE_NAME
+ " (host_name, time, opt_out, type)"
+ " VALUES "
+ " (?, ?, ?, ?)";
+
+ sql::Statement statement_insert(
+ db->GetCachedStatement(SQL_FROM_HERE, kSqlInsert));
+ statement_insert.BindString(0, host_name);
+ statement_insert.BindInt64(1, (now - base::Time()).InMicroseconds());
+ statement_insert.BindBool(2, opt_out);
+ statement_insert.BindInt(3, type);
+ statement_insert.Run();
+}
+
+// Removes OptOut entries for |host_name| if the per-host row limit is exceeded.
+// Removes OptOut entries if per data base row limit is exceeded.
+void MaybeEvictHostEntryFromDataBase(sql::Database* db,
+ const std::string& host_name) {
+ // Delete the oldest entries if there are more than |MaxRowsPerHostInOptOutDB|
+ // for |host_name|.
+ // DELETE ... LIMIT -1 OFFSET x means delete all but the first x entries.
+ static const char kSqlDeleteByHost[] =
+ "DELETE FROM " OPT_OUT_TABLE_NAME
+ " WHERE ROWID IN"
+ " (SELECT ROWID from " OPT_OUT_TABLE_NAME
+ " WHERE host_name == ?"
+ " ORDER BY time DESC"
+ " LIMIT -1 OFFSET ?)";
+
+ sql::Statement statement_delete_by_host(
+ db->GetCachedStatement(SQL_FROM_HERE, kSqlDeleteByHost));
+ statement_delete_by_host.BindString(0, host_name);
+ statement_delete_by_host.BindInt(1, MaxRowsPerHostInOptOutDB());
+ statement_delete_by_host.Run();
+}
+
+// Deletes every entry for |type|.
+void ClearBlocklistForTypeInDataBase(sql::Database* db, int type) {
+ static const char kSql[] =
+ "DELETE FROM " OPT_OUT_TABLE_NAME " WHERE type == ?";
+ sql::Statement statement(db->GetUniqueStatement(kSql));
+ statement.BindInt(0, type);
+ statement.Run();
+}
+
+// Retrieves the list of previously enabled types with their version from the
+// Enabled table.
+BlocklistData::AllowedTypesAndVersions GetStoredEntries(sql::Database* db) {
+ static const char kSqlLoadEnabledTypesVersions[] =
+ "SELECT type, version FROM " ENABLED_TYPES_TABLE_NAME;
+
+ sql::Statement statement(
+ db->GetUniqueStatement(kSqlLoadEnabledTypesVersions));
+
+ BlocklistData::AllowedTypesAndVersions stored_entries;
+ while (statement.Step()) {
+ int type = statement.ColumnInt(0);
+ int version = statement.ColumnInt(1);
+ stored_entries.insert({type, version});
+ }
+ return stored_entries;
+}
+
+// Adds a newly enabled |type| with its |version| to the Enabled types table.
+void InsertEnabledTypesInDataBase(sql::Database* db, int type, int version) {
+ static const char kSqlInsert[] = "INSERT INTO " ENABLED_TYPES_TABLE_NAME
+ " (type, version)"
+ " VALUES "
+ " (?, ?)";
+
+ sql::Statement statement_insert(db->GetUniqueStatement(kSqlInsert));
+ statement_insert.BindInt(0, type);
+ statement_insert.BindInt(1, version);
+ statement_insert.Run();
+}
+
+// Updates the |version| of an enabled |type| in the Enabled table.
+void UpdateEnabledTypesInDataBase(sql::Database* db, int type, int version) {
+ static const char kSqlUpdate[] = "UPDATE " ENABLED_TYPES_TABLE_NAME
+ " SET version = ?"
+ " WHERE type = ?";
+
+ sql::Statement statement_update(
+ db->GetCachedStatement(SQL_FROM_HERE, kSqlUpdate));
+ statement_update.BindInt(0, version);
+ statement_update.BindInt(1, type);
+ statement_update.Run();
+}
+
+// Checks the current set of enabled types (with their current version)
+// and where a type is now disabled or has a different version, cleans up
+// any associated blocklist entries.
+void CheckAndReconcileEnabledTypesWithDataBase(
+ sql::Database* db,
+ const BlocklistData::AllowedTypesAndVersions& allowed_types) {
+ BlocklistData::AllowedTypesAndVersions stored_entries = GetStoredEntries(db);
+
+ for (auto enabled_it : allowed_types) {
+ int type = enabled_it.first;
+ int current_version = enabled_it.second;
+ auto stored_it = stored_entries.find(type);
+ if (stored_it == stored_entries.end()) {
+ InsertEnabledTypesInDataBase(db, type, current_version);
+ } else {
+ if (stored_it->second != current_version) {
+ DCHECK_GE(current_version, stored_it->second);
+ ClearBlocklistForTypeInDataBase(db, type);
+ UpdateEnabledTypesInDataBase(db, type, current_version);
+ }
+ }
+ }
+ // Do not delete types that are not in |allowed_types|. They will get cleaned
+ // up eventually when they expire if the type is truly gone. However, if the
+ // type has been removed temporarily (like in a holdback experiment), then
+ // it'll still be around for the next time it is used.
+}
+
+void LoadBlockListFromDataBase(
+ sql::Database* db,
+ std::unique_ptr<BlocklistData> blocklist_data,
+ scoped_refptr<base::SingleThreadTaskRunner> runner,
+ LoadBlockListCallback callback) {
+ // First handle any update needed wrt enabled types and their versions.
+ CheckAndReconcileEnabledTypesWithDataBase(db,
+ blocklist_data->allowed_types());
+
+ // Gets the table sorted by host and time. Limits the number of hosts using
+ // most recent opt_out time as the limiting function. Sorting is free due to
+ // the table structure, and it improves performance in the loop below.
+ static const char kSql[] =
+ "SELECT host_name, time, opt_out, type"
+ " FROM " OPT_OUT_TABLE_NAME " ORDER BY host_name, time DESC";
+
+ sql::Statement statement(db->GetUniqueStatement(kSql));
+
+ int count = 0;
+ while (statement.Step()) {
+ ++count;
+ blocklist_data->AddEntry(statement.ColumnString(0), statement.ColumnBool(2),
+ statement.ColumnInt64(3),
+ base::Time() + base::TimeDelta::FromMicroseconds(
+ statement.ColumnInt64(1)),
+ true);
+ }
+
+ if (count > MaxRowsInOptOutDB()) {
+ // Delete the oldest entries if there are more than |kMaxEntriesInDB|.
+ // DELETE ... LIMIT -1 OFFSET x means delete all but the first x entries.
+ static const char kSqlDeleteByDBSize[] =
+ "DELETE FROM " OPT_OUT_TABLE_NAME
+ " WHERE ROWID IN"
+ " (SELECT ROWID from " OPT_OUT_TABLE_NAME
+ " ORDER BY time DESC"
+ " LIMIT -1 OFFSET ?)";
+
+ sql::Statement statement_delete(
+ db->GetCachedStatement(SQL_FROM_HERE, kSqlDeleteByDBSize));
+ statement_delete.BindInt(0, MaxRowsInOptOutDB());
+ statement_delete.Run();
+ }
+
+ runner->PostTask(FROM_HERE, base::BindOnce(std::move(callback),
+ std::move(blocklist_data)));
+}
+
+// Synchronous implementations, these are run on the background thread
+// and actually do the work to access the SQL data base.
+void LoadBlockListSync(sql::Database* db,
+ const base::FilePath& path,
+ std::unique_ptr<BlocklistData> blocklist_data,
+ scoped_refptr<base::SingleThreadTaskRunner> runner,
+ LoadBlockListCallback callback) {
+ if (!db->is_open())
+ InitDatabase(db, path);
+
+ LoadBlockListFromDataBase(db, std::move(blocklist_data), runner,
+ std::move(callback));
+}
+
+// Deletes every row in the table that has entry time between |begin_time| and
+// |end_time|.
+void ClearBlockListSync(sql::Database* db,
+ base::Time begin_time,
+ base::Time end_time) {
+ static const char kSql[] =
+ "DELETE FROM " OPT_OUT_TABLE_NAME " WHERE time >= ? and time <= ?";
+
+ sql::Statement statement(db->GetUniqueStatement(kSql));
+ statement.BindInt64(0, (begin_time - base::Time()).InMicroseconds());
+ statement.BindInt64(1, (end_time - base::Time()).InMicroseconds());
+ statement.Run();
+}
+
+void AddEntrySync(bool opt_out,
+ const std::string& host_name,
+ int type,
+ base::Time now,
+ sql::Database* db) {
+ sql::Transaction transaction(db);
+ if (!transaction.Begin())
+ return;
+ AddEntryToDataBase(db, opt_out, host_name, type, now);
+ MaybeEvictHostEntryFromDataBase(db, host_name);
+ transaction.Commit();
+}
+
+} // namespace
+
+OptOutStoreSQL::OptOutStoreSQL(
+ scoped_refptr<base::SingleThreadTaskRunner> io_task_runner,
+ scoped_refptr<base::SequencedTaskRunner> background_task_runner,
+ const base::FilePath& path)
+ : io_task_runner_(io_task_runner),
+ background_task_runner_(background_task_runner),
+ db_file_path_(path) {}
+
+OptOutStoreSQL::~OptOutStoreSQL() {
+ DCHECK(io_task_runner_->BelongsToCurrentThread());
+ if (db_) {
+ background_task_runner_->DeleteSoon(FROM_HERE, db_.release());
+ }
+}
+
+void OptOutStoreSQL::AddEntry(bool opt_out,
+ const std::string& host_name,
+ int type,
+ base::Time now) {
+ DCHECK(io_task_runner_->BelongsToCurrentThread());
+ DCHECK(db_);
+ background_task_runner_->PostTask(
+ FROM_HERE,
+ base::BindOnce(&AddEntrySync, opt_out, host_name, type, now, db_.get()));
+}
+
+void OptOutStoreSQL::ClearBlockList(base::Time begin_time,
+ base::Time end_time) {
+ DCHECK(io_task_runner_->BelongsToCurrentThread());
+ DCHECK(db_);
+ background_task_runner_->PostTask(
+ FROM_HERE,
+ base::BindOnce(&ClearBlockListSync, db_.get(), begin_time, end_time));
+}
+
+void OptOutStoreSQL::LoadBlockList(
+ std::unique_ptr<BlocklistData> blocklist_data,
+ LoadBlockListCallback callback) {
+ DCHECK(io_task_runner_->BelongsToCurrentThread());
+ if (!db_)
+ db_ = std::make_unique<sql::Database>();
+ background_task_runner_->PostTask(
+ FROM_HERE,
+ base::BindOnce(&LoadBlockListSync, db_.get(), db_file_path_,
+ std::move(blocklist_data),
+ base::ThreadTaskRunnerHandle::Get(), std::move(callback)));
+}
+
+} // namespace blocklist
diff --git a/chromium/components/blocklist/opt_out_blocklist/sql/opt_out_store_sql.h b/chromium/components/blocklist/opt_out_blocklist/sql/opt_out_store_sql.h
new file mode 100644
index 00000000000..4e2c90ae264
--- /dev/null
+++ b/chromium/components/blocklist/opt_out_blocklist/sql/opt_out_store_sql.h
@@ -0,0 +1,68 @@
+// Copyright 2016 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.
+
+#ifndef COMPONENTS_BLOCKLIST_OPT_OUT_BLOCKLIST_SQL_OPT_OUT_STORE_SQL_H_
+#define COMPONENTS_BLOCKLIST_OPT_OUT_BLOCKLIST_SQL_OPT_OUT_STORE_SQL_H_
+
+#include <stdint.h>
+
+#include <memory>
+#include <string>
+#include <vector>
+
+#include "base/files/file_path.h"
+#include "base/macros.h"
+#include "base/threading/thread_checker.h"
+#include "base/time/time.h"
+#include "components/blocklist/opt_out_blocklist/opt_out_store.h"
+
+namespace base {
+class SequencedTaskRunner;
+class SingleThreadTaskRunner;
+} // namespace base
+
+namespace sql {
+class Database;
+} // namespace sql
+
+namespace blocklist {
+
+// OptOutStoreSQL is an instance of OptOutStore
+// which is implemented using a SQLite database.
+class OptOutStoreSQL : public OptOutStore {
+ public:
+ OptOutStoreSQL(
+ scoped_refptr<base::SingleThreadTaskRunner> io_task_runner,
+ scoped_refptr<base::SequencedTaskRunner> background_task_runner,
+ const base::FilePath& database_dir);
+ ~OptOutStoreSQL() override;
+
+ // OptOutStore implementation:
+ void AddEntry(bool opt_out,
+ const std::string& host_name,
+ int type,
+ base::Time now) override;
+ void ClearBlockList(base::Time begin_time, base::Time end_time) override;
+ void LoadBlockList(std::unique_ptr<BlocklistData> blocklist_data,
+ LoadBlockListCallback callback) override;
+
+ private:
+ // Thread this object is accessed on.
+ scoped_refptr<base::SingleThreadTaskRunner> io_task_runner_;
+
+ // Background thread where all SQL access should be run.
+ scoped_refptr<base::SequencedTaskRunner> background_task_runner_;
+
+ // Path to the database on disk.
+ const base::FilePath db_file_path_;
+
+ // SQL connection to the SQLite database.
+ std::unique_ptr<sql::Database> db_;
+
+ DISALLOW_COPY_AND_ASSIGN(OptOutStoreSQL);
+};
+
+} // namespace blocklist
+
+#endif // COMPONENTS_BLOCKLIST_OPT_OUT_BLOCKLIST_SQL_OPT_OUT_STORE_SQL_H_
diff --git a/chromium/components/blocklist/opt_out_blocklist/sql/opt_out_store_sql_unittest.cc b/chromium/components/blocklist/opt_out_blocklist/sql/opt_out_store_sql_unittest.cc
new file mode 100644
index 00000000000..4aac3cc5a9a
--- /dev/null
+++ b/chromium/components/blocklist/opt_out_blocklist/sql/opt_out_store_sql_unittest.cc
@@ -0,0 +1,310 @@
+// Copyright 2018 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/blocklist/opt_out_blocklist/sql/opt_out_store_sql.h"
+
+#include <map>
+#include <memory>
+#include <string>
+
+#include "base/bind.h"
+#include "base/command_line.h"
+#include "base/files/file_path.h"
+#include "base/files/file_util.h"
+#include "base/files/scoped_temp_dir.h"
+#include "base/run_loop.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/test/metrics/histogram_tester.h"
+#include "base/test/simple_test_clock.h"
+#include "base/test/task_environment.h"
+#include "base/threading/thread_task_runner_handle.h"
+#include "base/time/time.h"
+#include "components/blocklist/opt_out_blocklist/opt_out_blocklist_data.h"
+#include "components/blocklist/opt_out_blocklist/opt_out_blocklist_item.h"
+#include "components/blocklist/opt_out_blocklist/opt_out_store.h"
+#include "sql/test/test_helpers.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace blocklist {
+
+namespace {
+
+const base::FilePath::CharType kOptOutFilename[] = FILE_PATH_LITERAL("OptOut");
+
+} // namespace
+
+class OptOutStoreSQLTest : public testing::Test {
+ public:
+ OptOutStoreSQLTest() {}
+ ~OptOutStoreSQLTest() override {}
+
+ // Called when |store_| is done loading.
+ void OnLoaded(std::unique_ptr<BlocklistData> blocklist_data) {
+ blocklist_data_ = std::move(blocklist_data);
+ }
+
+ // Initializes the store and get the data from it.
+ void Load() {
+ // Choose reasonable constants.
+ std::unique_ptr<BlocklistData> data = std::make_unique<BlocklistData>(
+ std::make_unique<BlocklistData::Policy>(base::TimeDelta::FromMinutes(5),
+ 1, 1),
+ std::make_unique<BlocklistData::Policy>(base::TimeDelta::FromDays(30),
+ 10, 6u),
+ std::make_unique<BlocklistData::Policy>(base::TimeDelta::FromDays(30),
+ 4, 2u),
+ nullptr, 10, allowed_types_);
+
+ store_->LoadBlockList(
+ std::move(data),
+ base::BindOnce(&OptOutStoreSQLTest::OnLoaded, base::Unretained(this)));
+ base::RunLoop().RunUntilIdle();
+ }
+
+ // Destroys the database connection and |store_|.
+ void DestroyStore() {
+ store_.reset();
+ base::RunLoop().RunUntilIdle();
+ }
+
+ // Creates a store that operates on one thread.
+ void Create() {
+ store_ = std::make_unique<OptOutStoreSQL>(
+ base::ThreadTaskRunnerHandle::Get(),
+ base::ThreadTaskRunnerHandle::Get(),
+ temp_dir_.GetPath().Append(kOptOutFilename));
+ }
+
+ // Sets up initialization of |store_|.
+ void CreateAndLoad() {
+ Create();
+ Load();
+ }
+
+ void SetEnabledTypes(BlocklistData::AllowedTypesAndVersions allowed_types) {
+ allowed_types_ = std::move(allowed_types);
+ }
+
+ // Creates a directory for the test.
+ void SetUp() override { ASSERT_TRUE(temp_dir_.CreateUniqueTempDir()); }
+
+ // Delete |store_| if it hasn't been deleted.
+ void TearDown() override { DestroyStore(); }
+
+ protected:
+ base::test::SingleThreadTaskEnvironment task_environment_;
+
+ // The backing SQL store.
+ std::unique_ptr<OptOutStoreSQL> store_;
+
+ // The map returned from |store_|.
+ std::unique_ptr<BlocklistData> blocklist_data_;
+
+ // The directory for the database.
+ base::ScopedTempDir temp_dir_;
+
+ private:
+ BlocklistData::AllowedTypesAndVersions allowed_types_;
+};
+
+TEST_F(OptOutStoreSQLTest, TestErrorRecovery) {
+ // Creates the database and corrupt to test the recovery method.
+ std::string test_host = "host.com";
+ BlocklistData::AllowedTypesAndVersions allowed_types;
+ allowed_types.insert({1, 0});
+ SetEnabledTypes(std::move(allowed_types));
+ CreateAndLoad();
+ store_->AddEntry(true, test_host, 1, base::Time::Now());
+ base::RunLoop().RunUntilIdle();
+ DestroyStore();
+
+ // Corrupts the database by adjusting the header size.
+ EXPECT_TRUE(sql::test::CorruptSizeInHeader(
+ temp_dir_.GetPath().Append(kOptOutFilename)));
+ base::RunLoop().RunUntilIdle();
+
+ allowed_types.clear();
+ allowed_types.insert({1, 0});
+ SetEnabledTypes(std::move(allowed_types));
+ CreateAndLoad();
+ // The data should be recovered.
+ EXPECT_EQ(1U, blocklist_data_->block_list_item_host_map().size());
+ const auto& iter =
+ blocklist_data_->block_list_item_host_map().find(test_host);
+
+ EXPECT_NE(blocklist_data_->block_list_item_host_map().end(), iter);
+ EXPECT_EQ(1U, iter->second.OptOutRecordsSizeForTesting());
+}
+
+TEST_F(OptOutStoreSQLTest, TestPersistance) {
+ // Tests if data is stored as expected in the SQLite database.
+ std::string test_host = "host.com";
+ BlocklistData::AllowedTypesAndVersions allowed_types;
+ allowed_types.insert({1, 0});
+ SetEnabledTypes(std::move(allowed_types));
+ CreateAndLoad();
+ base::Time now = base::Time::Now();
+ store_->AddEntry(true, test_host, 1, now);
+ base::RunLoop().RunUntilIdle();
+
+ // Replace the store effectively destroying the current one and forcing it
+ // to write its data to disk.
+ DestroyStore();
+
+ // Reload and test for persistence
+ allowed_types.clear();
+ allowed_types.insert({1, 0});
+ SetEnabledTypes(std::move(allowed_types));
+ CreateAndLoad();
+ EXPECT_EQ(1U, blocklist_data_->block_list_item_host_map().size());
+ const auto& iter =
+ blocklist_data_->block_list_item_host_map().find(test_host);
+
+ EXPECT_NE(blocklist_data_->block_list_item_host_map().end(), iter);
+ EXPECT_EQ(1U, iter->second.OptOutRecordsSizeForTesting());
+ EXPECT_EQ(now, iter->second.most_recent_opt_out_time().value());
+}
+
+TEST_F(OptOutStoreSQLTest, TestMaxRows) {
+ // Tests that the number of rows are culled down to the row limit at each
+ // load.
+ std::string test_host_a = "host_a.com";
+ std::string test_host_b = "host_b.com";
+ std::string test_host_c = "host_c.com";
+ base::CommandLine* command_line = base::CommandLine::ForCurrentProcess();
+ size_t row_limit = 2;
+ std::string row_limit_string = base::NumberToString(row_limit);
+ command_line->AppendSwitchASCII("max-opt-out-rows", row_limit_string);
+ BlocklistData::AllowedTypesAndVersions allowed_types;
+ allowed_types.insert({1, 0});
+ SetEnabledTypes(std::move(allowed_types));
+ CreateAndLoad();
+ base::SimpleTestClock clock;
+
+ // Create three different entries with different hosts.
+ store_->AddEntry(true, test_host_a, 1, clock.Now());
+ clock.Advance(base::TimeDelta::FromSeconds(1));
+
+ store_->AddEntry(true, test_host_b, 1, clock.Now());
+ base::Time host_b_time = clock.Now();
+ clock.Advance(base::TimeDelta::FromSeconds(1));
+
+ store_->AddEntry(false, test_host_c, 1, clock.Now());
+ base::RunLoop().RunUntilIdle();
+ // Replace the store effectively destroying the current one and forcing it
+ // to write its data to disk.
+ DestroyStore();
+
+ // Reload and test for persistence
+ allowed_types.clear();
+ allowed_types.insert({1, 0});
+ SetEnabledTypes(std::move(allowed_types));
+ CreateAndLoad();
+ // The delete happens after the load, so it is possible to load more than
+ // |row_limit| into the in memory map.
+ EXPECT_EQ(row_limit + 1, blocklist_data_->block_list_item_host_map().size());
+
+ DestroyStore();
+ allowed_types.clear();
+ allowed_types.insert({1, 0});
+ SetEnabledTypes(std::move(allowed_types));
+ CreateAndLoad();
+
+ EXPECT_EQ(row_limit, blocklist_data_->block_list_item_host_map().size());
+ const auto& iter_host_b =
+ blocklist_data_->block_list_item_host_map().find(test_host_b);
+ const auto& iter_host_c =
+ blocklist_data_->block_list_item_host_map().find(test_host_c);
+
+ EXPECT_EQ(blocklist_data_->block_list_item_host_map().end(),
+ blocklist_data_->block_list_item_host_map().find(test_host_a));
+ EXPECT_NE(blocklist_data_->block_list_item_host_map().end(), iter_host_b);
+ EXPECT_NE(blocklist_data_->block_list_item_host_map().end(), iter_host_c);
+ EXPECT_EQ(host_b_time,
+ iter_host_b->second.most_recent_opt_out_time().value());
+ EXPECT_EQ(1U, iter_host_b->second.OptOutRecordsSizeForTesting());
+}
+
+TEST_F(OptOutStoreSQLTest, TestMaxRowsPerHost) {
+ // Tests that each host is limited to |row_limit| rows.
+ std::string test_host = "host.com";
+ base::CommandLine* command_line = base::CommandLine::ForCurrentProcess();
+ size_t row_limit = 2;
+ std::string row_limit_string = base::NumberToString(row_limit);
+ command_line->AppendSwitchASCII("max-opt-out-rows-per-host",
+ row_limit_string);
+ BlocklistData::AllowedTypesAndVersions allowed_types;
+ allowed_types.insert({1, 0});
+ SetEnabledTypes(std::move(allowed_types));
+ CreateAndLoad();
+ base::SimpleTestClock clock;
+
+ base::Time last_opt_out_time;
+ for (size_t i = 0; i < row_limit; i++) {
+ store_->AddEntry(true, test_host, 1, clock.Now());
+ last_opt_out_time = clock.Now();
+ clock.Advance(base::TimeDelta::FromSeconds(1));
+ }
+
+ clock.Advance(base::TimeDelta::FromSeconds(1));
+ store_->AddEntry(false, test_host, 1, clock.Now());
+
+ base::RunLoop().RunUntilIdle();
+ // Replace the store effectively destroying the current one and forcing it
+ // to write its data to disk.
+ DestroyStore();
+
+ // Reload and test for persistence.
+ allowed_types.clear();
+ allowed_types.insert({1, 0});
+ SetEnabledTypes(std::move(allowed_types));
+ CreateAndLoad();
+
+ EXPECT_EQ(1U, blocklist_data_->block_list_item_host_map().size());
+ const auto& iter =
+ blocklist_data_->block_list_item_host_map().find(test_host);
+
+ EXPECT_NE(blocklist_data_->block_list_item_host_map().end(), iter);
+ EXPECT_EQ(last_opt_out_time, iter->second.most_recent_opt_out_time().value());
+ EXPECT_EQ(row_limit, iter->second.OptOutRecordsSizeForTesting());
+ clock.Advance(base::TimeDelta::FromSeconds(1));
+ // If both entries' opt out states are stored correctly, then this should not
+ // be block listed.
+ EXPECT_FALSE(iter->second.IsBlockListed(clock.Now()));
+}
+
+TEST_F(OptOutStoreSQLTest, TestTypesVersionUpdateClearsBlocklistEntry) {
+ // Tests if data is cleared for new version of type.
+ std::string test_host = "host.com";
+ BlocklistData::AllowedTypesAndVersions allowed_types;
+ allowed_types.insert({1, 1});
+ SetEnabledTypes(std::move(allowed_types));
+ CreateAndLoad();
+ base::Time now = base::Time::Now();
+ store_->AddEntry(true, test_host, 1, now);
+ base::RunLoop().RunUntilIdle();
+
+ // Force data write to database then reload it and verify block list entry
+ // is present.
+ DestroyStore();
+ allowed_types.clear();
+ allowed_types.insert({1, 1});
+ SetEnabledTypes(std::move(allowed_types));
+ CreateAndLoad();
+ const auto& iter =
+ blocklist_data_->block_list_item_host_map().find(test_host);
+ EXPECT_NE(blocklist_data_->block_list_item_host_map().end(), iter);
+ EXPECT_EQ(1U, iter->second.OptOutRecordsSizeForTesting());
+
+ DestroyStore();
+ allowed_types.clear();
+ allowed_types.insert({1, 2});
+ SetEnabledTypes(std::move(allowed_types));
+ CreateAndLoad();
+ const auto& iter2 =
+ blocklist_data_->block_list_item_host_map().find(test_host);
+ EXPECT_EQ(blocklist_data_->block_list_item_host_map().end(), iter2);
+}
+
+} // namespace blocklist