blob: d7570bfb55380c77889851a2d969ee50696e6461 [file] [log] [blame]
// 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_tracker.h"
#include <algorithm>
#include <utility>
#include "base/base64.h"
#include "base/bind.h"
#include "components/autofill_assistant/browser/script.h"
#include "components/autofill_assistant/browser/script_executor.h"
namespace autofill_assistant {
ScriptTracker::ScriptTracker(ScriptExecutorDelegate* delegate,
ScriptTracker::Listener* listener)
: delegate_(delegate),
listener_(listener),
reported_runnable_scripts_(false),
weak_ptr_factory_(this) {
DCHECK(delegate_);
DCHECK(listener_);
}
ScriptTracker::~ScriptTracker() = default;
void ScriptTracker::SetScripts(std::vector<std::unique_ptr<Script>> scripts) {
ClearAvailableScripts();
for (auto& script : scripts) {
available_scripts_[script.get()] = std::move(script);
}
}
void ScriptTracker::CheckScripts(const base::TimeDelta& max_duration) {
// In case checks are still running, terminate them.
TerminatePendingChecks();
DCHECK(pending_runnable_scripts_.empty());
batch_element_checker_ =
delegate_->GetWebController()->CreateBatchElementChecker();
for (const auto& entry : available_scripts_) {
Script* script = entry.first;
script->precondition->Check(
delegate_->GetWebController()->GetUrl(), batch_element_checker_.get(),
delegate_->GetParameters(), executed_scripts_,
base::BindOnce(&ScriptTracker::OnPreconditionCheck,
weak_ptr_factory_.GetWeakPtr(), script));
}
if (batch_element_checker_->all_found() &&
pending_runnable_scripts_.empty() && reported_runnable_scripts_) {
// There are no runnable scripts, even though we haven't checked the DOM
// yet. Report it all immediately.
UpdateRunnableScriptsIfNecessary();
listener_->OnNoRunnableScriptsAnymore();
OnCheckDone();
return;
}
batch_element_checker_->Run(
max_duration,
/* try_done= */
base::BindRepeating(&ScriptTracker::UpdateRunnableScriptsIfNecessary,
base::Unretained(this)),
/* all_done= */
base::BindOnce(&ScriptTracker::OnCheckDone, base::Unretained(this)));
// base::Unretained(this) is safe since this instance owns
// batch_element_checker_.
}
void ScriptTracker::ExecuteScript(const std::string& script_path,
ScriptExecutor::RunScriptCallback callback) {
if (running()) {
DLOG(ERROR) << "Do not expect executing the script (" << script_path
<< " when there is a script running.";
ScriptExecutor::Result result;
result.success = false;
std::move(callback).Run(result);
return;
}
executed_scripts_[script_path] = SCRIPT_STATUS_RUNNING;
executor_ = std::make_unique<ScriptExecutor>(
script_path, last_server_payload_, this, delegate_);
ScriptExecutor::RunScriptCallback run_script_callback = base::BindOnce(
&ScriptTracker::OnScriptRun, weak_ptr_factory_.GetWeakPtr(), script_path,
std::move(callback));
TerminatePendingChecks();
executor_->Run(std::move(run_script_callback));
}
void ScriptTracker::ClearRunnableScripts() {
runnable_scripts_.clear();
}
base::Value ScriptTracker::GetDebugContext() const {
base::Value dict(base::Value::Type::DICTIONARY);
std::string last_server_payload_js = last_server_payload_;
base::Base64Encode(last_server_payload_js, &last_server_payload_js);
dict.SetKey("last-payload", base::Value(last_server_payload_js));
std::vector<base::Value> executed_scripts_js;
for (const auto& entry : executed_scripts_) {
base::Value script_js = base::Value(base::Value::Type::DICTIONARY);
script_js.SetKey(entry.first, base::Value(entry.second));
executed_scripts_js.push_back(std::move(script_js));
}
dict.SetKey("executed-scripts", base::Value(executed_scripts_js));
std::vector<base::Value> available_scripts_js;
for (const auto& entry : available_scripts_)
available_scripts_js.push_back(base::Value(entry.second->handle.path));
dict.SetKey("available-scripts", base::Value(available_scripts_js));
std::vector<base::Value> runnable_scripts_js;
for (const auto& entry : runnable_scripts_) {
base::Value script_js = base::Value(base::Value::Type::DICTIONARY);
script_js.SetKey("name", base::Value(entry.name));
script_js.SetKey("path", base::Value(entry.path));
script_js.SetKey("initial_prompt", base::Value(entry.initial_prompt));
script_js.SetKey("autostart", base::Value(entry.autostart));
script_js.SetKey("highlight", base::Value(entry.highlight));
runnable_scripts_js.push_back(std::move(script_js));
}
dict.SetKey("runnable-scripts", base::Value(runnable_scripts_js));
return dict;
}
void ScriptTracker::OnScriptRun(
const std::string& script_path,
ScriptExecutor::RunScriptCallback original_callback,
ScriptExecutor::Result result) {
executor_.reset();
executed_scripts_[script_path] =
result.success ? SCRIPT_STATUS_SUCCESS : SCRIPT_STATUS_FAILURE;
std::move(original_callback).Run(result);
}
void ScriptTracker::UpdateRunnableScriptsIfNecessary() {
if (!RunnablesHaveChanged())
return;
runnable_scripts_.clear();
std::sort(pending_runnable_scripts_.begin(), pending_runnable_scripts_.end(),
[](const Script* a, const Script* b) {
// Runnable scripts with lowest priority value are displayed
// first. The display order of scripts with the same priority is
// arbitrary. Fallback to ordering by name, arbitrarily, for the
// behavior to be consistent.
return std::tie(a->priority, a->handle.name) <
std::tie(b->priority, b->handle.name);
});
for (Script* script : pending_runnable_scripts_) {
runnable_scripts_.push_back(script->handle);
}
reported_runnable_scripts_ = true;
listener_->OnRunnableScriptsChanged(runnable_scripts_);
}
void ScriptTracker::OnCheckDone() {
TerminatePendingChecks();
}
void ScriptTracker::TerminatePendingChecks() {
batch_element_checker_.reset();
pending_runnable_scripts_.clear();
}
bool ScriptTracker::RunnablesHaveChanged() {
if (runnable_scripts_.size() != pending_runnable_scripts_.size())
return true;
std::set<std::string> pending_paths;
for (Script* script : pending_runnable_scripts_) {
pending_paths.insert(script->handle.path);
}
std::set<std::string> current_paths;
for (const auto& handle : runnable_scripts_) {
current_paths.insert(handle.path);
}
return pending_paths != current_paths;
}
void ScriptTracker::OnPreconditionCheck(Script* script,
bool met_preconditions) {
if (available_scripts_.find(script) == available_scripts_.end()) {
// Result is not relevant anymore.
return;
}
if (met_preconditions)
pending_runnable_scripts_.push_back(script);
}
void ScriptTracker::ClearAvailableScripts() {
available_scripts_.clear();
// Clearing available_scripts_ has cancelled any pending precondition checks,
// ending them.
TerminatePendingChecks();
}
void ScriptTracker::OnServerPayloadChanged(const std::string& server_payload) {
last_server_payload_ = server_payload;
}
} // namespace autofill_assistant