blob: 639c298e641f5d9cfe3a763644a6966764db4ce4 [file] [log] [blame]
// Copyright 2021 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/autofill_assistant/browser/starter_heuristic.h"
#include "base/bind.h"
#include "base/callback.h"
#include "base/json/json_reader.h"
#include "base/logging.h"
#include "base/metrics/field_trial_params.h"
#include "base/task/task_traits.h"
#include "base/task/thread_pool.h"
#include "base/values.h"
#include "components/autofill_assistant/browser/features.h"
#include "components/autofill_assistant/browser/intent_strings.h"
#include "components/autofill_assistant/browser/url_utils.h"
#include "components/url_matcher/url_matcher_constants.h"
namespace autofill_assistant {
// String parameter containing the JSON-encoded parameter dictionary.
const char kJsonParameterDictKey[] = "json_parameters";
constexpr base::FeatureParam<std::string> kFieldTrialParams{
&features::kAutofillAssistantUrlHeuristics, kJsonParameterDictKey, ""};
// Array of strings, containing the list of globally denylisted domains.
const char kDenylistedDomainsKey[] = "denylistedDomains";
// Array of heuristics, each with its own intent and conditions.
const char kHeuristicsKey[] = "heuristics";
// String. The intent associated with a specific heuristic.
const char kHeuristicIntentKey[] = "intent";
// UrlFilter dictionary. The URL condition set defining a specific intent's
// heuristic. See also components/url_matcher/url_matcher_factory.h
const char kHeuristicUrlConditionSetKey[] = "conditionSet";
StarterHeuristic::StarterHeuristic() {
InitFromTrialParams();
}
StarterHeuristic::~StarterHeuristic() = default;
void StarterHeuristic::InitFromTrialParams() {
DCHECK(matcher_id_to_intent_map_.empty())
<< "Called after already initialized";
std::string parameters = kFieldTrialParams.Get();
if (parameters.empty()) {
return;
}
absl::optional<base::Value> dict = base::JSONReader::Read(parameters);
if (!dict || !dict->is_dict()) {
VLOG(1) << "Failed to parse field trial params as JSON object: "
<< parameters;
if (VLOG_IS_ON(1)) {
auto err = base::JSONReader::ReadAndReturnValueWithError(parameters);
VLOG(1) << err.error_message << ", line: " << err.error_line
<< ", col: " << err.error_column;
}
return;
}
// Read mandatory list of heuristics.
auto* heuristics = dict->FindListKey(kHeuristicsKey);
if (heuristics == nullptr || !heuristics->is_list()) {
VLOG(1) << "Field trial params did not contain heuristics";
return;
}
url_matcher::URLMatcherConditionSet::Vector condition_sets;
base::flat_map<url_matcher::URLMatcherConditionSet::ID, std::string> mapping;
url_matcher::URLMatcherConditionSet::ID next_condition_set_id = 0;
for (const auto& heuristic : heuristics->GetList()) {
auto* intent =
heuristic.FindKeyOfType(kHeuristicIntentKey, base::Value::Type::STRING);
auto* url_conditions = heuristic.FindKeyOfType(
kHeuristicUrlConditionSetKey, base::Value::Type::DICTIONARY);
if (!intent || !url_conditions) {
VLOG(1) << "Heuristic did not contain intent or url_conditions";
return;
}
std::string error;
const auto& url_conditions_dict =
base::Value::AsDictionaryValue(*url_conditions);
condition_sets.emplace_back(
url_matcher::URLMatcherFactory::CreateFromURLFilterDictionary(
url_matcher_.condition_factory(), &url_conditions_dict,
next_condition_set_id, &error));
if (!error.empty()) {
VLOG(1) << "Error pasing url conditions: " << error;
return;
}
mapping[next_condition_set_id++] = *intent->GetIfString();
}
// Read optional list of denylisted domains.
auto* denylisted_domains_value = dict->FindListKey(kDenylistedDomainsKey);
base::flat_set<std::string> denylisted_domains;
if (denylisted_domains_value != nullptr) {
for (const auto& domain : denylisted_domains_value->GetList()) {
if (!domain.is_string()) {
VLOG(1) << "Invalid type for denylisted domain";
return;
}
denylisted_domains.insert(*domain.GetIfString());
}
}
denylisted_domains_ = std::move(denylisted_domains);
url_matcher_.AddConditionSets(condition_sets);
matcher_id_to_intent_map_ = std::move(mapping);
}
absl::optional<std::string> StarterHeuristic::IsHeuristicMatch(
const GURL& url) const {
if (matcher_id_to_intent_map_.empty() || !url.is_valid()) {
return absl::nullopt;
}
if (denylisted_domains_.count(
url_utils::GetOrganizationIdentifyingDomain(url)) > 0) {
return absl::nullopt;
}
std::set<url_matcher::URLMatcherConditionSet::ID> matches =
url_matcher_.MatchURL(url);
if (matches.empty()) {
return absl::nullopt;
}
// Return the first matching intent.
auto first_matching_it = matcher_id_to_intent_map_.find(*matches.begin());
if (first_matching_it == matcher_id_to_intent_map_.end()) {
DCHECK(false);
return absl::nullopt;
}
return first_matching_it->second;
}
void StarterHeuristic::RunHeuristicAsync(
const GURL& url,
base::OnceCallback<void(absl::optional<std::string> intent)> callback)
const {
base::ThreadPool::PostTaskAndReplyWithResult(
FROM_HERE, {base::TaskShutdownBehavior::SKIP_ON_SHUTDOWN},
base::BindOnce(&StarterHeuristic::IsHeuristicMatch,
base::RetainedRef(this), url),
std::move(callback));
}
} // namespace autofill_assistant