| // Copyright 2018 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/script_executor.h" | 
 |  | 
 | #include <cstdio> | 
 | #include <ostream> | 
 | #include <string> | 
 | #include <utility> | 
 |  | 
 | #include "base/bind.h" | 
 | #include "base/callback.h" | 
 | #include "base/callback_helpers.h" | 
 | #include "base/single_thread_task_runner.h" | 
 | #include "base/threading/thread_task_runner_handle.h" | 
 | #include "base/time/time.h" | 
 | #include "components/autofill/core/browser/data_model/credit_card.h" | 
 | #include "components/autofill_assistant/browser/actions/action.h" | 
 | #include "components/autofill_assistant/browser/actions/action_delegate_util.h" | 
 | #include "components/autofill_assistant/browser/batch_element_checker.h" | 
 | #include "components/autofill_assistant/browser/client_status.h" | 
 | #include "components/autofill_assistant/browser/full_card_requester.h" | 
 | #include "components/autofill_assistant/browser/protocol_utils.h" | 
 | #include "components/autofill_assistant/browser/service/service.h" | 
 | #include "components/autofill_assistant/browser/trigger_context.h" | 
 | #include "components/autofill_assistant/browser/wait_for_document_operation.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 "components/strings/grit/components_strings.h" | 
 | #include "net/http/http_status_code.h" | 
 | #include "ui/base/l10n/l10n_util.h" | 
 |  | 
 | namespace autofill_assistant { | 
 | namespace { | 
 |  | 
 | std::ostream& operator<<(std::ostream& out, | 
 |                          const ScriptExecutor::AtEnd& at_end) { | 
 | #ifdef NDEBUG | 
 |   out << static_cast<int>(at_end); | 
 |   return out; | 
 | #else | 
 |   switch (at_end) { | 
 |     case ScriptExecutor::CONTINUE: | 
 |       out << "CONTINUE"; | 
 |       break; | 
 |     case ScriptExecutor::SHUTDOWN: | 
 |       out << "SHUTDOWN"; | 
 |       break; | 
 |     case ScriptExecutor::SHUTDOWN_GRACEFULLY: | 
 |       out << "SHUTDOWN_GRACEFULLY"; | 
 |       break; | 
 |     case ScriptExecutor::CLOSE_CUSTOM_TAB: | 
 |       out << "CLOSE_CUSTOM_TAB"; | 
 |       break; | 
 |       // Intentionally no default case to make compilation fail if a new value | 
 |       // was added to the enum but not to this list. | 
 |   } | 
 |   return out; | 
 | #endif  // NDEBUG | 
 | } | 
 |  | 
 | }  // namespace | 
 |  | 
 | ScriptExecutor::ScriptExecutor( | 
 |     const std::string& script_path, | 
 |     std::unique_ptr<TriggerContext> additional_context, | 
 |     const std::string& global_payload, | 
 |     const std::string& script_payload, | 
 |     ScriptExecutor::Listener* listener, | 
 |     std::map<std::string, ScriptStatusProto>* scripts_state, | 
 |     const std::vector<std::unique_ptr<Script>>* ordered_interrupts, | 
 |     ScriptExecutorDelegate* delegate) | 
 |     : script_path_(script_path), | 
 |       additional_context_(std::move(additional_context)), | 
 |       last_global_payload_(global_payload), | 
 |       initial_script_payload_(script_payload), | 
 |       last_script_payload_(script_payload), | 
 |       listener_(listener), | 
 |       delegate_(delegate), | 
 |       ordered_interrupts_(ordered_interrupts), | 
 |       scripts_state_(scripts_state) { | 
 |   DCHECK(delegate_); | 
 |   DCHECK(ordered_interrupts_); | 
 | } | 
 |  | 
 | ScriptExecutor::~ScriptExecutor() { | 
 |   delegate_->RemoveNavigationListener(this); | 
 |   delegate_->RemoveListener(this); | 
 | } | 
 |  | 
 | ScriptExecutor::Result::Result() = default; | 
 | ScriptExecutor::Result::~Result() = default; | 
 |  | 
 | void ScriptExecutor::Run(const UserData* user_data, | 
 |                          RunScriptCallback callback) { | 
 | #ifdef NDEBUG | 
 |   VLOG(2) << "Starting script"; | 
 | #else | 
 |   DVLOG(2) << "Starting script " << script_path_; | 
 | #endif | 
 |   (*scripts_state_)[script_path_] = SCRIPT_STATUS_RUNNING; | 
 |  | 
 |   DCHECK(user_data); | 
 |   user_data_ = user_data; | 
 |  | 
 |   delegate_->AddNavigationListener(this); | 
 |   delegate_->AddListener(this); | 
 |  | 
 |   callback_ = std::move(callback); | 
 |   DCHECK(delegate_->GetService()); | 
 |  | 
 | #ifdef NDEBUG | 
 |   VLOG(2) << "GetActions for (redacted)"; | 
 | #else | 
 |   VLOG(2) << "GetActions for " << delegate_->GetCurrentURL().host(); | 
 | #endif | 
 |  | 
 |   delegate_->GetService()->GetActions( | 
 |       script_path_, delegate_->GetScriptURL(), | 
 |       TriggerContext( | 
 |           {delegate_->GetTriggerContext(), additional_context_.get()}), | 
 |       last_global_payload_, last_script_payload_, | 
 |       base::BindOnce(&ScriptExecutor::OnGetActions, | 
 |                      weak_ptr_factory_.GetWeakPtr(), base::TimeTicks::Now())); | 
 | } | 
 |  | 
 | const UserData* ScriptExecutor::GetUserData() const { | 
 |   DCHECK(user_data_); | 
 |   return user_data_; | 
 | } | 
 |  | 
 | UserModel* ScriptExecutor::GetUserModel() { | 
 |   return delegate_->GetUserModel(); | 
 | } | 
 |  | 
 | void ScriptExecutor::OnNavigationStateChanged() { | 
 |   NavigationInfoProto& navigation_info = current_action_data_.navigation_info; | 
 |   if (delegate_->IsNavigatingToNewDocument()) { | 
 |     navigation_info.set_started(true); | 
 |     navigation_info.set_unexpected(expected_navigation_step_ != | 
 |                                    ExpectedNavigationStep::EXPECTED); | 
 |   } else { | 
 |     navigation_info.set_ended(true); | 
 |   } | 
 |  | 
 |   if (delegate_->HasNavigationError()) { | 
 |     navigation_info.set_has_error(true); | 
 |   } | 
 |  | 
 |   switch (expected_navigation_step_) { | 
 |     case ExpectedNavigationStep::UNEXPECTED: | 
 |       break; | 
 |  | 
 |     case ExpectedNavigationStep::EXPECTED: | 
 |       if (delegate_->IsNavigatingToNewDocument()) { | 
 |         expected_navigation_step_ = ExpectedNavigationStep::STARTED; | 
 |       } | 
 |       break; | 
 |  | 
 |     case ExpectedNavigationStep::STARTED: | 
 |       if (!delegate_->IsNavigatingToNewDocument()) { | 
 |         expected_navigation_step_ = ExpectedNavigationStep::DONE; | 
 |         if (on_expected_navigation_done_) | 
 |           std::move(on_expected_navigation_done_) | 
 |               .Run(!delegate_->HasNavigationError()); | 
 |       } | 
 |       // Early return since current_action_data_ is no longer valid at this | 
 |       // point. | 
 |       return; | 
 |  | 
 |     case ExpectedNavigationStep::DONE: | 
 |       // nothing to do | 
 |       break; | 
 |   } | 
 |  | 
 |   // Potentially terminate an ongoing prompt action. | 
 |   if (navigation_info.ended() && | 
 |       current_action_data_.end_prompt_on_navigation_callback) { | 
 |     std::move(current_action_data_.end_prompt_on_navigation_callback).Run(); | 
 |   } | 
 | } | 
 |  | 
 | void ScriptExecutor::OnPause(const std::string& message, | 
 |                              const std::string& button_label) { | 
 |   if (current_action_index_.has_value()) { | 
 |     DCHECK_LT(*current_action_index_, actions_.size()); | 
 |     if (actions_[*current_action_index_]->ShouldInterruptOnPause()) { | 
 |       actions_[*current_action_index_] = ProtocolUtils::CreateAction( | 
 |           this, actions_[*current_action_index_]->proto()); | 
 |       current_action_data_ = CurrentActionData(); | 
 |       current_action_index_.reset(); | 
 |     } | 
 |   } | 
 |  | 
 |   delegate_->ClearInfoBox(); | 
 |   delegate_->SetDetails(nullptr, base::TimeDelta()); | 
 |   delegate_->SetCollectUserDataOptions(nullptr); | 
 |   delegate_->SetForm(nullptr, base::DoNothing(), base::DoNothing()); | 
 |  | 
 |   last_status_message_ = GetStatusMessage(); | 
 |   delegate_->SetStatusMessage(message); | 
 |  | 
 |   auto user_actions = std::make_unique<std::vector<UserAction>>(); | 
 |  | 
 |   UserAction undo_action; | 
 |   Chip undo_chip; | 
 |   undo_chip.type = ChipType::HIGHLIGHTED_ACTION; | 
 |   undo_chip.text = button_label; | 
 |   undo_action.chip() = undo_chip; | 
 |   undo_action.SetCallback(base::BindOnce(&ScriptExecutor::OnResume, | 
 |                                          weak_ptr_factory_.GetWeakPtr())); | 
 |   user_actions->emplace_back(std::move(undo_action)); | 
 |  | 
 |   delegate_->SetUserActions(std::move(user_actions)); | 
 |   delegate_->EnterState(AutofillAssistantState::STOPPED); | 
 |   is_paused_ = true; | 
 | } | 
 |  | 
 | void ScriptExecutor::OnResume() { | 
 |   DCHECK(is_paused_); | 
 |   is_paused_ = false; | 
 |  | 
 |   delegate_->EnterState(AutofillAssistantState::RUNNING); | 
 |   delegate_->SetStatusMessage(last_status_message_); | 
 |  | 
 |   if (!current_action_index_.has_value()) { | 
 |     ProcessNextAction(); | 
 |   } | 
 | } | 
 |  | 
 | void ScriptExecutor::RunElementChecks(BatchElementChecker* checker) { | 
 |   return checker->Run(delegate_->GetWebController()); | 
 | } | 
 |  | 
 | void ScriptExecutor::ShortWaitForElement( | 
 |     const Selector& selector, | 
 |     base::OnceCallback<void(const ClientStatus&, base::TimeDelta)> callback) { | 
 |   current_action_data_.wait_for_dom = std::make_unique<WaitForDomOperation>( | 
 |       this, delegate_, delegate_->GetSettings().short_wait_for_element_deadline, | 
 |       /* allow_interrupt= */ false, /* observer= */ nullptr, | 
 |       base::BindRepeating(&ScriptExecutor::CheckElementMatches, | 
 |                           weak_ptr_factory_.GetWeakPtr(), selector), | 
 |       base::BindOnce(&ScriptExecutor::OnShortWaitForElement, | 
 |                      weak_ptr_factory_.GetWeakPtr(), std::move(callback))); | 
 |   current_action_data_.wait_for_dom->Run(); | 
 | } | 
 |  | 
 | void ScriptExecutor::ShortWaitForElementWithSlowWarning( | 
 |     const Selector& selector, | 
 |     base::OnceCallback<void(const ClientStatus&, base::TimeDelta)> callback) { | 
 |   current_action_data_.wait_for_dom = std::make_unique<WaitForDomOperation>( | 
 |       this, delegate_, delegate_->GetSettings().short_wait_for_element_deadline, | 
 |       /* allow_interrupt= */ false, /* observer= */ nullptr, | 
 |       base::BindRepeating(&ScriptExecutor::CheckElementMatches, | 
 |                           weak_ptr_factory_.GetWeakPtr(), selector), | 
 |       base::BindOnce(&ScriptExecutor::OnShortWaitForElement, | 
 |                      weak_ptr_factory_.GetWeakPtr(), std::move(callback))); | 
 |   current_action_data_.wait_for_dom->SetTimeoutWarningCallback( | 
 |       base::BindOnce(&ScriptExecutor::MaybeShowSlowWebsiteWarning, | 
 |                      weak_ptr_factory_.GetWeakPtr())); | 
 |   current_action_data_.wait_for_dom->Run(); | 
 | } | 
 |  | 
 | void ScriptExecutor::WaitForDom( | 
 |     base::TimeDelta max_wait_time, | 
 |     bool allow_interrupt, | 
 |     WaitForDomObserver* observer, | 
 |     base::RepeatingCallback<void(BatchElementChecker*, | 
 |                                  base::OnceCallback<void(const ClientStatus&)>)> | 
 |         check_elements, | 
 |     base::OnceCallback<void(const ClientStatus&, base::TimeDelta)> callback) { | 
 |   current_action_data_.wait_for_dom = std::make_unique<WaitForDomOperation>( | 
 |       this, delegate_, max_wait_time, allow_interrupt, observer, check_elements, | 
 |       base::BindOnce(&ScriptExecutor::OnWaitForElementVisibleWithInterrupts, | 
 |                      weak_ptr_factory_.GetWeakPtr(), std::move(callback))); | 
 |   current_action_data_.wait_for_dom->Run(); | 
 | } | 
 |  | 
 | void ScriptExecutor::WaitForDomWithSlowWarning( | 
 |     base::TimeDelta max_wait_time, | 
 |     bool allow_interrupt, | 
 |     WaitForDomObserver* observer, | 
 |     base::RepeatingCallback<void(BatchElementChecker*, | 
 |                                  base::OnceCallback<void(const ClientStatus&)>)> | 
 |         check_elements, | 
 |     base::OnceCallback<void(const ClientStatus&, base::TimeDelta)> callback) { | 
 |   current_action_data_.wait_for_dom = std::make_unique<WaitForDomOperation>( | 
 |       this, delegate_, max_wait_time, allow_interrupt, observer, check_elements, | 
 |       base::BindOnce(&ScriptExecutor::OnWaitForElementVisibleWithInterrupts, | 
 |                      weak_ptr_factory_.GetWeakPtr(), std::move(callback))); | 
 |   current_action_data_.wait_for_dom->SetTimeoutWarningCallback( | 
 |       base::BindOnce(&ScriptExecutor::MaybeShowSlowWebsiteWarning, | 
 |                      weak_ptr_factory_.GetWeakPtr())); | 
 |   current_action_data_.wait_for_dom->Run(); | 
 | } | 
 |  | 
 | void ScriptExecutor::SetStatusMessage(const std::string& message) { | 
 |   delegate_->SetStatusMessage(message); | 
 | } | 
 |  | 
 | std::string ScriptExecutor::GetStatusMessage() { | 
 |   return delegate_->GetStatusMessage(); | 
 | } | 
 |  | 
 | void ScriptExecutor::SetBubbleMessage(const std::string& message) { | 
 |   delegate_->SetBubbleMessage(message); | 
 | } | 
 |  | 
 | std::string ScriptExecutor::GetBubbleMessage() { | 
 |   return delegate_->GetBubbleMessage(); | 
 | } | 
 |  | 
 | void ScriptExecutor::FindElement(const Selector& selector, | 
 |                                  ElementFinder::Callback callback) const { | 
 |   VLOG(3) << __func__ << " " << selector; | 
 |   delegate_->GetWebController()->FindElement(selector, /* strict_mode= */ true, | 
 |                                              std::move(callback)); | 
 | } | 
 |  | 
 | void ScriptExecutor::FindAllElements(const Selector& selector, | 
 |                                      ElementFinder::Callback callback) const { | 
 |   VLOG(3) << __func__ << " " << selector; | 
 |   delegate_->GetWebController()->FindAllElements(selector, std::move(callback)); | 
 | } | 
 |  | 
 | void ScriptExecutor::CollectUserData( | 
 |     CollectUserDataOptions* collect_user_data_options) { | 
 |   collect_user_data_options->confirm_callback = base::BindOnce( | 
 |       &ScriptExecutor::OnGetUserData, weak_ptr_factory_.GetWeakPtr(), | 
 |       std::move(collect_user_data_options->confirm_callback)); | 
 |   collect_user_data_options->additional_actions_callback = base::BindOnce( | 
 |       &ScriptExecutor::OnAdditionalActionTriggered, | 
 |       weak_ptr_factory_.GetWeakPtr(), | 
 |       std::move(collect_user_data_options->additional_actions_callback)); | 
 |   collect_user_data_options->terms_link_callback = | 
 |       base::BindOnce(&ScriptExecutor::OnTermsAndConditionsLinkClicked, | 
 |                      weak_ptr_factory_.GetWeakPtr(), | 
 |                      std::move(collect_user_data_options->terms_link_callback)); | 
 |   delegate_->SetCollectUserDataOptions(collect_user_data_options); | 
 |   delegate_->EnterState(AutofillAssistantState::PROMPT); | 
 | } | 
 |  | 
 | void ScriptExecutor::SetLastSuccessfulUserDataOptions( | 
 |     std::unique_ptr<CollectUserDataOptions> collect_user_data_options) { | 
 |   delegate_->SetLastSuccessfulUserDataOptions( | 
 |       std::move(collect_user_data_options)); | 
 | } | 
 |  | 
 | const CollectUserDataOptions* ScriptExecutor::GetLastSuccessfulUserDataOptions() | 
 |     const { | 
 |   return delegate_->GetLastSuccessfulUserDataOptions(); | 
 | } | 
 |  | 
 | void ScriptExecutor::WriteUserData( | 
 |     base::OnceCallback<void(UserData*, UserData::FieldChange*)> | 
 |         write_callback) { | 
 |   delegate_->WriteUserData(std::move(write_callback)); | 
 | } | 
 |  | 
 | void ScriptExecutor::OnGetUserData( | 
 |     base::OnceCallback<void(UserData*, const UserModel*)> callback, | 
 |     UserData* user_data, | 
 |     const UserModel* user_model) { | 
 |   delegate_->EnterState(AutofillAssistantState::RUNNING); | 
 |   delegate_->SetUserActions(nullptr); | 
 |   std::move(callback).Run(user_data, user_model); | 
 | } | 
 |  | 
 | void ScriptExecutor::OnAdditionalActionTriggered( | 
 |     base::OnceCallback<void(int, UserData*, const UserModel*)> callback, | 
 |     int index, | 
 |     UserData* user_data, | 
 |     const UserModel* user_model) { | 
 |   delegate_->EnterState(AutofillAssistantState::RUNNING); | 
 |   std::move(callback).Run(index, user_data, user_model); | 
 | } | 
 |  | 
 | void ScriptExecutor::OnTermsAndConditionsLinkClicked( | 
 |     base::OnceCallback<void(int, UserData*, const UserModel*)> callback, | 
 |     int link, | 
 |     UserData* user_data, | 
 |     const UserModel* user_model) { | 
 |   delegate_->EnterState(AutofillAssistantState::RUNNING); | 
 |   std::move(callback).Run(link, user_data, user_model); | 
 | } | 
 |  | 
 | void ScriptExecutor::GetFullCard(const autofill::CreditCard* credit_card, | 
 |                                  GetFullCardCallback callback) { | 
 |   DCHECK(credit_card); | 
 |  | 
 |   // User might be asked to provide the cvc. | 
 |   delegate_->EnterState(AutofillAssistantState::MODAL_DIALOG); | 
 |  | 
 |   std::unique_ptr<FullCardRequester> full_card_requester = | 
 |       std::make_unique<FullCardRequester>(); | 
 |   FullCardRequester* full_card_requester_ptr = full_card_requester.get(); | 
 |   full_card_requester_ptr->GetFullCard( | 
 |       GetWebContents(), credit_card, | 
 |       base::BindOnce(&ScriptExecutor::OnGetFullCard, | 
 |                      weak_ptr_factory_.GetWeakPtr(), | 
 |                      std::move(full_card_requester), std::move(callback))); | 
 | } | 
 |  | 
 | void ScriptExecutor::OnGetFullCard( | 
 |     std::unique_ptr<FullCardRequester> full_card_requester, | 
 |     GetFullCardCallback callback, | 
 |     const ClientStatus& status, | 
 |     std::unique_ptr<autofill::CreditCard> card, | 
 |     const std::u16string& cvc) { | 
 |   delegate_->EnterState(AutofillAssistantState::RUNNING); | 
 |   std::move(callback).Run(status, std::move(card), cvc); | 
 | } | 
 |  | 
 | void ScriptExecutor::Prompt( | 
 |     std::unique_ptr<std::vector<UserAction>> user_actions, | 
 |     bool disable_force_expand_sheet, | 
 |     base::OnceCallback<void()> end_on_navigation_callback, | 
 |     bool browse_mode, | 
 |     bool browse_mode_invisible) { | 
 |   // First communicate to the delegate that prompt actions should or should not | 
 |   // expand the sheet intitially. | 
 |   delegate_->SetExpandSheetForPromptAction(!disable_force_expand_sheet); | 
 |   delegate_->SetBrowseModeInvisible(browse_mode_invisible); | 
 |   if (browse_mode) { | 
 |     delegate_->EnterState(AutofillAssistantState::BROWSE); | 
 |   } else if (delegate_->EnterState(AutofillAssistantState::PROMPT)) { | 
 |     if (touchable_element_area_) { | 
 |       // Prompt() reproduces the end-of-script appearance and behavior during | 
 |       // script execution. This includes allowing access to touchable elements, | 
 |       // set through a previous call to the focus action with touchable_elements | 
 |       // set. | 
 |       delegate_->SetTouchableElementArea(*touchable_element_area_); | 
 |  | 
 |       // The touchable element and overlays are cleared by calling | 
 |       // ScriptExecutor::CleanUpAfterPrompt | 
 |     } | 
 |  | 
 |     if (end_on_navigation_callback) { | 
 |       current_action_data_.end_prompt_on_navigation_callback = | 
 |           std::move(end_on_navigation_callback); | 
 |     } | 
 |   } | 
 |  | 
 |   if (user_actions != nullptr) { | 
 |     for (auto& user_action : *user_actions) { | 
 |       if (!user_action.HasCallback()) | 
 |         continue; | 
 |  | 
 |       user_action.AddInterceptor(base::BindOnce( | 
 |           &ScriptExecutor::OnChosen, weak_ptr_factory_.GetWeakPtr())); | 
 |     } | 
 |     delegate_->SetUserActions(std::move(user_actions)); | 
 |   } | 
 | } | 
 |  | 
 | void ScriptExecutor::CleanUpAfterPrompt() { | 
 |   delegate_->SetUserActions(nullptr); | 
 |   // Mark touchable_elements_ as consumed, so that it won't affect the next | 
 |   // prompt or the end of the script. | 
 |   touchable_element_area_.reset(); | 
 |  | 
 |   delegate_->ClearTouchableElementArea(); | 
 |   delegate_->SetExpandSheetForPromptAction(true); | 
 |   delegate_->SetBrowseModeInvisible(false); | 
 |   delegate_->EnterState(AutofillAssistantState::RUNNING); | 
 | } | 
 |  | 
 | void ScriptExecutor::SetBrowseDomainsAllowlist( | 
 |     std::vector<std::string> domains) { | 
 |   delegate_->SetBrowseDomainsAllowlist(std::move(domains)); | 
 | } | 
 |  | 
 | void ScriptExecutor::OnChosen(UserAction::Callback callback, | 
 |                               std::unique_ptr<TriggerContext> context) { | 
 |   if (context->GetDirectAction()) { | 
 |     current_action_data_.direct_action = true; | 
 |   } | 
 |   std::move(callback).Run(std::move(context)); | 
 | } | 
 |  | 
 | void ScriptExecutor::FillAddressForm( | 
 |     const autofill::AutofillProfile* profile, | 
 |     const Selector& selector, | 
 |     base::OnceCallback<void(const ClientStatus&)> callback) { | 
 |   delegate_->GetWebController()->FillAddressForm(profile, selector, | 
 |                                                  std::move(callback)); | 
 | } | 
 |  | 
 | void ScriptExecutor::FillCardForm( | 
 |     std::unique_ptr<autofill::CreditCard> card, | 
 |     const std::u16string& cvc, | 
 |     const Selector& selector, | 
 |     base::OnceCallback<void(const ClientStatus&)> callback) { | 
 |   delegate_->GetWebController()->FillCardForm(std::move(card), cvc, selector, | 
 |                                               std::move(callback)); | 
 | } | 
 |  | 
 | void ScriptExecutor::RetrieveElementFormAndFieldData( | 
 |     const Selector& selector, | 
 |     base::OnceCallback<void(const ClientStatus&, | 
 |                             const autofill::FormData&, | 
 |                             const autofill::FormFieldData&)> callback) { | 
 |   delegate_->GetWebController()->RetrieveElementFormAndFieldData( | 
 |       selector, std::move(callback)); | 
 | } | 
 |  | 
 | void ScriptExecutor::ScrollToElementPosition( | 
 |     const Selector& selector, | 
 |     const TopPadding& top_padding, | 
 |     std::unique_ptr<ElementFinder::Result> container, | 
 |     const ElementFinder::Result& element, | 
 |     base::OnceCallback<void(const ClientStatus&)> callback) { | 
 |   last_focused_element_selector_ = selector; | 
 |   last_focused_element_top_padding_ = top_padding; | 
 |   delegate_->GetWebController()->ScrollToElementPosition( | 
 |       std::move(container), element, top_padding, std::move(callback)); | 
 | } | 
 |  | 
 | void ScriptExecutor::SetTouchableElementArea( | 
 |     const ElementAreaProto& touchable_element_area) { | 
 |   touchable_element_area_ = | 
 |       std::make_unique<ElementAreaProto>(touchable_element_area); | 
 | } | 
 |  | 
 | void ScriptExecutor::SetProgress(int progress) { | 
 |   delegate_->SetProgress(progress); | 
 | } | 
 |  | 
 | bool ScriptExecutor::SetProgressActiveStepIdentifier( | 
 |     const std::string& active_step_identifier) { | 
 |   return delegate_->SetProgressActiveStepIdentifier(active_step_identifier); | 
 | } | 
 |  | 
 | void ScriptExecutor::SetProgressActiveStep(int active_step) { | 
 |   delegate_->SetProgressActiveStep(active_step); | 
 | } | 
 |  | 
 | void ScriptExecutor::SetProgressVisible(bool visible) { | 
 |   delegate_->SetProgressVisible(visible); | 
 | } | 
 |  | 
 | void ScriptExecutor::SetProgressBarErrorState(bool error) { | 
 |   delegate_->SetProgressBarErrorState(error); | 
 | } | 
 |  | 
 | void ScriptExecutor::SetStepProgressBarConfiguration( | 
 |     const ShowProgressBarProto::StepProgressBarConfiguration& configuration) { | 
 |   delegate_->SetStepProgressBarConfiguration(configuration); | 
 | } | 
 |  | 
 | void ScriptExecutor::ExpectNavigation() { | 
 |   // TODO(b/160948417): Clean this up such that the logic is not required in | 
 |   //  both |ScriptExecutor| and |Controller|. | 
 |   delegate_->ExpectNavigation(); | 
 |   expected_navigation_step_ = ExpectedNavigationStep::EXPECTED; | 
 | } | 
 |  | 
 | bool ScriptExecutor::ExpectedNavigationHasStarted() { | 
 |   return expected_navigation_step_ != ExpectedNavigationStep::EXPECTED; | 
 | } | 
 |  | 
 | bool ScriptExecutor::WaitForNavigation( | 
 |     base::OnceCallback<void(bool)> callback) { | 
 |   switch (expected_navigation_step_) { | 
 |     case ExpectedNavigationStep::UNEXPECTED: | 
 |       return false; | 
 |  | 
 |     case ExpectedNavigationStep::DONE: | 
 |       std::move(callback).Run(!delegate_->HasNavigationError()); | 
 |       break; | 
 |  | 
 |     case ExpectedNavigationStep::EXPECTED: | 
 |     case ExpectedNavigationStep::STARTED: | 
 |       on_expected_navigation_done_ = std::move(callback); | 
 |       break; | 
 |  | 
 |       // No default to make compilation fail if not all cases are covered | 
 |   } | 
 |   return true; | 
 | } | 
 |  | 
 | void ScriptExecutor::WaitForDocumentReadyState( | 
 |     base::TimeDelta max_wait_time, | 
 |     DocumentReadyState min_ready_state, | 
 |     const ElementFinder::Result& optional_frame_element, | 
 |     base::OnceCallback<void(const ClientStatus&, | 
 |                             DocumentReadyState, | 
 |                             base::TimeDelta)> callback) { | 
 |   current_action_data_.wait_for_document = | 
 |       std::make_unique<WaitForDocumentOperation>( | 
 |           delegate_, max_wait_time, min_ready_state, optional_frame_element, | 
 |           std::move(callback)); | 
 |   current_action_data_.wait_for_document->Run(); | 
 | } | 
 |  | 
 | void ScriptExecutor::WaitUntilDocumentIsInReadyState( | 
 |     base::TimeDelta max_wait_time, | 
 |     DocumentReadyState min_ready_state, | 
 |     const ElementFinder::Result& optional_frame_element, | 
 |     base::OnceCallback<void(const ClientStatus&, base::TimeDelta)> callback) { | 
 |   WaitForDocumentReadyState( | 
 |       max_wait_time, min_ready_state, optional_frame_element, | 
 |       base::BindOnce(&ScriptExecutor::OnWaitForDocumentReadyState, | 
 |                      weak_ptr_factory_.GetWeakPtr(), std::move(callback))); | 
 | } | 
 |  | 
 | void ScriptExecutor::OnWaitForDocumentReadyState( | 
 |     base::OnceCallback<void(const ClientStatus&, base::TimeDelta)> callback, | 
 |     const ClientStatus& status, | 
 |     DocumentReadyState ready_state, | 
 |     base::TimeDelta wait_time) { | 
 |   std::move(callback).Run(status, wait_time); | 
 | } | 
 |  | 
 | void ScriptExecutor::LoadURL(const GURL& url) { | 
 |   delegate_->GetWebController()->LoadURL(url); | 
 | } | 
 |  | 
 | void ScriptExecutor::Shutdown(bool show_feedback_chip) { | 
 |   // The following handles the case where scripts end with tell + stop | 
 |   // differently from just stop. TODO(b/806868): Make that difference explicit: | 
 |   // add an optional message to stop and update the scripts to use that. | 
 |   if (previous_action_type_ == ActionProto::kTell) { | 
 |     delegate_->SetShowFeedbackChip(show_feedback_chip); | 
 |     at_end_ = SHUTDOWN_GRACEFULLY; | 
 |   } else { | 
 |     at_end_ = SHUTDOWN; | 
 |   } | 
 | } | 
 |  | 
 | void ScriptExecutor::Close() { | 
 |   at_end_ = CLOSE_CUSTOM_TAB; | 
 |   should_stop_script_ = true; | 
 | } | 
 |  | 
 | autofill::PersonalDataManager* ScriptExecutor::GetPersonalDataManager() { | 
 |   return delegate_->GetPersonalDataManager(); | 
 | } | 
 |  | 
 | WebsiteLoginManager* ScriptExecutor::GetWebsiteLoginManager() const { | 
 |   return delegate_->GetWebsiteLoginManager(); | 
 | } | 
 |  | 
 | content::WebContents* ScriptExecutor::GetWebContents() { | 
 |   return delegate_->GetWebContents(); | 
 | } | 
 |  | 
 | ElementStore* ScriptExecutor::GetElementStore() const { | 
 |   return delegate_->GetElementStore(); | 
 | } | 
 |  | 
 | WebController* ScriptExecutor::GetWebController() const { | 
 |   return delegate_->GetWebController(); | 
 | } | 
 |  | 
 | std::string ScriptExecutor::GetEmailAddressForAccessTokenAccount() { | 
 |   return delegate_->GetEmailAddressForAccessTokenAccount(); | 
 | } | 
 |  | 
 | std::string ScriptExecutor::GetLocale() { | 
 |   return delegate_->GetLocale(); | 
 | } | 
 |  | 
 | void ScriptExecutor::SetDetails(std::unique_ptr<Details> details, | 
 |                                 base::TimeDelta delay) { | 
 |   return delegate_->SetDetails(std::move(details), delay); | 
 | } | 
 |  | 
 | void ScriptExecutor::AppendDetails(std::unique_ptr<Details> details, | 
 |                                    base::TimeDelta delay) { | 
 |   return delegate_->AppendDetails(std::move(details), delay); | 
 | } | 
 |  | 
 | void ScriptExecutor::ClearInfoBox() { | 
 |   delegate_->ClearInfoBox(); | 
 | } | 
 |  | 
 | void ScriptExecutor::SetInfoBox(const InfoBox& info_box) { | 
 |   delegate_->SetInfoBox(info_box); | 
 | } | 
 |  | 
 | void ScriptExecutor::SetViewportMode(ViewportMode mode) { | 
 |   delegate_->SetViewportMode(mode); | 
 | } | 
 |  | 
 | ViewportMode ScriptExecutor::GetViewportMode() { | 
 |   return delegate_->GetViewportMode(); | 
 | } | 
 |  | 
 | void ScriptExecutor::SetPeekMode( | 
 |     ConfigureBottomSheetProto::PeekMode peek_mode) { | 
 |   delegate_->SetPeekMode(peek_mode); | 
 | } | 
 |  | 
 | ConfigureBottomSheetProto::PeekMode ScriptExecutor::GetPeekMode() { | 
 |   return delegate_->GetPeekMode(); | 
 | } | 
 |  | 
 | void ScriptExecutor::ExpandBottomSheet() { | 
 |   return delegate_->ExpandBottomSheet(); | 
 | } | 
 |  | 
 | void ScriptExecutor::CollapseBottomSheet() { | 
 |   return delegate_->CollapseBottomSheet(); | 
 | } | 
 |  | 
 | void ScriptExecutor::WaitForWindowHeightChange( | 
 |     base::OnceCallback<void(const ClientStatus&)> callback) { | 
 |   delegate_->GetWebController()->WaitForWindowHeightChange(std::move(callback)); | 
 | } | 
 |  | 
 | const ClientSettings& ScriptExecutor::GetSettings() const { | 
 |   return delegate_->GetSettings(); | 
 | } | 
 |  | 
 | bool ScriptExecutor::SetForm( | 
 |     std::unique_ptr<FormProto> form, | 
 |     base::RepeatingCallback<void(const FormProto::Result*)> changed_callback, | 
 |     base::OnceCallback<void(const ClientStatus&)> cancel_callback) { | 
 |   return delegate_->SetForm(std::move(form), std::move(changed_callback), | 
 |                             std::move(cancel_callback)); | 
 | } | 
 |  | 
 | void ScriptExecutor::RequireUI() { | 
 |   delegate_->RequireUI(); | 
 | } | 
 |  | 
 | void ScriptExecutor::SetGenericUi( | 
 |     std::unique_ptr<GenericUserInterfaceProto> generic_ui, | 
 |     base::OnceCallback<void(const ClientStatus&)> end_action_callback, | 
 |     base::OnceCallback<void(const ClientStatus&)> | 
 |         view_inflation_finished_callback) { | 
 |   delegate_->SetGenericUi(std::move(generic_ui), std::move(end_action_callback), | 
 |                           std::move(view_inflation_finished_callback)); | 
 | } | 
 |  | 
 | void ScriptExecutor::SetPersistentGenericUi( | 
 |     std::unique_ptr<GenericUserInterfaceProto> generic_ui, | 
 |     base::OnceCallback<void(const ClientStatus&)> | 
 |         view_inflation_finished_callback) { | 
 |   delegate_->SetPersistentGenericUi( | 
 |       std::move(generic_ui), std::move(view_inflation_finished_callback)); | 
 | } | 
 |  | 
 | void ScriptExecutor::ClearGenericUi() { | 
 |   delegate_->ClearGenericUi(); | 
 | } | 
 |  | 
 | void ScriptExecutor::ClearPersistentGenericUi() { | 
 |   delegate_->ClearPersistentGenericUi(); | 
 | } | 
 |  | 
 | void ScriptExecutor::SetOverlayBehavior( | 
 |     ConfigureUiStateProto::OverlayBehavior overlay_behavior) { | 
 |   delegate_->SetOverlayBehavior(overlay_behavior); | 
 | } | 
 |  | 
 | void ScriptExecutor::MaybeShowSlowWebsiteWarning( | 
 |     base::OnceCallback<void(bool)> callback) { | 
 |   bool should_show_warning = | 
 |       !delegate_->GetSettings().only_show_website_warning_once || | 
 |       !website_warning_already_shown_; | 
 |   // MaybeShowSlowWarning is only called if should_sown_warning is true. | 
 |   bool warning_was_shown = | 
 |       should_show_warning && | 
 |       MaybeShowSlowWarning( | 
 |           delegate_->GetSettings().slow_website_message, | 
 |           delegate_->GetSettings().enable_slow_website_warnings); | 
 |   website_warning_already_shown_ |= warning_was_shown; | 
 |   if (callback) { | 
 |     std::move(callback).Run(warning_was_shown); | 
 |   } | 
 | } | 
 |  | 
 | void ScriptExecutor::MaybeShowSlowConnectionWarning() { | 
 |   bool should_show_warning = | 
 |       !delegate_->GetSettings().only_show_connection_warning_once || | 
 |       !connection_warning_already_shown_; | 
 |   base::TimeDelta delay = base::TimeDelta::FromMilliseconds(0); | 
 |   // MaybeShowSlowWarning is only called if should_sown_warning is true. | 
 |   bool warning_was_shown = | 
 |       should_show_warning && | 
 |       MaybeShowSlowWarning( | 
 |           delegate_->GetSettings().slow_connection_message, | 
 |           delegate_->GetSettings().enable_slow_connection_warnings); | 
 |   if (warning_was_shown) { | 
 |     delay = delegate_->GetSettings().minimum_warning_duration; | 
 |   } | 
 |   connection_warning_already_shown_ |= warning_was_shown; | 
 |   base::ThreadTaskRunnerHandle::Get()->PostDelayedTask( | 
 |       FROM_HERE, | 
 |       base::BindOnce(&ScriptExecutor::ProcessNextAction, | 
 |                      weak_ptr_factory_.GetWeakPtr()), | 
 |       delay); | 
 | } | 
 |  | 
 | bool ScriptExecutor::MaybeShowSlowWarning(const std::string& message, | 
 |                                           bool enabled) { | 
 |   if (message.empty() || !enabled || !delegate_->ShouldShowWarning()) { | 
 |     return false; | 
 |   } | 
 |  | 
 |   if (delegate_->GetSettings().only_show_warning_once && | 
 |       (connection_warning_already_shown_ || website_warning_already_shown_)) { | 
 |     return false; | 
 |   } | 
 |  | 
 |   const std::string& previous_message = GetStatusMessage(); | 
 |   switch (delegate_->GetSettings().message_mode) { | 
 |     case ClientSettingsProto::SlowWarningSettings::CONCATENATE: | 
 |       if (previous_message.find(message) == std::string::npos) { | 
 |         SetStatusMessage(previous_message + message); | 
 |       } | 
 |       break; | 
 |     case ClientSettingsProto::SlowWarningSettings::REPLACE: | 
 |       SetStatusMessage(message); | 
 |       break; | 
 |     case ClientSettingsProto::SlowWarningSettings::UNKNOWN: | 
 |       return false; | 
 |   } | 
 |  | 
 |   return true; | 
 | } | 
 |  | 
 | void ScriptExecutor::DispatchJsEvent( | 
 |     base::OnceCallback<void(const ClientStatus&)> callback) const { | 
 |   delegate_->GetWebController()->DispatchJsEvent(std::move(callback)); | 
 | } | 
 |  | 
 | base::WeakPtr<ActionDelegate> ScriptExecutor::GetWeakPtr() const { | 
 |   return weak_ptr_factory_.GetWeakPtr(); | 
 | } | 
 |  | 
 | void ScriptExecutor::OnGetActions(base::TimeTicks start_time, | 
 |                                   int http_status, | 
 |                                   const std::string& response) { | 
 |   VLOG(2) << __func__ << " http-status=" << http_status; | 
 |   batch_start_time_ = base::TimeTicks::Now(); | 
 |   const base::TimeDelta& roundtrip_duration = batch_start_time_ - start_time; | 
 |   // Doesn't trigger when the script is completed. | 
 |   roundtrip_timing_stats_.set_roundtrip_time_ms( | 
 |       roundtrip_duration.InMilliseconds()); | 
 |   bool success = | 
 |       http_status == net::HTTP_OK && ProcessNextActionResponse(response); | 
 |   if (should_stop_script_) { | 
 |     // The last action forced the script to stop. Sending the result of the | 
 |     // action is considered best effort in this situation. Report a successful | 
 |     // run to the caller no matter what, so we don't confuse users with an error | 
 |     // message. | 
 |     RunCallback(true); | 
 |     return; | 
 |   } | 
 |  | 
 |   if (!success) { | 
 |     RunCallback(false); | 
 |     return; | 
 |   } | 
 |  | 
 |   if (roundtrip_duration < delegate_->GetSettings().slow_roundtrip_threshold) { | 
 |     consecutive_slow_roundtrip_counter_ = 0; | 
 |   } else { | 
 |     consecutive_slow_roundtrip_counter_++; | 
 |   } | 
 |  | 
 |   if (!actions_.empty()) { | 
 |     if (consecutive_slow_roundtrip_counter_ >= | 
 |         delegate_->GetSettings().max_consecutive_slow_roundtrips) { | 
 |       consecutive_slow_roundtrip_counter_ = 0; | 
 |       MaybeShowSlowConnectionWarning(); | 
 |     } else { | 
 |       ProcessNextAction(); | 
 |     } | 
 |     return; | 
 |   } | 
 |  | 
 |   RunCallback(true); | 
 | } | 
 |  | 
 | bool ScriptExecutor::ProcessNextActionResponse(const std::string& response) { | 
 |   processed_actions_.clear(); | 
 |   actions_.clear(); | 
 |  | 
 |   bool should_update_scripts = false; | 
 |   std::vector<std::unique_ptr<Script>> scripts; | 
 |   bool parse_result = ProtocolUtils::ParseActions( | 
 |       this, response, &last_global_payload_, &last_script_payload_, &actions_, | 
 |       &scripts, &should_update_scripts); | 
 |   if (!parse_result) { | 
 |     return false; | 
 |   } | 
 |  | 
 |   ReportPayloadsToListener(); | 
 |   if (should_update_scripts) { | 
 |     ReportScriptsUpdateToListener(std::move(scripts)); | 
 |   } | 
 |   return true; | 
 | } | 
 |  | 
 | void ScriptExecutor::ReportPayloadsToListener() { | 
 |   if (!listener_) | 
 |     return; | 
 |  | 
 |   listener_->OnServerPayloadChanged(last_global_payload_, last_script_payload_); | 
 | } | 
 |  | 
 | void ScriptExecutor::ReportScriptsUpdateToListener( | 
 |     std::vector<std::unique_ptr<Script>> scripts) { | 
 |   if (!listener_) | 
 |     return; | 
 |  | 
 |   listener_->OnScriptListChanged(std::move(scripts)); | 
 | } | 
 |  | 
 | void ScriptExecutor::RunCallback(bool success) { | 
 |   if (should_clean_contextual_ui_on_finish_ || !success) { | 
 |     SetDetails(nullptr, base::TimeDelta()); | 
 |     ClearPersistentGenericUi(); | 
 |     should_clean_contextual_ui_on_finish_ = false; | 
 |   } | 
 |  | 
 |   Result result; | 
 |   result.success = success; | 
 |   result.at_end = at_end_; | 
 |   result.touchable_element_area = std::move(touchable_element_area_); | 
 |  | 
 |   RunCallbackWithResult(result); | 
 | } | 
 |  | 
 | void ScriptExecutor::RunCallbackWithResult(const Result& result) { | 
 |   DCHECK(callback_); | 
 |   (*scripts_state_)[script_path_] = | 
 |       result.success ? SCRIPT_STATUS_SUCCESS : SCRIPT_STATUS_FAILURE; | 
 |   std::move(callback_).Run(result); | 
 | } | 
 |  | 
 | void ScriptExecutor::ProcessNextAction() { | 
 |   current_action_index_.reset(); | 
 |   if (is_paused_) { | 
 |     return; | 
 |   } | 
 |  | 
 |   // We could get into a strange situation if ProcessNextAction is called before | 
 |   // the action was reported as processed, which should not happen. In that case | 
 |   // we could have more |processed_actions| than |actions_|. | 
 |   if (actions_.size() <= processed_actions_.size()) { | 
 |     DCHECK_EQ(actions_.size(), processed_actions_.size()); | 
 |     VLOG(2) << __func__ << ", get more actions"; | 
 |     GetNextActions(); | 
 |     return; | 
 |   } | 
 |  | 
 |   current_action_index_ = processed_actions_.size(); | 
 |   Action* action = actions_[*current_action_index_].get(); | 
 |   should_clean_contextual_ui_on_finish_ = action->proto().clean_contextual_ui(); | 
 |   int delay_ms = action->proto().action_delay_ms(); | 
 |   if (delay_ms > 0) { | 
 |     base::ThreadTaskRunnerHandle::Get()->PostDelayedTask( | 
 |         FROM_HERE, | 
 |         base::BindOnce(&ScriptExecutor::ProcessAction, | 
 |                        weak_ptr_factory_.GetWeakPtr(), action), | 
 |         base::TimeDelta::FromMilliseconds(delay_ms)); | 
 |   } else { | 
 |     ProcessAction(action); | 
 |   } | 
 | } | 
 |  | 
 | void ScriptExecutor::ProcessAction(Action* action) { | 
 |   VLOG(2) << "Begin action: " << *action; | 
 |  | 
 |   current_action_data_ = CurrentActionData(); | 
 |   current_action_data_.navigation_info.set_has_error( | 
 |       delegate_->HasNavigationError()); | 
 |  | 
 |   action->ProcessAction(base::BindOnce(&ScriptExecutor::OnProcessedAction, | 
 |                                        weak_ptr_factory_.GetWeakPtr(), | 
 |                                        base::TimeTicks::Now())); | 
 | } | 
 |  | 
 | void ScriptExecutor::GetNextActions() { | 
 |   base::TimeTicks get_next_actions_start = base::TimeTicks::Now(); | 
 |   roundtrip_timing_stats_.set_client_time_ms( | 
 |       (get_next_actions_start - batch_start_time_).InMilliseconds()); | 
 |   VLOG(2) << "Batch timing stats"; | 
 |   VLOG(2) << "Roundtrip time: " << roundtrip_timing_stats_.roundtrip_time_ms(); | 
 |   VLOG(2) << "Client execution time: " | 
 |           << roundtrip_timing_stats_.client_time_ms(); | 
 |   delegate_->GetService()->GetNextActions( | 
 |       TriggerContext( | 
 |           {delegate_->GetTriggerContext(), additional_context_.get()}), | 
 |       last_global_payload_, last_script_payload_, processed_actions_, | 
 |       roundtrip_timing_stats_, | 
 |       base::BindOnce(&ScriptExecutor::OnGetActions, | 
 |                      weak_ptr_factory_.GetWeakPtr(), get_next_actions_start)); | 
 | } | 
 |  | 
 | void ScriptExecutor::OnProcessedAction( | 
 |     base::TimeTicks start_time, | 
 |     std::unique_ptr<ProcessedActionProto> processed_action_proto) { | 
 |   base::TimeDelta run_time = base::TimeTicks::Now() - start_time; | 
 |   previous_action_type_ = processed_action_proto->action().action_info_case(); | 
 |   processed_actions_.emplace_back(*processed_action_proto); | 
 |  | 
 | #ifdef NDEBUG | 
 |   VLOG(2) << "Action completed"; | 
 | #else | 
 |   VLOG(2) << "Requested delay ms: " | 
 |           << processed_action_proto->timing_stats().delay_ms(); | 
 |   VLOG(2) << "Active time ms: " | 
 |           << processed_action_proto->timing_stats().active_time_ms(); | 
 |   VLOG(2) << "Wait time ms: " | 
 |           << processed_action_proto->timing_stats().wait_time_ms(); | 
 |   VLOG(2) << "Run time: " << run_time; | 
 | #endif | 
 |  | 
 |   auto& processed_action = processed_actions_.back(); | 
 |   processed_action.set_run_time_ms(run_time.InMilliseconds()); | 
 |   processed_action.set_direct_action(current_action_data_.direct_action); | 
 |   *processed_action.mutable_navigation_info() = | 
 |       current_action_data_.navigation_info; | 
 |  | 
 |   if (processed_action.status() != ProcessedActionStatusProto::ACTION_APPLIED) { | 
 |     VLOG(1) << "Action failed: " << processed_action.status(); | 
 |     // Remove unexecuted actions, this will cause the |ProcessNextActions| call | 
 |     // to immediately ask for new actions. | 
 |     actions_.resize(processed_actions_.size()); | 
 |   } | 
 |   ProcessNextAction(); | 
 | } | 
 |  | 
 | void ScriptExecutor::CheckElementMatches( | 
 |     const Selector& selector, | 
 |     BatchElementChecker* checker, | 
 |     base::OnceCallback<void(const ClientStatus&)> callback) { | 
 |   checker->AddElementCheck( | 
 |       selector, | 
 |       base::BindOnce(&ScriptExecutor::CheckElementMatchesCallback, | 
 |                      weak_ptr_factory_.GetWeakPtr(), std::move(callback))); | 
 | } | 
 |  | 
 | void ScriptExecutor::CheckElementMatchesCallback( | 
 |     base::OnceCallback<void(const ClientStatus&)> callback, | 
 |     const ClientStatus& status, | 
 |     const ElementFinder::Result& ignored_element) { | 
 |   std::move(callback).Run(status); | 
 | } | 
 |  | 
 | void ScriptExecutor::OnShortWaitForElement( | 
 |     base::OnceCallback<void(const ClientStatus&, base::TimeDelta)> callback, | 
 |     const ClientStatus& element_status, | 
 |     const Result* interrupt_result, | 
 |     base::TimeDelta wait_time) { | 
 |   // Interrupts cannot run, so should never be reported. | 
 |   DCHECK(!interrupt_result); | 
 |  | 
 |   std::move(callback).Run(element_status, wait_time); | 
 | } | 
 |  | 
 | void ScriptExecutor::OnWaitForElementVisibleWithInterrupts( | 
 |     base::OnceCallback<void(const ClientStatus&, base::TimeDelta)> callback, | 
 |     const ClientStatus& element_status, | 
 |     const Result* interrupt_result, | 
 |     base::TimeDelta wait_time) { | 
 |   if (interrupt_result) { | 
 |     if (!interrupt_result->success) { | 
 |       std::move(callback).Run(ClientStatus(INTERRUPT_FAILED), wait_time); | 
 |       return; | 
 |     } | 
 |     if (interrupt_result->at_end != CONTINUE) { | 
 |       at_end_ = interrupt_result->at_end; | 
 |       should_stop_script_ = true; | 
 |       std::move(callback).Run(ClientStatus(MANUAL_FALLBACK), wait_time); | 
 |       return; | 
 |     } | 
 |   } | 
 |   std::move(callback).Run(element_status, wait_time); | 
 | } | 
 |  | 
 | ScriptExecutor::WaitForDomOperation::WaitForDomOperation( | 
 |     ScriptExecutor* main_script, | 
 |     ScriptExecutorDelegate* delegate, | 
 |     base::TimeDelta max_wait_time, | 
 |     bool allow_interrupt, | 
 |     WaitForDomObserver* observer, | 
 |     base::RepeatingCallback<void(BatchElementChecker*, | 
 |                                  base::OnceCallback<void(const ClientStatus&)>)> | 
 |         check_elements, | 
 |     WaitForDomOperation::Callback callback) | 
 |     : main_script_(main_script), | 
 |       delegate_(delegate), | 
 |       max_wait_time_(max_wait_time), | 
 |       allow_interrupt_(allow_interrupt), | 
 |       observer_(observer), | 
 |       check_elements_(std::move(check_elements)), | 
 |       callback_(std::move(callback)), | 
 |       timeout_warning_delay_( | 
 |           main_script->delegate_->GetSettings().warning_delay), | 
 |       retry_timer_(main_script->delegate_->GetSettings() | 
 |                        .periodic_element_check_interval) {} | 
 |  | 
 | ScriptExecutor::WaitForDomOperation::~WaitForDomOperation() { | 
 |   delegate_->RemoveNavigationListener(this); | 
 | } | 
 |  | 
 | void ScriptExecutor::WaitForDomOperation::Run() { | 
 |   delegate_->AddNavigationListener(this); | 
 |   wait_time_stopwatch_.Start(); | 
 |   Start(); | 
 | } | 
 |  | 
 | void ScriptExecutor::WaitForDomOperation::SetTimeoutWarningCallback( | 
 |     WarningCallback warning_callback) { | 
 |   warning_callback_ = std::move(warning_callback); | 
 | } | 
 |  | 
 | void ScriptExecutor::WaitForDomOperation::Start() { | 
 |   retry_timer_.Start( | 
 |       max_wait_time_, | 
 |       base::BindRepeating(&ScriptExecutor::WaitForDomOperation::RunChecks, | 
 |                           // safe since this instance owns retry_timer_ | 
 |                           base::Unretained(this)), | 
 |       base::BindOnce(&ScriptExecutor::WaitForDomOperation::RunCallback, | 
 |                      base::Unretained(this))); | 
 | } | 
 |  | 
 | void ScriptExecutor::WaitForDomOperation::Pause() { | 
 |   if (interrupt_executor_) { | 
 |     // If an interrupt is running, it'll be the one to be paused, if necessary. | 
 |     return; | 
 |   } | 
 |  | 
 |   retry_timer_.Cancel(); | 
 | } | 
 |  | 
 | void ScriptExecutor::WaitForDomOperation::Continue() { | 
 |   if (retry_timer_.running() || !callback_) | 
 |     return; | 
 |  | 
 |   Start(); | 
 | } | 
 |  | 
 | void ScriptExecutor::WaitForDomOperation::OnNavigationStateChanged() { | 
 |   if (delegate_->IsNavigatingToNewDocument()) { | 
 |     Pause(); | 
 |   } else { | 
 |     Continue(); | 
 |   } | 
 | } | 
 |  | 
 | void ScriptExecutor::WaitForDomOperation::OnServerPayloadChanged( | 
 |     const std::string& global_payload, | 
 |     const std::string& script_payload) { | 
 |   // Interrupts and main scripts share global payloads, but not script payloads. | 
 |   main_script_->last_global_payload_ = global_payload; | 
 |   main_script_->ReportPayloadsToListener(); | 
 | } | 
 |  | 
 | void ScriptExecutor::WaitForDomOperation::OnScriptListChanged( | 
 |     std::vector<std::unique_ptr<Script>> scripts) { | 
 |   main_script_->ReportScriptsUpdateToListener(std::move(scripts)); | 
 | } | 
 |  | 
 | void ScriptExecutor::WaitForDomOperation::TimeoutWarning() { | 
 |   if (warning_callback_) { | 
 |     std::move(warning_callback_) | 
 |         .Run(base::BindOnce( | 
 |             &ScriptExecutor::WaitForDomOperation::SetSlowWarningStatus, | 
 |             weak_ptr_factory_.GetWeakPtr())); | 
 |   } | 
 | } | 
 |  | 
 | void ScriptExecutor::WaitForDomOperation::SetSlowWarningStatus(bool was_shown) { | 
 |   if (was_shown) { | 
 |     warning_status_ = WARNING_SHOWN; | 
 |   } else { | 
 |     warning_status_ = WARNING_TRIGGERED; | 
 |   } | 
 | } | 
 |  | 
 | void ScriptExecutor::WaitForDomOperation::RunChecks( | 
 |     base::OnceCallback<void(const ClientStatus&)> report_attempt_result) { | 
 |   warning_timer_ = std::make_unique<base::OneShotTimer>(); | 
 |   warning_timer_->Start( | 
 |       FROM_HERE, timeout_warning_delay_, | 
 |       base::BindOnce(&ScriptExecutor::WaitForDomOperation::TimeoutWarning, | 
 |                      weak_ptr_factory_.GetWeakPtr())); | 
 |   wait_time_total_ = | 
 |       (wait_time_stopwatch_.TotalElapsed() < retry_timer_.period()) | 
 |           // It's the first run of the checks, set the total time waited to 0. | 
 |           ? base::TimeDelta::FromSeconds(0) | 
 |           // If this is not the first run of the checks, in order to estimate | 
 |           // the real cost of periodic checks, half the duration of the retry | 
 |           // timer period is removed from the total wait time. This is to | 
 |           // account for the fact that the conditions could have been satisfied | 
 |           // at any point between the two consecutive checks. | 
 |           : wait_time_stopwatch_.TotalElapsed() - retry_timer_.period() / 2; | 
 |   // Reset state possibly left over from previous runs. | 
 |   element_check_result_ = ClientStatus(); | 
 |   runnable_interrupts_.clear(); | 
 |   batch_element_checker_ = std::make_unique<BatchElementChecker>(); | 
 |   check_elements_.Run(batch_element_checker_.get(), | 
 |                       base::BindOnce(&WaitForDomOperation::OnElementCheckDone, | 
 |                                      base::Unretained(this))); | 
 |   if (allow_interrupt_) { | 
 |     for (const std::unique_ptr<Script>& interrupt : | 
 |          *main_script_->ordered_interrupts_) { | 
 |       if (ran_interrupts_.find(interrupt->handle.path) != | 
 |           ran_interrupts_.end()) { | 
 |         continue; | 
 |       } | 
 |  | 
 |       interrupt->precondition->Check( | 
 |           delegate_->GetCurrentURL(), batch_element_checker_.get(), | 
 |           *delegate_->GetTriggerContext(), *main_script_->scripts_state_, | 
 |           base::BindOnce(&WaitForDomOperation::OnPreconditionCheckDone, | 
 |                          weak_ptr_factory_.GetWeakPtr(), | 
 |                          interrupt->handle.path)); | 
 |     } | 
 |   } | 
 |  | 
 |   batch_element_checker_->AddAllDoneCallback( | 
 |       base::BindOnce(&WaitForDomOperation::OnAllChecksDone, | 
 |                      base::Unretained(this), std::move(report_attempt_result))); | 
 |   batch_element_checker_->Run(delegate_->GetWebController()); | 
 | } | 
 |  | 
 | void ScriptExecutor::WaitForDomOperation::OnPreconditionCheckDone( | 
 |     const std::string& interrupt_path, | 
 |     bool precondition_match) { | 
 |   if (precondition_match) | 
 |     runnable_interrupts_.insert(interrupt_path); | 
 | } | 
 |  | 
 | void ScriptExecutor::WaitForDomOperation::OnElementCheckDone( | 
 |     const ClientStatus& element_status) { | 
 |   element_check_result_ = element_status; | 
 |  | 
 |   // Wait for all checks to run before reporting that the element was found to | 
 |   // the caller, so interrupts have a chance to run. | 
 | } | 
 |  | 
 | void ScriptExecutor::WaitForDomOperation::OnAllChecksDone( | 
 |     base::OnceCallback<void(const ClientStatus&)> report_attempt_result) { | 
 |   warning_timer_->Stop(); | 
 |   if (runnable_interrupts_.empty()) { | 
 |     // Since no interrupts fired, allow previously-run interrupts to be run | 
 |     // again in the next round. This is meant to give elements one round to | 
 |     // disappear and avoid the simplest form of loops. A round with interrupts | 
 |     // firing doesn't count as one round here, because an interrupt can run | 
 |     // quickly and return immediately, without waiting for | 
 |     // periodic_element_check_interval. | 
 |     ran_interrupts_.clear(); | 
 |   } else { | 
 |     // We must go through runnable_interrupts_ to make sure priority order is | 
 |     // respected in case more than one interrupt is ready to run. | 
 |     for (const std::unique_ptr<Script>& interrupt : | 
 |          *main_script_->ordered_interrupts_) { | 
 |       const std::string& path = interrupt->handle.path; | 
 |       if (runnable_interrupts_.find(path) != runnable_interrupts_.end()) { | 
 |         RunInterrupt(path); | 
 |         return; | 
 |       } | 
 |     } | 
 |   } | 
 |   std::move(report_attempt_result).Run(element_check_result_); | 
 | } | 
 |  | 
 | void ScriptExecutor::WaitForDomOperation::RunInterrupt( | 
 |     const std::string& path) { | 
 |   batch_element_checker_.reset(); | 
 |   if (observer_) | 
 |     observer_->OnInterruptStarted(); | 
 |  | 
 |   SavePreInterruptState(); | 
 |   ran_interrupts_.insert(path); | 
 |   interrupt_executor_ = std::make_unique<ScriptExecutor>( | 
 |       path, | 
 |       std::make_unique<TriggerContext>(std::vector<const TriggerContext*>{ | 
 |           main_script_->additional_context_.get()}), | 
 |       main_script_->last_global_payload_, main_script_->initial_script_payload_, | 
 |       /* listener= */ this, main_script_->scripts_state_, &no_interrupts_, | 
 |       delegate_); | 
 |   delegate_->EnterState(AutofillAssistantState::RUNNING); | 
 |   delegate_->SetUserActions(nullptr); | 
 |   interrupt_executor_->Run( | 
 |       main_script_->user_data_, | 
 |       base::BindOnce(&ScriptExecutor::WaitForDomOperation::OnInterruptDone, | 
 |                      base::Unretained(this))); | 
 |   // base::Unretained(this) is safe because interrupt_executor_ belongs to this | 
 | } | 
 |  | 
 | void ScriptExecutor::WaitForDomOperation::OnInterruptDone( | 
 |     const ScriptExecutor::Result& result) { | 
 |   interrupt_executor_.reset(); | 
 |   if (!result.success || result.at_end != ScriptExecutor::CONTINUE) { | 
 |     RunCallbackWithResult(ClientStatus(INTERRUPT_FAILED), &result); | 
 |     return; | 
 |   } | 
 |   if (observer_) | 
 |     observer_->OnInterruptFinished(); | 
 |  | 
 |   RestoreStatusMessage(); | 
 |   RestorePreInterruptScroll(); | 
 |  | 
 |   // Restart. We use the original wait time since the interruption could have | 
 |   // triggered any kind of actions, including actions that wait on the user. We | 
 |   // don't trust a previous element_found_ result, since it could have changed. | 
 |   Start(); | 
 | } | 
 |  | 
 | void ScriptExecutor::WaitForDomOperation::RunCallback( | 
 |     const ClientStatus& element_status) { | 
 |   RunCallbackWithResult(element_status, nullptr); | 
 | } | 
 |  | 
 | void ScriptExecutor::WaitForDomOperation::RunCallbackWithResult( | 
 |     const ClientStatus& element_status, | 
 |     const ScriptExecutor::Result* result) { | 
 |   // stop element checking if one is still in progress | 
 |   batch_element_checker_.reset(); | 
 |   retry_timer_.Cancel(); | 
 |   warning_timer_->Stop(); | 
 |  | 
 |   if (!callback_) | 
 |     return; | 
 |  | 
 |   ClientStatus status(element_status); | 
 |   status.set_slow_warning_status(warning_status_); | 
 |  | 
 |   std::move(callback_).Run(status, result, wait_time_total_); | 
 | } | 
 |  | 
 | void ScriptExecutor::WaitForDomOperation::SavePreInterruptState() { | 
 |   if (saved_pre_interrupt_state_) | 
 |     return; | 
 |  | 
 |   pre_interrupt_status_ = delegate_->GetStatusMessage(); | 
 |   saved_pre_interrupt_state_ = true; | 
 | } | 
 |  | 
 | void ScriptExecutor::WaitForDomOperation::RestoreStatusMessage() { | 
 |   if (!saved_pre_interrupt_state_) | 
 |     return; | 
 |  | 
 |   delegate_->SetStatusMessage(pre_interrupt_status_); | 
 | } | 
 |  | 
 | void ScriptExecutor::WaitForDomOperation::RestorePreInterruptScroll() { | 
 |   if (!saved_pre_interrupt_state_) | 
 |     return; | 
 |  | 
 |   if (!main_script_->last_focused_element_selector_.empty()) { | 
 |     auto actions = | 
 |         std::make_unique<action_delegate_util::ElementActionVector>(); | 
 |     action_delegate_util::AddStepIgnoreTiming( | 
 |         base::BindOnce(&ActionDelegate::WaitUntilDocumentIsInReadyState, | 
 |                        main_script_->GetWeakPtr(), | 
 |                        delegate_->GetSettings().document_ready_check_timeout, | 
 |                        DOCUMENT_INTERACTIVE), | 
 |         actions.get()); | 
 |     actions->emplace_back(base::BindOnce( | 
 |         &ActionDelegate::ScrollToElementPosition, main_script_->GetWeakPtr(), | 
 |         main_script_->last_focused_element_selector_, | 
 |         main_script_->last_focused_element_top_padding_, nullptr)); | 
 |     action_delegate_util::FindElementAndPerform( | 
 |         main_script_, main_script_->last_focused_element_selector_, | 
 |         base::BindOnce(&action_delegate_util::PerformAll, std::move(actions)), | 
 |         base::DoNothing()); | 
 |   } | 
 | } | 
 |  | 
 | ScriptExecutor::CurrentActionData::CurrentActionData() = default; | 
 | ScriptExecutor::CurrentActionData::~CurrentActionData() = default; | 
 | ScriptExecutor::CurrentActionData& ScriptExecutor::CurrentActionData::operator=( | 
 |     ScriptExecutor::CurrentActionData&& other) = default; | 
 |  | 
 | std::ostream& operator<<(std::ostream& out, | 
 |                          const ScriptExecutor::Result& result) { | 
 |   result.success ? out << "succeeded. " : out << "failed. "; | 
 |   out << "at_end = " << result.at_end; | 
 |   return out; | 
 | } | 
 |  | 
 | }  // namespace autofill_assistant |