blob: a6fe7023bc8b20c464add499ba4f893b66c3fd0d [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/batch_element_checker.h"
#include <utility>
#include "base/bind.h"
#include "base/single_thread_task_runner.h"
#include "base/threading/thread_task_runner_handle.h"
#include "base/time/time.h"
#include "components/autofill_assistant/browser/web_controller.h"
namespace autofill_assistant {
namespace {
// Waiting period between two checks.
static constexpr base::TimeDelta kCheckPeriod =
base::TimeDelta::FromMilliseconds(100);
} // namespace
BatchElementChecker::BatchElementChecker(WebController* web_controller)
: web_controller_(web_controller),
pending_checks_count_(0),
all_found_(false),
started_(false),
weak_ptr_factory_(this) {
DCHECK(web_controller);
}
BatchElementChecker::~BatchElementChecker() {}
void BatchElementChecker::AddElementCheck(ElementCheckType check_type,
const Selector& selector,
ElementCheckCallback callback) {
DCHECK(!started_);
element_check_callbacks_[std::make_pair(check_type, selector)].emplace_back(
std::move(callback));
}
void BatchElementChecker::AddFieldValueCheck(const Selector& selector,
GetFieldValueCallback callback) {
DCHECK(!started_);
get_field_value_callbacks_[selector].emplace_back(std::move(callback));
}
void BatchElementChecker::Run(const base::TimeDelta& duration,
base::RepeatingCallback<void()> try_done,
base::OnceCallback<void()> all_done) {
started_ = true;
int64_t try_count = duration / kCheckPeriod;
if (try_count <= 0) {
try_count = 1;
}
Try(base::BindOnce(&BatchElementChecker::OnTryDone,
weak_ptr_factory_.GetWeakPtr(), try_count, try_done,
std::move(all_done)));
}
bool BatchElementChecker::all_found() {
if (!started_) {
return element_check_callbacks_.empty() &&
get_field_value_callbacks_.empty();
}
return all_found_;
}
void BatchElementChecker::Try(base::OnceCallback<void()> try_done_callback) {
DCHECK(!try_done_callback_);
try_done_callback_ = std::move(try_done_callback);
DCHECK_EQ(pending_checks_count_, 0);
pending_checks_count_ =
element_check_callbacks_.size() + get_field_value_callbacks_.size() + 1;
for (auto& entry : element_check_callbacks_) {
if (entry.second.empty()) {
pending_checks_count_--;
continue;
}
const auto& call_arguments = entry.first;
web_controller_->ElementCheck(
call_arguments.first, call_arguments.second,
base::BindOnce(
&BatchElementChecker::OnElementChecked,
weak_ptr_factory_.GetWeakPtr(),
// Guaranteed to exist for the lifetime of this instance, because
// the map isn't modified after Run has been called.
base::Unretained(&entry.second)));
}
for (auto& entry : get_field_value_callbacks_) {
if (entry.second.empty()) {
pending_checks_count_--;
continue;
}
web_controller_->GetFieldValue(
entry.first,
base::BindOnce(
&BatchElementChecker::OnGetFieldValue,
weak_ptr_factory_.GetWeakPtr(),
// Guaranteed to exist for the lifetime of this instance, because
// the map isn't modified after Run has been called.
base::Unretained(&entry.second)));
}
// The extra +1 of pending_check_count and this check happening last
// guarantees that all_done cannot be called before the end of this function.
// Without this, callbacks could be called synchronously by the web
// controller, the call all_done, which could delete this instance and all its
// datastructures while the function is still going through them.
//
// TODO(crbug.com/806868): make sure 'all_done' callback is called
// asynchronously and fix unit tests accordingly.
pending_checks_count_--;
CheckTryDone();
}
void BatchElementChecker::OnTryDone(int64_t remaining_attempts,
base::RepeatingCallback<void()> try_done,
base::OnceCallback<void()> all_done) {
// Warning: try_done or all_done can indirectly delete this. this must not
// be used after calling either of these.
if (all_found_) {
try_done.Run();
std::move(all_done).Run();
return;
}
--remaining_attempts;
if (remaining_attempts <= 0) {
// GiveUp is run before calling try_done, so its effects are visible right
// away.
GiveUp();
try_done.Run();
std::move(all_done).Run();
return;
}
base::ThreadTaskRunnerHandle::Get()->PostDelayedTask(
FROM_HERE,
base::BindOnce(
&BatchElementChecker::Try, weak_ptr_factory_.GetWeakPtr(),
base::BindOnce(&BatchElementChecker::OnTryDone,
weak_ptr_factory_.GetWeakPtr(), remaining_attempts,
try_done, std::move(all_done))),
kCheckPeriod);
// try_done must be called after creating the delayed task, in case
// try_done.Run() deletes this.
try_done.Run();
}
void BatchElementChecker::GiveUp() {
for (auto& entry : element_check_callbacks_) {
RunCallbacks(&entry.second, false);
}
for (auto& entry : get_field_value_callbacks_) {
RunCallbacks(&entry.second, false, "");
}
}
void BatchElementChecker::OnElementChecked(
std::vector<ElementCheckCallback>* callbacks,
bool exists) {
pending_checks_count_--;
if (exists)
RunCallbacks(callbacks, true);
CheckTryDone();
}
void BatchElementChecker::OnGetFieldValue(
std::vector<GetFieldValueCallback>* callbacks,
bool exists,
const std::string& value) {
pending_checks_count_--;
if (exists)
RunCallbacks(callbacks, exists, value);
CheckTryDone();
}
void BatchElementChecker::CheckTryDone() {
DCHECK_GE(pending_checks_count_, 0);
if (pending_checks_count_ <= 0 && try_done_callback_) {
all_found_ = !HasMoreChecksToRun();
std::move(try_done_callback_).Run();
}
}
void BatchElementChecker::RunCallbacks(
std::vector<ElementCheckCallback>* callbacks,
bool result) {
for (auto& callback : *callbacks) {
std::move(callback).Run(result);
}
callbacks->clear();
}
void BatchElementChecker::RunCallbacks(
std::vector<GetFieldValueCallback>* callbacks,
bool exists,
const std::string& value) {
for (auto& callback : *callbacks) {
std::move(callback).Run(exists, value);
}
callbacks->clear();
}
bool BatchElementChecker::HasMoreChecksToRun() {
for (const auto& entry : element_check_callbacks_) {
if (!entry.second.empty())
return true;
}
for (const auto& entry : get_field_value_callbacks_) {
if (!entry.second.empty())
return true;
}
return false;
}
} // namespace autofill_assistant