Commit 11a2ea0b authored by Clemens Arbesser's avatar Clemens Arbesser Committed by Commit Bot

[Autofill Assistant] Adds the native coordinator for trigger scripts.

This is currently not yet wired up to the android UI, but is otherwise
fully functional. There are some smaller follow-ups that I extracted
from this CL:
- Update to metrics enums
- Wiring it up to android

Bug: b/171776026
Change-Id: I7a05d97ccabb8c2bbaa3bb574d74772e9c4dbfb0
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2517696
Commit-Queue: Clemens Arbesser <arbesser@google.com>
Reviewed-by: default avatarMarian Fechete <marianfe@google.com>
Cr-Commit-Position: refs/heads/master@{#825274}
parent 05f9f025
...@@ -354,6 +354,10 @@ source_set("unit_tests") { ...@@ -354,6 +354,10 @@ source_set("unit_tests") {
"test_util.h", "test_util.h",
"trigger_context_unittest.cc", "trigger_context_unittest.cc",
"trigger_scripts/dynamic_trigger_conditions_unittest.cc", "trigger_scripts/dynamic_trigger_conditions_unittest.cc",
"trigger_scripts/mock_dynamic_trigger_conditions.cc",
"trigger_scripts/mock_dynamic_trigger_conditions.h",
"trigger_scripts/mock_static_trigger_conditions.cc",
"trigger_scripts/mock_static_trigger_conditions.h",
"trigger_scripts/static_trigger_conditions_unittest.cc", "trigger_scripts/static_trigger_conditions_unittest.cc",
"trigger_scripts/trigger_script_coordinator_unittest.cc", "trigger_scripts/trigger_script_coordinator_unittest.cc",
"trigger_scripts/trigger_script_unittest.cc", "trigger_scripts/trigger_script_unittest.cc",
......
...@@ -286,11 +286,32 @@ bool ProtocolUtils::ParseActions(ActionDelegate* delegate, ...@@ -286,11 +286,32 @@ bool ProtocolUtils::ParseActions(ActionDelegate* delegate,
return true; return true;
} }
// static
std::string ProtocolUtils::CreateGetTriggerScriptsRequest(
const GURL& url,
const ClientContextProto& client_context,
const std::map<std::string, std::string>& script_parameters) {
GetTriggerScriptsRequestProto request_proto;
request_proto.set_url(url.spec());
*request_proto.mutable_client_context() = client_context;
if (!script_parameters.empty()) {
AppendScriptParametersToRepeatedField(
script_parameters, request_proto.mutable_debug_script_parameters());
}
std::string serialized_request_proto;
bool success = request_proto.SerializeToString(&serialized_request_proto);
DCHECK(success);
return serialized_request_proto;
}
// static // static
bool ProtocolUtils::ParseTriggerScripts( bool ProtocolUtils::ParseTriggerScripts(
const std::string& response, const std::string& response,
std::vector<std::unique_ptr<TriggerScript>>* trigger_scripts) { std::vector<std::unique_ptr<TriggerScript>>* trigger_scripts,
std::vector<std::string>* additional_allowed_domains) {
DCHECK(trigger_scripts); DCHECK(trigger_scripts);
DCHECK(additional_allowed_domains);
GetTriggerScriptsResponseProto response_proto; GetTriggerScriptsResponseProto response_proto;
if (!response_proto.ParseFromString(response)) { if (!response_proto.ParseFromString(response)) {
...@@ -302,6 +323,11 @@ bool ProtocolUtils::ParseTriggerScripts( ...@@ -302,6 +323,11 @@ bool ProtocolUtils::ParseTriggerScripts(
trigger_scripts->emplace_back( trigger_scripts->emplace_back(
std::make_unique<TriggerScript>(trigger_script_proto)); std::make_unique<TriggerScript>(trigger_script_proto));
} }
for (const auto& allowed_domain :
response_proto.additional_allowed_domains()) {
additional_allowed_domains->emplace_back(allowed_domain);
}
return true; return true;
} }
......
...@@ -54,6 +54,12 @@ class ProtocolUtils { ...@@ -54,6 +54,12 @@ class ProtocolUtils {
const RoundtripTimingStats& timing_stats, const RoundtripTimingStats& timing_stats,
const ClientContextProto& client_context); const ClientContextProto& client_context);
// Create request to get the available trigger scripts for |url|.
static std::string CreateGetTriggerScriptsRequest(
const GURL& url,
const ClientContextProto& client_context,
const std::map<std::string, std::string>& script_parameters);
// Create an action from the |action|. // Create an action from the |action|.
static std::unique_ptr<Action> CreateAction(ActionDelegate* delegate, static std::unique_ptr<Action> CreateAction(ActionDelegate* delegate,
const ActionProto& action); const ActionProto& action);
...@@ -79,7 +85,8 @@ class ProtocolUtils { ...@@ -79,7 +85,8 @@ class ProtocolUtils {
// |trigger_scripts|. Returns false if parsing failed, else true. // |trigger_scripts|. Returns false if parsing failed, else true.
static bool ParseTriggerScripts( static bool ParseTriggerScripts(
const std::string& response, const std::string& response,
std::vector<std::unique_ptr<TriggerScript>>* trigger_scripts); std::vector<std::unique_ptr<TriggerScript>>* trigger_scripts,
std::vector<std::string>* additional_allowed_domains);
private: private:
// To avoid instantiate this class by accident. // To avoid instantiate this class by accident.
......
...@@ -327,19 +327,35 @@ TEST_F(ProtocolUtilsTest, ParseActionsUpdateScriptListFullFeatured) { ...@@ -327,19 +327,35 @@ TEST_F(ProtocolUtilsTest, ParseActionsUpdateScriptListFullFeatured) {
TEST_F(ProtocolUtilsTest, ParseTriggerScriptsParseError) { TEST_F(ProtocolUtilsTest, ParseTriggerScriptsParseError) {
std::vector<std::unique_ptr<TriggerScript>> trigger_scripts; std::vector<std::unique_ptr<TriggerScript>> trigger_scripts;
EXPECT_FALSE(ProtocolUtils::ParseTriggerScripts("invalid", &trigger_scripts)); std::vector<std::string> additional_allowed_domains;
EXPECT_FALSE(ProtocolUtils::ParseTriggerScripts("invalid", &trigger_scripts,
&additional_allowed_domains));
EXPECT_TRUE(trigger_scripts.empty()); EXPECT_TRUE(trigger_scripts.empty());
} }
TEST_F(ProtocolUtilsTest, CreateGetTriggerScriptsRequest) {
std::map<std::string, std::string> parameters = {{"key_a", "value_a"},
{"key_b", "value_b"}};
GetTriggerScriptsRequestProto request;
EXPECT_TRUE(
request.ParseFromString(ProtocolUtils::CreateGetTriggerScriptsRequest(
GURL("http://example.com/"), client_context_proto_, parameters)));
AssertClientContext(request.client_context());
AssertScriptParameters(request.debug_script_parameters(), parameters);
EXPECT_EQ("http://example.com/", request.url());
}
TEST_F(ProtocolUtilsTest, ParseTriggerScriptsValid) { TEST_F(ProtocolUtilsTest, ParseTriggerScriptsValid) {
GetTriggerScriptsResponseProto proto; GetTriggerScriptsResponseProto proto;
proto.add_additional_allowed_domains("example.com");
proto.add_additional_allowed_domains("other-example.com");
TriggerScriptProto trigger_script_1; TriggerScriptProto trigger_script_1;
*trigger_script_1.mutable_trigger_condition()->mutable_selector() = *trigger_script_1.mutable_trigger_condition()->mutable_selector() =
ToSelectorProto("fake_element_1"); ToSelectorProto("fake_element_1");
TriggerScriptProto trigger_script_2; TriggerScriptProto trigger_script_2;
trigger_script_2.set_on_trigger_condition_no_longer_true(
TriggerScriptProto::CANCEL_SESSION);
*proto.add_trigger_scripts() = trigger_script_1; *proto.add_trigger_scripts() = trigger_script_1;
*proto.add_trigger_scripts() = trigger_script_2; *proto.add_trigger_scripts() = trigger_script_2;
...@@ -348,12 +364,16 @@ TEST_F(ProtocolUtilsTest, ParseTriggerScriptsValid) { ...@@ -348,12 +364,16 @@ TEST_F(ProtocolUtilsTest, ParseTriggerScriptsValid) {
proto.SerializeToString(&proto_str); proto.SerializeToString(&proto_str);
std::vector<std::unique_ptr<TriggerScript>> trigger_scripts; std::vector<std::unique_ptr<TriggerScript>> trigger_scripts;
EXPECT_TRUE(ProtocolUtils::ParseTriggerScripts(proto_str, &trigger_scripts)); std::vector<std::string> additional_allowed_domains;
EXPECT_TRUE(ProtocolUtils::ParseTriggerScripts(proto_str, &trigger_scripts,
&additional_allowed_domains));
EXPECT_THAT( EXPECT_THAT(
trigger_scripts, trigger_scripts,
ElementsAre( ElementsAre(
Pointee(Property(&TriggerScript::AsProto, Eq(trigger_script_1))), Pointee(Property(&TriggerScript::AsProto, Eq(trigger_script_1))),
Pointee(Property(&TriggerScript::AsProto, Eq(trigger_script_2))))); Pointee(Property(&TriggerScript::AsProto, Eq(trigger_script_2)))));
EXPECT_THAT(additional_allowed_domains,
ElementsAre("example.com", "other-example.com"));
} }
} // namespace } // namespace
......
...@@ -438,8 +438,8 @@ message Empty {} ...@@ -438,8 +438,8 @@ message Empty {}
// RPC request to fetch the available trigger scripts for a particular domain. // RPC request to fetch the available trigger scripts for a particular domain.
message GetTriggerScriptsRequestProto { message GetTriggerScriptsRequestProto {
// The domain for which to fetch the trigger scripts. // The exact url for which to fetch the trigger scripts.
optional string domain = 1; optional string url = 1;
// The client context. // The client context.
// NOTE: Currently, this will only contain the Chrome version number for // NOTE: Currently, this will only contain the Chrome version number for
// privacy reasons. // privacy reasons.
...@@ -452,6 +452,11 @@ message GetTriggerScriptsRequestProto { ...@@ -452,6 +452,11 @@ message GetTriggerScriptsRequestProto {
message GetTriggerScriptsResponseProto { message GetTriggerScriptsResponseProto {
// The available trigger scripts, if any. // The available trigger scripts, if any.
repeated TriggerScriptProto trigger_scripts = 1; repeated TriggerScriptProto trigger_scripts = 1;
// A list of additional domains and subdomains. Trigger scripts will
// automatically cancel the ongoing session if the user navigates away from
// the original domain or any of the additional domains.
repeated string additional_allowed_domains = 2;
} }
// A trigger script contains the full specification for a trigger script that is // A trigger script contains the full specification for a trigger script that is
...@@ -478,12 +483,11 @@ message TriggerScriptProto { ...@@ -478,12 +483,11 @@ message TriggerScriptProto {
// The |trigger_condition| must be true for the script to trigger. // The |trigger_condition| must be true for the script to trigger.
optional TriggerScriptConditionProto trigger_condition = 1; optional TriggerScriptConditionProto trigger_condition = 1;
// The action that should automatically be executed when a trigger condition
// is first true and then stops being true.
optional TriggerScriptAction on_trigger_condition_no_longer_true = 2;
// The user interface to show. // The user interface to show.
optional TriggerScriptUIProto user_interface = 3; optional TriggerScriptUIProto user_interface = 3;
reserved 2;
} }
message TriggerScriptConditionProto { message TriggerScriptConditionProto {
......
...@@ -22,7 +22,7 @@ namespace autofill_assistant { ...@@ -22,7 +22,7 @@ namespace autofill_assistant {
class DynamicTriggerConditions { class DynamicTriggerConditions {
public: public:
DynamicTriggerConditions(); DynamicTriggerConditions();
~DynamicTriggerConditions(); virtual ~DynamicTriggerConditions();
// Adds the selector trigger conditions specified in |proto| to the list of // Adds the selector trigger conditions specified in |proto| to the list of
// selectors to be queried in |Update|. // selectors to be queried in |Update|.
......
// Copyright 2020 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "components/autofill_assistant/browser/trigger_scripts/mock_dynamic_trigger_conditions.h"
namespace autofill_assistant {
MockDynamicTriggerConditions::MockDynamicTriggerConditions() = default;
MockDynamicTriggerConditions::~MockDynamicTriggerConditions() = default;
} // namespace autofill_assistant
// Copyright 2020 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef COMPONENTS_AUTOFILL_ASSISTANT_BROWSER_TRIGGER_SCRIPTS_MOCK_DYNAMIC_TRIGGER_CONDITIONS_H_
#define COMPONENTS_AUTOFILL_ASSISTANT_BROWSER_TRIGGER_SCRIPTS_MOCK_DYNAMIC_TRIGGER_CONDITIONS_H_
#include "components/autofill_assistant/browser/trigger_scripts/dynamic_trigger_conditions.h"
#include "testing/gmock/include/gmock/gmock.h"
namespace autofill_assistant {
class MockDynamicTriggerConditions : public DynamicTriggerConditions {
public:
MockDynamicTriggerConditions();
~MockDynamicTriggerConditions() override;
MOCK_CONST_METHOD1(GetSelectorMatches,
base::Optional<bool>(const Selector& selector));
void Update(WebController* web_controller,
base::OnceCallback<void(void)> callback) override {
OnUpdate(web_controller, callback);
}
MOCK_METHOD2(OnUpdate,
void(WebController* web_controller,
base::OnceCallback<void(void)>& callback));
};
} // namespace autofill_assistant
#endif // COMPONENTS_AUTOFILL_ASSISTANT_BROWSER_TRIGGER_SCRIPTS_MOCK_DYNAMIC_TRIGGER_CONDITIONS_H_
// Copyright 2020 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "components/autofill_assistant/browser/trigger_scripts/mock_static_trigger_conditions.h"
namespace autofill_assistant {
MockStaticTriggerConditions::MockStaticTriggerConditions() = default;
MockStaticTriggerConditions::~MockStaticTriggerConditions() = default;
} // namespace autofill_assistant
// Copyright 2020 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef COMPONENTS_AUTOFILL_ASSISTANT_BROWSER_TRIGGER_SCRIPTS_MOCK_STATIC_TRIGGER_CONDITIONS_H_
#define COMPONENTS_AUTOFILL_ASSISTANT_BROWSER_TRIGGER_SCRIPTS_MOCK_STATIC_TRIGGER_CONDITIONS_H_
#include "components/autofill_assistant/browser/trigger_scripts/static_trigger_conditions.h"
#include "testing/gmock/include/gmock/gmock.h"
namespace autofill_assistant {
class MockStaticTriggerConditions : public StaticTriggerConditions {
public:
MockStaticTriggerConditions();
~MockStaticTriggerConditions() override;
MOCK_METHOD4(Init,
void(Client* client,
const GURL& url,
TriggerContext* trigger_context,
base::OnceCallback<void(void)> callback));
MOCK_METHOD1(set_is_first_time_user, void(bool));
MOCK_CONST_METHOD0(is_first_time_user, bool());
MOCK_CONST_METHOD0(has_stored_login_credentials, bool());
MOCK_CONST_METHOD1(is_in_experiment, bool(int experiment_id));
};
} // namespace autofill_assistant
#endif // COMPONENTS_AUTOFILL_ASSISTANT_BROWSER_TRIGGER_SCRIPTS_MOCK_STATIC_TRIGGER_CONDITIONS_H_
...@@ -31,6 +31,10 @@ void StaticTriggerConditions::Init(Client* client, ...@@ -31,6 +31,10 @@ void StaticTriggerConditions::Init(Client* client,
weak_ptr_factory_.GetWeakPtr())); weak_ptr_factory_.GetWeakPtr()));
} }
void StaticTriggerConditions::set_is_first_time_user(bool first_time_user) {
is_first_time_user_ = first_time_user;
}
bool StaticTriggerConditions::is_first_time_user() const { bool StaticTriggerConditions::is_first_time_user() const {
return is_first_time_user_; return is_first_time_user_;
} }
......
...@@ -22,7 +22,7 @@ namespace autofill_assistant { ...@@ -22,7 +22,7 @@ namespace autofill_assistant {
class StaticTriggerConditions { class StaticTriggerConditions {
public: public:
StaticTriggerConditions(); StaticTriggerConditions();
~StaticTriggerConditions(); virtual ~StaticTriggerConditions();
// Initializes the field values according to |url| and the current state of // Initializes the field values according to |url| and the current state of
// |client|. Invokes |callback| when done. |client| and |trigger_context| must // |client|. Invokes |callback| when done. |client| and |trigger_context| must
...@@ -33,6 +33,7 @@ class StaticTriggerConditions { ...@@ -33,6 +33,7 @@ class StaticTriggerConditions {
const GURL& url, const GURL& url,
TriggerContext* trigger_context, TriggerContext* trigger_context,
base::OnceCallback<void(void)> callback); base::OnceCallback<void(void)> callback);
virtual void set_is_first_time_user(bool first_time_user);
virtual bool is_first_time_user() const; virtual bool is_first_time_user() const;
virtual bool has_stored_login_credentials() const; virtual bool has_stored_login_credentials() const;
virtual bool is_in_experiment(int experiment_id) const; virtual bool is_in_experiment(int experiment_id) const;
......
...@@ -55,5 +55,11 @@ TEST_F(StaticTriggerConditionsTest, Init) { ...@@ -55,5 +55,11 @@ TEST_F(StaticTriggerConditionsTest, Init) {
EXPECT_TRUE(static_trigger_conditions_.is_in_experiment(4)); EXPECT_TRUE(static_trigger_conditions_.is_in_experiment(4));
} }
TEST_F(StaticTriggerConditionsTest, SetIsFirstTimeUser) {
EXPECT_TRUE(static_trigger_conditions_.is_first_time_user());
static_trigger_conditions_.set_is_first_time_user(false);
EXPECT_FALSE(static_trigger_conditions_.is_first_time_user());
}
} // namespace } // namespace
} // namespace autofill_assistant } // namespace autofill_assistant
...@@ -77,4 +77,12 @@ TriggerScriptProto TriggerScript::AsProto() const { ...@@ -77,4 +77,12 @@ TriggerScriptProto TriggerScript::AsProto() const {
return proto_; return proto_;
} }
bool TriggerScript::waiting_for_precondition_no_longer_true() const {
return waiting_for_precondition_no_longer_true_;
}
void TriggerScript::waiting_for_precondition_no_longer_true(bool waiting) {
waiting_for_precondition_no_longer_true_ = waiting;
}
} // namespace autofill_assistant } // namespace autofill_assistant
...@@ -28,10 +28,16 @@ class TriggerScript { ...@@ -28,10 +28,16 @@ class TriggerScript {
TriggerScriptProto AsProto() const; TriggerScriptProto AsProto() const;
// Whether the trigger script is currently waiting for its precondition to be
// fulfilled or for its precondition to stop being fulfilled.
bool waiting_for_precondition_no_longer_true() const;
void waiting_for_precondition_no_longer_true(bool waiting);
private: private:
friend class TriggerScriptTest; friend class TriggerScriptTest;
TriggerScriptProto proto_; TriggerScriptProto proto_;
bool waiting_for_precondition_no_longer_true_ = false;
}; };
} // namespace autofill_assistant } // namespace autofill_assistant
......
...@@ -4,22 +4,153 @@ ...@@ -4,22 +4,153 @@
#include "components/autofill_assistant/browser/trigger_scripts/trigger_script_coordinator.h" #include "components/autofill_assistant/browser/trigger_scripts/trigger_script_coordinator.h"
#include <map>
#include "components/autofill_assistant/browser/client_context.h"
#include "components/autofill_assistant/browser/protocol_utils.h"
#include "components/autofill_assistant/browser/url_utils.h"
#include "components/version_info/version_info.h"
#include "content/public/browser/browser_task_traits.h"
#include "content/public/browser/browser_thread.h"
#include "net/http/http_status_code.h"
namespace {
const char kScriptParameterDebugBundleId[] = "DEBUG_BUNDLE_ID";
const char kScriptParameterDebugBundleVersion[] = "DEBUG_BUNDLE_VERSION";
const char kScriptParameterDebugSocketId[] = "DEBUG_SOCKET_ID";
std::map<std::string, std::string> ExtractDebugScriptParameters(
const autofill_assistant::TriggerContext& trigger_context) {
std::map<std::string, std::string> debug_script_parameters;
auto debug_bundle_id =
trigger_context.GetParameter(kScriptParameterDebugBundleId);
auto debug_bundle_version =
trigger_context.GetParameter(kScriptParameterDebugBundleVersion);
auto debug_socket_id =
trigger_context.GetParameter(kScriptParameterDebugSocketId);
if (debug_bundle_id) {
debug_script_parameters.insert(
{kScriptParameterDebugBundleId, *debug_bundle_id});
}
if (debug_bundle_version) {
debug_script_parameters.insert(
{kScriptParameterDebugBundleVersion, *debug_bundle_version});
}
if (debug_socket_id) {
debug_script_parameters.insert(
{kScriptParameterDebugSocketId, *debug_socket_id});
}
return debug_script_parameters;
}
} // namespace
namespace autofill_assistant { namespace autofill_assistant {
// TODO(b/171776026): implement this stub.
TriggerScriptCoordinator::TriggerScriptCoordinator( TriggerScriptCoordinator::TriggerScriptCoordinator(
Client* client, Client* client,
std::unique_ptr<WebController> web_controller, std::unique_ptr<WebController> web_controller,
std::unique_ptr<ServiceRequestSender> request_sender, std::unique_ptr<ServiceRequestSender> request_sender,
const GURL& get_trigger_scripts_server) const GURL& get_trigger_scripts_server,
: content::WebContentsObserver(client->GetWebContents()) {} std::unique_ptr<StaticTriggerConditions> static_trigger_conditions,
std::unique_ptr<DynamicTriggerConditions> dynamic_trigger_conditions)
: content::WebContentsObserver(client->GetWebContents()),
client_(client),
request_sender_(std::move(request_sender)),
get_trigger_scripts_server_(get_trigger_scripts_server),
web_controller_(std::move(web_controller)),
static_trigger_conditions_(std::move(static_trigger_conditions)),
dynamic_trigger_conditions_(std::move(dynamic_trigger_conditions)) {}
TriggerScriptCoordinator::~TriggerScriptCoordinator() = default; TriggerScriptCoordinator::~TriggerScriptCoordinator() = default;
void TriggerScriptCoordinator::Start(const GURL& url) {} void TriggerScriptCoordinator::Start(
const GURL& deeplink_url,
std::unique_ptr<TriggerContext> trigger_context) {
deeplink_url_ = deeplink_url;
GURL current_url = client_->GetWebContents()->GetLastCommittedURL();
if (!url_utils::IsInDomainOrSubDomain(current_url, deeplink_url_)) {
LOG(ERROR) << "Trigger script requested for domain other than the deeplink";
NotifyOnTriggerScriptFinished(
Metrics::LiteScriptFinishedState::LITE_SCRIPT_UNKNOWN_FAILURE);
return;
}
trigger_context_ = std::make_unique<TriggerContextImpl>(
ExtractDebugScriptParameters(*trigger_context),
trigger_context->experiment_ids());
ClientContextProto client_context;
client_context.mutable_chrome()->set_chrome_version(
version_info::GetProductNameAndVersionForUserAgent());
request_sender_->SendRequest(
get_trigger_scripts_server_,
ProtocolUtils::CreateGetTriggerScriptsRequest(
deeplink_url_, client_context, trigger_context_->GetParameters()),
base::BindOnce(&TriggerScriptCoordinator::OnGetTriggerScripts,
weak_ptr_factory_.GetWeakPtr()));
}
void TriggerScriptCoordinator::OnGetTriggerScripts(
int http_status,
const std::string& response) {
if (http_status != net::HTTP_OK) {
NotifyOnTriggerScriptFinished(
Metrics::LiteScriptFinishedState::LITE_SCRIPT_GET_ACTIONS_FAILED);
return;
}
trigger_scripts_.clear();
additional_allowed_domains_.clear();
if (!ProtocolUtils::ParseTriggerScripts(response, &trigger_scripts_,
&additional_allowed_domains_)) {
NotifyOnTriggerScriptFinished(
Metrics::LiteScriptFinishedState::LITE_SCRIPT_GET_ACTIONS_PARSE_ERROR);
return;
}
StartCheckingTriggerConditions();
}
void TriggerScriptCoordinator::PerformTriggerScriptAction( void TriggerScriptCoordinator::PerformTriggerScriptAction(
TriggerScriptProto::TriggerScriptAction action) {} TriggerScriptProto::TriggerScriptAction action) {
switch (action) {
case TriggerScriptProto::NOT_NOW:
if (visible_trigger_script_ != -1) {
trigger_scripts_[visible_trigger_script_]
->waiting_for_precondition_no_longer_true(true);
HideTriggerScript();
}
return;
case TriggerScriptProto::CANCEL_SESSION:
HideTriggerScript();
NotifyOnTriggerScriptFinished(
Metrics::LiteScriptFinishedState::LITE_SCRIPT_PROMPT_FAILED_CLOSE);
return;
case TriggerScriptProto::CANCEL_FOREVER:
HideTriggerScript();
NotifyOnTriggerScriptFinished(
Metrics::LiteScriptFinishedState::LITE_SCRIPT_PROMPT_FAILED_CLOSE);
return;
case TriggerScriptProto::SHOW_CANCEL_POPUP:
NOTREACHED();
return;
case TriggerScriptProto::ACCEPT:
if (visible_trigger_script_ == -1) {
NOTREACHED();
return;
}
// Do not hide the trigger script here, to facilitate a smooth transition
// to the regular flow.
NotifyOnTriggerScriptFinished(
Metrics::LiteScriptFinishedState::LITE_SCRIPT_PROMPT_SUCCEEDED);
return;
case TriggerScriptProto::UNDEFINED:
return;
}
}
void TriggerScriptCoordinator::AddObserver(Observer* observer) { void TriggerScriptCoordinator::AddObserver(Observer* observer) {
observers_.AddObserver(observer); observers_.AddObserver(observer);
...@@ -29,7 +160,168 @@ void TriggerScriptCoordinator::RemoveObserver(const Observer* observer) { ...@@ -29,7 +160,168 @@ void TriggerScriptCoordinator::RemoveObserver(const Observer* observer) {
observers_.RemoveObserver(observer); observers_.RemoveObserver(observer);
} }
void TriggerScriptCoordinator::DidFinishNavigation(
content::NavigationHandle* navigation_handle) {
// Ignore navigation events if any of the following is true:
// - not in the main frame.
// - document does not change (e.g., same page history navigation).
// - WebContents stays at the existing URL (e.g., downloads).
if (!navigation_handle->IsInMainFrame() ||
navigation_handle->IsSameDocument() ||
!navigation_handle->HasCommitted()) {
return;
}
// Chrome has encountered an error and is now displaying an error message
// (e.g., network connection lost). This will cancel the current trigger
// script session.
if (navigation_handle->IsErrorPage()) {
PerformTriggerScriptAction(TriggerScriptProto::CANCEL_SESSION);
return;
}
// The user has navigated away from the target domain. This will cancel the
// current trigger script session.
if (!url_utils::IsInDomainOrSubDomain(web_contents()->GetLastCommittedURL(),
deeplink_url_) &&
!url_utils::IsInDomainOrSubDomain(web_contents()->GetLastCommittedURL(),
additional_allowed_domains_)) {
PerformTriggerScriptAction(TriggerScriptProto::CANCEL_SESSION);
return;
}
}
void TriggerScriptCoordinator::OnVisibilityChanged( void TriggerScriptCoordinator::OnVisibilityChanged(
content::Visibility visibility) {} content::Visibility visibility) {
bool visible = visibility == content::Visibility::VISIBLE;
if (web_contents_visible_ == visible) {
return;
}
web_contents_visible_ = visible;
if (web_contents_visible_) {
// Restore UI on tab switch. NOTE: an arbitrary amount of time can pass
// between tab-hide and tab-show. It is not guaranteed that the trigger
// script that was shown before is still available, hence we need to fetch
// it again.
DCHECK(visible_trigger_script_ == -1);
Start(deeplink_url_, std::move(trigger_context_));
} else {
// Hide UI on tab switch.
StopCheckingTriggerConditions();
HideTriggerScript();
}
}
void TriggerScriptCoordinator::StartCheckingTriggerConditions() {
is_checking_trigger_conditions_ = true;
static_trigger_conditions_->Init(
client_, deeplink_url_, trigger_context_.get(),
base::BindOnce(&TriggerScriptCoordinator::CheckDynamicTriggerConditions,
weak_ptr_factory_.GetWeakPtr()));
}
void TriggerScriptCoordinator::CheckDynamicTriggerConditions() {
dynamic_trigger_conditions_->Update(
web_controller_.get(),
base::BindOnce(
&TriggerScriptCoordinator::OnDynamicTriggerConditionsEvaluated,
weak_ptr_factory_.GetWeakPtr()));
}
void TriggerScriptCoordinator::StopCheckingTriggerConditions() {
is_checking_trigger_conditions_ = false;
}
void TriggerScriptCoordinator::ShowTriggerScript(int index) {
if (visible_trigger_script_ == index) {
return;
}
visible_trigger_script_ = index;
static_trigger_conditions_->set_is_first_time_user(false);
auto proto = trigger_scripts_[index]->AsProto().user_interface();
for (Observer& observer : observers_) {
observer.OnTriggerScriptShown(proto);
}
}
void TriggerScriptCoordinator::HideTriggerScript() {
if (visible_trigger_script_ == -1) {
return;
}
visible_trigger_script_ = -1;
for (Observer& observer : observers_) {
observer.OnTriggerScriptHidden();
}
}
void TriggerScriptCoordinator::OnDynamicTriggerConditionsEvaluated() {
if (!web_contents_visible_ || !is_checking_trigger_conditions_) {
return;
}
std::vector<bool> evaluated_trigger_conditions;
for (const auto& trigger_script : trigger_scripts_) {
evaluated_trigger_conditions.emplace_back(
trigger_script->EvaluateTriggerConditions(
*static_trigger_conditions_, *dynamic_trigger_conditions_));
}
// Trigger condition for the currently shown trigger script is no longer true.
if (visible_trigger_script_ != -1 &&
!evaluated_trigger_conditions[visible_trigger_script_]) {
HideTriggerScript();
// Do not return here: a different trigger script may have become eligible
// at the same time.
}
for (size_t i = 0; i < trigger_scripts_.size(); ++i) {
// The currently visible trigger script is still visible, nothing to do.
if (visible_trigger_script_ != -1 &&
i == static_cast<size_t>(visible_trigger_script_) &&
evaluated_trigger_conditions[i]) {
DCHECK(!trigger_scripts_[i]->waiting_for_precondition_no_longer_true());
continue;
}
// The script was waiting for the precondition to no longer be true.
// It can now resume regular precondition checking.
if (!evaluated_trigger_conditions[i] &&
trigger_scripts_[i]->waiting_for_precondition_no_longer_true()) {
trigger_scripts_[i]->waiting_for_precondition_no_longer_true(false);
continue;
}
if (evaluated_trigger_conditions[i] && visible_trigger_script_ != -1 &&
i != static_cast<size_t>(visible_trigger_script_)) {
// Should not happen, as trigger script conditions should be mutually
// exclusive. If it happens, we just ignore it. This is essentially
// first-come-first-serve, prioritizing scripts w.r.t. occurrence in the
// proto.
continue;
}
// A new trigger script has become eligible for showing.
if (evaluated_trigger_conditions[i] &&
!trigger_scripts_[i]->waiting_for_precondition_no_longer_true()) {
ShowTriggerScript(i);
}
}
content::GetUIThreadTaskRunner({})->PostDelayedTask(
FROM_HERE,
base::BindOnce(&TriggerScriptCoordinator::CheckDynamicTriggerConditions,
weak_ptr_factory_.GetWeakPtr()),
periodic_element_check_interval_);
}
void TriggerScriptCoordinator::NotifyOnTriggerScriptFinished(
Metrics::LiteScriptFinishedState state) {
for (Observer& observer : observers_) {
observer.OnTriggerScriptFinished(state);
}
}
} // namespace autofill_assistant } // namespace autofill_assistant
...@@ -12,11 +12,17 @@ ...@@ -12,11 +12,17 @@
#include "base/memory/weak_ptr.h" #include "base/memory/weak_ptr.h"
#include "base/observer_list.h" #include "base/observer_list.h"
#include "base/observer_list_types.h" #include "base/observer_list_types.h"
#include "base/time/time.h"
#include "components/autofill_assistant/browser/client.h" #include "components/autofill_assistant/browser/client.h"
#include "components/autofill_assistant/browser/metrics.h"
#include "components/autofill_assistant/browser/service.pb.h" #include "components/autofill_assistant/browser/service.pb.h"
#include "components/autofill_assistant/browser/service/service_request_sender.h" #include "components/autofill_assistant/browser/service/service_request_sender.h"
#include "components/autofill_assistant/browser/trigger_context.h"
#include "components/autofill_assistant/browser/trigger_scripts/dynamic_trigger_conditions.h"
#include "components/autofill_assistant/browser/trigger_scripts/static_trigger_conditions.h"
#include "components/autofill_assistant/browser/trigger_scripts/trigger_script.h" #include "components/autofill_assistant/browser/trigger_scripts/trigger_script.h"
#include "components/autofill_assistant/browser/web/web_controller.h" #include "components/autofill_assistant/browser/web/web_controller.h"
#include "content/public/browser/navigation_handle.h"
#include "content/public/browser/web_contents.h" #include "content/public/browser/web_contents.h"
#include "content/public/browser/web_contents_observer.h" #include "content/public/browser/web_contents_observer.h"
#include "url/gurl.h" #include "url/gurl.h"
...@@ -37,21 +43,29 @@ class TriggerScriptCoordinator : public content::WebContentsObserver { ...@@ -37,21 +43,29 @@ class TriggerScriptCoordinator : public content::WebContentsObserver {
virtual void OnTriggerScriptShown(const TriggerScriptUIProto& proto) = 0; virtual void OnTriggerScriptShown(const TriggerScriptUIProto& proto) = 0;
virtual void OnTriggerScriptHidden() = 0; virtual void OnTriggerScriptHidden() = 0;
virtual void OnTriggerScriptFinished(int state) = 0; // TODO(b/171776026): Add new states to our metrics and use them in the
// coordinator.
virtual void OnTriggerScriptFinished(
Metrics::LiteScriptFinishedState state) = 0;
}; };
// |client| and |web_contents| must outlive this instance. // |client| and |web_contents| must outlive this instance.
TriggerScriptCoordinator(Client* client, TriggerScriptCoordinator(
std::unique_ptr<WebController> web_controller, Client* client,
std::unique_ptr<ServiceRequestSender> request_sender, std::unique_ptr<WebController> web_controller,
const GURL& get_trigger_scripts_server); std::unique_ptr<ServiceRequestSender> request_sender,
const GURL& get_trigger_scripts_server,
std::unique_ptr<StaticTriggerConditions> static_trigger_conditions,
std::unique_ptr<DynamicTriggerConditions> dynamic_trigger_conditions);
~TriggerScriptCoordinator() override; ~TriggerScriptCoordinator() override;
TriggerScriptCoordinator(const TriggerScriptCoordinator&) = delete; TriggerScriptCoordinator(const TriggerScriptCoordinator&) = delete;
TriggerScriptCoordinator& operator=(const TriggerScriptCoordinator&) = delete; TriggerScriptCoordinator& operator=(const TriggerScriptCoordinator&) = delete;
// Retrieves all trigger scripts for |url| and starts evaluating their // Retrieves all trigger scripts for |deeplink_url| and starts evaluating
// preconditions. Observers will be notified of all relevant status updates. // their trigger conditions. Observers will be notified of all relevant status
void Start(const GURL& url); // updates.
void Start(const GURL& deeplink_url,
std::unique_ptr<TriggerContext> trigger_context);
// Performs |action|. This is usually invoked by the UI as a result of user // Performs |action|. This is usually invoked by the UI as a result of user
// interactions. // interactions.
...@@ -62,17 +76,74 @@ class TriggerScriptCoordinator : public content::WebContentsObserver { ...@@ -62,17 +76,74 @@ class TriggerScriptCoordinator : public content::WebContentsObserver {
void RemoveObserver(const Observer* observer); void RemoveObserver(const Observer* observer);
private: private:
struct PendingTriggerScript { friend class TriggerScriptCoordinatorTest;
TriggerScript trigger_script;
bool waiting_for_precondition_no_longer_true = false;
};
// From content::WebContentsObserver. // From content::WebContentsObserver.
void DidFinishNavigation(
content::NavigationHandle* navigation_handle) override;
void OnVisibilityChanged(content::Visibility visibility) override; void OnVisibilityChanged(content::Visibility visibility) override;
void StartCheckingTriggerConditions();
void StopCheckingTriggerConditions();
void ShowTriggerScript(int index);
void HideTriggerScript();
void CheckDynamicTriggerConditions();
void OnDynamicTriggerConditionsEvaluated();
void OnGetTriggerScripts(int http_status, const std::string& response);
void NotifyOnTriggerScriptFinished(Metrics::LiteScriptFinishedState state);
// Used to retrieve deps and also to request shutdown and, if applicable,
// start of the regular script.
Client* client_;
// The original deeplink to request trigger scripts for.
GURL deeplink_url_;
// List of additional domains. If the user leaves the (sub)domain of
// |deeplink_url_| or |additional_allowed_domains_|, the session stops.
std::vector<std::string> additional_allowed_domains_;
// The trigger context for the most recent |Start|. This is stored as a member
// to allow pausing and resuming the same trigger flow.
std::unique_ptr<TriggerContext> trigger_context_;
// Keeps track of whether the tab is currently visible or not. While
// invisible, trigger scripts are hidden and condition evaluation is
// suspended.
bool web_contents_visible_ = true;
// Whether the coordinator is currently checking trigger conditions.
bool is_checking_trigger_conditions_ = false;
// Index of the trigger script that is currently being shown. -1 if no script
// is being shown.
int visible_trigger_script_ = -1;
// Used to request trigger scripts from the backend.
std::unique_ptr<ServiceRequestSender> request_sender_;
// The URL of the server that should be contacted by |request_sender_|.
GURL get_trigger_scripts_server_;
// The web controller to evaluate element conditions.
std::unique_ptr<WebController> web_controller_;
// The list of currently registered observers. // The list of currently registered observers.
base::ObserverList<Observer> observers_; base::ObserverList<Observer> observers_;
// The list of trigger scripts that were fetched from the backend.
std::vector<std::unique_ptr<TriggerScript>> trigger_scripts_;
// Evaluate and cache the results for static and dynamic trigger conditions.
std::unique_ptr<StaticTriggerConditions> static_trigger_conditions_;
std::unique_ptr<DynamicTriggerConditions> dynamic_trigger_conditions_;
// The time between consecutive evaluations of dynamic trigger conditions.
// TODO(arbesser): Maybe make this configurable in proto?
base::TimeDelta periodic_element_check_interval_ =
base::TimeDelta::FromSeconds(1);
base::WeakPtrFactory<TriggerScriptCoordinator> weak_ptr_factory_{this}; base::WeakPtrFactory<TriggerScriptCoordinator> weak_ptr_factory_{this};
}; };
......
...@@ -4,12 +4,17 @@ ...@@ -4,12 +4,17 @@
#include "components/autofill_assistant/browser/trigger_scripts/trigger_script_coordinator.h" #include "components/autofill_assistant/browser/trigger_scripts/trigger_script_coordinator.h"
#include "base/test/gmock_callback_support.h"
#include "base/test/mock_callback.h"
#include "base/test/task_environment.h"
#include "components/autofill_assistant/browser/mock_client.h" #include "components/autofill_assistant/browser/mock_client.h"
#include "components/autofill_assistant/browser/service/mock_service_request_sender.h" #include "components/autofill_assistant/browser/service/mock_service_request_sender.h"
#include "components/autofill_assistant/browser/test_util.h"
#include "components/autofill_assistant/browser/trigger_scripts/mock_dynamic_trigger_conditions.h"
#include "components/autofill_assistant/browser/trigger_scripts/mock_static_trigger_conditions.h"
#include "components/autofill_assistant/browser/web/mock_web_controller.h" #include "components/autofill_assistant/browser/web/mock_web_controller.h"
#include "components/version_info/version_info.h"
#include "base/test/gmock_callback_support.h" #include "content/public/test/navigation_simulator.h"
#include "base/test/mock_callback.h"
#include "content/public/test/test_renderer_host.h" #include "content/public/test/test_renderer_host.h"
#include "content/public/test/web_contents_tester.h" #include "content/public/test/web_contents_tester.h"
#include "net/http/http_status_code.h" #include "net/http/http_status_code.h"
...@@ -17,24 +22,33 @@ ...@@ -17,24 +22,33 @@
namespace autofill_assistant { namespace autofill_assistant {
using ::base::test::RunOnceCallback;
using ::testing::_;
using ::testing::Eq;
using ::testing::NaggyMock;
using ::testing::NiceMock; using ::testing::NiceMock;
using ::testing::Return; using ::testing::Return;
using ::testing::UnorderedElementsAreArray;
namespace {
class MockObserver : public TriggerScriptCoordinator::Observer { class MockObserver : public TriggerScriptCoordinator::Observer {
public: public:
MOCK_METHOD1(OnTriggerScriptShown, void(const TriggerScriptUIProto& proto)); MOCK_METHOD1(OnTriggerScriptShown, void(const TriggerScriptUIProto& proto));
MOCK_METHOD0(OnTriggerScriptHidden, void()); MOCK_METHOD0(OnTriggerScriptHidden, void());
MOCK_METHOD1(OnTriggerScriptFinished, void(int state)); MOCK_METHOD1(OnTriggerScriptFinished,
void(Metrics::LiteScriptFinishedState state));
}; };
const char kFakeDeepLink[] = "https://example.com/q?data=test";
const char kFakeSubdomainDeepLink[] = "https://b.example.com";
const char kFakeServerUrl[] = const char kFakeServerUrl[] =
"https://www.fake.backend.com/trigger_script_server"; "https://www.fake.backend.com/trigger_script_server";
class TriggerScriptCoordinatorTest : public content::RenderViewHostTestHarness { class TriggerScriptCoordinatorTest : public content::RenderViewHostTestHarness {
public: public:
TriggerScriptCoordinatorTest() = default; TriggerScriptCoordinatorTest()
: content::RenderViewHostTestHarness(
base::test::TaskEnvironment::MainThreadType::UI,
base::test::TaskEnvironment::TimeSource::MOCK_TIME) {}
~TriggerScriptCoordinatorTest() override = default; ~TriggerScriptCoordinatorTest() override = default;
void SetUp() override { void SetUp() override {
...@@ -49,25 +63,352 @@ class TriggerScriptCoordinatorTest : public content::RenderViewHostTestHarness { ...@@ -49,25 +63,352 @@ class TriggerScriptCoordinatorTest : public content::RenderViewHostTestHarness {
auto mock_web_controller = std::make_unique<NiceMock<MockWebController>>(); auto mock_web_controller = std::make_unique<NiceMock<MockWebController>>();
mock_web_controller_ = mock_web_controller.get(); mock_web_controller_ = mock_web_controller.get();
auto mock_static_trigger_conditions =
std::make_unique<NiceMock<MockStaticTriggerConditions>>();
mock_static_trigger_conditions_ = mock_static_trigger_conditions.get();
auto mock_dynamic_trigger_conditions =
std::make_unique<NiceMock<MockDynamicTriggerConditions>>();
mock_dynamic_trigger_conditions_ = mock_dynamic_trigger_conditions.get();
coordinator_ = std::make_unique<TriggerScriptCoordinator>( coordinator_ = std::make_unique<TriggerScriptCoordinator>(
&mock_client_, std::move(mock_web_controller), &mock_client_, std::move(mock_web_controller),
std::move(mock_request_sender), GURL(kFakeServerUrl)); std::move(mock_request_sender), GURL(kFakeServerUrl),
std::move(mock_static_trigger_conditions),
std::move(mock_dynamic_trigger_conditions));
coordinator_->AddObserver(&mock_observer_); coordinator_->AddObserver(&mock_observer_);
SimulateNavigateToUrl(GURL(kFakeDeepLink));
} }
void TearDown() override { coordinator_->RemoveObserver(&mock_observer_); } void TearDown() override { coordinator_->RemoveObserver(&mock_observer_); }
void CallOnVisibilityChangedForTest(content::Visibility visibility) {
coordinator_->OnVisibilityChanged(visibility);
}
void SimulateNavigateToUrl(const GURL& url) {
content::WebContentsTester::For(web_contents())->SetLastCommittedURL(url);
content::NavigationSimulator::NavigateAndCommitFromDocument(
url, web_contents()->GetMainFrame());
content::WebContentsTester::For(web_contents())->TestSetIsLoading(false);
}
protected: protected:
NiceMock<MockServiceRequestSender>* mock_request_sender_; NiceMock<MockServiceRequestSender>* mock_request_sender_;
NiceMock<MockWebController>* mock_web_controller_; NiceMock<MockWebController>* mock_web_controller_;
NiceMock<MockClient> mock_client_; NiceMock<MockClient> mock_client_;
NiceMock<MockObserver> mock_observer_; NaggyMock<MockObserver> mock_observer_;
std::unique_ptr<TriggerScriptCoordinator> coordinator_; std::unique_ptr<TriggerScriptCoordinator> coordinator_;
NiceMock<MockStaticTriggerConditions>* mock_static_trigger_conditions_;
NiceMock<MockDynamicTriggerConditions>* mock_dynamic_trigger_conditions_;
}; };
TEST_F(TriggerScriptCoordinatorTest, SmokeTest) { TEST_F(TriggerScriptCoordinatorTest, StartSucceedsForCorrectDomain) {
// stub SimulateNavigateToUrl(GURL(kFakeDeepLink));
EXPECT_CALL(*mock_request_sender_, OnSendRequest).Times(1);
coordinator_->Start(GURL(kFakeDeepLink),
std::make_unique<TriggerContextImpl>());
}
TEST_F(TriggerScriptCoordinatorTest, StartSucceedsForSubDomain) {
SimulateNavigateToUrl(GURL(kFakeSubdomainDeepLink));
EXPECT_CALL(*mock_request_sender_, OnSendRequest).Times(1);
coordinator_->Start(GURL(kFakeDeepLink),
std::make_unique<TriggerContextImpl>());
}
TEST_F(TriggerScriptCoordinatorTest, StartFailsForDifferentDomain) {
SimulateNavigateToUrl(GURL("https://different.com/example"));
EXPECT_CALL(*mock_request_sender_, OnSendRequest).Times(0);
EXPECT_CALL(
mock_observer_,
OnTriggerScriptFinished(
Metrics::LiteScriptFinishedState::LITE_SCRIPT_UNKNOWN_FAILURE));
coordinator_->Start(GURL(kFakeDeepLink),
std::make_unique<TriggerContextImpl>());
}
TEST_F(TriggerScriptCoordinatorTest, StartSendsOnlyApprovedFields) {
std::map<std::string, std::string> input_script_params{
{"keyA", "valueA"},
{"DEBUG_BUNDLE_ID", "bundle_id"},
{"DEBUG_SOCKET_ID", "socket_id"},
{"keyB", "valueB"},
{"DEBUG_BUNDLE_VERSION", "socket_version"}};
std::map<std::string, std::string> expected_script_params{
{"DEBUG_BUNDLE_ID", "bundle_id"},
{"DEBUG_SOCKET_ID", "socket_id"},
{"DEBUG_BUNDLE_VERSION", "socket_version"}};
EXPECT_CALL(*mock_request_sender_, OnSendRequest(GURL(kFakeServerUrl), _, _))
.WillOnce([&](const GURL& url, const std::string& request_body,
ServiceRequestSender::ResponseCallback& callback) {
GetTriggerScriptsRequestProto request;
ASSERT_TRUE(request.ParseFromString(request_body));
EXPECT_THAT(request.url(), Eq(kFakeDeepLink));
std::map<std::string, std::string> params;
for (const auto& param : request.debug_script_parameters()) {
params[param.name()] = param.value();
}
EXPECT_THAT(params, UnorderedElementsAreArray(expected_script_params));
ClientContextProto expected_client_context;
expected_client_context.mutable_chrome()->set_chrome_version(
version_info::GetProductNameAndVersionForUserAgent());
EXPECT_THAT(request.client_context(), Eq(expected_client_context));
});
coordinator_->Start(GURL(kFakeDeepLink),
std::make_unique<TriggerContextImpl>(
/* params = */ input_script_params,
/* exp = */ "1,2,4"));
}
TEST_F(TriggerScriptCoordinatorTest, StopOnNetworkError) {
EXPECT_CALL(*mock_request_sender_, OnSendRequest(GURL(kFakeServerUrl), _, _))
.WillOnce(RunOnceCallback<2>(net::HTTP_FORBIDDEN, ""));
EXPECT_CALL(
mock_observer_,
OnTriggerScriptFinished(
Metrics::LiteScriptFinishedState::LITE_SCRIPT_GET_ACTIONS_FAILED));
coordinator_->Start(GURL(kFakeDeepLink),
std::make_unique<TriggerContextImpl>());
}
TEST_F(TriggerScriptCoordinatorTest, StopOnParsingError) {
EXPECT_CALL(*mock_request_sender_, OnSendRequest(GURL(kFakeServerUrl), _, _))
.WillOnce(RunOnceCallback<2>(net::HTTP_OK, "invalid"));
EXPECT_CALL(mock_observer_,
OnTriggerScriptFinished(Metrics::LiteScriptFinishedState::
LITE_SCRIPT_GET_ACTIONS_PARSE_ERROR));
coordinator_->Start(GURL(kFakeDeepLink),
std::make_unique<TriggerContextImpl>());
}
TEST_F(TriggerScriptCoordinatorTest, StartChecksStaticAndDynamicConditions) {
GetTriggerScriptsResponseProto response;
auto* trigger_condition_all_of = response.add_trigger_scripts()
->mutable_trigger_condition()
->mutable_all_of();
*trigger_condition_all_of->add_conditions()->mutable_selector() =
ToSelectorProto("#selector");
trigger_condition_all_of->add_conditions()->mutable_is_first_time_user();
std::string serialized_response;
response.SerializeToString(&serialized_response);
EXPECT_CALL(*mock_request_sender_, OnSendRequest(GURL(kFakeServerUrl), _, _))
.WillOnce(RunOnceCallback<2>(net::HTTP_OK, serialized_response));
EXPECT_CALL(*mock_static_trigger_conditions_, Init)
.WillOnce(RunOnceCallback<3>());
ON_CALL(*mock_dynamic_trigger_conditions_, OnUpdate(mock_web_controller_, _))
.WillByDefault(RunOnceCallback<1>());
ON_CALL(*mock_dynamic_trigger_conditions_, GetSelectorMatches)
.WillByDefault(Return(false));
coordinator_->Start(GURL(kFakeDeepLink),
std::make_unique<TriggerContextImpl>());
EXPECT_CALL(*mock_dynamic_trigger_conditions_,
OnUpdate(mock_web_controller_, _))
.WillOnce(RunOnceCallback<1>());
task_environment()->FastForwardBy(base::TimeDelta::FromSeconds(1));
}
TEST_F(TriggerScriptCoordinatorTest, ShowAndHideTriggerScript) {
GetTriggerScriptsResponseProto response;
*response.add_trigger_scripts()
->mutable_trigger_condition()
->mutable_selector() = ToSelectorProto("#selector");
std::string serialized_response;
response.SerializeToString(&serialized_response);
EXPECT_CALL(*mock_request_sender_, OnSendRequest(GURL(kFakeServerUrl), _, _))
.WillOnce(RunOnceCallback<2>(net::HTTP_OK, serialized_response));
EXPECT_CALL(*mock_static_trigger_conditions_, Init)
.WillOnce(RunOnceCallback<3>());
ON_CALL(*mock_dynamic_trigger_conditions_, OnUpdate(mock_web_controller_, _))
.WillByDefault(RunOnceCallback<1>());
ON_CALL(*mock_dynamic_trigger_conditions_, GetSelectorMatches)
.WillByDefault(Return(true));
EXPECT_CALL(mock_observer_, OnTriggerScriptShown).Times(1);
coordinator_->Start(GURL(kFakeDeepLink),
std::make_unique<TriggerContextImpl>());
// Condition stays true, no further notification should be sent to observers.
task_environment()->FastForwardBy(base::TimeDelta::FromSeconds(1));
// Condition turns false, trigger script is hidden.
ON_CALL(*mock_dynamic_trigger_conditions_, GetSelectorMatches)
.WillByDefault(Return(false));
EXPECT_CALL(mock_observer_, OnTriggerScriptHidden).Times(1);
task_environment()->FastForwardBy(base::TimeDelta::FromSeconds(1));
// Condition is true again, trigger script is shown again.
ON_CALL(*mock_dynamic_trigger_conditions_, GetSelectorMatches)
.WillByDefault(Return(true));
EXPECT_CALL(mock_observer_, OnTriggerScriptShown).Times(1);
task_environment()->FastForwardBy(base::TimeDelta::FromSeconds(1));
}
TEST_F(TriggerScriptCoordinatorTest, PauseAndResumeOnTabVisibilityChange) {
GetTriggerScriptsResponseProto response;
*response.add_trigger_scripts()
->mutable_trigger_condition()
->mutable_selector() = ToSelectorProto("#selector");
std::string serialized_response;
response.SerializeToString(&serialized_response);
EXPECT_CALL(*mock_request_sender_, OnSendRequest(GURL(kFakeServerUrl), _, _))
.WillOnce(RunOnceCallback<2>(net::HTTP_OK, serialized_response));
EXPECT_CALL(*mock_static_trigger_conditions_, Init)
.WillOnce(RunOnceCallback<3>());
EXPECT_CALL(*mock_dynamic_trigger_conditions_,
OnUpdate(mock_web_controller_, _))
.WillOnce(RunOnceCallback<1>());
EXPECT_CALL(*mock_dynamic_trigger_conditions_, GetSelectorMatches)
.WillOnce(Return(true));
EXPECT_CALL(mock_observer_, OnTriggerScriptShown).Times(1);
coordinator_->Start(GURL(kFakeDeepLink),
std::make_unique<TriggerContextImpl>());
// When a tab becomes invisible, the trigger script is hidden and trigger
// condition evaluation is suspended.
EXPECT_CALL(mock_observer_, OnTriggerScriptHidden).Times(1);
EXPECT_CALL(*mock_dynamic_trigger_conditions_,
OnUpdate(mock_web_controller_, _))
.Times(0);
CallOnVisibilityChangedForTest(content::Visibility::HIDDEN);
// When a hidden tab becomes visible again, the trigger scripts must be
// fetched again.
EXPECT_CALL(*mock_request_sender_, OnSendRequest(GURL(kFakeServerUrl), _, _))
.WillOnce(RunOnceCallback<2>(net::HTTP_OK, serialized_response));
EXPECT_CALL(*mock_static_trigger_conditions_, Init)
.WillOnce(RunOnceCallback<3>());
EXPECT_CALL(*mock_dynamic_trigger_conditions_,
OnUpdate(mock_web_controller_, _))
.WillOnce(RunOnceCallback<1>());
EXPECT_CALL(*mock_dynamic_trigger_conditions_, GetSelectorMatches)
.WillOnce(Return(true));
EXPECT_CALL(mock_observer_, OnTriggerScriptShown).Times(1);
CallOnVisibilityChangedForTest(content::Visibility::VISIBLE);
}
TEST_F(TriggerScriptCoordinatorTest, PerformTriggerScriptActionNotNow) {
GetTriggerScriptsResponseProto response;
*response.add_trigger_scripts()
->mutable_trigger_condition()
->mutable_selector() = ToSelectorProto("#selector");
std::string serialized_response;
response.SerializeToString(&serialized_response);
EXPECT_CALL(*mock_request_sender_, OnSendRequest(GURL(kFakeServerUrl), _, _))
.WillOnce(RunOnceCallback<2>(net::HTTP_OK, serialized_response));
EXPECT_CALL(*mock_static_trigger_conditions_, Init)
.WillOnce(RunOnceCallback<3>());
ON_CALL(*mock_dynamic_trigger_conditions_, OnUpdate(mock_web_controller_, _))
.WillByDefault(RunOnceCallback<1>());
EXPECT_CALL(*mock_dynamic_trigger_conditions_, GetSelectorMatches)
.WillOnce(Return(true));
EXPECT_CALL(mock_observer_, OnTriggerScriptShown).Times(1);
coordinator_->Start(GURL(kFakeDeepLink),
std::make_unique<TriggerContextImpl>());
EXPECT_CALL(mock_observer_, OnTriggerScriptHidden).Times(1);
coordinator_->PerformTriggerScriptAction(TriggerScriptProto::NOT_NOW);
// Despite the trigger condition still being true, the trigger script is not
// shown again until the condition has become first false and then true again.
EXPECT_CALL(*mock_dynamic_trigger_conditions_, GetSelectorMatches)
.WillOnce(Return(true));
EXPECT_CALL(mock_observer_, OnTriggerScriptShown).Times(0);
task_environment()->FastForwardBy(base::TimeDelta::FromSeconds(1));
EXPECT_CALL(*mock_dynamic_trigger_conditions_, GetSelectorMatches)
.WillOnce(Return(false));
task_environment()->FastForwardBy(base::TimeDelta::FromSeconds(1));
EXPECT_CALL(*mock_dynamic_trigger_conditions_, GetSelectorMatches)
.WillOnce(Return(true));
EXPECT_CALL(mock_observer_, OnTriggerScriptShown).Times(1);
task_environment()->FastForwardBy(base::TimeDelta::FromSeconds(1));
}
TEST_F(TriggerScriptCoordinatorTest, PerformTriggerScriptActionCancelSession) {
// TODO(b/171776026): Implement this once the new states have been added to
// the metrics.
}
TEST_F(TriggerScriptCoordinatorTest, PerformTriggerScriptActionCancelForever) {
// TODO(b/171776026): Implement this once the new states have been added to
// the metrics.
}
TEST_F(TriggerScriptCoordinatorTest, PerformTriggerScriptActionAccept) {
GetTriggerScriptsResponseProto response;
*response.add_trigger_scripts()
->mutable_trigger_condition()
->mutable_selector() = ToSelectorProto("#selector");
std::string serialized_response;
response.SerializeToString(&serialized_response);
EXPECT_CALL(*mock_request_sender_, OnSendRequest(GURL(kFakeServerUrl), _, _))
.WillOnce(RunOnceCallback<2>(net::HTTP_OK, serialized_response));
EXPECT_CALL(*mock_static_trigger_conditions_, Init)
.WillOnce(RunOnceCallback<3>());
ON_CALL(*mock_dynamic_trigger_conditions_, OnUpdate(mock_web_controller_, _))
.WillByDefault(RunOnceCallback<1>());
EXPECT_CALL(*mock_dynamic_trigger_conditions_, GetSelectorMatches)
.WillOnce(Return(true));
EXPECT_CALL(mock_observer_, OnTriggerScriptShown).Times(1);
coordinator_->Start(GURL(kFakeDeepLink),
std::make_unique<TriggerContextImpl>());
EXPECT_CALL(
mock_observer_,
OnTriggerScriptFinished(
Metrics::LiteScriptFinishedState::LITE_SCRIPT_PROMPT_SUCCEEDED))
.Times(1);
coordinator_->PerformTriggerScriptAction(TriggerScriptProto::ACCEPT);
}
TEST_F(TriggerScriptCoordinatorTest, CancelOnNavigateAway) {
GetTriggerScriptsResponseProto response;
response.add_additional_allowed_domains("other-example.com");
*response.add_trigger_scripts()
->mutable_trigger_condition()
->mutable_selector() = ToSelectorProto("#selector");
std::string serialized_response;
response.SerializeToString(&serialized_response);
EXPECT_CALL(*mock_request_sender_, OnSendRequest(GURL(kFakeServerUrl), _, _))
.WillOnce(RunOnceCallback<2>(net::HTTP_OK, serialized_response));
EXPECT_CALL(*mock_static_trigger_conditions_, Init)
.WillOnce(RunOnceCallback<3>());
ON_CALL(*mock_dynamic_trigger_conditions_, OnUpdate(mock_web_controller_, _))
.WillByDefault(RunOnceCallback<1>());
EXPECT_CALL(*mock_dynamic_trigger_conditions_, GetSelectorMatches)
.WillOnce(Return(true));
EXPECT_CALL(mock_observer_, OnTriggerScriptShown).Times(1);
coordinator_->Start(GURL(kFakeDeepLink),
std::make_unique<TriggerContextImpl>());
// Same-domain navigation is ok.
EXPECT_CALL(mock_observer_, OnTriggerScriptFinished(_)).Times(0);
SimulateNavigateToUrl(GURL("https://example.com/cart"));
// Navigating to sub-domain of original domain is ok.
SimulateNavigateToUrl(GURL("https://subdomain.example.com/test"));
// Navigating to whitelisted domain is ok.
SimulateNavigateToUrl(GURL("https://other-example.com/page"));
// Navigating to subdomain of whitelisted domain is ok.
SimulateNavigateToUrl(GURL("https://other-example.com/page"));
// Navigating to non-whitelisted domain is not ok.
EXPECT_CALL(mock_observer_, OnTriggerScriptFinished(_)).Times(1);
SimulateNavigateToUrl(GURL("https://example.different.com/page"));
} }
} // namespace
} // namespace autofill_assistant } // namespace autofill_assistant
...@@ -8,6 +8,8 @@ ...@@ -8,6 +8,8 @@
#include "base/test/mock_callback.h" #include "base/test/mock_callback.h"
#include "components/autofill_assistant/browser/service.pb.h" #include "components/autofill_assistant/browser/service.pb.h"
#include "components/autofill_assistant/browser/trigger_scripts/dynamic_trigger_conditions.h" #include "components/autofill_assistant/browser/trigger_scripts/dynamic_trigger_conditions.h"
#include "components/autofill_assistant/browser/trigger_scripts/mock_dynamic_trigger_conditions.h"
#include "components/autofill_assistant/browser/trigger_scripts/mock_static_trigger_conditions.h"
#include "components/autofill_assistant/browser/trigger_scripts/static_trigger_conditions.h" #include "components/autofill_assistant/browser/trigger_scripts/static_trigger_conditions.h"
#include "testing/gmock/include/gmock/gmock.h" #include "testing/gmock/include/gmock/gmock.h"
...@@ -16,32 +18,6 @@ namespace autofill_assistant { ...@@ -16,32 +18,6 @@ namespace autofill_assistant {
using ::testing::NiceMock; using ::testing::NiceMock;
using ::testing::Return; using ::testing::Return;
class MockStaticTriggerConditions : public StaticTriggerConditions {
public:
MOCK_METHOD4(Init,
void(Client* client,
const GURL& url,
TriggerContext* trigger_context,
base::OnceCallback<void(void)> callback));
MOCK_CONST_METHOD0(is_first_time_user, bool());
MOCK_CONST_METHOD0(has_stored_login_credentials, bool());
MOCK_CONST_METHOD1(is_in_experiment, bool(int experiment_id));
};
class MockDynamicTriggerConditions : public DynamicTriggerConditions {
public:
MOCK_CONST_METHOD1(GetSelectorMatches,
base::Optional<bool>(const Selector& selector));
void Update(WebController* web_controller,
base::OnceCallback<void(void)> callback) override {
OnUpdate(web_controller, callback);
}
MOCK_METHOD2(OnUpdate,
void(WebController* web_controller,
base::OnceCallback<void(void)>& callback));
};
class TriggerScriptTest : public testing::Test { class TriggerScriptTest : public testing::Test {
public: public:
TriggerScriptTest() : trigger_script_(TriggerScriptProto()) {} TriggerScriptTest() : trigger_script_(TriggerScriptProto()) {}
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment