| // 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 <string> |
| #include <utility> |
| |
| #include "base/bind.h" |
| #include "base/callback.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/credit_card.h" |
| #include "components/autofill_assistant/browser/batch_element_checker.h" |
| #include "components/autofill_assistant/browser/client_memory.h" |
| #include "components/autofill_assistant/browser/protocol_utils.h" |
| #include "components/autofill_assistant/browser/self_delete_full_card_requester.h" |
| #include "components/autofill_assistant/browser/service.h" |
| #include "components/autofill_assistant/browser/ui_controller.h" |
| #include "components/autofill_assistant/browser/web_controller.h" |
| #include "components/strings/grit/components_strings.h" |
| #include "ui/base/l10n/l10n_util.h" |
| |
| namespace autofill_assistant { |
| namespace { |
| |
| // Maximum amount of time normal actions should implicitly wait for a selector |
| // to show up. |
| constexpr base::TimeDelta kShortWaitForElementDeadline = |
| base::TimeDelta::FromSeconds(2); |
| } // namespace |
| |
| ScriptExecutor::ScriptExecutor( |
| const std::string& script_path, |
| const std::string& global_payload, |
| const std::string& script_payload, |
| ScriptExecutor::Listener* listener, |
| std::map<std::string, ScriptStatusProto>* scripts_state, |
| const std::vector<Script*>* ordered_interrupts, |
| ScriptExecutorDelegate* delegate) |
| : script_path_(script_path), |
| last_global_payload_(global_payload), |
| initial_script_payload_(script_payload), |
| last_script_payload_(script_payload), |
| listener_(listener), |
| delegate_(delegate), |
| at_end_(CONTINUE), |
| should_stop_script_(false), |
| should_clean_contextual_ui_on_finish_(false), |
| previous_action_type_(ActionProto::ACTION_INFO_NOT_SET), |
| scripts_state_(scripts_state), |
| ordered_interrupts_(ordered_interrupts), |
| weak_ptr_factory_(this) { |
| DCHECK(delegate_); |
| DCHECK(ordered_interrupts_); |
| } |
| ScriptExecutor::~ScriptExecutor() {} |
| |
| ScriptExecutor::Result::Result() = default; |
| ScriptExecutor::Result::~Result() = default; |
| |
| void ScriptExecutor::Run(RunScriptCallback callback) { |
| (*scripts_state_)[script_path_] = SCRIPT_STATUS_RUNNING; |
| |
| callback_ = std::move(callback); |
| DCHECK(delegate_->GetService()); |
| |
| delegate_->GetService()->GetActions( |
| script_path_, delegate_->GetWebController()->GetUrl(), |
| delegate_->GetParameters(), last_global_payload_, last_script_payload_, |
| base::BindOnce(&ScriptExecutor::OnGetActions, |
| weak_ptr_factory_.GetWeakPtr())); |
| } |
| |
| std::unique_ptr<BatchElementChecker> |
| ScriptExecutor::CreateBatchElementChecker() { |
| return delegate_->GetWebController()->CreateBatchElementChecker(); |
| } |
| |
| void ScriptExecutor::ShortWaitForElementExist( |
| const Selector& selector, |
| base::OnceCallback<void(bool)> callback) { |
| WaitForElement(kShortWaitForElementDeadline, kExistenceCheck, selector, |
| std::move(callback)); |
| } |
| |
| void ScriptExecutor::WaitForElementVisible( |
| base::TimeDelta max_wait_time, |
| bool allow_interrupt, |
| const Selector& selector, |
| base::OnceCallback<void(ProcessedActionStatusProto)> callback) { |
| if (!allow_interrupt || ordered_interrupts_->empty()) { |
| // No interrupts to worry about. Just run normal wait. |
| WaitForElement( |
| max_wait_time, kVisibilityCheck, selector, |
| base::BindOnce(&ScriptExecutor::OnWaitForElementVisibleNoInterrupts, |
| weak_ptr_factory_.GetWeakPtr(), std::move(callback))); |
| return; |
| } |
| wait_with_interrupts_ = std::make_unique<WaitWithInterrupts>( |
| this, max_wait_time, kVisibilityCheck, selector, |
| base::BindOnce(&ScriptExecutor::OnWaitForElementVisibleWithInterrupts, |
| weak_ptr_factory_.GetWeakPtr(), std::move(callback))); |
| wait_with_interrupts_->Run(); |
| } |
| |
| void ScriptExecutor::SetStatusMessage(const std::string& message) { |
| delegate_->SetStatusMessage(message); |
| } |
| |
| std::string ScriptExecutor::GetStatusMessage() { |
| return delegate_->GetStatusMessage(); |
| } |
| |
| void ScriptExecutor::ClickOrTapElement( |
| const Selector& selector, |
| base::OnceCallback<void(bool)> callback) { |
| delegate_->GetWebController()->ClickOrTapElement(selector, |
| std::move(callback)); |
| } |
| |
| void ScriptExecutor::GetPaymentInformation( |
| payments::mojom::PaymentOptionsPtr payment_options, |
| base::OnceCallback<void(std::unique_ptr<PaymentInformation>)> callback, |
| const std::string& title, |
| const std::vector<std::string>& supported_basic_card_networks) { |
| delegate_->EnterState(AutofillAssistantState::PROMPT); |
| delegate_->GetUiController()->GetPaymentInformation( |
| std::move(payment_options), |
| base::BindOnce(&ScriptExecutor::OnGetPaymentInformation, |
| weak_ptr_factory_.GetWeakPtr(), std::move(callback)), |
| title, supported_basic_card_networks); |
| } |
| |
| void ScriptExecutor::OnGetPaymentInformation( |
| base::OnceCallback<void(std::unique_ptr<PaymentInformation>)> callback, |
| std::unique_ptr<PaymentInformation> result) { |
| delegate_->EnterState(AutofillAssistantState::RUNNING); |
| std::move(callback).Run(std::move(result)); |
| } |
| |
| void ScriptExecutor::GetFullCard(GetFullCardCallback callback) { |
| DCHECK(GetClientMemory()->selected_card()); |
| |
| // User might be asked to provide the cvc. |
| delegate_->EnterState(AutofillAssistantState::MODAL_DIALOG); |
| |
| // TODO(crbug.com/806868): Consider refactoring SelfDeleteFullCardRequester |
| // so as to unit test it. |
| (new SelfDeleteFullCardRequester()) |
| ->GetFullCard( |
| GetWebContents(), GetClientMemory()->selected_card(), |
| base::BindOnce(&ScriptExecutor::OnGetFullCard, |
| weak_ptr_factory_.GetWeakPtr(), std::move(callback))); |
| } |
| |
| void ScriptExecutor::OnGetFullCard(GetFullCardCallback callback, |
| std::unique_ptr<autofill::CreditCard> card, |
| const base::string16& cvc) { |
| delegate_->EnterState(AutofillAssistantState::RUNNING); |
| std::move(callback).Run(std::move(card), cvc); |
| } |
| |
| void ScriptExecutor::Prompt(std::unique_ptr<std::vector<Chip>> chips) { |
| if (touchable_element_area_) { |
| // SetChips 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_elements_ currently set in the script is reset, so that it |
| // won't affect the real end of the script. |
| touchable_element_area_.reset(); |
| |
| // The touchable element and overlays are cleared again in |
| // ScriptExecutor::OnChosen or ScriptExecutor::ClearChips |
| } |
| |
| // We change the chips callback with a callback that cleans up the state |
| // before calling the initial callback. |
| for (auto& chip : *chips) { |
| chip.callback = base::BindOnce(&ScriptExecutor::OnChosen, |
| weak_ptr_factory_.GetWeakPtr(), |
| std::move(chip.callback)); |
| } |
| |
| delegate_->EnterState(AutofillAssistantState::PROMPT); |
| delegate_->GetUiController()->SetChips(std::move(chips)); |
| } |
| |
| void ScriptExecutor::CancelPrompt() { |
| delegate_->GetUiController()->ClearChips(); |
| CleanUpAfterPrompt(); |
| } |
| |
| void ScriptExecutor::CleanUpAfterPrompt() { |
| delegate_->ClearTouchableElementArea(); |
| delegate_->EnterState(AutofillAssistantState::RUNNING); |
| } |
| |
| void ScriptExecutor::OnChosen(base::OnceClosure callback) { |
| CleanUpAfterPrompt(); |
| std::move(callback).Run(); |
| } |
| |
| void ScriptExecutor::FillAddressForm(const autofill::AutofillProfile* profile, |
| const Selector& selector, |
| base::OnceCallback<void(bool)> callback) { |
| delegate_->GetWebController()->FillAddressForm(profile, selector, |
| std::move(callback)); |
| } |
| |
| void ScriptExecutor::FillCardForm(std::unique_ptr<autofill::CreditCard> card, |
| const base::string16& cvc, |
| const Selector& selector, |
| base::OnceCallback<void(bool)> callback) { |
| delegate_->GetWebController()->FillCardForm(std::move(card), cvc, selector, |
| std::move(callback)); |
| } |
| |
| void ScriptExecutor::SelectOption(const Selector& selector, |
| const std::string& selected_option, |
| base::OnceCallback<void(bool)> callback) { |
| delegate_->GetWebController()->SelectOption(selector, selected_option, |
| std::move(callback)); |
| } |
| |
| void ScriptExecutor::HighlightElement(const Selector& selector, |
| base::OnceCallback<void(bool)> callback) { |
| delegate_->GetWebController()->HighlightElement(selector, |
| std::move(callback)); |
| } |
| |
| void ScriptExecutor::FocusElement(const Selector& selector, |
| base::OnceCallback<void(bool)> callback) { |
| last_focused_element_selector_ = selector; |
| delegate_->GetWebController()->FocusElement(selector, std::move(callback)); |
| } |
| |
| void ScriptExecutor::SetTouchableElementArea( |
| const ElementAreaProto& touchable_element_area) { |
| touchable_element_area_ = |
| std::make_unique<ElementAreaProto>(touchable_element_area); |
| } |
| |
| void ScriptExecutor::ShowProgressBar(int progress) { |
| delegate_->GetUiController()->ShowProgressBar(progress); |
| } |
| |
| void ScriptExecutor::HideProgressBar() { |
| delegate_->GetUiController()->HideProgressBar(); |
| } |
| |
| void ScriptExecutor::SetFieldValue(const Selector& selector, |
| const std::string& value, |
| bool simulate_key_presses, |
| base::OnceCallback<void(bool)> callback) { |
| delegate_->GetWebController()->SetFieldValue( |
| selector, value, simulate_key_presses, std::move(callback)); |
| } |
| |
| void ScriptExecutor::SetAttribute(const Selector& selector, |
| const std::vector<std::string>& attribute, |
| const std::string& value, |
| base::OnceCallback<void(bool)> callback) { |
| delegate_->GetWebController()->SetAttribute(selector, attribute, value, |
| std::move(callback)); |
| } |
| |
| void ScriptExecutor::SendKeyboardInput( |
| const Selector& selector, |
| const std::vector<std::string>& text_parts, |
| base::OnceCallback<void(bool)> callback) { |
| delegate_->GetWebController()->SendKeyboardInput(selector, text_parts, |
| std::move(callback)); |
| } |
| |
| void ScriptExecutor::GetOuterHtml( |
| const Selector& selector, |
| base::OnceCallback<void(bool, const std::string&)> callback) { |
| delegate_->GetWebController()->GetOuterHtml(selector, std::move(callback)); |
| } |
| |
| void ScriptExecutor::LoadURL(const GURL& url) { |
| delegate_->GetWebController()->LoadURL(url); |
| } |
| |
| void ScriptExecutor::Shutdown() { |
| // 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) { |
| at_end_ = SHUTDOWN_GRACEFULLY; |
| } else { |
| at_end_ = SHUTDOWN; |
| } |
| } |
| |
| void ScriptExecutor::Terminate() { |
| if (wait_with_interrupts_) |
| wait_with_interrupts_->Terminate(); |
| at_end_ = TERMINATE; |
| should_stop_script_ = true; |
| } |
| |
| void ScriptExecutor::Close() { |
| at_end_ = CLOSE_CUSTOM_TAB; |
| should_stop_script_ = true; |
| } |
| |
| void ScriptExecutor::Restart() { |
| at_end_ = RESTART; |
| } |
| |
| void ScriptExecutor::StopCurrentScriptAndShutdown(const std::string& message) { |
| // Use a default message when |message| is empty. |
| delegate_->SetStatusMessage( |
| message.empty() ? l10n_util::GetStringUTF8(IDS_AUTOFILL_ASSISTANT_GIVE_UP) |
| : message); |
| at_end_ = SHUTDOWN_GRACEFULLY; |
| should_stop_script_ = true; |
| } |
| |
| ClientMemory* ScriptExecutor::GetClientMemory() { |
| return delegate_->GetClientMemory(); |
| } |
| |
| autofill::PersonalDataManager* ScriptExecutor::GetPersonalDataManager() { |
| return delegate_->GetPersonalDataManager(); |
| } |
| |
| content::WebContents* ScriptExecutor::GetWebContents() { |
| return delegate_->GetWebContents(); |
| } |
| |
| void ScriptExecutor::ClearDetails() { |
| delegate_->ClearDetails(); |
| } |
| |
| void ScriptExecutor::SetDetails(const Details& details) { |
| return delegate_->SetDetails(details); |
| } |
| |
| void ScriptExecutor::OnGetActions(bool result, const std::string& response) { |
| bool success = result && 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 (!actions_.empty()) { |
| 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( |
| 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) { |
| DCHECK(callback_); |
| if (should_clean_contextual_ui_on_finish_ || !success) { |
| ClearDetails(); |
| 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) { |
| (*scripts_state_)[script_path_] = |
| result.success ? SCRIPT_STATUS_SUCCESS : SCRIPT_STATUS_FAILURE; |
| std::move(callback_).Run(result); |
| } |
| |
| void ScriptExecutor::ProcessNextAction() { |
| // 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()); |
| // Request more actions to execute. |
| GetNextActions(); |
| return; |
| } |
| |
| Action* action = actions_[processed_actions_.size()].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) { |
| action->ProcessAction(this, base::BindOnce(&ScriptExecutor::OnProcessedAction, |
| weak_ptr_factory_.GetWeakPtr())); |
| } |
| |
| void ScriptExecutor::GetNextActions() { |
| delegate_->GetService()->GetNextActions( |
| last_global_payload_, last_script_payload_, processed_actions_, |
| base::BindOnce(&ScriptExecutor::OnGetActions, |
| weak_ptr_factory_.GetWeakPtr())); |
| } |
| |
| void ScriptExecutor::OnProcessedAction( |
| std::unique_ptr<ProcessedActionProto> processed_action_proto) { |
| previous_action_type_ = processed_action_proto->action().action_info_case(); |
| processed_actions_.emplace_back(*processed_action_proto); |
| |
| auto& processed_action = processed_actions_.back(); |
| if (at_end_ == TERMINATE) { |
| // Let the backend know that the script has been terminated. The original |
| // action status doesn't matter. |
| processed_action.set_status( |
| ProcessedActionStatusProto::USER_ABORTED_ACTION); |
| } |
| if (processed_action.status() != ProcessedActionStatusProto::ACTION_APPLIED) { |
| // Report error immediately, interrupting action processing. |
| GetNextActions(); |
| return; |
| } |
| ProcessNextAction(); |
| } |
| |
| void ScriptExecutor::WaitForElement(base::TimeDelta max_wait_time, |
| ElementCheckType check_type, |
| const Selector& selector, |
| base::OnceCallback<void(bool)> callback) { |
| DCHECK(!batch_element_checker_); |
| batch_element_checker_ = CreateBatchElementChecker(); |
| batch_element_checker_->AddElementCheck(check_type, selector, |
| base::DoNothing()); |
| batch_element_checker_->Run( |
| max_wait_time, /* try_done= */ base::DoNothing(), /* all_done= */ |
| base::BindOnce(&ScriptExecutor::OnWaitForElement, |
| weak_ptr_factory_.GetWeakPtr(), std::move(callback))); |
| } |
| |
| void ScriptExecutor::OnWaitForElement(base::OnceCallback<void(bool)> callback) { |
| bool all_found = batch_element_checker_->all_found(); |
| batch_element_checker_.reset(); |
| std::move(callback).Run(all_found); |
| } |
| |
| void ScriptExecutor::OnWaitForElementVisibleWithInterrupts( |
| base::OnceCallback<void(ProcessedActionStatusProto)> callback, |
| bool element_found, |
| const Result* interrupt_result, |
| const std::set<std::string>& interrupt_paths) { |
| ran_interrupts_.insert(interrupt_paths.begin(), interrupt_paths.end()); |
| if (interrupt_result) { |
| if (!interrupt_result->success) { |
| std::move(callback).Run(INTERRUPT_FAILED); |
| return; |
| } |
| if (interrupt_result->at_end != CONTINUE) { |
| at_end_ = interrupt_result->at_end; |
| should_stop_script_ = true; |
| std::move(callback).Run(MANUAL_FALLBACK); |
| return; |
| } |
| } |
| OnWaitForElementVisibleNoInterrupts(std::move(callback), element_found); |
| } |
| |
| void ScriptExecutor::OnWaitForElementVisibleNoInterrupts( |
| base::OnceCallback<void(ProcessedActionStatusProto)> callback, |
| bool element_found) { |
| std::move(callback).Run(element_found ? ACTION_APPLIED |
| : ELEMENT_RESOLUTION_FAILED); |
| } |
| |
| ScriptExecutor::WaitWithInterrupts::WaitWithInterrupts( |
| ScriptExecutor* main_script, |
| base::TimeDelta max_wait_time, |
| ElementCheckType check_type, |
| const Selector& selector, |
| WaitWithInterrupts::Callback callback) |
| : main_script_(main_script), |
| max_wait_time_(max_wait_time), |
| check_type_(check_type), |
| selector_(selector), |
| callback_(std::move(callback)), |
| element_found_(false), |
| weak_ptr_factory_(this) {} |
| |
| ScriptExecutor::WaitWithInterrupts::~WaitWithInterrupts() = default; |
| |
| void ScriptExecutor::WaitWithInterrupts::Run() { |
| // Reset state possibly left over from previous runs. |
| element_found_ = false; |
| runnable_interrupts_.clear(); |
| batch_element_checker_ = |
| main_script_->delegate_->GetWebController()->CreateBatchElementChecker(); |
| |
| batch_element_checker_->AddElementCheck( |
| check_type_, selector_, |
| base::BindOnce(&WaitWithInterrupts::OnElementCheckDone, |
| base::Unretained(this))); |
| for (const auto* interrupt : *main_script_->ordered_interrupts_) { |
| if (ran_interrupts_.find(interrupt->handle.path) != ran_interrupts_.end()) { |
| // Only run an interrupt once in a WaitWithInterrupts, to avoid loops. |
| continue; |
| } |
| |
| interrupt->precondition->Check( |
| main_script_->delegate_->GetWebController()->GetUrl(), |
| batch_element_checker_.get(), main_script_->delegate_->GetParameters(), |
| *main_script_->scripts_state_, |
| base::BindOnce(&WaitWithInterrupts::OnPreconditionCheckDone, |
| weak_ptr_factory_.GetWeakPtr(), |
| base::Unretained(interrupt))); |
| } |
| // The base::Unretained(this) above are safe, since the pointers belong to the |
| // main script, which own this instance. |
| |
| batch_element_checker_->Run( |
| max_wait_time_, |
| base::BindRepeating(&WaitWithInterrupts::OnTryDone, |
| weak_ptr_factory_.GetWeakPtr()), |
| base::BindOnce(&WaitWithInterrupts::OnAllDone, |
| weak_ptr_factory_.GetWeakPtr())); |
| } |
| |
| void ScriptExecutor::WaitWithInterrupts::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::WaitWithInterrupts::OnScriptListChanged( |
| std::vector<std::unique_ptr<Script>> scripts) { |
| main_script_->ReportScriptsUpdateToListener(std::move(scripts)); |
| } |
| |
| void ScriptExecutor::WaitWithInterrupts::OnPreconditionCheckDone( |
| const Script* interrupt, |
| bool precondition_match) { |
| if (precondition_match) |
| runnable_interrupts_.insert(interrupt); |
| } |
| |
| void ScriptExecutor::WaitWithInterrupts::OnElementCheckDone(bool found) { |
| element_found_ = found; |
| // 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::WaitWithInterrupts::OnTryDone() { |
| if (!runnable_interrupts_.empty()) { |
| // 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 auto* interrupt : *main_script_->ordered_interrupts_) { |
| if (runnable_interrupts_.find(interrupt) != runnable_interrupts_.end()) { |
| RunInterrupt(interrupt); |
| return; |
| } |
| } |
| } |
| |
| if (element_found_) |
| RunCallback(true, nullptr); |
| } |
| |
| void ScriptExecutor::WaitWithInterrupts::OnAllDone() { |
| // This means that we've reached the end of the timeout. Report whether we |
| // found the element unless an interrupt has just been started by OnTryDone. |
| if (!interrupt_executor_) |
| RunCallback(element_found_, nullptr); |
| } |
| |
| void ScriptExecutor::WaitWithInterrupts::RunInterrupt(const Script* interrupt) { |
| batch_element_checker_.reset(); |
| SavePreInterruptState(); |
| ran_interrupts_.insert(interrupt->handle.path); |
| interrupt_executor_ = std::make_unique<ScriptExecutor>( |
| interrupt->handle.path, main_script_->last_global_payload_, |
| main_script_->initial_script_payload_, |
| /* listener= */ this, main_script_->scripts_state_, &no_interrupts_, |
| main_script_->delegate_); |
| interrupt_executor_->Run( |
| base::BindOnce(&ScriptExecutor::WaitWithInterrupts::OnInterruptDone, |
| base::Unretained(this))); |
| // base::Unretained(this) is safe because interrupt_executor_ belongs to this |
| } |
| |
| void ScriptExecutor::WaitWithInterrupts::OnInterruptDone( |
| const ScriptExecutor::Result& result) { |
| interrupt_executor_.reset(); |
| if (!result.success || result.at_end != ScriptExecutor::CONTINUE) { |
| RunCallback(false, &result); |
| return; |
| } |
| RestorePreInterruptUiState(); |
| |
| // 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. |
| Run(); |
| } |
| |
| void ScriptExecutor::WaitWithInterrupts::RunCallback( |
| bool found, |
| const ScriptExecutor::Result* result) { |
| // stop element checking if one is still in progress |
| batch_element_checker_.reset(); |
| if (!callback_) |
| return; |
| |
| RestorePreInterruptScroll(found); |
| std::move(callback_).Run(found, result, ran_interrupts_); |
| } |
| |
| void ScriptExecutor::WaitWithInterrupts::SavePreInterruptState() { |
| if (saved_pre_interrupt_state_) |
| return; |
| |
| pre_interrupt_status_ = main_script_->delegate_->GetStatusMessage(); |
| saved_pre_interrupt_state_ = true; |
| } |
| |
| void ScriptExecutor::WaitWithInterrupts::RestorePreInterruptUiState() { |
| if (!saved_pre_interrupt_state_) |
| return; |
| |
| main_script_->delegate_->SetStatusMessage(pre_interrupt_status_); |
| } |
| |
| void ScriptExecutor::WaitWithInterrupts::RestorePreInterruptScroll( |
| bool element_found) { |
| if (!saved_pre_interrupt_state_) |
| return; |
| |
| auto* delegate = main_script_->delegate_; |
| if (element_found) { |
| delegate->GetWebController()->FocusElement(selector_, base::DoNothing()); |
| } else if (!main_script_->last_focused_element_selector_.empty()) { |
| delegate->GetWebController()->FocusElement( |
| main_script_->last_focused_element_selector_, base::DoNothing()); |
| } |
| } |
| |
| void ScriptExecutor::WaitWithInterrupts::Terminate() { |
| if (interrupt_executor_) |
| interrupt_executor_->Terminate(); |
| } |
| |
| } // namespace autofill_assistant |