Commit a42a7895 authored by Stephane Zermatten's avatar Stephane Zermatten Committed by Commit Bot

[Autofill Assistant] Avoid race conditions in graceful shutdown.

Before this patch, there were cases where the controller would enter
graceful shutdown because of a script, with a successful message, and
then notice an error, and change that message to an error. The error was
usually caused by DidStartNavigation noticing an unexpected navigation
event, triggered by an action run at the end of the last script.

This patch attempts to avoid such case by:
 - only checking for unexpected navigation events in the PROMPT state.
 This specifically excludes the STOPPED and RUNNING states.
 - not reporting errors or accepting script updates (OnGetScript) in the
 STOPPED state
 - stopping script checks when entering the fatal error state

Bug: 806868
Change-Id: I408d2652257d9524740e44c79b7fa5201ef7cc17
Reviewed-on: https://chromium-review.googlesource.com/c/1436047
Commit-Queue: Stephane Zermatten <szermatt@chromium.org>
Reviewed-by: default avatarMathias Carlen <mcarlen@chromium.org>
Cr-Commit-Position: refs/heads/master@{#626937}
parent 80399d3a
...@@ -148,6 +148,8 @@ const Details* Controller::GetDetails() const { ...@@ -148,6 +148,8 @@ const Details* Controller::GetDetails() const {
void Controller::EnterState(AutofillAssistantState state) { void Controller::EnterState(AutofillAssistantState state) {
if (state_ == state) if (state_ == state)
return; return;
DCHECK_NE(state_, AutofillAssistantState::STOPPED)
<< "Unexpected transition from STOPPED to " << static_cast<int>(state);
state_ = state; state_ = state;
GetUiController()->OnStateChanged(state); GetUiController()->OnStateChanged(state);
...@@ -205,10 +207,8 @@ void Controller::OnPeriodicScriptCheck() { ...@@ -205,10 +207,8 @@ void Controller::OnPeriodicScriptCheck() {
if (should_fail_after_checking_scripts_ && if (should_fail_after_checking_scripts_ &&
++total_script_check_count_ >= kAutostartCheckCountLimit) { ++total_script_check_count_ >= kAutostartCheckCountLimit) {
should_fail_after_checking_scripts_ = false; should_fail_after_checking_scripts_ = false;
SetStatusMessage( OnFatalError(l10n_util::GetStringUTF8(IDS_AUTOFILL_ASSISTANT_DEFAULT_ERROR),
l10n_util::GetStringUTF8(IDS_AUTOFILL_ASSISTANT_DEFAULT_ERROR)); Metrics::AUTOSTART_TIMEOUT);
stop_reason_ = Metrics::AUTOSTART_TIMEOUT;
EnterState(AutofillAssistantState::STOPPED);
return; return;
} }
...@@ -224,6 +224,9 @@ void Controller::OnPeriodicScriptCheck() { ...@@ -224,6 +224,9 @@ void Controller::OnPeriodicScriptCheck() {
void Controller::OnGetScripts(const GURL& url, void Controller::OnGetScripts(const GURL& url,
bool result, bool result,
const std::string& response) { const std::string& response) {
if (state_ == AutofillAssistantState::STOPPED)
return;
// If the domain of the current URL changed since the request was sent, the // If the domain of the current URL changed since the request was sent, the
// response is not relevant anymore and can be safely discarded. // response is not relevant anymore and can be safely discarded.
if (url.host() != script_domain_) if (url.host() != script_domain_)
...@@ -266,10 +269,8 @@ void Controller::OnScriptExecuted(const std::string& script_path, ...@@ -266,10 +269,8 @@ void Controller::OnScriptExecuted(const std::string& script_path,
const ScriptExecutor::Result& result) { const ScriptExecutor::Result& result) {
if (!result.success) { if (!result.success) {
LOG(ERROR) << "Failed to execute script " << script_path; LOG(ERROR) << "Failed to execute script " << script_path;
SetStatusMessage( OnFatalError(l10n_util::GetStringUTF8(IDS_AUTOFILL_ASSISTANT_DEFAULT_ERROR),
l10n_util::GetStringUTF8(IDS_AUTOFILL_ASSISTANT_DEFAULT_ERROR)); Metrics::SCRIPT_FAILED);
stop_reason_ = Metrics::SCRIPT_FAILED;
EnterState(AutofillAssistantState::STOPPED);
return; return;
} }
...@@ -321,8 +322,13 @@ void Controller::OnScriptExecuted(const std::string& script_path, ...@@ -321,8 +322,13 @@ void Controller::OnScriptExecuted(const std::string& script_path,
GetOrCheckScripts(web_contents()->GetLastCommittedURL()); GetOrCheckScripts(web_contents()->GetLastCommittedURL());
} }
void Controller::GiveUp(Metrics::DropOutReason reason) { void Controller::OnFatalError(const std::string& error_message,
SetStatusMessage(l10n_util::GetStringUTF8(IDS_AUTOFILL_ASSISTANT_GIVE_UP)); Metrics::DropOutReason reason) {
if (state_ == AutofillAssistantState::STOPPED)
return;
StopPeriodicScriptChecks();
SetStatusMessage(error_message);
stop_reason_ = reason; stop_reason_ = reason;
EnterState(AutofillAssistantState::STOPPED); EnterState(AutofillAssistantState::STOPPED);
} }
...@@ -481,7 +487,8 @@ void Controller::OnNoRunnableScriptsAnymore() { ...@@ -481,7 +487,8 @@ void Controller::OnNoRunnableScriptsAnymore() {
// We're navigated to a page that has no scripts or the scripts have reached a // We're navigated to a page that has no scripts or the scripts have reached a
// state from which they cannot recover through a DOM change. // state from which they cannot recover through a DOM change.
GiveUp(Metrics::NO_SCRIPTS); OnFatalError(l10n_util::GetStringUTF8(IDS_AUTOFILL_ASSISTANT_GIVE_UP),
Metrics::NO_SCRIPTS);
return; return;
} }
...@@ -489,7 +496,7 @@ void Controller::OnRunnableScriptsChanged( ...@@ -489,7 +496,7 @@ void Controller::OnRunnableScriptsChanged(
const std::vector<ScriptHandle>& runnable_scripts) { const std::vector<ScriptHandle>& runnable_scripts) {
// Script selection is disabled when a script is already running. We will // Script selection is disabled when a script is already running. We will
// check again and maybe update when the current script has finished. // check again and maybe update when the current script has finished.
if (script_tracker()->running()) if (script_tracker()->running() || state_ == AutofillAssistantState::STOPPED)
return; return;
if (!runnable_scripts.empty()) { if (!runnable_scripts.empty()) {
...@@ -558,10 +565,8 @@ void Controller::DidFinishLoad(content::RenderFrameHost* render_frame_host, ...@@ -558,10 +565,8 @@ void Controller::DidFinishLoad(content::RenderFrameHost* render_frame_host,
void Controller::DidStartNavigation( void Controller::DidStartNavigation(
content::NavigationHandle* navigation_handle) { content::NavigationHandle* navigation_handle) {
if (!started_) // The following types of navigations are allowed for the main frame, when
return; // in PROMPT state:
// The following types of navigations are allowed for the main frame:
// - first-time URL load // - first-time URL load
// - script-directed navigation, while a script is running unless // - script-directed navigation, while a script is running unless
// there's a touchable area. // there's a touchable area.
...@@ -575,16 +580,14 @@ void Controller::DidStartNavigation( ...@@ -575,16 +580,14 @@ void Controller::DidStartNavigation(
// //
// Everything else, such as going back to a previous page, or refreshing the // Everything else, such as going back to a previous page, or refreshing the
// page is considered an end condition. // page is considered an end condition.
if (navigation_handle->IsInMainFrame() && if (state_ == AutofillAssistantState::PROMPT &&
navigation_handle->IsInMainFrame() &&
web_contents()->GetLastCommittedURL().is_valid() && web_contents()->GetLastCommittedURL().is_valid() &&
!navigation_handle->WasServerRedirect() && !navigation_handle->WasServerRedirect() &&
!navigation_handle->IsSameDocument() && !navigation_handle->IsSameDocument() &&
!navigation_handle->IsRendererInitiated()) { !navigation_handle->IsRendererInitiated()) {
// The action can define a touchable element area that prevents navigation. OnFatalError(l10n_util::GetStringUTF8(IDS_AUTOFILL_ASSISTANT_GIVE_UP),
if (!script_tracker_ || !script_tracker()->running() || Metrics::NAVIGATION);
touchable_element_area()->HasElements()) {
GiveUp(Metrics::NAVIGATION);
}
} }
} }
......
...@@ -103,7 +103,10 @@ class Controller : public ScriptExecutorDelegate, ...@@ -103,7 +103,10 @@ class Controller : public ScriptExecutorDelegate,
void StartPeriodicScriptChecks(); void StartPeriodicScriptChecks();
void StopPeriodicScriptChecks(); void StopPeriodicScriptChecks();
void OnPeriodicScriptCheck(); void OnPeriodicScriptCheck();
void GiveUp(Metrics::DropOutReason reason);
// Shows the given message and stops the controller with |reason|.
void OnFatalError(const std::string& error_message,
Metrics::DropOutReason reason);
// Runs autostart scripts from |runnable_scripts|, if the conditions are // Runs autostart scripts from |runnable_scripts|, if the conditions are
// right. Returns true if a script was auto-started. // right. Returns true if a script was auto-started.
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment