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") {
"test_util.h",
"trigger_context_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/trigger_script_coordinator_unittest.cc",
"trigger_scripts/trigger_script_unittest.cc",
......
......@@ -286,11 +286,32 @@ bool ProtocolUtils::ParseActions(ActionDelegate* delegate,
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
bool ProtocolUtils::ParseTriggerScripts(
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(additional_allowed_domains);
GetTriggerScriptsResponseProto response_proto;
if (!response_proto.ParseFromString(response)) {
......@@ -302,6 +323,11 @@ bool ProtocolUtils::ParseTriggerScripts(
trigger_scripts->emplace_back(
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;
}
......
......@@ -54,6 +54,12 @@ class ProtocolUtils {
const RoundtripTimingStats& timing_stats,
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|.
static std::unique_ptr<Action> CreateAction(ActionDelegate* delegate,
const ActionProto& action);
......@@ -79,7 +85,8 @@ class ProtocolUtils {
// |trigger_scripts|. Returns false if parsing failed, else true.
static bool ParseTriggerScripts(
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:
// To avoid instantiate this class by accident.
......
......@@ -327,19 +327,35 @@ TEST_F(ProtocolUtilsTest, ParseActionsUpdateScriptListFullFeatured) {
TEST_F(ProtocolUtilsTest, ParseTriggerScriptsParseError) {
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());
}
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) {
GetTriggerScriptsResponseProto proto;
proto.add_additional_allowed_domains("example.com");
proto.add_additional_allowed_domains("other-example.com");
TriggerScriptProto trigger_script_1;
*trigger_script_1.mutable_trigger_condition()->mutable_selector() =
ToSelectorProto("fake_element_1");
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_2;
......@@ -348,12 +364,16 @@ TEST_F(ProtocolUtilsTest, ParseTriggerScriptsValid) {
proto.SerializeToString(&proto_str);
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(
trigger_scripts,
ElementsAre(
Pointee(Property(&TriggerScript::AsProto, Eq(trigger_script_1))),
Pointee(Property(&TriggerScript::AsProto, Eq(trigger_script_2)))));
EXPECT_THAT(additional_allowed_domains,
ElementsAre("example.com", "other-example.com"));
}
} // namespace
......
......@@ -438,8 +438,8 @@ message Empty {}
// RPC request to fetch the available trigger scripts for a particular domain.
message GetTriggerScriptsRequestProto {
// The domain for which to fetch the trigger scripts.
optional string domain = 1;
// The exact url for which to fetch the trigger scripts.
optional string url = 1;
// The client context.
// NOTE: Currently, this will only contain the Chrome version number for
// privacy reasons.
......@@ -452,6 +452,11 @@ message GetTriggerScriptsRequestProto {
message GetTriggerScriptsResponseProto {
// The available trigger scripts, if any.
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
......@@ -478,12 +483,11 @@ message TriggerScriptProto {
// The |trigger_condition| must be true for the script to trigger.
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.
optional TriggerScriptUIProto user_interface = 3;
reserved 2;
}
message TriggerScriptConditionProto {
......
......@@ -22,7 +22,7 @@ namespace autofill_assistant {
class DynamicTriggerConditions {
public:
DynamicTriggerConditions();
~DynamicTriggerConditions();
virtual ~DynamicTriggerConditions();
// Adds the selector trigger conditions specified in |proto| to the list of
// 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,
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 {
return is_first_time_user_;
}
......
......@@ -22,7 +22,7 @@ namespace autofill_assistant {
class StaticTriggerConditions {
public:
StaticTriggerConditions();
~StaticTriggerConditions();
virtual ~StaticTriggerConditions();
// Initializes the field values according to |url| and the current state of
// |client|. Invokes |callback| when done. |client| and |trigger_context| must
......@@ -33,6 +33,7 @@ class StaticTriggerConditions {
const GURL& url,
TriggerContext* trigger_context,
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 has_stored_login_credentials() const;
virtual bool is_in_experiment(int experiment_id) const;
......
......@@ -55,5 +55,11 @@ TEST_F(StaticTriggerConditionsTest, Init) {
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 autofill_assistant
......@@ -77,4 +77,12 @@ TriggerScriptProto TriggerScript::AsProto() const {
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
......@@ -28,10 +28,16 @@ class TriggerScript {
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:
friend class TriggerScriptTest;
TriggerScriptProto proto_;
bool waiting_for_precondition_no_longer_true_ = false;
};
} // namespace autofill_assistant
......
......@@ -12,11 +12,17 @@
#include "base/memory/weak_ptr.h"
#include "base/observer_list.h"
#include "base/observer_list_types.h"
#include "base/time/time.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/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/web/web_controller.h"
#include "content/public/browser/navigation_handle.h"
#include "content/public/browser/web_contents.h"
#include "content/public/browser/web_contents_observer.h"
#include "url/gurl.h"
......@@ -37,21 +43,29 @@ class TriggerScriptCoordinator : public content::WebContentsObserver {
virtual void OnTriggerScriptShown(const TriggerScriptUIProto& proto) = 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.
TriggerScriptCoordinator(Client* client,
TriggerScriptCoordinator(
Client* client,
std::unique_ptr<WebController> web_controller,
std::unique_ptr<ServiceRequestSender> request_sender,
const GURL& get_trigger_scripts_server);
const GURL& get_trigger_scripts_server,
std::unique_ptr<StaticTriggerConditions> static_trigger_conditions,
std::unique_ptr<DynamicTriggerConditions> dynamic_trigger_conditions);
~TriggerScriptCoordinator() override;
TriggerScriptCoordinator(const TriggerScriptCoordinator&) = delete;
TriggerScriptCoordinator& operator=(const TriggerScriptCoordinator&) = delete;
// Retrieves all trigger scripts for |url| and starts evaluating their
// preconditions. Observers will be notified of all relevant status updates.
void Start(const GURL& url);
// Retrieves all trigger scripts for |deeplink_url| and starts evaluating
// their trigger conditions. Observers will be notified of all relevant status
// 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
// interactions.
......@@ -62,17 +76,74 @@ class TriggerScriptCoordinator : public content::WebContentsObserver {
void RemoveObserver(const Observer* observer);
private:
struct PendingTriggerScript {
TriggerScript trigger_script;
bool waiting_for_precondition_no_longer_true = false;
};
friend class TriggerScriptCoordinatorTest;
// From content::WebContentsObserver.
void DidFinishNavigation(
content::NavigationHandle* navigation_handle) 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.
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};
};
......
......@@ -8,6 +8,8 @@
#include "base/test/mock_callback.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/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 "testing/gmock/include/gmock/gmock.h"
......@@ -16,32 +18,6 @@ namespace autofill_assistant {
using ::testing::NiceMock;
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 {
public:
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