| // 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/actions/action_delegate_util.h" |
| |
| #include "base/callback.h" |
| #include "base/time/time.h" |
| #include "components/autofill_assistant/browser/action_value.pb.h" |
| #include "components/autofill_assistant/browser/actions/action_delegate.h" |
| #include "components/autofill_assistant/browser/client_settings.h" |
| #include "components/autofill_assistant/browser/selector.h" |
| #include "components/autofill_assistant/browser/service.pb.h" |
| #include "components/autofill_assistant/browser/string_conversions_util.h" |
| #include "components/autofill_assistant/browser/user_data_util.h" |
| #include "components/autofill_assistant/browser/web/element_finder.h" |
| #include "components/autofill_assistant/browser/web/element_store.h" |
| #include "components/autofill_assistant/browser/web/web_controller.h" |
| #include "ui/events/keycodes/dom/dom_key.h" |
| #include "ui/events/keycodes/dom/keycode_converter.h" |
| |
| namespace autofill_assistant { |
| namespace action_delegate_util { |
| namespace { |
| |
| void RetainElementAndExecuteCallback( |
| std::unique_ptr<ElementFinder::Result> element, |
| base::OnceCallback<void(const ClientStatus&)> callback, |
| const ClientStatus& status) { |
| DCHECK(element != nullptr); |
| std::move(callback).Run(status); |
| } |
| |
| void FindElementAndPerformImpl( |
| const ActionDelegate* delegate, |
| const Selector& selector, |
| element_action_util::ElementActionCallback perform, |
| base::OnceCallback<void(const ClientStatus&)> done) { |
| delegate->FindElement( |
| selector, base::BindOnce(&element_action_util::TakeElementAndPerform, |
| std::move(perform), std::move(done))); |
| } |
| |
| // Call |done| with the |status| while ignoring the |wait_time|. |
| void IgnoreTimingResult(base::OnceCallback<void(const ClientStatus&)> done, |
| const ClientStatus& status, |
| base::TimeDelta wait_time) { |
| std::move(done).Run(status); |
| } |
| |
| // Execute |action| and ignore the timing result. |
| void RunAndIgnoreTiming( |
| base::OnceCallback<void( |
| const ElementFinder::Result&, |
| base::OnceCallback<void(const ClientStatus&, base::TimeDelta)>)> action, |
| const ElementFinder::Result& element, |
| base::OnceCallback<void(const ClientStatus&)> done) { |
| std::move(action).Run(element, |
| base::BindOnce(&IgnoreTimingResult, std::move(done))); |
| } |
| |
| // Call |done| with a successful status, no matter what |status|. |
| // |
| // Note that the status details, if any, filled in |status| are conserved. |
| void IgnoreErrorStatus(base::OnceCallback<void(const ClientStatus&)> done, |
| const ClientStatus& status) { |
| if (status.ok()) { |
| std::move(done).Run(status); |
| return; |
| } |
| std::move(done).Run(status.WithStatusOverride(ACTION_APPLIED)); |
| } |
| |
| // Execute |action| but skip any failures by transforming failed ClientStatus |
| // into successes. |
| // |
| // Note that the status details filled by the failed action are conserved. |
| void SkipIfFail(element_action_util::ElementActionCallback action, |
| const ElementFinder::Result& element, |
| base::OnceCallback<void(const ClientStatus&)> done) { |
| std::move(action).Run(element, |
| base::BindOnce(&IgnoreErrorStatus, std::move(done))); |
| } |
| |
| // Adds a sequence of actions that execute a click. |
| void AddClickOrTapSequence(const ActionDelegate* delegate, |
| ClickType click_type, |
| element_action_util::ElementActionVector* actions) { |
| AddStepIgnoreTiming( |
| base::BindOnce(&ActionDelegate::WaitUntilDocumentIsInReadyState, |
| delegate->GetWeakPtr(), |
| delegate->GetSettings().document_ready_check_timeout, |
| DOCUMENT_INTERACTIVE), |
| actions); |
| actions->emplace_back( |
| base::BindOnce(&WebController::ScrollIntoView, |
| delegate->GetWebController()->GetWeakPtr(), true)); |
| if (click_type == ClickType::JAVASCRIPT) { |
| actions->emplace_back( |
| base::BindOnce(&WebController::JsClickElement, |
| delegate->GetWebController()->GetWeakPtr())); |
| } else { |
| AddStepIgnoreTiming( |
| base::BindOnce(&WebController::WaitUntilElementIsStable, |
| delegate->GetWebController()->GetWeakPtr(), |
| delegate->GetSettings().box_model_check_count, |
| delegate->GetSettings().box_model_check_interval), |
| actions); |
| actions->emplace_back( |
| base::BindOnce(&WebController::ClickOrTapElement, |
| delegate->GetWebController()->GetWeakPtr(), click_type)); |
| } |
| } |
| |
| void OnResolveTextValue( |
| base::OnceCallback<void(const std::string&, |
| const ElementFinder::Result&, |
| base::OnceCallback<void(const ClientStatus&)>)> |
| perform, |
| const ElementFinder::Result& element, |
| base::OnceCallback<void(const ClientStatus&)> done, |
| const ClientStatus& status, |
| const std::string& value) { |
| if (!status.ok()) { |
| std::move(done).Run(status); |
| return; |
| } |
| std::move(perform).Run(value, element, std::move(done)); |
| } |
| |
| } // namespace |
| |
| void PerformWithTextValue( |
| const ActionDelegate* delegate, |
| const TextValue& text_value, |
| base::OnceCallback<void(const std::string&, |
| const ElementFinder::Result&, |
| base::OnceCallback<void(const ClientStatus&)>)> |
| perform, |
| const ElementFinder::Result& element, |
| base::OnceCallback<void(const ClientStatus&)> done) { |
| user_data::ResolveTextValue( |
| text_value, element, delegate, |
| base::BindOnce(&OnResolveTextValue, std::move(perform), element, |
| std::move(done))); |
| } |
| |
| void PerformWithElementValue( |
| const ActionDelegate* delegate, |
| const ClientIdProto& client_id, |
| base::OnceCallback<void(const ElementFinder::Result&, |
| const ElementFinder::Result&, |
| base::OnceCallback<void(const ClientStatus&)>)> |
| perform, |
| const ElementFinder::Result& element, |
| base::OnceCallback<void(const ClientStatus&)> done) { |
| std::unique_ptr<ElementFinder::Result> element_result = |
| std::make_unique<ElementFinder::Result>(); |
| ElementFinder::Result* element_result_ptr = element_result.get(); |
| ClientStatus element_status = delegate->GetElementStore()->GetElement( |
| client_id.identifier(), element_result_ptr); |
| if (!element_status.ok()) { |
| std::move(done).Run(element_status); |
| return; |
| } |
| |
| std::move(perform).Run( |
| *element_result_ptr, element, |
| base::BindOnce(&RetainElementAndExecuteCallback, |
| std::move(element_result), std::move(done))); |
| } |
| |
| void AddOptionalStep(OptionalStep optional_step, |
| element_action_util::ElementActionCallback step, |
| element_action_util::ElementActionVector* actions) { |
| switch (optional_step) { |
| case STEP_UNSPECIFIED: |
| NOTREACHED() << __func__ << " unspecified optional_step"; |
| FALLTHROUGH; |
| |
| case SKIP_STEP: |
| break; |
| |
| case REPORT_STEP_RESULT: |
| actions->emplace_back(base::BindOnce(&SkipIfFail, std::move(step))); |
| break; |
| |
| case REQUIRE_STEP_SUCCESS: |
| actions->emplace_back(std::move(step)); |
| break; |
| } |
| } |
| |
| void AddStepIgnoreTiming( |
| base::OnceCallback<void( |
| const ElementFinder::Result&, |
| base::OnceCallback<void(const ClientStatus&, base::TimeDelta)>)> step, |
| element_action_util::ElementActionVector* actions) { |
| actions->emplace_back(base::BindOnce(&RunAndIgnoreTiming, std::move(step))); |
| } |
| |
| void FindElementAndPerform(const ActionDelegate* delegate, |
| const Selector& selector, |
| element_action_util::ElementActionCallback perform, |
| base::OnceCallback<void(const ClientStatus&)> done) { |
| FindElementAndPerformImpl(delegate, selector, std::move(perform), |
| std::move(done)); |
| } |
| |
| void ClickOrTapElement(const ActionDelegate* delegate, |
| const Selector& selector, |
| ClickType click_type, |
| base::OnceCallback<void(const ClientStatus&)> done) { |
| FindElementAndPerformImpl( |
| delegate, selector, |
| base::BindOnce(&PerformClickOrTapElement, delegate, click_type), |
| std::move(done)); |
| } |
| |
| void PerformClickOrTapElement( |
| const ActionDelegate* delegate, |
| ClickType click_type, |
| const ElementFinder::Result& element, |
| base::OnceCallback<void(const ClientStatus&)> done) { |
| VLOG(3) << __func__ << " click_type=" << click_type; |
| |
| auto actions = std::make_unique<element_action_util::ElementActionVector>(); |
| AddClickOrTapSequence(delegate, click_type, actions.get()); |
| element_action_util::PerformAll(std::move(actions), element, std::move(done)); |
| } |
| |
| void SendKeyboardInput(const ActionDelegate* delegate, |
| const Selector& selector, |
| const std::vector<UChar32> codepoints, |
| int delay_in_millis, |
| bool use_focus, |
| base::OnceCallback<void(const ClientStatus&)> done) { |
| FindElementAndPerformImpl( |
| delegate, selector, |
| base::BindOnce(&PerformSendKeyboardInput, delegate, codepoints, |
| delay_in_millis, use_focus), |
| std::move(done)); |
| } |
| |
| void PerformSendKeyboardInput( |
| const ActionDelegate* delegate, |
| const std::vector<UChar32> codepoints, |
| int delay_in_millis, |
| bool use_focus, |
| const ElementFinder::Result& element, |
| base::OnceCallback<void(const ClientStatus&)> done) { |
| VLOG(3) << __func__ << " focus: " << use_focus; |
| |
| auto actions = std::make_unique<element_action_util::ElementActionVector>(); |
| if (use_focus) { |
| actions->emplace_back( |
| base::BindOnce(&WebController::FocusField, |
| delegate->GetWebController()->GetWeakPtr())); |
| } else { |
| AddClickOrTapSequence(delegate, ClickType::TAP, actions.get()); |
| } |
| actions->emplace_back(base::BindOnce( |
| &WebController::SendKeyboardInput, |
| delegate->GetWebController()->GetWeakPtr(), codepoints, delay_in_millis)); |
| |
| element_action_util::PerformAll(std::move(actions), element, std::move(done)); |
| } |
| |
| void SetFieldValue(const ActionDelegate* delegate, |
| const Selector& selector, |
| const std::string& value, |
| KeyboardValueFillStrategy fill_strategy, |
| int key_press_delay_in_millisecond, |
| base::OnceCallback<void(const ClientStatus&)> done) { |
| FindElementAndPerformImpl( |
| delegate, selector, |
| base::BindOnce(&PerformSetFieldValue, delegate, value, fill_strategy, |
| key_press_delay_in_millisecond), |
| std::move(done)); |
| } |
| void PerformSetFieldValue(const ActionDelegate* delegate, |
| const std::string& value, |
| KeyboardValueFillStrategy fill_strategy, |
| int key_press_delay_in_millisecond, |
| const ElementFinder::Result& element, |
| base::OnceCallback<void(const ClientStatus&)> done) { |
| #ifdef NDEBUG |
| VLOG(3) << __func__ << " value=(redacted)" |
| << ", strategy=" << fill_strategy; |
| #else |
| DVLOG(3) << __func__ << " value=" << value << ", strategy=" << fill_strategy; |
| #endif |
| |
| auto actions = std::make_unique<element_action_util::ElementActionVector>(); |
| if (value.empty()) { |
| actions->emplace_back(base::BindOnce( |
| &WebController::SetValueAttribute, |
| delegate->GetWebController()->GetWeakPtr(), std::string())); |
| } else { |
| switch (fill_strategy) { |
| case UNSPECIFIED_KEYBAORD_STRATEGY: |
| case SET_VALUE: |
| actions->emplace_back( |
| base::BindOnce(&WebController::SetValueAttribute, |
| delegate->GetWebController()->GetWeakPtr(), value)); |
| break; |
| case SIMULATE_KEY_PRESSES: |
| actions->emplace_back(base::BindOnce( |
| &WebController::SetValueAttribute, |
| delegate->GetWebController()->GetWeakPtr(), std::string())); |
| AddClickOrTapSequence(delegate, ClickType::CLICK, actions.get()); |
| actions->emplace_back(base::BindOnce( |
| &WebController::SendKeyboardInput, |
| delegate->GetWebController()->GetWeakPtr(), UTF8ToUnicode(value), |
| key_press_delay_in_millisecond)); |
| break; |
| case SIMULATE_KEY_PRESSES_SELECT_VALUE: { |
| actions->emplace_back( |
| base::BindOnce(&WebController::SelectFieldValue, |
| delegate->GetWebController()->GetWeakPtr())); |
| KeyEvent clear_event; |
| clear_event.add_command("SelectAll"); |
| clear_event.add_command("DeleteBackward"); |
| clear_event.set_key( |
| ui::KeycodeConverter::DomKeyToKeyString(ui::DomKey::BACKSPACE)); |
| actions->emplace_back(base::BindOnce( |
| &WebController::SendKeyEvent, |
| delegate->GetWebController()->GetWeakPtr(), clear_event)); |
| actions->emplace_back(base::BindOnce( |
| &WebController::SendKeyboardInput, |
| delegate->GetWebController()->GetWeakPtr(), UTF8ToUnicode(value), |
| key_press_delay_in_millisecond)); |
| break; |
| } |
| case SIMULATE_KEY_PRESSES_FOCUS: |
| actions->emplace_back(base::BindOnce( |
| &WebController::SetValueAttribute, |
| delegate->GetWebController()->GetWeakPtr(), std::string())); |
| actions->emplace_back( |
| base::BindOnce(&WebController::FocusField, |
| delegate->GetWebController()->GetWeakPtr())); |
| actions->emplace_back(base::BindOnce( |
| &WebController::SendKeyboardInput, |
| delegate->GetWebController()->GetWeakPtr(), UTF8ToUnicode(value), |
| key_press_delay_in_millisecond)); |
| break; |
| } |
| } |
| |
| element_action_util::PerformAll(std::move(actions), element, std::move(done)); |
| } |
| |
| } // namespace action_delegate_util |
| } // namespace autofill_assistant |