Commit ae61a59a authored by Clemens Arbesser's avatar Clemens Arbesser Committed by Commit Bot

[Autofill Assistant] Added ForEach interaction.

Note: this is an alternative solution for http://crrev/c/2235698

This interaction executes a number of callbacks for the input loop value. This is intended to be used to inflate UI elements for client-only values, i.e., for values that the backend can't specify.

Internally, ForEach loops are implemented by introducing the concept of callback contexts, which will change value and view lookup accordingly.

In particular, callback contexts are used to automatically replace placeholders of the form ${i} in value and view identifiers (where 'i' is the loop identifier). This allows creating and referencing values and views with templated names, such as "created_view_${i}" and "value[${i}]".

Bug: b/145043394
Change-Id: I53089252fe1cc14b2b1fb74cfc56d7314bc4b37c
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2241975
Commit-Queue: Clemens Arbesser <arbesser@google.com>
Reviewed-by: default avatarSandro Maggi <sandromaggi@google.com>
Reviewed-by: default avatarMarian Fechete <marianfe@google.com>
Cr-Commit-Position: refs/heads/master@{#780785}
parent 4654d8e5
...@@ -489,16 +489,17 @@ GenericUiControllerAndroid::~GenericUiControllerAndroid() { ...@@ -489,16 +489,17 @@ GenericUiControllerAndroid::~GenericUiControllerAndroid() {
std::unique_ptr<GenericUiControllerAndroid> std::unique_ptr<GenericUiControllerAndroid>
GenericUiControllerAndroid::CreateFromProto( GenericUiControllerAndroid::CreateFromProto(
const GenericUserInterfaceProto& proto, const GenericUserInterfaceProto& proto,
const std::map<std::string, std::string> context,
base::android::ScopedJavaGlobalRef<jobject> jcontext, base::android::ScopedJavaGlobalRef<jobject> jcontext,
base::android::ScopedJavaGlobalRef<jobject> jdelegate, base::android::ScopedJavaGlobalRef<jobject> jdelegate,
EventHandler* event_handler, EventHandler* event_handler,
UserModel* user_model, UserModel* user_model,
BasicInteractions* basic_interactions) { BasicInteractions* basic_interactions) {
// Create view layout. // Create view layout.
auto view_handler = std::make_unique<ViewHandlerAndroid>(); auto view_handler = std::make_unique<ViewHandlerAndroid>(context);
auto interaction_handler = std::make_unique<InteractionHandlerAndroid>( auto interaction_handler = std::make_unique<InteractionHandlerAndroid>(
event_handler, user_model, basic_interactions, view_handler.get(), context, event_handler, user_model, basic_interactions,
jcontext, jdelegate); view_handler.get(), jcontext, jdelegate);
JNIEnv* env = base::android::AttachCurrentThread(); JNIEnv* env = base::android::AttachCurrentThread();
auto jroot_view = auto jroot_view =
proto.has_root_view() proto.has_root_view()
......
...@@ -28,6 +28,7 @@ class GenericUiControllerAndroid { ...@@ -28,6 +28,7 @@ class GenericUiControllerAndroid {
// Ownership of the arguments is not changed. // Ownership of the arguments is not changed.
static std::unique_ptr<GenericUiControllerAndroid> CreateFromProto( static std::unique_ptr<GenericUiControllerAndroid> CreateFromProto(
const GenericUserInterfaceProto& proto, const GenericUserInterfaceProto& proto,
const std::map<std::string, std::string> context,
base::android::ScopedJavaGlobalRef<jobject> jcontext, base::android::ScopedJavaGlobalRef<jobject> jcontext,
base::android::ScopedJavaGlobalRef<jobject> jdelegate, base::android::ScopedJavaGlobalRef<jobject> jdelegate,
EventHandler* event_handler, EventHandler* event_handler,
......
...@@ -14,6 +14,7 @@ ...@@ -14,6 +14,7 @@
#include "chrome/browser/android/autofill_assistant/generic_ui_interactions_android.h" #include "chrome/browser/android/autofill_assistant/generic_ui_interactions_android.h"
#include "chrome/browser/android/autofill_assistant/view_handler_android.h" #include "chrome/browser/android/autofill_assistant/view_handler_android.h"
#include "components/autofill_assistant/browser/basic_interactions.h" #include "components/autofill_assistant/browser/basic_interactions.h"
#include "components/autofill_assistant/browser/field_formatter.h"
#include "components/autofill_assistant/browser/generic_ui.pb.h" #include "components/autofill_assistant/browser/generic_ui.pb.h"
#include "components/autofill_assistant/browser/ui_delegate.h" #include "components/autofill_assistant/browser/ui_delegate.h"
#include "components/autofill_assistant/browser/user_model.h" #include "components/autofill_assistant/browser/user_model.h"
...@@ -21,14 +22,113 @@ ...@@ -21,14 +22,113 @@
namespace autofill_assistant { namespace autofill_assistant {
namespace {
// Helper RAII class that sets the execution context for callbacks and unsets
// the context upon deletion. Simply unsetting the context after running the
// callbacks is unsafe, as a callback may have ended the action, thus deleting
// the context and leading to a crash.
class SetExecutionContext {
public:
SetExecutionContext(base::WeakPtr<UserModel> user_model,
base::WeakPtr<ViewHandlerAndroid> view_handler,
const std::map<std::string, std::string>& context)
: user_model_(user_model),
view_handler_(view_handler),
context_(context) {
if (user_model_ != nullptr) {
user_model_->AddIdentifierPlaceholders(context_);
}
if (view_handler_ != nullptr) {
view_handler_->AddIdentifierPlaceholders(context_);
}
}
~SetExecutionContext() {
if (user_model_ != nullptr) {
user_model_->RemoveIdentifierPlaceholders(context_);
}
if (view_handler_ != nullptr) {
view_handler_->RemoveIdentifierPlaceholders(context_);
}
}
private:
base::WeakPtr<UserModel> user_model_;
base::WeakPtr<ViewHandlerAndroid> view_handler_;
std::map<std::string, std::string> context_;
};
// Runs |callbacks| using the context provided by |interaction_handler| and
// |additional_context|.
// Note: parameters are passed by value, as their owner may go out of scope
// before all callbacks have been processed.
void RunWithContext(
std::vector<InteractionHandlerAndroid::InteractionCallback> callbacks,
std::map<std::string, std::string> additional_context,
base::WeakPtr<InteractionHandlerAndroid> interaction_handler,
base::WeakPtr<UserModel> user_model,
base::WeakPtr<ViewHandlerAndroid> view_handler) {
if (!interaction_handler || !user_model || !view_handler) {
return;
}
// Context is set via RAII to ensure that it is properly unset when done.
interaction_handler->AddContext(additional_context);
SetExecutionContext set_context(user_model, view_handler,
interaction_handler->GetContext());
for (const auto& callback : callbacks) {
callback.Run();
// A callback may have caused |interaction_handler| to go out of scope.
if (!interaction_handler) {
return;
}
}
if (interaction_handler != nullptr) {
interaction_handler->RemoveContext(additional_context);
}
}
void RunForEachLoop(
const ForEachProto& proto,
const std::vector<InteractionHandlerAndroid::InteractionCallback>&
callbacks,
base::WeakPtr<InteractionHandlerAndroid> interaction_handler,
base::WeakPtr<UserModel> user_model,
base::WeakPtr<ViewHandlerAndroid> view_handler) {
if (!interaction_handler || !user_model || !view_handler) {
return;
}
auto loop_value = user_model->GetValue(proto.loop_value_model_identifier());
if (!loop_value.has_value()) {
VLOG(2) << "Error running ForEach loop: "
<< proto.loop_value_model_identifier() << " not found in model";
return;
}
for (int i = 0; i < GetValueSize(*loop_value); ++i) {
// Temporarily add "<loop_counter> -> i" to execution context.
// Note: interactions may create nested UI instances. Those instances
// will inherit their parents' current context, which includes the
// placeholder for the loop variable currently being iterated.
RunWithContext(callbacks, /* additional_context = */
{{proto.loop_counter(), base::NumberToString(i)}},
interaction_handler, user_model, view_handler);
}
}
} // namespace
InteractionHandlerAndroid::InteractionHandlerAndroid( InteractionHandlerAndroid::InteractionHandlerAndroid(
const std::map<std::string, std::string>& context,
EventHandler* event_handler, EventHandler* event_handler,
UserModel* user_model, UserModel* user_model,
BasicInteractions* basic_interactions, BasicInteractions* basic_interactions,
ViewHandlerAndroid* view_handler, ViewHandlerAndroid* view_handler,
base::android::ScopedJavaGlobalRef<jobject> jcontext, base::android::ScopedJavaGlobalRef<jobject> jcontext,
base::android::ScopedJavaGlobalRef<jobject> jdelegate) base::android::ScopedJavaGlobalRef<jobject> jdelegate)
: event_handler_(event_handler), : context_(context),
event_handler_(event_handler),
user_model_(user_model), user_model_(user_model),
basic_interactions_(basic_interactions), basic_interactions_(basic_interactions),
view_handler_(view_handler), view_handler_(view_handler),
...@@ -54,6 +154,20 @@ void InteractionHandlerAndroid::StopListening() { ...@@ -54,6 +154,20 @@ void InteractionHandlerAndroid::StopListening() {
is_listening_ = false; is_listening_ = false;
} }
void InteractionHandlerAndroid::AddContext(
const std::map<std::string, std::string>& context) {
for (const auto& value : context) {
context_[value.first] = value.second;
}
}
void InteractionHandlerAndroid::RemoveContext(
const std::map<std::string, std::string>& context) {
for (const auto& value : context) {
context_.erase(value.first);
}
}
UserModel* InteractionHandlerAndroid::GetUserModel() const { UserModel* InteractionHandlerAndroid::GetUserModel() const {
return user_model_; return user_model_;
} }
...@@ -101,9 +215,11 @@ void InteractionHandlerAndroid::AddInteraction( ...@@ -101,9 +215,11 @@ void InteractionHandlerAndroid::AddInteraction(
void InteractionHandlerAndroid::OnEvent(const EventHandler::EventKey& key) { void InteractionHandlerAndroid::OnEvent(const EventHandler::EventKey& key) {
auto it = interactions_.find(key); auto it = interactions_.find(key);
if (it != interactions_.end()) { if (it != interactions_.end()) {
for (auto& callback : it->second) { RunWithContext(it->second, /* additional_context = */ {},
callback.Run(); this->GetWeakPtr(), user_model_->GetWeakPtr(),
} view_handler_->GetWeakPtr());
// Note: it is unsafe to call any code after running callbacks, because
// a callback may effectively delete *this.
} }
} }
...@@ -277,13 +393,45 @@ InteractionHandlerAndroid::CreateInteractionCallbackFromProto( ...@@ -277,13 +393,45 @@ InteractionHandlerAndroid::CreateInteractionCallbackFromProto(
base::BindRepeating(&android_interactions::ClearViewContainer, base::BindRepeating(&android_interactions::ClearViewContainer,
proto.clear_view_container().view_identifier(), proto.clear_view_container().view_identifier(),
view_handler_, jdelegate_)); view_handler_, jdelegate_));
case CallbackProto::kForEach: {
if (proto.for_each().loop_counter().empty()) {
VLOG(1) << "Error creating ForEach interaction: "
"loop_counter not set";
return base::nullopt;
}
if (proto.for_each().loop_value_model_identifier().empty()) {
VLOG(1) << "Error creating ForEach interaction: "
"loop_value_model_identifier not set";
return base::nullopt;
}
std::vector<InteractionHandlerAndroid::InteractionCallback> callbacks;
for (const auto& callback_proto : proto.for_each().callbacks()) {
auto callback = CreateInteractionCallbackFromProto(callback_proto);
if (!callback.has_value()) {
VLOG(1) << "Error creating ForEach interaction: failed to create "
"callback";
return base::nullopt;
}
callbacks.emplace_back(*callback);
}
return base::Optional<InteractionCallback>(base::BindRepeating(
&RunForEachLoop, proto.for_each(), callbacks, GetWeakPtr(),
user_model_->GetWeakPtr(), view_handler_->GetWeakPtr()));
}
case CallbackProto::KIND_NOT_SET: case CallbackProto::KIND_NOT_SET:
VLOG(1) << "Error creating interaction: kind not set"; VLOG(1) << "Error creating interaction: kind not set";
return base::nullopt; return base::nullopt;
} }
} }
void InteractionHandlerAndroid::DeleteNestedUi(const std::string& identifier) { void InteractionHandlerAndroid::DeleteNestedUi(const std::string& input) {
// Replace all placeholders in the input.
auto formatted_identifier = field_formatter::FormatString(input, context_);
if (!formatted_identifier.has_value()) {
VLOG(2) << "Error deleting nested UI: placeholder not found for " << input;
return;
}
std::string identifier = *formatted_identifier;
auto it = nested_ui_controllers_.find(identifier); auto it = nested_ui_controllers_.find(identifier);
if (it != nested_ui_controllers_.end()) { if (it != nested_ui_controllers_.end()) {
nested_ui_controllers_.erase(it); nested_ui_controllers_.erase(it);
...@@ -292,7 +440,14 @@ void InteractionHandlerAndroid::DeleteNestedUi(const std::string& identifier) { ...@@ -292,7 +440,14 @@ void InteractionHandlerAndroid::DeleteNestedUi(const std::string& identifier) {
const GenericUiControllerAndroid* InteractionHandlerAndroid::CreateNestedUi( const GenericUiControllerAndroid* InteractionHandlerAndroid::CreateNestedUi(
const GenericUserInterfaceProto& proto, const GenericUserInterfaceProto& proto,
const std::string& identifier) { const std::string& input) {
// Replace all placeholders in the input.
auto formatted_identifier = field_formatter::FormatString(input, context_);
if (!formatted_identifier.has_value()) {
VLOG(2) << "Error creating nested UI: placeholder not found for " << input;
return nullptr;
}
std::string identifier = *formatted_identifier;
if (nested_ui_controllers_.find(identifier) != nested_ui_controllers_.end()) { if (nested_ui_controllers_.find(identifier) != nested_ui_controllers_.end()) {
VLOG(2) << "Error creating nested UI: " << identifier VLOG(2) << "Error creating nested UI: " << identifier
<< " already exixsts (did you forget to clear the previous " << " already exixsts (did you forget to clear the previous "
...@@ -300,7 +455,7 @@ const GenericUiControllerAndroid* InteractionHandlerAndroid::CreateNestedUi( ...@@ -300,7 +455,7 @@ const GenericUiControllerAndroid* InteractionHandlerAndroid::CreateNestedUi(
return nullptr; return nullptr;
} }
auto nested_ui = GenericUiControllerAndroid::CreateFromProto( auto nested_ui = GenericUiControllerAndroid::CreateFromProto(
proto, jcontext_, jdelegate_, event_handler_, user_model_, proto, context_, jcontext_, jdelegate_, event_handler_, user_model_,
basic_interactions_); basic_interactions_);
const auto* nested_ui_ptr = nested_ui.get(); const auto* nested_ui_ptr = nested_ui.get();
if (nested_ui) { if (nested_ui) {
......
...@@ -36,6 +36,7 @@ class InteractionHandlerAndroid : public EventHandler::Observer { ...@@ -36,6 +36,7 @@ class InteractionHandlerAndroid : public EventHandler::Observer {
// Constructor. All dependencies must outlive this instance. // Constructor. All dependencies must outlive this instance.
InteractionHandlerAndroid( InteractionHandlerAndroid(
const std::map<std::string, std::string>& context,
EventHandler* event_handler, EventHandler* event_handler,
UserModel* user_model, UserModel* user_model,
BasicInteractions* basic_interactions, BasicInteractions* basic_interactions,
...@@ -49,6 +50,15 @@ class InteractionHandlerAndroid : public EventHandler::Observer { ...@@ -49,6 +50,15 @@ class InteractionHandlerAndroid : public EventHandler::Observer {
void StartListening(); void StartListening();
void StopListening(); void StopListening();
// Adds |context| to the current context of this interaction handler.
void AddContext(const std::map<std::string, std::string>& context);
// Removes the keys in |context| from this handler's context.
void RemoveContext(const std::map<std::string, std::string>& context);
// Returns a copy of the current context.
std::map<std::string, std::string> GetContext() const { return context_; }
// Access to the user model that this interaction handler is bound to. // Access to the user model that this interaction handler is bound to.
UserModel* GetUserModel() const; UserModel* GetUserModel() const;
...@@ -103,6 +113,11 @@ class InteractionHandlerAndroid : public EventHandler::Observer { ...@@ -103,6 +113,11 @@ class InteractionHandlerAndroid : public EventHandler::Observer {
std::map<EventHandler::EventKey, std::vector<InteractionCallback>> std::map<EventHandler::EventKey, std::vector<InteractionCallback>>
interactions_; interactions_;
// These key-value pairs specify context variables that the handler will use
// to resolve views and values. Nested instances will inherit their parents'
// context variables. Special interactions, such as ForEach, may modify the
// context while they are being executed.
std::map<std::string, std::string> context_;
EventHandler* event_handler_ = nullptr; EventHandler* event_handler_ = nullptr;
UserModel* user_model_ = nullptr; UserModel* user_model_ = nullptr;
BasicInteractions* basic_interactions_ = nullptr; BasicInteractions* basic_interactions_ = nullptr;
......
...@@ -1691,7 +1691,8 @@ UiControllerAndroid::CreateGenericUiControllerForProto( ...@@ -1691,7 +1691,8 @@ UiControllerAndroid::CreateGenericUiControllerForProto(
auto jcontext = auto jcontext =
Java_AutofillAssistantUiController_getContext(env, java_object_); Java_AutofillAssistantUiController_getContext(env, java_object_);
return GenericUiControllerAndroid::CreateFromProto( return GenericUiControllerAndroid::CreateFromProto(
proto, base::android::ScopedJavaGlobalRef<jobject>(jcontext), proto, /* context = */ {},
base::android::ScopedJavaGlobalRef<jobject>(jcontext),
generic_ui_delegate_.GetJavaObject(), ui_delegate_->GetEventHandler(), generic_ui_delegate_.GetJavaObject(), ui_delegate_->GetEventHandler(),
ui_delegate_->GetUserModel(), ui_delegate_->GetBasicInteractions()); ui_delegate_->GetUserModel(), ui_delegate_->GetBasicInteractions());
} }
......
...@@ -3,14 +3,28 @@ ...@@ -3,14 +3,28 @@
// found in the LICENSE file. // found in the LICENSE file.
#include "chrome/browser/android/autofill_assistant/view_handler_android.h" #include "chrome/browser/android/autofill_assistant/view_handler_android.h"
#include "components/autofill_assistant/browser/field_formatter.h"
namespace autofill_assistant { namespace autofill_assistant {
ViewHandlerAndroid::ViewHandlerAndroid() = default; ViewHandlerAndroid::ViewHandlerAndroid(
const std::map<std::string, std::string>& identifier_placeholders)
: identifier_placeholders_(identifier_placeholders) {}
ViewHandlerAndroid::~ViewHandlerAndroid() = default; ViewHandlerAndroid::~ViewHandlerAndroid() = default;
base::WeakPtr<ViewHandlerAndroid> ViewHandlerAndroid::GetWeakPtr() {
return weak_ptr_factory_.GetWeakPtr();
}
base::Optional<base::android::ScopedJavaGlobalRef<jobject>> base::Optional<base::android::ScopedJavaGlobalRef<jobject>>
ViewHandlerAndroid::GetView(const std::string& view_identifier) const { ViewHandlerAndroid::GetView(const std::string& input) const {
// Replace all placeholders in the input.
auto formatted_identifier =
field_formatter::FormatString(input, identifier_placeholders_);
if (!formatted_identifier.has_value()) {
return base::nullopt;
}
std::string view_identifier = *formatted_identifier;
auto it = views_.find(view_identifier); auto it = views_.find(view_identifier);
if (it == views_.end()) { if (it == views_.end()) {
return base::nullopt; return base::nullopt;
...@@ -20,10 +34,31 @@ ViewHandlerAndroid::GetView(const std::string& view_identifier) const { ...@@ -20,10 +34,31 @@ ViewHandlerAndroid::GetView(const std::string& view_identifier) const {
// Adds a view to the set of managed views. // Adds a view to the set of managed views.
void ViewHandlerAndroid::AddView( void ViewHandlerAndroid::AddView(
const std::string& view_identifier, const std::string& input,
base::android::ScopedJavaGlobalRef<jobject> jview) { base::android::ScopedJavaGlobalRef<jobject> jview) {
// Replace all placeholders in the input.
auto formatted_identifier =
field_formatter::FormatString(input, identifier_placeholders_);
if (!formatted_identifier.has_value()) {
return;
}
std::string view_identifier = *formatted_identifier;
DCHECK(views_.find(view_identifier) == views_.end()); DCHECK(views_.find(view_identifier) == views_.end());
views_.emplace(view_identifier, jview); views_.emplace(view_identifier, jview);
} }
void ViewHandlerAndroid::AddIdentifierPlaceholders(
const std::map<std::string, std::string> placeholders) {
for (const auto& placeholder : placeholders) {
identifier_placeholders_[placeholder.first] = placeholder.second;
}
}
void ViewHandlerAndroid::RemoveIdentifierPlaceholders(
const std::map<std::string, std::string> placeholders) {
for (const auto& placeholder : placeholders) {
identifier_placeholders_.erase(placeholder.first);
}
}
} // namespace autofill_assistant } // namespace autofill_assistant
...@@ -10,6 +10,7 @@ ...@@ -10,6 +10,7 @@
#include <string> #include <string>
#include "base/android/jni_android.h" #include "base/android/jni_android.h"
#include "base/memory/weak_ptr.h"
#include "base/optional.h" #include "base/optional.h"
namespace autofill_assistant { namespace autofill_assistant {
...@@ -17,13 +18,18 @@ namespace autofill_assistant { ...@@ -17,13 +18,18 @@ namespace autofill_assistant {
// Manages a map of view-identifier -> android view instances. // Manages a map of view-identifier -> android view instances.
class ViewHandlerAndroid { class ViewHandlerAndroid {
public: public:
ViewHandlerAndroid(); explicit ViewHandlerAndroid(
const std::map<std::string, std::string>& identifier_placeholders);
~ViewHandlerAndroid(); ~ViewHandlerAndroid();
ViewHandlerAndroid(const ViewHandlerAndroid&) = delete; ViewHandlerAndroid(const ViewHandlerAndroid&) = delete;
ViewHandlerAndroid& operator=(const ViewHandlerAndroid&) = delete; ViewHandlerAndroid& operator=(const ViewHandlerAndroid&) = delete;
base::WeakPtr<ViewHandlerAndroid> GetWeakPtr();
// Returns the view associated with |view_identifier| or base::nullopt if // Returns the view associated with |view_identifier| or base::nullopt if
// there is no such view. // there is no such view.
// -Placeholders in |view_identifier| of the form ${key} are automatically
// replaced (see |AddIdentifierPlaceholders|).
base::Optional<base::android::ScopedJavaGlobalRef<jobject>> GetView( base::Optional<base::android::ScopedJavaGlobalRef<jobject>> GetView(
const std::string& view_identifier) const; const std::string& view_identifier) const;
...@@ -31,8 +37,21 @@ class ViewHandlerAndroid { ...@@ -31,8 +37,21 @@ class ViewHandlerAndroid {
void AddView(const std::string& view_identifier, void AddView(const std::string& view_identifier,
base::android::ScopedJavaGlobalRef<jobject> jview); base::android::ScopedJavaGlobalRef<jobject> jview);
// Adds a set of placeholders (overwrite if necessary). When looking up views
// by identifier, all occurrences of ${key} are automatically replaced by
// their value. Example: the current set of placeholders contains "i" -> "1".
// Looking up the view "view_${i}" will now actually lookup "view_1".
void AddIdentifierPlaceholders(
const std::map<std::string, std::string> placeholders);
// Removes a set of placeholders.
void RemoveIdentifierPlaceholders(
const std::map<std::string, std::string> placeholders);
private: private:
std::map<std::string, base::android::ScopedJavaGlobalRef<jobject>> views_; std::map<std::string, base::android::ScopedJavaGlobalRef<jobject>> views_;
std::map<std::string, std::string> identifier_placeholders_;
base::WeakPtrFactory<ViewHandlerAndroid> weak_ptr_factory_{this};
}; };
} // namespace autofill_assistant } // namespace autofill_assistant
......
...@@ -51,6 +51,7 @@ message CallbackProto { ...@@ -51,6 +51,7 @@ message CallbackProto {
ShowGenericUiPopupProto show_generic_popup = 13; ShowGenericUiPopupProto show_generic_popup = 13;
CreateNestedGenericUiProto create_nested_ui = 14; CreateNestedGenericUiProto create_nested_ui = 14;
ClearViewContainerProto clear_view_container = 15; ClearViewContainerProto clear_view_container = 15;
ForEachProto for_each = 16;
} }
// Optional model identifier pointing to a single boolean. If set, the // Optional model identifier pointing to a single boolean. If set, the
// callback will only be invoked if the condition is true. // callback will only be invoked if the condition is true.
...@@ -392,3 +393,18 @@ message ClearViewContainerProto { ...@@ -392,3 +393,18 @@ message ClearViewContainerProto {
// The view container to clear. // The view container to clear.
optional string view_identifier = 1; optional string view_identifier = 1;
} }
// Invokes |callbacks| for each item in the loop value. Automatically replaces
// instances of "${i}" in model and view identifiers with the loop counter.
message ForEachProto {
// The loop counter, usually "i", "j", etc. Callbacks may use this counter
// in view and model identifiers by using "${i}"" placeholders, e.g.,
// "profiles[${i}]" or "my_view_${i}".
optional string loop_counter = 1;
// The value list to loop over.
optional string loop_value_model_identifier = 2;
// The callbacks to invoke for every iteration of the loop.
repeated CallbackProto callbacks = 3;
}
...@@ -3,6 +3,7 @@ ...@@ -3,6 +3,7 @@
// found in the LICENSE file. // found in the LICENSE file.
#include "components/autofill_assistant/browser/user_model.h" #include "components/autofill_assistant/browser/user_model.h"
#include "components/autofill_assistant/browser/field_formatter.h"
#include "base/strings/string_number_conversions.h" #include "base/strings/string_number_conversions.h"
#include "base/strings/string_util.h" #include "base/strings/string_util.h"
...@@ -55,9 +56,17 @@ base::WeakPtr<UserModel> UserModel::GetWeakPtr() { ...@@ -55,9 +56,17 @@ base::WeakPtr<UserModel> UserModel::GetWeakPtr() {
return weak_ptr_factory_.GetWeakPtr(); return weak_ptr_factory_.GetWeakPtr();
} }
void UserModel::SetValue(const std::string& identifier, void UserModel::SetValue(const std::string& input,
const ValueProto& value, const ValueProto& value,
bool force_notification) { bool force_notification) {
// Replace all placeholders in the input.
auto formatted_identifier =
field_formatter::FormatString(input, identifier_placeholders_);
if (!formatted_identifier.has_value()) {
VLOG(2) << "Error setting value: placeholder not found for " << input;
return;
}
std::string identifier = *formatted_identifier;
auto result = values_.emplace(identifier, value); auto result = values_.emplace(identifier, value);
if (!force_notification && !result.second && result.first->second == value && if (!force_notification && !result.second && result.first->second == value &&
value.is_client_side_only() == value.is_client_side_only() ==
...@@ -72,8 +81,14 @@ void UserModel::SetValue(const std::string& identifier, ...@@ -72,8 +81,14 @@ void UserModel::SetValue(const std::string& identifier,
} }
} }
base::Optional<ValueProto> UserModel::GetValue( base::Optional<ValueProto> UserModel::GetValue(const std::string& input) const {
const std::string& identifier) const { // Replace all placeholders in the input.
auto formatted_identifier =
field_formatter::FormatString(input, identifier_placeholders_);
if (!formatted_identifier.has_value()) {
return base::nullopt;
}
std::string identifier = *formatted_identifier;
auto it = values_.find(identifier); auto it = values_.find(identifier);
if (it != values_.end()) { if (it != values_.end()) {
return it->second; return it->second;
...@@ -145,6 +160,20 @@ void UserModel::RemoveObserver(Observer* observer) { ...@@ -145,6 +160,20 @@ void UserModel::RemoveObserver(Observer* observer) {
observers_.RemoveObserver(observer); observers_.RemoveObserver(observer);
} }
void UserModel::AddIdentifierPlaceholders(
const std::map<std::string, std::string> placeholders) {
for (const auto& placeholder : placeholders) {
identifier_placeholders_[placeholder.first] = placeholder.second;
}
}
void UserModel::RemoveIdentifierPlaceholders(
const std::map<std::string, std::string> placeholders) {
for (const auto& placeholder : placeholders) {
identifier_placeholders_.erase(placeholder.first);
}
}
void UserModel::SetAutofillCreditCards( void UserModel::SetAutofillCreditCards(
std::unique_ptr<std::vector<std::unique_ptr<autofill::CreditCard>>> std::unique_ptr<std::vector<std::unique_ptr<autofill::CreditCard>>>
credit_cards) { credit_cards) {
......
...@@ -48,8 +48,10 @@ class UserModel { ...@@ -48,8 +48,10 @@ class UserModel {
bool force_notification = false); bool force_notification = false);
// Returns the value for |identifier| or nullopt if there is no such value. // Returns the value for |identifier| or nullopt if there is no such value.
// Also supports the array operator to retrieve a specific element of a list, // - Placeholders in |identifier| of the form ${key} are automatically
// e.g., "identifier[0]" to get the first item. // replaced (see |AddIdentifierPlaceholders|).
// - Also supports the array operator to retrieve
// a specific element of a list, e.g., "identifier[0]" to get the first item.
base::Optional<ValueProto> GetValue(const std::string& identifier) const; base::Optional<ValueProto> GetValue(const std::string& identifier) const;
// Returns the value for |reference| or nullopt if there is no such value. // Returns the value for |reference| or nullopt if there is no such value.
...@@ -72,6 +74,17 @@ class UserModel { ...@@ -72,6 +74,17 @@ class UserModel {
return values; return values;
} }
// Adds a set of placeholders (overwrite if necessary). When looking up values
// by identifier, all occurrences of ${key} are automatically replaced by
// their value. Example: the current set of placeholders contains "i" -> "1".
// Looking up the value "value[${i}]" will now actually lookup "value[1]".
void AddIdentifierPlaceholders(
const std::map<std::string, std::string> placeholders);
// Removes a set of placeholders.
void RemoveIdentifierPlaceholders(
const std::map<std::string, std::string> placeholders);
// Replaces the set of available autofill credit cards. // Replaces the set of available autofill credit cards.
void SetAutofillCreditCards( void SetAutofillCreditCards(
std::unique_ptr<std::vector<std::unique_ptr<autofill::CreditCard>>> std::unique_ptr<std::vector<std::unique_ptr<autofill::CreditCard>>>
...@@ -105,6 +118,7 @@ class UserModel { ...@@ -105,6 +118,7 @@ class UserModel {
friend class UserModelTest; friend class UserModelTest;
std::map<std::string, ValueProto> values_; std::map<std::string, ValueProto> values_;
std::map<std::string, std::string> identifier_placeholders_;
std::map<std::string, std::unique_ptr<autofill::CreditCard>> credit_cards_; std::map<std::string, std::unique_ptr<autofill::CreditCard>> credit_cards_;
std::map<std::string, std::unique_ptr<autofill::AutofillProfile>> profiles_; std::map<std::string, std::unique_ptr<autofill::AutofillProfile>> profiles_;
base::ObserverList<Observer> observers_; base::ObserverList<Observer> observers_;
......
...@@ -366,4 +366,75 @@ TEST_F(UserModelTest, ClientSideOnlyNotifications) { ...@@ -366,4 +366,75 @@ TEST_F(UserModelTest, ClientSideOnlyNotifications) {
EXPECT_TRUE(GetValues().at("identifier").is_client_side_only()); EXPECT_TRUE(GetValues().at("identifier").is_client_side_only());
} }
TEST_F(UserModelTest, GetValueWithPlaceholders) {
ValueProto value;
value.mutable_strings()->add_values("a");
value.mutable_strings()->add_values("b");
value.mutable_strings()->add_values("c");
model_.SetValue("multi_value", value);
model_.SetValue("single_value_0", SimpleValue(std::string("d")));
model_.SetValue("single_value_1", SimpleValue(std::string("e")));
model_.SetValue("single_value_2", SimpleValue(std::string("f")));
EXPECT_EQ(model_.GetValue("multi_value[${i}]"), base::nullopt);
EXPECT_EQ(model_.GetValue("single_value_i"), base::nullopt);
model_.AddIdentifierPlaceholders({{"i", "0"}});
EXPECT_EQ(model_.GetValue("multi_value[${i}]"),
SimpleValue(std::string("a")));
EXPECT_EQ(model_.GetValue("single_value_${i}"),
SimpleValue(std::string("d")));
// Add placeholder.
model_.AddIdentifierPlaceholders({{"j", "1"}});
EXPECT_EQ(model_.GetValue("multi_value[${j}]"),
SimpleValue(std::string("b")));
EXPECT_EQ(model_.GetValue("single_value_${j}"),
SimpleValue(std::string("e")));
EXPECT_EQ(model_.GetValue("single_value_${j}[${i}]"),
SimpleValue(std::string("e")));
// Overwrite placeholder.
model_.AddIdentifierPlaceholders({{"i", "2"}});
EXPECT_EQ(model_.GetValue("multi_value[${i}]"),
SimpleValue(std::string("c")));
EXPECT_EQ(model_.GetValue("single_value_${i}"),
SimpleValue(std::string("f")));
EXPECT_EQ(model_.GetValue("single_value_${j}[${i}]"), base::nullopt);
// Remove placeholder (the value does not matter, it's just about the key).
model_.RemoveIdentifierPlaceholders({{"i", "123"}});
EXPECT_EQ(model_.GetValue("multi_value[${i}]"), base::nullopt);
EXPECT_EQ(model_.GetValue("single_value_${i}"), base::nullopt);
EXPECT_EQ(model_.GetValue("single_value_${j}"),
SimpleValue(std::string("e")));
}
TEST_F(UserModelTest, SetValueWithPlaceholders) {
ValueProto value;
value.mutable_strings()->add_values("a");
value.mutable_strings()->add_values("b");
value.mutable_strings()->add_values("c");
model_.SetValue("value_${i}", value);
EXPECT_EQ(model_.GetValue("value_${i}"), base::nullopt);
model_.AddIdentifierPlaceholders({{"i", "0"}});
model_.SetValue("value_${i}", value);
EXPECT_EQ(model_.GetValue("value_0"), value);
EXPECT_EQ(model_.GetValue("value_${i}"), value);
model_.RemoveIdentifierPlaceholders({{"i", "0"}});
EXPECT_EQ(model_.GetValue("value_0"), value);
EXPECT_EQ(model_.GetValue("value_${i}"), base::nullopt);
model_.AddIdentifierPlaceholders({{"i", "0"}});
model_.AddIdentifierPlaceholders({{"j", "1"}});
model_.SetValue("value_${i}_${j}", value);
EXPECT_EQ(model_.GetValue("value_0_1"), value);
EXPECT_EQ(model_.GetValue("value_${i}_${j}"), value);
model_.RemoveIdentifierPlaceholders({{"j", "1"}});
EXPECT_EQ(model_.GetValue("value_${i}_${j}"), base::nullopt);
model_.SetValue("value_${i}", value);
EXPECT_EQ(model_.GetValue("value_${i}"), value);
}
} // namespace autofill_assistant } // namespace autofill_assistant
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